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

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

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)

SwiftUI - can I make one element in Form fill the whole screen width (without horizontal margins)?

I would like a single item inside SwiftUI Form to run from side to side, without having Form's default margins.
Unfortunately, whatever I do (like ading a wider .frame, negative horizontal padding, or .offset), the team image view seems to be always cropped by the form to fit the form area (= has horizontal margins).
Is it possible to make the Image below touch the left and right side of the screen?
I am using Form for my app settings, but I would like to add a full-width section there (think eg. a banner to promote a feature).
SwiftUI Playgrounds code:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
Form {
Section(
header: Text("First section")
) {
Text("Hello world")
}
Text("The image below should be stretched to touch the left and right window edge, without being cropped by the Form.")
Image(systemName: "sun.max.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.listRowInsets(EdgeInsets()) // this is supposed to fix the problem, but all it does is to set the default item inner padding to zero, so the image at least touches the edge of teal area.
.listRowBackground(Color.teal)
Section(
header: Text("Last section")
) {
Text("Hello world")
}
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
How it looks:
Unfortunately, SwiftUI Form is very temperamental and forces you to strictly adhere to the standard iOS Settings screen formatting.
Fortunately, you can re-implement similar formatting yourself with SwiftUI!
For the top, something like:
VStack(spacing: 4) {
Text("FIRST SECTION")
.font(.system(size: 12, weight: .regular))
.foregroundColor(.gray)
.padding(.leading)
Text("Hello, world!")
.font(.system(size: 15, weight: .regular))
.foregroundColor(.black)
.padding(.horizontal)
.frame(height: 44, alignment: .center)
.background(Color.white)
.cornerRadius(10)
}

SwiftUI how to align the view‘s leading to the most super view’s leading?

I’m new to SwiftUI and I’m making a widget. The default code included a text view which is both x-centered and y-centered in the super view(which I don’t know if there’s the same concept in SwiftUI).
This is my code:
struct WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading){
Text(Date(), style: .time)
.padding(.leading)
}
}
}
I want to align the view’s leading to the super view’s leading instead of positioning the text in the center. So I tried to add a padding to the text’s leading, strangely, it seems that the text view is not a direct subview of the superview, it is positioned in a invisible centered view instead, since I end up with having a text view that slightly deviate the center, looks like this:
This is what I want:
I tried the position method and it was the only one worked, not perfectly though, since it is based on the center point.
I’m looking for a better solution.
Here is a Playground that gives an example of a view that looks like your picture:
import UIKit
import SwiftUI
import PlaygroundSupport
struct ViewWithText : View {
var body : some View {
HStack {
Text("Here")
.background(Color.yellow)
}
.frame(width: 320, height: 480, alignment: .leading)
.background(Color.gray)
}
}
let hostingController = UIHostingController(rootView: ViewWithText())
PlaygroundSupport.PlaygroundPage.current.liveView = hostingController
With simple padding and background you can see why it does not work for you as you want.
VStack(alignment: .leading){
Text(Date(), style: .time)
.padding(.leading)
.background(.yellow)
}
.padding()
.background(Color.gray)
The base size of the VStack is the same as the size of it's children. Solution would be to increase the size of the VStack or the size of the Text
In widget I suggest to set the parent (V)Stack's size (maxWidth) as big as it can get (.infinity) :
VStack(alignment: .leading){
Text(Date(), style: .time)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Btw setting padding(.leading) is causing the "text view that slightly deviate the center" as this added a base padding to the left size of the Text. It just increases the width of the text, but still not fully to the available width.

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:

Resources