Padding based on the screen size - swift

I have a card view:
var body: some View {
VStack {
Text(tip)
.padding(.bottom)
.font(.custom("Roboto-Light", size:10))
Text(tipTitle)
.multilineTextAlignment(.center)
.font(.custom("Roboto-Bold", size:24))
Text(tipMessage)
.font(.custom("Roboto-Light", size:14))
.multilineTextAlignment(.center)
.padding(.top,1)
Image(image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 150)
}
.padding()
.background(Color("grey"))
.cornerRadius(15)
.padding(.horizontal)
}
When I use these cards, the padding varies based on the text I add:
How can we fix the card size so the sizes for the cards are always the same?

A VStack will hug its contents, making itself as small as possible. If you want it to fill the available space, then make the contents as wide as possible. Your variable element is probably Text(tipMessage), so you should add a modifier to it, before the padding modifier, to make it fill the available space:
.frame(maxWidth: .infinity, alignment: .center)
A more complete screenshot showing the actual card contents would have been more helpful than the one you posted.

Related

Align heights of HStack members

I want to align the heights of two HStack members. The expected outcome would be that the image has the same size as the text. The current outcome is that the image has more height than the text. This is my current setup:
HStack {
Text("Sample")
.font(.largeTitle)
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
)
Spacer()
Image(systemName: "checkmark.seal.fill")
.resizable()
.scaledToFit()
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
)
}
What I've tried:
.fixedSize() -> I tried to tack this modifier onto the Image but the result was that the Image's height got smaller than the text's. Probably because the SFSymbol's intrinsic height is smaller than the .largeTitle intrinsic height.
AlignmentGuide -> I tried to create a custom alignment guide where I initially thought I could say "align bottom of Image and Text and align top of Image and Text" and therefore have the same height. But it seemed like you can only apply a single alignment guide per stack view.
GeometryReader -> I tried to wrap the HStack in a GeometryReader in which I tacked the .frame(height: proxy.frame.height) view modifier on the Text and Image. This also did not help because it somehow just made some white space around the views.
How it is:
How I want it:
Wrap your Image in a Text. Since your image is from SF Symbols, SwiftUI will scale it to match the dynamic type size. (I'm not sure how it will scale other images.)
VStack {
let background = RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
ForEach(Font.TextStyle.allCases, id: \.self) { style in
HStack {
Text("\(style)" as String)
.padding()
.background(background)
Spacer()
Text(Image(systemName: "checkmark.seal.fill"))
.padding()
.background(background)
}
.font(.system(style))
}
}
You can get the size of the Image small by adding a .frame() modifier to your HStack. See the code below,
HStack {
// Some Content
}
.frame(height: 60) // Interchangeable with frame(maxHeight: 60)
The Result:
For your exact example, I found 60 to be the sweet spot. But if you wanted a more dynamic solution, I'd make a few changes to your code. See the code below.
HStack {
Text("Sample")
.font(.largeTitle)
.frame(maxHeight: .infinity) // force the text to take whatever height given to the Parent View, which is the HStack
.padding(.horizontal) // Add padding to the Text to the horizontal axis
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
)
Spacer()
Image(systemName: "checkmark.seal.fill")
.resizable()
.scaledToFit()
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)
)
}
.background(Color.gray)
.frame(height: 100) // Change this value and the embedded Views will fit dynamically
The output will work as shown in the GIF below,
Here is an upgrade version of rob answer which support Assets Image plus system Image! Almost any Image! Like this:
struct ContentView: View {
var body: some View {
VStack {
ForEach(Font.TextStyle.allCases, id: \.self) { style in
HStack {
Text(String(describing: style))
.padding()
.background(Color.pink.opacity(0.5).cornerRadius(10.0))
Spacer()
}
.font(.system(style))
.background(
Image("swiftPunk")
.resizable()
.scaledToFit()
.padding()
.background(Color.yellow.cornerRadius(10.0))
, alignment: .trailing)
}
}
}
}
Result:

Swiftui Custom RoundedRectangle and Overlay Problem

I notice when i use an overlay on a rounded rectangle that there are some parts that are not overlayed, or maybe i'm doing it wrong
and then i tried to switch the view to a regular Iphone SE display, and i notice something that i really dislike, the top is not convered by red
Is there a way to fix it?? or any keyword for me to search? cuz there's probably someone with the same question as i am
Thanks
RoundedRectangle(cornerRadius:25)
.frame(height: 350, alignment:.center)
.foregroundColor(Color("Kolor1"))
.overlay(
ZStack {
Text("Makan")
HStack {
Spacer().frame(width:80)
VStack {
Spacer()
Image("Labtek")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 360, height: 260, alignment:.center)
}
}
}
)
.edgesIgnoringSafeArea(.top)

