mysterious unwanted space got added in Text view - swift

It's simpler to just read the code and see the end result in the image, my question is why is Actuation Force and Bottom-out Force not aligned with the text views above them? In Actuation Force text view, there seems to be some mysterious padding added to its left while Bottom-out Force does not have it?
VStack {
VStack {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("**Pre-travel Distance**")
Text("how far down the key must be pressed for it to actuate")
}
.frame(width: 250)
.background(.red)
SwitchPropertyCircularView(upperBoundValue: theSwitch.preTravelDistance!, unit: "mm")
}
.background(.blue)
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("**Total Travel Distance**")
Text("how far down the key must be pressed for it to bottom out")
}
.frame(width: 250)
.background(.red)
SwitchPropertyCircularView(upperBoundValue: theSwitch.totalTravelDistance!, unit: "mm")
}
.background(.blue)
}
.frame(width: 380)
.background(Color.gray)
VStack {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("**Actuation Force**")
Text("the force needed to register a keypress")
}
.frame(width: 250)
.background(.red)
SwitchPropertyCircularView(upperBoundValue: Double(theSwitch.actuationForce!), unit: "gf")
}
.background(.blue)
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("**Bottom-out Force**")
Text("the force needed for a switch to bottom out")
}
.frame(width: 250)
.background(.red)
SwitchPropertyCircularView(upperBoundValue: Double(theSwitch.bottomOutForce!), unit: "gf")
}
.background(.blue)
}
.frame(width: 380)
.background(Color.gray)
}

On "Bottom-out Force," it looks like the first line is wide enough before it wraps words that it pushes the Text width to the maximum width of the view. On the others, it gets wrapped with shorter line lengths, and then ends up adding padding to center the text within the view.
You can fix this by adding a .leading alignment to your VStack. For example:
VStack(alignment: .leading, spacing: 5) {
Text("**Actuation Force**")
Text("the force needed to register a keypress")
}
.frame(width: 250, alignment: .leading) //<-- Here
Repeat for each VStack that shares the same qualities.

Related

Override HStack alignment for one child

Is there a way to dynamically override the alignment property of an HStack in an individual element?
Consider this scenario
There is a parent HStack with alignment = bottom
There are 3 elements inside the HStack of different sizes
I want the 3rd element to align to the top of the HStack. This alignment is different from the Hstack's bottom alignment
var body: some View {
HStack(alignment: .bottom) {
Rectangle()
.fill(.yellow)
.frame(height: 100)
Rectangle()
.fill(.blue)
.frame(height: 20)
// I want this to go to the top of the HStack
Rectangle()
.fill(.green)
.frame(height: 50)
}
.background {
Color.red
}
}
I'm trying to get the HStack to respect the highest height of 100 and just alter the last element's alignment.
I've tried wrapping the 3rd element in another stack but that only works if I specify a maxHeight equal to the tallest height among the parent's children, 100.
This means these rectangles have to know about their sibling elements.
HStack {
Rectangle()
.fill(.green)
.frame(height: 50)
}
.frame(maxHeight: 100, alignment: .top)
You could try this:
var body: some View {
HStack(alignment: .bottom) {
Rectangle()
.fill(.yellow)
.frame(height: 100)
Rectangle()
.fill(.blue)
.frame(height: 20)
VStack {
Rectangle()
.fill(.green)
.frame(height: 50)
Spacer()
}
}
.background {
Color.red
}
}
If, for some reason, you want to limit the range of how much space can the Spacer take up, you can add a modifier as per following example:
VStack {
Rectangle()
.fill(.green)
.frame(height: 50)
Spacer()
.frame(minHeight: 10, maxHeight: 50)
}

Different verticalAlignment in HStack in SwiftUI

