Get frame/dimensions from SwiftUI view(s) - ios

Let's assume, to present a specific piece of information I have two different kinds of custom SwiftUI views. For the sake of an example my data is two Strings and the options to display them is either in a
HStack { Text() Spacer() Text() }
or
VStack {
Text()
Text()
}
style. In order to select the best fitting one, I would need to render them and make a choice based on resulting dimensions. For instance, if one of the text views in the first style would approach 50% of the window size, I would rather go with the second style.
How would I go about this without the user seeing the temporary views?
I know about GeometryReader, but I don't know how I could render my "candidate views" off screen, determine sizes and then make a selection for my actual view hierarchy.
Any hints?

I don't know how to work properly with SwiftUI. However, the following is an approach for what you want using UIKit and Labels for text:
let max_width = UIScreen.main.bounds.width/2
if label.intrinsicContentSize.width > max_width{
//The text would occupy more than 50% of the screen in width
}else{
//The text won't occupy more than 50% of the screen in width
}
You would have to run the previous function on each Label and there you can decide.
I know this is not a complete answer because you are working with SwiftUI, but someone may know how to apply the previous code on there.

Related

SwiftUI: Conditionally hide a view without recreating it

The issue with Conditional View Modifiers
I made heavy use of conditional view modifiers in SwiftUI until I had some issues with it and recently discovered it is a bad idea.
From my understanding now, doing something like this:
if condition {
view
} else {
view.hidden()
}
means that SwiftUI will treat view in both cases as completely different views. So if you jump from if to else or vice versa, view is recreated, including running onAppear and any initial animations. Besides that, it also breaks animating view from one branch into the other. Even adding .id() doesn't seem to solve this.
I attached a minimum viable example so you can see this behaviour for yourself. And needless to say, this is bad. It has major performance implications, breaks animations and is even worse if you perform some heavy operation (like a network request) in your onAppear.
The solution?
The solution appears to be to do something like this:
view.padding(condition? 10 : 0)
Everything works peachy, SwiftUI keeps view around, great.
The problem: .isHidden()
Now, I need to conditionally hide a view. And to be honest, I have no idea how to achieve it. .isHidden() has no parameter to enable or disable it, so there is no way to use the same approach as shown above.
So: How can I conditionally hide a view without recreating it? The reason I don't want to recreate the view is that onAppear gets called again and it reset the state of the view, triggering animations to the initial state again. Or am I using the wrong approach entirely in SwiftUI world here?
A possible approach is to use opacity+disabled, like
view
.opacity(isHidden ? 0 : 1)
.disabled(isHidden ? true : false)
In SwiftUI a View struct is just data, SwiftUI is what diffs the data and decides what actual UIViews to add, remove, update on screen. So if you want to hide something just give it empty data, e.g.
Text(showText ? "Some text" : "")
It would be helpful to know what data it is you are trying to show/hide.

SwiftUI LazyVGrid animating change of "page"

I’m trying to treat an entire LazyVGrid as pages, and animate when switching forward or back. A simple slide animation would be fine.
I currently have something along the lines of
LazyVGrid(
columns: [array of column info],
alignment: .center,
spacing: 4
) {
ForEach(cells) { cell in
CellView(cell: cell)
}
}
I have pages of cells that get set on the state when you hit the fwd or back buttons, and they are immediately re-rendered. However, it would be cool to set a forward/back slide when the buttons are hit. I haven’t been able to figure this out easily with transition and animation.
If there’s an entirely different library doing this, I’d be open to trying it.
Thanks so much!

How to wrap Text around some view in a HStack?