Text goes off screen after using .aspectRatio(contentMode: .fill)

I am trying to make an app with a background image using SwiftUI. However, the image is not the same aspect ratio as the screen, making me use .aspectRatio(contentMode: .fill) to fill the entire screen with it. This works completely fine until I start adding text. When adding text, it now goes off the screen instead of wrapping like it normally should do.
Here's my code:
struct FeaturesView: View
{
var body: some View
{
ZStack
{
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
}
}
And this is the preview:
As you can see, the text goes off the screen. I have tried using ´.frame()´ and specifying a width and height to fix it, but that causes issues when using the view inside other views. I am using the Xcode 12 beta.
I'm new to Swift and SwiftUI, so all help is appreciated :)
Of course it goes, because image expands frame of container, ie ZStack, wider than screen width.
Here is a solution - make image to live own life in real background and do not affect others. Tested with Xcode 12 / iOS 14.
var body: some View
{
ZStack
{
// ... other content
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
)
}
this happen because your ZStack using width that equal to your image, try to wrap your image using VStack and geometryReader .
GeometryReader { geometry in
ZStack {
Image() // your image
VStack(alignment: .leading) {
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}.foregroundColor(.white)
}.frame(maxWidth: geometry.size.width)
}
I'm write this on my windows it might some spelling error

HStack with SF Symbols Image not aligned centered

I have this simple SwiftUI code. I want all symbols to be aligned centered, just like the cloud symbol.
struct ContentView : View {
var body: some View {
HStack(alignment: .center, spacing: 10.0) {
Image(systemName: "cloud.sun")
Image(systemName: "cloud")
Image(systemName: "cloud.bolt")
Text("Text")
}.font(.title)
}
}
But as you can see below, the first and the last symbol are not centered. Am I missing something, or is this a bug?
Cheers!
This is what it's going on.
The Image views are not resizing.
It looks like they're not aware of their intrinsic content size, or maybe it reports the wrong value.
To fix it:
struct ContentView : View {
var body: some View {
HStack(alignment: .center, spacing: 10.0) {
Image(systemName: "cloud.sun")
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.red)
Image(systemName: "cloud")
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.yellow)
Image(systemName: "cloud.bolt")
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.pink)
Text("Text").background(Color.green)
}
.frame(width: 250, height: 50)
.background(Color.gray)
.font(.title)
}
}
...make the Images resizable, and also make sure the aspect ratio is set to .fit, or they will stretch.
Set also frame on the HStack or it will expand to fill the whole screen.
#MartinR suggested an even better solution - creating the images via UIImage - see his comment below.
struct ContentView : View {
var body: some View {
HStack {
Image(uiImage: UIImage(systemName: "cloud.sun")!)
.background(Color.red)
Image(uiImage: UIImage(systemName: "cloud")!)
.background(Color.yellow)
Image(uiImage: UIImage(systemName: "cloud.bolt")!)
.background(Color.pink)
Text("Text").background(Color.green)
}
.background(Color.gray)
.font(.title)
}
}
Output:
I came across the same problem as you: SF Symbols are not reporting the correct content size on iOS 13. (Though, it is fixed on iOS 14 and above.)
The problem with using UIImage as proposed in Matteo Pacinis solution is the poor compatibility with the SwiftUI dynamics: foreground color and font size (and dynamic type!) are not simply taken from the current SwiftUI context but have to be duplicated into the UIImage configuration (using UIImage(systemName:, withConfiguration:)) wich is often not practical. (see hackingwithswift.com for how to use this).
Looking at the problem above I propose the following solution:
HStack(alignment: .center, spacing: 10.0) {
Image(systemName: "cloud.sun")
.hidden()
.overlay(
Image(systemName: "cloud.sun")
.resizable()
.aspectRatio(contentMode: .fill)
.background(Color.red)
)
Image(systemName: "cloud")
.hidden()
.overlay(
Image(systemName: "cloud")
.resizable()
.aspectRatio(contentMode: .fill)
.background(Color.yellow)
)
Image(systemName: "cloud.bolt")
.hidden()
.overlay(
Image(systemName: "cloud.bolt")
.resizable()
.aspectRatio(contentMode: .fill)
.background(Color.pink)
)
Text("Text")
.background(Color.green)
}
.background(Color.gray)
.font(.title)
It looks like a lot of code duplication but it has the advantage, that the symbols scale according to your font, respect the foregroundColor modifier and align according to your desired alignment
Output:
But there is still some issue with this approach: the image still has not the correct intrinsic size, the symbols are simply drawn "nicely centered". This means that the height of the HStack still depends of the height of the Text element.
If you simply want to draw the symbols in .largeTitle font and the text in .body font, the result would look like below possibly causing overlapping with neighbouring views:
I will still investigate further to find a solution ensuring the correct view size, as this really annoys me.