I'm trying to create simple cell layout in SwiftUI but I somehow stumbled on problem how to define different vertical alignments of elements in same HStack:
This is basically what I'm trying to achieve:
Whole view should be a cell, where there are some arbitrary paddings(24 on top, 20 at bottom). What is important is following:
HStack contains icon (red), vstack (title and description) and another icon (green)
Red icon should be aligned to the top of the HStack as well as vstack with texts
Green icon should be centered in the whole view
I've tried to achieve this with following code:
VStack(spacing: 0) {
HStack(alignment: .top, spacing: 24) {
Image(nsImage: viewModel.icon)
.frame(width: 20.0, height: 20.0)
VStack(alignment: .leading, spacing: 4) {
Text(viewModel.title)
Text(viewModel.text)
}
Spacer()
Image(nsImage: "viewModel.next")
}
.padding([.top], 24)
.padding([.bottom], 20)
Divider()
}
Without luck as obviously the green icon is also aligned to the top. I've tried to mess around with layout guides without success.
Another solution I've tried is
VStack(spacing: 0) {
HStack(alignment: .top, spacing: 24) {
Image(nsImage: viewModel.icon)
.frame(width: 20.0, height: 20.0)
VStack(alignment: .leading, spacing: 4) {
Text(viewModel.title)
Text(viewModel.text)
}
Spacer()
VStack {
Spacer()
Image(nsImage: "viewModel.next")
Spacer()
}
}
.padding([.top], 24)
.padding([.bottom], 20)
Divider()
}
which doesn't work either as I have more of these 'cells' in super view and their height is stretched to fill the superview.
Any idea how to achieve this?
I would treat the left-hand image and text as a single, top-aligned HStack, then put that in another HStack aligned centrally with the right-hand image. In shorthand, omitting spacing etc.:
HStack(alignment: .center) {
HStack(alignment: .top) {
Image(nsImage: ...)
VStack(alignment: .leading) {
Text(...)
Text(...)
}
}
Spacer()
Image(nsImage: ...)
}
That way, you only have a spacer working in the horizontal axis, so your overall vertical frame will be determined by the content alone.

SwiftUI - LazyVGrid spacing

I have a LazyVGrid like so in SearchView.swift file
let layout = [
GridItem(.flexible()),
GridItem(.flexible())
]
NavigationView {
ZStack {
VStack {
....Some other stuff here
ScrollView (showsIndicators: false) {
LazyVGrid(columns: layout) {
ForEach(searchViewModel.allUsers, id: \.uid) { user in
NavigationLink(destination: ProfileDetailedView(userData: user)) {
profileCard(profileURL: user.profileURL, username: user.username, age: user.age, country: user.city)
}
}
}
}
}
}
}
My profileCard.swift looks like:
var body: some View {
ZStack {
Image.image(urlString: profileURL,
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
}
)
.frame(width: 185, height: 250)
.overlay(
LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .center, endPoint: .bottom)
)
.overlay(
VStack (alignment: .leading) {
Text("\(username.trimmingCharacters(in: .whitespacesAndNewlines)), ")
.font(.custom("Roboto-Bold", size:14))
.foregroundColor(Color.white)
+ Text("\(age)")
.font(.custom("Roboto-Light", size:14))
.foregroundColor(Color.white)
HStack {
Text("\(country)")
.font(.custom("Roboto-Light", size:14))
.foregroundColor(.white)
}
}
.padding(.leading, 15)
.padding(.bottom, 15)
,alignment: .bottomLeading
)
.cornerRadius(12)
}
}
This is producing 2 different spaces on different screen sizes:
iPhone 12:
iPhone 12 Pro Max
Im trying to get the same amount of spacing between the cards (horizontal and verticle) and the around the cards on all devices, any help on achieving this?
UPDATE
Following the example by #Adrien has gotten me closer to the problem, but when I use an image, the results change completely
let columns = Array(repeating: GridItem(.flexible(), spacing: 20, alignment: .center), count: 2)
ScrollView (showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(searchViewModel.allUsers, id: \.uid) { user in
NavigationLink(destination: ProfileDetailedView(userData: user)) {
HStack {
Image("placeholder-avatar")
.resizable()
.aspectRatio(contentMode: .fill)
}
.frame(maxWidth: .infinity, minHeight: 240) // HERE 1
.background(Color.black)
.cornerRadius(25)
}
}
}.padding(20)
}
The array of GridItem only fixes the behavior of the container of each cell. Here it is flexible, but that does not mean that it will modify its content.
The View it contains can have three different behaviors :
it adapts to its container:
like Rectangle, Image().resizable(), or any View with a .frame(maxWidth: .infinity, maxHeight: .infinity)) modifier.
it adapts to its content (like Text, or HStack).
it has a fixed size (as is your case with .frame(width: 185, height: 250))
If you want the same spacing (vertical and horizontal) between cells, whatever the device, the content of your ProfileDetailedView must adapt to its container :
you have to modify your cell so that it adopts behavior 1.
you can use the spacing parameters of yours GridItem (horizontal spacing) and LazyVGrid (vertical).
Example:
struct SwiftUIView5: View {
let columns = Array(repeating: GridItem(.flexible(), spacing: 20, alignment: .center), count: 2) // HERE 2
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) { // HERE 2
ForEach((1...10), id: \.self) { number in
HStack {
Text(number.description)
.foregroundColor(.white)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, minHeight: 200) // HERE 1
.background(Color.black)
.cornerRadius(25)
}
}
.padding(20) // HERE 2
}
}
}
Actually, the spacing is a function of the screen size, in your case. You have hard-coded the size of the card (185x250), so the spacing is as expected, a bit tighter on smaller screens, bit more airy on larger screens.
Note that the vertical spacing is consistent and as expected. This seems logical because there is no vertical size constraint.
BTW, the two screenshots are not the same size, otherwise this aspect would be more obvious.
If you want to keep the size of the card fixed, you can perhaps at least keep the horizontal space between them constant, but then you will have larger margins on the sides.
Or you can adjust the size of the card dynamically. Check out GeometryReader, which will help you access runtime screen sizes.

