SwiftUI. How to set shrink behavior for Spacer? - ios

How to set default spacing between rgb views (100pt) if their container (VStack) not conflicts with bottom black view.(like iPhone 11 Pro Max). BUT shrinks if there is no space for 100p height.(like iPhone SE on the screenshot)
My code:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
VStack(spacing: 0) {
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.green)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
}
Spacer()
.frame(minHeight: 10, maxHeight: 600)
Rectangle() // keyboard
.frame(height: 200)
}
}
}
So the problem is:
Spacers with maxHeight: 100 have height = 10 (not 100) on iPhone 11 Pro Max. (BUT space between black view and VStack allows it)
How to make behavior I explained?

You need to use idealHeight alongside with .fixedSize modifier for Spacers:
Spacer()
.frame(minHeight: 10, idealHeight: 100, maxHeight: 600)
.fixedSize()

Use Spacer(minLength: 10) for the last spacer.
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
VStack(spacing: 0) {
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.green)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
}
Spacer(minLength: 10)
Rectangle() // keyboard
.frame(height: 200)
}
}
}
The problem in your code is that when you wrap a Spacer inside a frame like Spacer().frame(minHeight: 10, maxHeight: 600), it is first considered as a frame, then a Spacer inside that frame. And a frame has equal default layout priority as other views. So the parent will propose it the same amount of space as the inner VStack. By removing the frame modifier, the Spacer has the least layout priority, so the inner VStack will take as much space as possible except the minimum 10 points claimed by the spacer and 200 points for the rectangle.

I ran into a similar problem...
This fixed it for me:
Spacer().frame(minWidth: 0)

Related

Get elements to fill width screen in LazyHStack

