Text in a VStack being condensed - ios

I have a view similar to the below code. For some reason, my long text is being condensed, when it shouldn't be. Why is this?
struct ContentView: View {
var body: some View {
VStack {
ScrollView {
VStack(alignment: .leading, spacing: 32) {
HStack(alignment: .top, spacing: 8) {
Image(systemName: "hand.raised.fill")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(shortText)
Text(longDescription)
.lineSpacing(4)
// this is being condensed
}
}
}
.padding([.leading, .trailing])
}
}
VStack {
// Custom Button goes here
Spacer()
}
.frame(height: 70)
}
}

.layoutPriority(1) tells the layout system that it should give priority to the desired dimensions of this view over anything with a lower priority, so it won't be condensed.
The reason your Text is being condensed is because it's in a ScrollView.
In a non-scrolling context, the layout system allows the views to expand, using the available space (i.e. the screen size) as the limit.
In a scrolling context there is no limit to the available space, so allowing views to expand to their maximum size could result in some views having an infinite size (Color.red, for example, will try to take as much space as possible).
This is why your Text view is condensed to the minimum space it supports (one line height) as opposed to the maximum size it supports.
PS. There might be a combination of modifiers that is not layoutPriority(1) that allows for Text to report it's minimum size as the required size to show all of its contents. If I have time to try and find it I will update this answer.

Related

SwiftUI - Text elements not starting from same leading position

I am still a beginner in swiftUI.
Adding code and screenshot which will have explain my problem better. In the below screenshot, how can I align 'E' of first line with 'T' of 2nd line. I want both 'Text' elements to have same leading space i.e. both should start from the same position from left.
I have seen cases where people are using just one Text element and '\n' to move text to the next line and that aligns the text, but in my case I will be having more elements such as TextField and some more Text elements below these 2 texts hence I can't use the '\n' idea.
Code:
struct TestData: View {
var body: some View {
ZStack {
Image("backgroundImage").resizable().edgesIgnoringSafeArea(.all).scaledToFill()
VStack(alignment: .leading, spacing: 10) {
Text("Enter your Data")
.font(.largeTitle).bold()
.frame(width: UIScreen.main.bounds.width-50, height: 33.0)
Text("This is a very very long text to wrap on the next line. This text is of 2 lines.")
.font(.callout)
.frame(width: UIScreen.main.bounds.width-50, height: 80.0)
.foregroundColor(.gray)
.lineLimit(nil)
HStack {
// Contains image and textfield. Data will be entered in textfield.
}
// Move Text and Button elements.
}.offset(x: -10, y: -100)
}
}
}
struct TestData_Previews: PreviewProvider {
static var previews: some View {
TestData()
}
}
Screenshot:
In general, don't set fixed frames; SwiftUI tends to work better when you let the layout engine do its thing. Using maxWidth/maxHeight and minWidth/minHeight can be useful for giving clues to the layout engine about what you want.
Similarly with offset - This moves things around but doesn't change their layout bounding box, so you can end up with overlapping elements (which is fine if that is what you want).
For your layout, you can simply remove the frame and offset and use some padding to shift everything in from the leading edge:
VStack(alignment: .leading, spacing: 10) {
Text("Enter your Data").font(.largeTitle)
.bold()
Text("This is a very very long text to wrap on the next line. This text is of 2 lines.").font(.callout)
.foregroundColor(.gray)
.lineLimit(nil)
HStack {
// Contains image and textfield. Data will be entered in textfield.
}
}.padding(.leading,50)

`maxHeight` behaves exactly as `height` frame modifier