Making Button span across VStack

I currently have the following SwiftUI view:
HStack {
...
VStack {
TextField { ... }
SecureField { ... }
Button { ... }
}
...
}
I've added a .background(Color.green) to the Button, and as you can see, the view is very snug to the text.
I'm wondering if there's a way to adjust the width of the button so that it fills across VStack - something like a .fill mode for UIStackView.
The best way to do this is via .frame(maxWidth: .infinity)
https://developer.apple.com/documentation/swiftui/view-layout
If you want the button not to be centered you need to specify alignment.
e.g.: .frame(maxWidth: .infinity, alignment: .leading)
Button(action: handleSignInAction) {
Text("Sign In")
}
.frame(maxWidth: .infinity)
.background(Color.green)
Old answer from 2019:
You could use a HStack with a Text and Spacer to get a Button that fills the width of its parent:
Button(action: handleSignInAction) {
HStack {
Spacer()
Text("Sign In")
Spacer()
}
}.background(Color.green)
#d.felber's answer is almost complete, but you'd need a Spacer() on each side to center:
Button(action: {
// TODO: ...
}) {
HStack {
Spacer()
Text("Sign In")
Spacer()
}
}
.frame(maxWidth: .infinity)
Did the trick for me.
If you would like to stick to the method SwiftUI docs suggest you need to use GeometryReader and set the buttons width manually. Geometry reader updates its properties for different devices and upon rotation.
GeometryReader { geometry in
Button().frame(width: geometry.size.width, height: 100)
}
Use frame(maxWidth: .infinity) inside the Button like this:
Button(action: {...}) {
Text("Sign In")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.tint(.green)
Not like this, because the tappable area doesn't stretch:
Button(action: {}) {
Text("Sign In")
}
.frame(maxWidth: .infinity)
.background(Color.green)
Make the button text's frame the size of the UIScreen and then set the background color after it (make sure all style changes are done after changing the frame size, otherwise the style changes will only be visible on the original default frame). The frame size will propagate upward to increase the width of the button to the width of the screen as well.:
Button(action: {
// Sign in stuff
}) {
Text("Sign In")
.frame(width: UIScreen.main.bounds.width, height: nil, alignment: .center)
.background(Color.green)
}
You can also add some negative horizontal padding in between setting the frame and background in order to offset from the edge of the screen:
Button(action: {
// Sign in stuff
}) {
Text("Sign In")
.frame(width: UIScreen.main.bounds.width, height: nil, alignment: .center)
.padding(.horizontal, -10.0)
.background(Color.green)
}
Something like this?
Button(action: {
// Do your login thing here
}) {
Capsule()
.frame(height: 44)
.overlay(Text("Login").foregroundColor(Color.white)).padding()
}
I am not sure if there is a .fill method similar to UIStackView but, if what you want to do is provide some spacing on the Button (or any view for that matter) what worked for me is either setting the frame or padding
.frame(width: 300, alignment: .center) (we can also set a height here but, if not it should be able to infer the height based on the button text.
If you don't want to set an arbitrary width, you can also import UIKit and make use of UIScreen and get the devices full width. (There may be a SwiftUI way of getting this but, haven't found it at this time yet)
.frame(width: UIScreen.main.bounds.width, alignment: .center)
and then you can add a little bit of padding for some breathing room
.padding(.all, 20)
The issue with the padding is that it will add onto the additional width of the screen so we would have to take into account when setting the width.
(20 * 2 sides from leading and trailing)
.frame(width: UIScreen.main.bounds.width - 40, alignment: .center)
(breathing room is for when the text covers to the end of the screen or if your alignment is .leading or .trailing)