I wanted to ask if following scenario would be possible and if yes how I could approach the problem. The following code produces the attached image:
HStack(alignment: .firstTextBaseline) {
ZStack {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color(UIColor.secondarySystemFill))
Text("Test")
.clipShape(RoundedRectangle(cornerRadius: 5))
.foregroundColor(.secondary)
.layoutPriority(1)
.padding(.all, 5)
}
Text("This is a very long message with more words than one line can handle bla blabla blablabl")
}
Now in order to save more space I would like to have the next lines move to the left like so:
I have considered following approaches but none got me very far:
Using Swift UIs Text() + Text() instead of the HStack. This would not work as this method does not allow background colors (a .background() would converter the Text() to some View and therefore I can't combine it with another Text()). I could only change the .foregroundColor.
Maybe I could somehow write a UIKit wrapper for a UILabel with NSAttributedString. I don't like this method very much though, as changing background colors with NSAttributedString isn't a very good solution either.
Use a ZStack and maybe somehow throw a .clipShape() onto the longer right hand Text() so that it moves a little more to the right. This approach is also quite hacky since I don't quite know how to calculate the length of the first string so that I could move the second string.
Either I am missing something or this just isn't something that you could do in Swift UI or UIKit.

UIsegmentedControl title appearance

HI, i set my uisegmentedcontrol's width manually, but when the width gets too small, the words becomes ...
Is that anyway that it won't behave in this way? Instead, i just want to show the text just like the picture shown below.
I'd suggest changing your design here and going for a different approach.
The design that you seem to want makes readability pretty much impossible.
Plus, what happens if I'm using your app and add another "Active Project". What happens if I have 10 active projects?
Take the fact that the UI does not work as a sign that you are using the wrong UI for the problem you are trying solve.
I'd suggest possibly just have the current project title here with a button to maybe present a list of projects to switch to... or something.
The text has been truncated. If you want it to fit your segment, you need to update the segment control size based on the text length. If you just want to get rid of truncation, you can use the following snippet. However, it's not recommended, as later Apple might change the UISegmentControl hierarchy.
for item in segmentedControl.subviews {
for subview in item.subviews {
if subview.isKind(of: UILabel.self) {
let _label = subview as! UILabel
_label.numberOfLines = 0
_label.lineBreakMode = .byWordWrapping
}
}
}

Embedding Intrinsically Sized View Breaks Animation

I have built a test project to show what the goal is vs. what I currently have happening. The gif on the left shows exactly what I want the ending appearance to be. It is constructed with a single traditional view hierarchy. I need to achieve this with the pink view being an embedded/contained view. My attempts so far have only gotten me to the gif on the right.
The way the (pink) contained view grows is possibly an important detail: the blue subview changes it's height, and the whole apparatus gets a new intrinsic size because of all the connected vertical constraints. As you would expect, this is a simplification of my actual app, but I think it has all the important bits.
The main things I see that are strange:
The yellow/orange "other" view is not animating at all.
The pink contained view is animating nicely for it's own part, but it is animating it's position, even though it's frame has the same origin before and after the animation as shown here:
Here is the Storyboard of the right gif. Both the container view in the "parent" scene and the top view in the "child" scene have translatesAutoresizingMaskIntoConstraints set to false with runtime attributes.
The question then: **What must I change about my configuration to get all affected layout changes to animate (properly) when I have a size change in an intrinsically-sized and contained view? **
Edit: Tried manual embed
Since posting the question, I have tried a manual View Controller Containment strategy, and I got the exact same results as with the Storyboard technique, which is ultimately a good sign for the platform. There was 1 fewer view in the total hierarchy, but it didn't seem to make a difference.
Edit: Bounty and project
I have added a 100 point bounty to attract attention. I have also uploaded my sample project to this github repo. Check it out!
Changing your animation block in InnerViewController as follows does the trick.
var isCollapsed = false {
didSet {
let factor:CGFloat = isCollapsed ? 1.5 : 0.66
let existing = innerViewHeightConstraint.constant
UIView.animate(withDuration: 1.0) {
self.innerViewHeightConstraint.constant = existing * factor
self.view.layoutIfNeeded()
self.parent?.view.layoutIfNeeded()
}
}
}
The key difference is self.parent?.view.layoutIfNeeded(), which tells the embedding view controller to update the constraints as part of the animation, instead of immediately before the start of the animation.

Resources