Adding 2 backgrounds within a Z and HStack?

The color.red is my widgets main background but I would like to add a black background to the top behind the text "Line1" and image "2". So basically a black bar that stretches across the top under the text and image.
var body: some View {
ZStack {
Color.red
VStack(alignment: .leading, spacing: 0) {
HStack {
Text("Line1")
.foregroundColor(.orange)
.bold()
.font(.system(size: 17.5))
.padding(.top)
Spacer()
Image("2")
.resizable()
.frame(width: 25, height: 25)
.padding(.top)
}
.padding(.horizontal)
.border(Color.green)
VStack(alignment: .leading) {
Image("2")
.resizable()
.frame(width: 67, height: 30)
Text("State")
.foregroundColor(Color.green)
.font(.system(size: 15))
Text("1")
.foregroundColor(Color.blue)
.font(.system(size: 10))
.opacity(0.5)
Spacer()
Spacer()
}
.padding(.horizontal)
.border(Color.blue)
}
}
}
You can add a .background modifier to your first HStack:
HStack {
//content here
}
.padding(.horizontal)
.border(Color.green)
.background(Color.black)
Keep in mind that order of modifiers is important, so if you put the background before the padding, the padding will not have that background color -- that's why I used it last.

Is it possible to get the calculated "minimum" height for a VStack in Swift UI

I'm working with a card-like view, and I would essentialy like to know the minimum height needed to house the elements, and then add a border around that.
this becomes difficult since VStack and HStack seem to want to take up the most space possible, so I need to manually insert a .frame(...) to coercse this to work, but that makes it very unflexible.
In the picture I'm highlighting the VStack which, as you can see, is overfilling beyond the constraints of the 48px frame height.
code
var body: some View {
HStack(spacing: 0) {
Image("cocktail")
.resizable()
.scaledToFit()
.padding(Size.Spacing.unit * 2)
.background(self.color)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(self.color)
)
HStack(spacing: 0) {
VStack(alignment: .leading) {
Text("Negroni").font(.headline)
Spacer()
HStack {
Tag(name: "bitter")
Tag(name: "sweet")
Tag(name: "strong")
}
}
.padding(.leading, 10)
Spacer()
}
}
.padding(Size.Spacing.unit * 2)
.frame(height: 48.0)
}
maybe i misunderstand you, but i tried your example...unfortunately it was not compilable, so i had to change some things. but as you can see the result (vstack - yellow) is not taking to much space, maybe because of your padding?
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Image("cocktail")
.resizable()
.scaledToFit()
// .padding(Size.Spacing.unit * 2)
.background(Color.blue)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.yellow)
)
HStack(spacing: 0) {
VStack(alignment: .leading) {
Text("Negroni").font(.headline)
Spacer()
HStack {
Text("bitter")
Text("sweet")
Text("strong")
}
}
.background(Color.yellow)
.padding(.leading, 10)
Spacer()
}.background(Color.red)
}
// .padding(Size.Spacing.unit * 2)
.frame(height: 48.0)
}
}
since VStack and HStack seem to want to take up the most space possible
This assumption is incorrect - by default stacks are tight to content and content tight to minimum with default padding between UI elements. In provided code it is Spacer() responsibility on expanding to max available space. So to make minimum space for content, highlighted VStack should be changed as follows:
VStack(spacing: 0, alignment: .leading) {
Text("Negroni").font(.headline)
HStack {
Tag(name: "bitter")
Tag(name: "sweet")
Tag(name: "strong")
}
}