I'm confused by frame(maxHeight: ...) modifier. I would expect that the resulting view would have dynamic height capped at maxHeight. However, the height of the view is always maxHeight.
In the example below, I wanted the green rectangle to have 30px and the red one should fill the rest space. However, there's additional padding between the rectangles caused by maxHeight modifier.
Is there any other way to achieve what I want?
maxHeight seems useless to me know. It's pretty much the same as using height, isn't it?
Code
var body: some View {
VStack(spacing: 0) {
Color.green
.frame(height: 30)
.frame(maxHeight: 60, alignment: .top)
Color.red
}
.frame(height: 100)
.background(Color.black)
}
Preview:
Every modifier can (in general!) create a new view. So, you create at first green view unlimited, then limit it by 30px (so green filled that space), and then create another view with 60px (and because there is no stopper it filled that available space)... and then everything with red view below(!) considered above views. To get what you expected just remove .frame(maxHeight - it is not needed here.
... but, if you still want (for any reason) to keep it and fulfil expectation, then it is a matter for layout order - give a preference to second view, like
VStack(spacing: 0) {
Color.green
.frame(height: 30)
.frame(maxHeight: 60, alignment: .top)
Color.red
.layoutPriority(1) // << here !!
}
and you get

In a SwiftUI VStack how do I make multiple Text Views, each with multiple lines of text, the same width

I'm trying to make the text in the VStack the same width but don't know how. I saw it suggested elsewhere to set a frame maxWidth to .infinity for each of the Text Views but it doesn't have any effect when I try it in my code.
This is the code I have:
struct ContentView: View {
var body: some View {
VStack(alignment: .center, spacing: 8.0) {
Spacer()
Text("Youngsters can play in Kids mode. If you're feeling very adventurous you can play in BigKids mode.")
.padding(.horizontal)
Text("Kids mode uses large images and big differences between the number of images shown.")
.padding(.horizontal)
Text("BigKids mode, on the other hand, uses small images and small differences between the number of images shown. Go for it!")
.padding(.horizontal)
Spacer()
}
.frame(maxWidth: 600.0)
}
}
I'm using a frame maxWidth of 600 for the VStack as I don't want it wider than that when an iPad is used.
Here's the results of the code. Note that each Text View is a different width.

Match UIStackView's .fill alignment with SwiftUI VStack

I've got a VStack with 3 Texts in it. Each has a different length string. I'd like the VStack's width to be just big enough to fit the widest Text, but I'd also like all three Texts to fill the VStack horizontally.
By default, with this code:
VStack(spacing: 4.0) {
ForEach(1..<4) {
Text(Array<String>(repeating: "word", count: $0).joined(separator: " "))
.background(Color.gray)
}
}
I get:
I want:
In UIKit, I could do this with a vertical UIStackView whose alignment property was set to .fill. VStack doesn't have a .fill alignment. The suggested solution I've seen is to modify the frame of each child of the stack view (ie. each Text) with .infinity for maxWidth.
The suggestion I've found is to modify the Texts with .frame(maxWidth: .infinity). However, this makes the whole VStack expand to (presumably) its maximum size:
Is there a way to make the VStack naturally grow to the size of its widest child, and no larger, while making all its children the same width?
Just add .fixedSize().
struct ContentView: View {
var body: some View {
VStack(spacing: 4.0) {
ForEach(1..<4) {
Text(Array<String>(repeating: "word", count: $0).joined(separator: " "))
.frame(maxWidth: .infinity) /// keep the maxWidth
.background(Color.gray)
}
}
.fixedSize() /// here!
}
}
Result:

When there are different sized Text in a HStack, top alignment doesn’t apply to larger sized text

I have a HStack with multiple elements, particularly two Texts with different font sizes. I want both text to be aligned to the top of the view.
HStack(alignment: .top) {
Image(systemName: "cloud.drizzle.fill")
Text("14°")
.font(.largeTitle)
Text("86%")
.font(.callout)
Spacer()
}
However, the first (larger) Text is outputted below the other two:
Actually it's aligned correctly , add backgrounds to each Text and you will find that the frame of the Text is aligned correctly
but to solve the case that you are looking for , I did a hack for you , by doing some calculs
The result:
1) Alignement of the two Text
Put both of them in one HStack , with alignment: .firstTextBaseline
Then play on the second text , by adding a baselineOffset with (bigFont.capHeight - smallFont.capHeight)
You can learn more about fonts , but the main information that you need is this :
So your code will be :
HStack(alignment: .firstTextBaseline) {
Text("14°")
.font(Font(bigFont))
.background(Color.blue)
Text("86%")
.font(Font(smallFont))
.baselineOffset((bigFont.capHeight - smallFont.capHeight))
.background(Color.yellow)
Spacer()
}
2) Align the Image with the text :
by adding a padding which will be equal to bigFont.lineHeight-bigFont.ascender (go back to the picture on top , to see how I calculated it )
And the final code :
struct ContentView: View {
#State var pickerSelection = ""
let bigFont = UIFont.systemFont(ofSize: 50)
let smallFont = UIFont.systemFont(ofSize: 15)
var body: some View {
HStack(alignment:.top) {
Image(systemName: "cloud.drizzle.fill")
.background(Color.red)
.padding(.top, bigFont.lineHeight-bigFont.ascender)
HStack(alignment: .firstTextBaseline) {
Text("14°")
.font(Font(bigFont))
.background(Color.blue)
Text("86%")
.font(Font(smallFont))
.baselineOffset((bigFont.capHeight - smallFont.capHeight))
.background(Color.yellow)
Spacer()
}
}
}
}
PS : I added backgrounds to show you the real frames of each view
Currently the texts are aligned by top. but the large text has ascent height that is larger than small text. so the align is not top of text.
Unfortunately, SwiftUI doesn't support the alignment of top of text.
But you can align the top of text manually like as following code.
HStack(alignment: .top) {
Image(systemName: "cloud.drizzle.fill")
Text("14°")
.font(.largeTitle).padding(.top, -5.0)
Text("86%")
.font(.callout)
Spacer()
}

Resources