SwiftUI - Text elements not starting from same leading position - ios

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)

Related

How to handle text wrapping and alignment / padding with consecutive Text Views in SwiftUI?

When putting Text() views that have enough text to cause it to wrap, one after the other, they don't have the same alignment / padding unless they have a similar word structure.
I would like to both have a solution to this problem that allows for every Text view to have the same alignment and padding, as well as understand the SwiftUI Magic that I am apparently trying to fight.
I believe it has to do with the default "auto centering" alignment behavior of swiftUI but not sure why sentences of similar length don't appear with the same alignment / padding. And when I have tried to apply a group to the text views and apply collective padding or multiLineAlignment etc it doesn't seem to change the output.
struct TutorialView: View {
var body: some View {
VStack {
Text("About This App.")
.padding([.top, .bottom] , 20)
.font(.system(size: 30))
Text("This example text is meant to be long enough to wrap because when it is, then the alignment aint what we thought it would be...")
Text("This is meant to wrap and it will look different than the text above. even though it wraps too")
Text("It isn't always clear which will appear to have padding and which will hug the wall")
Text("Literally all of these are differnt...")
Spacer()
}
}
}
Approach
You are right, default alignment is center
Add different background colors and you will understand how they are aligned.
Code
I have modified your code just to demonstrate the layout
struct ContentView: View {
var body: some View {
VStack {
Text("About This App.")
.padding([.top, .bottom] , 20)
.font(.system(size: 30))
.background(.orange)
VStack(alignment: .leading) {
Text("This example text is meant to be long enough to wrap because when it is, then the alignment aint what we thought it would be...")
.background(.green)
Text("This is meant to wrap and it will look different than the text above. even though it wraps too")
.background(.blue)
Text("It isn't always clear which will appear to have padding and which will hug the wall")
.background(.red)
Text("Literally all of these are differnt...")
.background(.yellow)
Spacer()
}
}
}
}
Note:
There are times .fixedSize(horizontal:, vertical:) would come in handy (not used in this example)

Text in a VStack being condensed

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.

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()
}

SwiftUI Italic text clipping

I noticed that a Text with .italic() clips letters:
Setting frame size doesn't help:
.paddings() doesn't help either. kerning(5) I don't want to use as it fixes the problem partially, at the right edge only, but it adds unwanted letter spacing.
struct ItalicTest: View {
var body: some View {
Text("F")
.font(Font.system(size: 60))
.italic()
.fontWeight(.black)
.frame(width: 60, height: 60)
.background(Color.red)
}
}
I'd like to prevent clipping. Do you know a solution using pure SwiftUI?
I know this is an old question, but I just had the same issue and found a propper solution.
You will have to add padding to your text and ask swiftui to collapse the padding and text before rendering it.
struct ItalicTest: View {
var body: some View {
Text("F")
.font(Font.system(size: 60))
.italic()
.fontWeight(.black)
.padding(.horizontal) // <-- add padding
.drawingGroup() // collapse the view and render together
}
}

Resources