I'm trying to recreate a portion of the Twitter iOS app to learn SwiftUI and am wondering how to dynamically change the width of one view to be the width of another view. In my case, to have the underline be the same width as the Text view.
I have attached a screenshot to try and better explain what I'm referring to. Any help would be greatly appreciated, thanks!
Also here is the code I have so far:
import SwiftUI
struct GridViewHeader : View {
@State var leftPadding: Length = 0.0
@State var underLineWidth: Length = 100
var body: some View {
return VStack {
HStack {
Text("Tweets")
.tapAction {
self.leftPadding = 0
}
Spacer()
Text("Tweets & Replies")
.tapAction {
self.leftPadding = 100
}
Spacer()
Text("Media")
.tapAction {
self.leftPadding = 200
}
Spacer()
Text("Likes")
}
.frame(height: 50)
.padding(.horizontal, 10)
HStack {
Rectangle()
.frame(width: self.underLineWidth, height: 2, alignment: .bottom)
.padding(.leading, leftPadding)
.animation(.basic())
Spacer()
}
}
}
}
I have written a detailed explanation about using GeometryReader, view preferences and anchor preferences. The code below uses those concepts. For further information on how they work, check this article I posted: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
The solution below, will properly animate the underline:
I struggled to make this work and I agree with you. Sometimes, you just need to be able to pass up or down the hierarchy, some framing information. In fact, the WWDC2019 session 237 (Building Custom Views with SwiftUI), explains that views communicate their sizing continuously. It basically says Parent proposes size to child, childen decide how they want to layout theirselves and communicate back to the parent. How they do that? I suspect the anchorPreference has something to do with it. However it is very obscure and not at all documented yet. The API is exposed, but grasping how those long function prototypes work... that's a hell I do not have time for right now.
I think Apple has left this undocumented to force us rethink the whole framework and forget about "old" UIKit habits and start thinking declaratively. However, there are still times when this is needed. Have you ever wonder how the background modifier works? I would love to see that implementation. It would explain a lot! I'm hoping Apple will document preferences in the near future. I have been experimenting with custom PreferenceKey and it looks interesting.
Now back to your specific need, I managed to work it out. There are two dimensions you need (the x position and width of the text). One I get it fair and square, the other seems a bit of a hack. Nevertheless, it works perfectly.
The x position of the text I solved it by creating a custom horizontal alignment. More information on that check session 237 (at minute 19:00). Although I recommend you watch the whole thing, it sheds a lot of light on how the layout process works.
The width, however, I'm not so proud of... ;-) It requires DispatchQueue to avoid updating the view while being displayed. UPDATE: I fixed it in the second implementation down below
First implementation
Update: Better implementation without using DispatchQueue
My first solution works, but I was not too proud of the way the width is passed to the underline view.
I found a better way of achieving the same thing. It turns out, the background modifier is very powerful. It is much more than a modifier that can let you decorate the background of a view.
The basic steps are:
Text("text").background(TextGeometry())
. TextGeometry is a custom view that has a parent with the same size as the text view. That is what .background() does. Very powerful.It may all sound too complex, but the code illustrates it best. Here's the new implementation:
This solution is very wonderful.
But it became a compilation error now, it corrected. (Xcode11.1)
This is a whole code.
You just need to specify a frame with a height within it. Here's an example :
Here's a super simple solution, although it doesn't account for the tabs being stretched full width - but that should just be minor additional math for calculating the padding.
HorizontalTabs(tabs: ["one", "two", "three"])
renders this:Give this a try:
And here's what it looks like in preview:
Let me modestly suggest a slight modification of this bright answer: Version without using preferences:
Version using preferences and
GeometryReader
: