I'm beginner in SwiftUI and I had some misunderstanding with the .frame() modifier.
I want to create a right-aligned image, so I'm doing this:
Image(systemName: "xmark")
.frame(maxWidth: .infinity, alignment: .leading)
.padding(5)
.background(.gray)
.mask(Circle())
.padding()
But the image appears in the center of my screen. I changed my code to this:
Image(systemName: "xmark")
.padding(5)
.background(.gray)
.mask(Circle())
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
And it works the way I want it to, but I'm so confused as to why this code works. Can someone explain to me why this code works and the previous one doesn't?
I also see that people write code like that:
VStack {
...
}
.padding()
.frame(maxWidth: 200)
.frame(maxWidth: .infinity, alignment: .leading)
Does that even make sense? Aren't we redefining maxWidth with the second .frame() modifier?
Related
So I just want to do something pretty simple which is have an Image take half the size of its container and a Vstack the other half.
Im currently doing this to force both views to take as much space as they can with maxWidth: .infinity, so they end up taking equal amounts of space:
struct ContentView: View {
var body: some View {
HStack {
Image("landscape")
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 170)
VStack {
Text("Test")
Text("Test 2")
}
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: 170)
.background(Color.red)
.padding()
}
}
But it ends up in this poor result, sadly:
Any tips? I saw GeometryReader might come in handy here but seems to push my Views all the way to the top and end up with unwanted results.
you need to add a fixedSize() modifier to HStack and keep it's vertical to true.
var body: some View {
HStack {
Image("1")
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 170)
VStack {
Text("Test")
Text("Test 2")
}
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: 170)
.background(Color.red)
.padding()
.fixedSize(horizontal: false, vertical: true)
}
you can read more about fixedsize() here
Now your HStack will be like this
I'm creating an iOS minesweeper game and want to have a bar at the top with three pieces of information:
The high score (next to a trophy symbol) [LEFT]
The current time (next to a timer symbol) [CENTER]
The number of bombs left (next to a bomb symbol) [RIGHT]
However, the elements are not evenly aligned as you can see in the picture at the bottom- I'm guessing the spacers are automatically the same size. So, because the text on the left side of the screen is wider than that on the right, the middle text is offset to the right.
How can I align these items so the center time/image is perfectly in the middle with the other objects on the left/right?
My code looks like:
struct GameView: View {
#EnvironmentObject var game: Game
#State var labelHeight = CGFloat.zero
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("MINESWEEPER")
.font(.system(size: 25, weight: .bold, design: .monospaced))
.kerning(3)
.foregroundColor(.red)
HStack(alignment: .center, spacing: 5) {
Image(systemName: "rosette")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.highScoreString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image(systemName: "timer")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.timerString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
.overlay(GeometryReader(content: { geometry in
Color.clear
.onAppear(perform: {self.labelHeight = geometry.frame(in: .local).size.height})
}))
}
.frame(width: UIScreen.main.bounds.width * 0.9, height: labelHeight, alignment: .center)
BoardView()
}
}
}
Instead of aligning by spacers, move each part into separate view, say LeftHeader, CenterHeader, and RightHeader and use frame alignment, which gives exact separation by equal sizes, like
HStack {
LeftHeader()
.frame(maxWidth: .infinity, alignment: .leading)
CenterHeader()
.frame(maxWidth: .infinity, alignment: .center)
RightHeader()
.frame(maxWidth: .infinity, alignment: .trailing)
}
This can be achieved using a ZStack , HStack and Spacers.
ZStack{
HStack{
Text("Left")
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right")
}
}
Even if you remove left ( or right ) HStack, it won't affect the other components or the layout.
You can make it look bit nicer by adding paddings like this,
ZStack{
HStack{
Text("Left").padding([.leading])
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right").padding([.trailing])
}
}
I think you can create more one stack.
My solution like:
/**
* spacing is an example
* minWidth calculated by fontSize
*/
HStack(alignment: .center, spacing: 20){
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
}
Hope it useful for you.
How to align a Text with left alignment in a VStack that is occupying more space like this
This is the code am using
VStack(alignment:.leading) {
Text("Test")
.font(.system(size: 10))
.frame(maxWidth: .infinity, maxHeight: .infinity)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
You need to add alignment to Text frame, like
VStack(alignment:.leading) {
Text("Test")
.font(.system(size: 10))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) // << here !!
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
Try using this modifier on your Text view: .multilineTextAlignment(.leading)
I am creating a Widget in SwiftUI and I'm finding what I believe is a very silly issue.
I have an Image that I want to use as a background. I have set its aspect ratio as .fill but it doesn't seem to do it.
Here's the code:
var body: some View {
ZStack {
Image(affirmation.customImageName ?? "c_0")
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300, alignment: .center)
.background(Color.green)
HStack {
VStack {
Text(affirmation.title)
.font(.title)
.multilineTextAlignment(.leading)
}
Spacer()
}
.padding(.leading, 5)
.padding(.top, 5)
}
}
And how it looks:
Also, if I delete .frame(width: 300, height: 300, alignment: .center), the image won't use the whole area.
UPDATED:
Now the image resizes thanks to #staticVoidMan answer, adding resizable() did the trick. The issue now is that the Text seems to also resize and fill more than its space.
var body: some View {
ZStack {
Image(affirmation.customImageName ?? "c_0")
.resizable()
.aspectRatio(contentMode: .fill)
HStack {
Text(affirmation.title)
.font(.body)
.multilineTextAlignment(.leading)
.lineLimit(nil)
Spacer()
}
.padding(.leading, 5)
.padding(.top, 5)
}
Here's the image:
Thanks in advance
.resizable is an Image view modifier that allows it to occupy the maximum amount of space required.
ZStack by default can stretch indefinitely to fit all it's inner views and hence you need to define the frame via the .frame modifier so all the inner views stay within defined limits.
i.e.
ZStack {
//...
}
.frame(width: 300, height: 300)
Solution:
ZStack(alignment: .leading) {
Image("yourImageName")
.resizable()
.aspectRatio(contentMode: .fill)
.background(Color.green)
Text("Lorem ipsum dolor set amet")
.font(.title)
.padding(.leading, 5)
}
.frame(width: 300, height: 300)
ZStack(alignment: .leading) reduces the need of HStack:Text|Spacer to just the Text
.multilineTextAlignment(.leading) is default so not required
.lineLimit(nil) is default so not required
code
V/Hstack({}).frame(maxWidth: .infinity, maxHeight: .infinity).background(
Image("yourImageName")
.resizable()
.scaledToFill()
)
check your image resolution.I came into the image resolution problem. The image with resolution value 72x72 cannot fulfill the Widget background. Use the image with 144x144 resolution
Currently building a chat application and I need new messages to appear at the bottom of the screen. I also need to have messages aligned to the bottom. However, using VStack inside ScrollView features top alignment by default.
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)
}
What should I do to fix this?
A bit late to answer that one but it might help someone else. You could use a good old double rotation trick to achieve the result you want. Other solutions does not work because the maximum size inside a scroll view is undefined as it depends on its child views to compute its own content size.
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
}
.padding()
.rotationEffect(Angle(degrees: 180))
}
.rotationEffect(Angle(degrees: 180))
ScrollView does not propose the same height to its children (VStack in this case) as it was proposed to it. It just asks for minimal size. The solution is to make its direct child respond with the minimum height which was proposed to ScrollView itself. We can achieve this by reading the proposed height via GeometryReader and setting that as minimum height of the child.
GeometryReader { reader in
ScrollView {
VStack {
/// Spacer() or Color.red will now behave as expected
}
.frame(minHeight: reader.size.height)
}
}
I think you're missing a maxHeight in VStack
VStack(alignment: .leading) {
Spacer().frame(maxWidth: .infinity)
// content here
}
.frame(maxHeight: .infinity) // <- this
If you remove Spacer() and put it after the closing braces of your ForEach, it will fix it. Here is how your updated code will look like:
ScrollView {
VStack(alignment: .leading, spacing: 16) {
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
Spacer()
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)
}
Try changing all of your Alignment.topLeadings to Alignment.bottomLeading