I have a LazyHstack in a horizontal scrollview :
struct HorizontalSpotsList: View {
var spots: [Spot]
var body: some View {
ScrollView(.horizontal){
LazyHStack(alignment: .center, spacing: 0){
if (!spots.isEmpty){
ForEach(spots.prefix(upTo: 3) , id: \.self) { spot in
Text("Test").frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 25)
.fill(Color.white)).border(Color.blue)
}
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).border(Color.red)
}.frame(width: .infinity, height: 130).border(Color.green)
}
}
How can i get my lazyHStack elements to fill all the width screen ?
My LazyHstack doesn't seems to fill the width, i tried to apply .frame(width: .infinity) or .frame(maxWidth: .infinity) to different combinaisons of my scrollView / LazyHstack / Text element but it doesn't seems to work.
You could use Geometry Reader. You would have to apply a height however.
GeometryReader { geo in
ScrollView {
LazyHStack {
content
}.frame(width: geo.size.width, height: #)
}
}
Try adding Spacer() in between the Text elements.

SwiftUI - How to align elements in left, center, and right within HStack?

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.

SwiftUI ZStack takes all screen height but should be fixed height

My code:
public var body: some View {
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
Text("Text")
.padding(.bottom, 42)
.foregroundColor(.red)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.background(Color.red)
}
makes ZStack takes almost all screen height. But I expect it will take height from .frame() method.
I have a workaround for you, it's a bit messed up but works
public var body: some View {
ZStack {
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.zIndex(0)
ZStack {
VStack {
VStack {
Text("Text")
.padding(.top, 42)
.foregroundColor(.red)
}
.frame(width: UIScreen.main.bounds.width, height: 182)
VStack {
Text("Your texts here")
}
.frame(width: UIScreen.main.bounds.width)
Spacer()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
.zIndex(1)
}
.background(Color.red)
}
I simply made your ellipse on another layer and text on the other.
ZStack(alignment: .bottom) {
Ellipse()
.fill(.yellow)
}
.frame(width: 546, height: 364)
.position(x: UIScreen.main.bounds.size.width / 2, y: Spacing.padding_0_5)
.edgesIgnoringSafeArea(.top)
.zIndex(0)
The .zIndex(0) makes sure that the view is in the background.
ZStack {
VStack { // This VStack contains all your text
VStack { // First VStack
Text("Text")
.padding(.top, 42)
.foregroundColor(.red)
}
.frame(width: UIScreen.main.bounds.width, height: 182)
VStack { //Second VStack
Text("Your texts here")
}
.frame(width: UIScreen.main.bounds.width)
Spacer()
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
.zIndex(1)
Here, the ZStack takes up the entire screen. We added a VStack which contains your texts.
The first VStack has your main label over the Ellipse, and its frame is hardcoded according to the height of the Ellipse (1/2 the height as the other half of the ellipse is outside the screen).
The second VStack starts from the end of our first VStack which was the functionality needed, finally added a spacer() so that the text is placed at the top rather than middle.
The zIndex(1) makes sure that is placed over the elements at zIndex(0)

SwiftUI: Build View from bottom alignment in scrollView

Is there a way to build UI elements in SwiftUI inside scroll view with bottom Alignment?
My use case: I have a screen where the screen has
Spacer (What ever is left after allocating below elements)
LogoView
Spacer() - 30
Some Text - 4/5 lines
Spacer() - 50 (this will be calculated off of GR size Height)
HStack with Two Button - This should be pinned to bottom of view / ScrollView
I would like to know how Can I pin the HStack view to ScrollView Bottom
I've replicated my setup and reproduced my problems in a Swift playground like so
struct ContentView: View {
var body: some View {
GeometryReader { gr in
ScrollView {
VStack {
Spacer()
Image(systemName: "applelogo")
.resizable()
.frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
Spacer().padding(.bottom, gr.size.height * 0.01)
Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
.fontWeight(.regular)
.foregroundColor(.green)
.multilineTextAlignment(.center)
.padding(.top, gr.size.height * 0.05)
.padding(.horizontal, 40)
.layoutPriority(1)
Spacer()
.frame(minHeight: gr.size.height * 0.12)
HStack {
Button(action: {
}, label: {
Text("Button 1")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
Button(action: {
}, label: {
Text("Button 2")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
.padding(.horizontal, 20)
}
//.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
I understand that when scrollView is introduced in SwiftUI view Spacer length is changed to Zero, Would like to know what's the best way to achieve this
struct SampleView: View {
var body: some View {
GeometryReader { gr in
VStack {
ScrollView {
VStack {
// Fills whatever space is left
Rectangle()
.foregroundColor(.clear)
Image(systemName: "applelogo")
.resizable()
.frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
.padding(.bottom, gr.size.height * 0.06)
Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
.fontWeight(.regular)
.foregroundColor(.green)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
.layoutPriority(1)
// Fills 12 %
Rectangle()
.frame(height: gr.size.height * 0.12)
.foregroundColor(.clear)
HStack {
Button(action: {
}, label: {
Text("Button 1")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
Button(action: {
}, label: {
Text("Button 2")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
// Makes the content stretch to fill the whole scroll view, but won't be limited (it can grow beyond if needed)
.frame(minHeight: gr.size.height)
}
}
}
}
}
I wanted the scrollview to appear bottom to top instead of top to bottom. This had my answer: https://www.thirdrocktechkno.com/blog/implementing-reversed-scrolling-behaviour-in-swiftui/
TLDR: .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) on the scrollview AND on the items in the scrollview

SwiftUI Mask a rectangle inside a rounded rectangle

Hello there. I am wondering, in SwiftUI, how do you mask the contents of a rounded rectangle so that a child rectangle clips the corners.
In my example I have a white rounded rectangle and a pink rectangle on a zstack, I've tried to apply clipping, but the pink rectangle does not conform to the corners.
I've tried applying .mask to the white rectangle, but it gives different results to expectations (sometimes it doesn't show the pink rectangle).
I did find an example where you can set your own cornerRadius
Round Specific Corners SwiftUI
But I was wondering if perhaps there was a way to mask the internals/body of the pink rectangle so that it conforms to the parent's rounded rectangle?
My code follows;
var body: some View {
GeometryReader { geometry in
Color.gray
.edgesIgnoringSafeArea(.top)
.overlay(
ZStack (alignment: .topLeading) {
RoundedRectangle(cornerRadius: 16,
style: .continuous)
.foregroundColor(.white)
.shadow(radius: 10)
// Tried using .mask here
Rectangle()
.fill(Color.pink)
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 150, alignment: .top)
.clipped()
}
.frame(width: 300, height: 450, alignment: .center)
)
}
.edgesIgnoringSafeArea(.all)
}
Edit: To clarify:
The pink rectangle should remain as a rectangle, but clip the top left and right to match the parent white rounded rectangle.
If I correctly understood your goal, here is a solution - the only needed clip in right place is after internal content (two rectangles in this case) is constructed. So clipping with RoundedRectangle gives rounded corners around entire card. (As well as shadow most probably is needed to entire card, so placed at the end).
UPDATE: re-tested with Xcode 13.3 / iOS 15.4
ZStack (alignment: .topLeading) {
Rectangle()
.foregroundColor(.white)
Rectangle()
.fill(Color.pink)
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 150, alignment: .top)
}
.clipShape(RoundedRectangle(cornerRadius: 16)) // << here !!
.frame(width: 300, height: 450, alignment: .center)
.shadow(radius: 10)
#Asperi already posted a great answer, I have done this aswell with using mask modifier in SwiftUI. Furthermore you only have to set cornerRadius once.
VStack(spacing: 0)
{
ZStack(alignment: .center)
{
Rectangle()
.fill(Color.red)
.frame(width: 66, height: 20)
}
ZStack(alignment: .center)
{
Rectangle()
.fill(Color.white)
.frame(width: 66, height: 46)
}
}
.mask(Rectangle()
.cornerRadius(3.0)
.frame(width: 66, height: 66)
)
check this out
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
Color.gray
.edgesIgnoringSafeArea(.top)
.overlay(
ZStack (alignment: .topLeading) {
RoundedRectangle(cornerRadius: 16,
style: .continuous)
.foregroundColor(.white)
.shadow(radius: 10)
// Tried using .mask here
Rectangle()
.fill(Color.pink)
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 150, alignment: .top)
.clipShape(RoundedRectangle(cornerRadius: 16)) // <<<<<<
}
.frame(width: 300, height: 450, alignment: .center)
)
}
.edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I think the easier way is to apply cornerradius to ZStack
ZStack (alignment: .topLeading) {
RoundedRectangle(cornerRadius: 16,
style: .continuous)
.foregroundColor(.white)
.shadow(radius: 10)
// Tried using .mask here
Rectangle()
.fill(Color.pink)
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 150, alignment: .top)
//.clipped() //<<= here
}
.frame(width: 300, height: 450, alignment: .center)
.cornerRadius(20) //<<= here

Resources