How to baseline align TextField and Text in HStack - alignment

I have some trouble aligning TextFiled and Text in HStack.
The problem is with .lastTextBaseline alignment.
struct TextFieldCurrency: View {
#State var value = "32"
var body: some View {
HStack(alignment: .lastTextBaseline) {
TextField("price", text: $value)
.background(Color.yellow)
.modifier(TextFieldValueAppearance()) //font: 32
Text("PLN")
.background(Color.red)
.modifier(TextFiledUnitAppearance()) //font: 16
}.background(Color.green)
}
}
Code above produces this which is totally wrong.
But the same code with Text instead TextField.
So, my questions are:
Why this happen? How is TextField different from Text?
What is solution to baseline alignment?
I have Xcode 11.2 and Mac OS Catalina 10.15.0

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)

FocusState changes in SwiftUI cause the keyboard to bounce

I'm making a sign-in interface for iOS in SwiftUI. The user should be able to easily switch from the username text field to the password text field by tapping the "next" button on the software keyboard. It's working well but the keyboard always bounces a little when switching between the two text fields for some reason. Edit: As suggested in this answer I've added a Spacer into the VStack to make it fill the available space. The text fields aren't bouncing anymore but the keyboard unfortunately still is. I've updated the code and the GIF to reflect my changes.
After googling a little it seemed like this wasn't a very common issue. This question seemed to be similar to what happens to me but following the answer and wrapping the text fields in a ScrollView or a GeometryReader did not change anything at all. This is my code:
struct AuthenticationView: View {
#State var userName: String = ""
#State var userAuth: String = ""
#FocusState var currentFocus: FocusObject?
enum FocusObject: Hashable { case name, auth }
var body: some View {
VStack(spacing: 8) {
TextField("Username", text: $userName)
.focused($currentFocus, equals: .name)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 8)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = .auth }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.username)
.submitLabel(.next)
SecureField("Password", text: $userAuth)
.focused($currentFocus, equals: .auth)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 16)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = nil }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.password)
.submitLabel(.done)
Spacer() // This fixes the text fields
// But it does not fix the keyboard
}.padding(32)
}
}
Your current layout says:
Put the edit fields into a VStack.
Layout the VStack in the parent view by centering it in the available space. Note, that the VStack only uses a minimum size.
Now, when the keyboard appears, the available space of the parent view, i.e. its height, will be reduced accordingly.
Because the VStack is layout in the center, the text fields bounce up and down.
There are a couple of options:
Ensure the VStack extends its height and the text fields are aligned at the top. For example using a Spacer:
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
Spacer()
}.padding(32)
Using a ScrollView:
ScrollView {
Spacer(minLength: 80) // give some space at the top
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
}.padding(32)
}
It may not look pretty, but it should give you an idea, where to work on this issue (you may want to use a GeometryReader and a possibly a ScrollView to perfect your layout).
Another option is to use a Form. Put your fields into there, and with a Form you get also a head start which looks pretty nice. The reason why a Form works is because the same reasons why it works with a Spacer (aligns fields on top) and because of a ScrollView.
The fact that the keyboard disappears temporarily when you tap "Next" is unfortunate. I have no solution for this, so far.

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

Make the TextField Multiline - SwiftUI [duplicate]

This question already has answers here:
How do I create a multiline TextField in SwiftUI?
(17 answers)
Closed 9 months ago.
I start learning SwiftUI and I'm trying to make the TextField multiline but it didn't work, also when I click on the return button it dismisses the keyboard.
TextField("Description", text: $categoryTitle)
.lineLimit(nil)
so how I can fix it?
Since iOS 16
The lineLimit modifier works as expected if you choose .vertical value for the axis parameter. And it also supports range now:
TextField("Title", text: $text, axis: .vertical)
.lineLimit(5...10)
Since iOS 14
Form iOS 14 and with Xcode 12, it's available as TextEditor
struct ContentView: View {
#State var text: String = "Multiline \ntext \nis called \nTextEditor"
var body: some View {
TextEditor(text: $text)
}
}
iOS 13
Also, if you want to support iOS 13, you can take at look this answer to port UITextField inside SwiftUI with full access to all of its properties.
iOS 16.0+ supprts multiline TextField.
struct ContentView: View {
#State private var description: String = """
Join us, and let's force unwrap SwiftUl's
birthday presents. Note that although
this activity is optional, we may have
guards at the entry.
"""
var body: some View {
Form {
TextField("Description", text: $description, axis: .vertical)
.lineLimit(5) // You can restrict min & max visible lines
}
}
}

SwiftUI: minimumScaleFactor not applying evenly to stack elements

I have a horizontal stack of two pieces of text (the second highlighted in a blue). It fits fine on an iPhone XR, however when on a smaller device (like iPhone X), the text doesn't fit. I attempted to solve this by using minimumScaleFactor to scale the text. However, SwiftUI seems to make decisions on what to scale in the stack. In this example, on the smaller device, it removes the bolding and shrinks the first (non-blue) element only. The blue element remains unchanged. Any ideas as to why this would happen? How can I scale both bolded text elements down in size together? Thanks!
var normalText: String
var highlightedText: String
var body: some View {
HStack() {
Text(normalText)
.font(.largeTitle)
.bold()
.lineLimit(1)
Text(highlightedText)
.font(.largeTitle)
.bold()
.lineLimit(1)
.foregroundColor(.blue)
Spacer()
}
.minimumScaleFactor(0.5)
}
}
Here is how it displays on a smaller device:
And how it shows on a larger device:
struct ContentView: View {
var normalText: String = "Hello and Welcome to Stack "
var highlightedText: String = "Overflow"
var body: some View {
HStack() {
Group {
Text(normalText).bold() +
Text(highlightedText).bold()
.foregroundColor(.blue)
}
.lineLimit(1).font(.largeTitle)
Spacer()
}
.minimumScaleFactor(0.5)
}
}
+ is defined for 2 Text's but not 2 View's and as lineLimit() returns a View the joined text's needed to be Grouped. I made the combined string longer to force it to shrink.

Resources