Swift UI setting making rectangle square - swift

In SwiftUI, I have a VStack with a Rectangle inside. The rectangle automatically fills the parent VStack, which is great for the width, but I want to make the height equal to the width so it's square. How do I set the aspect ratio to 1:1 or set height = width?
VStack {
Rectangle()
.frame(height: ?? ) // I want to make the height equal to width so it's square
Spacer()
}

It's the .aspectRadio() modifier that you need:
public func aspectRatio(_ aspectRatio: CGFloat? = nil,
contentMode: ContentMode) -> some View
Parameters:
aspectRatio: The ratio of width to height to use for the resulting view. Use nil to maintain the current aspect ratio in the resulting view.
contentMode: A flag that indicates whether this view fits or fills the parent context.
Return Value: A view that constrains this view’s dimensions to the aspect ratio of the given size using contentMode as its scaling algorithm.
If you give it an explicit aspectRatio of 1.0 you can be sure that it will retain its square shape:
VStack {
Rectangle()
.aspectRatio(1.0, contentMode: .fit)
Spacer()
}

I assume you expect this
VStack {
Rectangle()
.aspectRatio(contentMode: .fit)
Spacer()
}

Related

SwiftUI layout in VStack where one child is offered a maxHeight but can use less than that height

I'm trying to build a layout inside a VStack that contains two children. The first child should take up all available space unused by the second child. The second child has a preferred size based on its own contents. I'd like to limit the height of the second child to a maximum height, but it should be able to take less than the maximum (when its own contents cannot make use of all the height). This should all be responsive to the root view size, which is the parent of the VStack (because the device can rotate).
My attempt uses the .frame(maxHeight: n) modifier, which seems to unconditionally takes up the entire n points of height, even when the view being modified doesn't use it. This results in whitespace rendered above and below the VStack's second child. This problem is shown in the Portrait preview below - the hasIdealSizeView only has a height of 57.6pts, but the frame that wraps that view has a height of 75pts.
import SwiftUI
struct StackWithOneLimitedHeightChild: View {
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
fullyExpandingView
hasIdealSizeView
.frame(maxHeight: geometry.size.height / 4)
}
}
}
var fullyExpandingView: some View {
Rectangle()
.fill(Color.blue)
}
var hasIdealSizeView: some View {
HStack {
Rectangle()
.aspectRatio(5/3, contentMode: .fit)
Rectangle()
.aspectRatio(5/3, contentMode: .fit)
}
// the following modifier just prints out the resulting height of this view in the layout
.overlay(alignment: .center) {
GeometryReader { geometry in
Text("Height: \(geometry.size.height)")
.font(.system(size: 12.0))
.foregroundColor(.red)
}
}
}
}
struct StackWithOneLimitedHeightChild_Previews: PreviewProvider {
static var previews: some View {
Group {
StackWithOneLimitedHeightChild()
.previewDisplayName("Portrait")
.previewLayout(PreviewLayout.fixed(width: 200, height: 300))
StackWithOneLimitedHeightChild()
.previewDisplayName("Landscape")
.previewLayout(PreviewLayout.fixed(width: 300, height: 180))
}
}
}
This observed result is consistent with how the .frame(maxHeight: n) modifier is described in the docs and online blog posts (the flow chart here is extremely helpful). Nonetheless, I can't seem to find another way to build this type of layout.
Related question: what are the expected use cases for .frame(maxHeight: n)? It seems to do the opposite of what I'd expect by unconditionally wrapping the view in a frame that is at least n points in height. It seems no different than .frame(height: n), using an explicit value for the offered height.
The behavior of .minHeight in this example is strange and far from intuitive. But I found a solution using a slightly different route:
This defines the minHeight for the expanding view (to get the desired layout in portrait mode), but adds a .layoutPriority to the second, making it define itself first and then give the remaining space to the upper view.
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
fullyExpandingView
.frame(minHeight: geometry.size.height / 4 * 3)
hasIdealSizeView
.layoutPriority(1)
}
}
}
There's probably a really short way to go about this but in the meantime here is what I did.
Firstly I created a struct for your hasIdealSizeView and I made it return a GeometryProxy, and with that i could return the height of the HStack, in this case, the same height you were printing on to the Text View. then with that I used the return proxy to check if the height is greater than the maximum, and if it is, i set it to the maximum, otherwise, set the height to nil, which basically allows the native SwiftUI flexible height:
//
// ContentView.swift
// Test
//
// Created by Denzel Anderson on 3/16/22.
//
import SwiftUI
struct StackWithOneLimitedHeightChild: View {
#State var viewHeight: CGFloat = 0
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
fullyExpandingView
.overlay(Text("\(viewHeight)"))
// GeometryReader { geo in
hasIdealSizeView { proxy in
viewHeight = proxy.size.height
}
.frame(height: viewHeight > geometry.size.height / 4 ? geometry.size.height / 4:nil)
}
.background(Color.green)
}
}
var fullyExpandingView: some View {
Rectangle()
.fill(Color.blue)
}
}
struct hasIdealSizeView: View {
var height: (GeometryProxy)->()
var body: some View {
HStack {
Rectangle()
.fill(.white)
.aspectRatio(5/3, contentMode: .fit)
Rectangle()
.fill(.white)
.aspectRatio(5/3, contentMode: .fit)
}
// the following modifier just prints out the resulting height of this view in the layout
.overlay(alignment: .center) {
GeometryReader { geometry in
Text("Height: \(geometry.size.height)")
.font(.system(size: 12.0))
.foregroundColor(.red)
.onAppear {
height(geometry)
}
}
}
}
}

How to align elements on ZStack in SwiftUI?

Problem
I am trying to create a view of a card with a symbol in the middle.
I tried to achieve this by creating a ZStack.
However, despite using .center alignment, the symbol always show in the top left.
Code
In the following code, the contentShape shows on the top-left despite alignment setting.
ZStack(alignment: .center) {
let baseShape = RoundedRectangle(cornerRadius: 10)
let contentShape = Rectangle()
.size(width: width, height: height)
.foregroundColor(getContentColor(color: card.color))
baseShape.fill().foregroundColor(.white)
baseShape.strokeBorder(lineWidth: 3, antialiased: true)
contentShape
}
Question
How do I properly align the contentShape at the center of the ZStack?
You need to use frame instead of size, because size is just for path drawing within provided rect, but rect here is entire area, so to fix use
ZStack(alignment: .center) {
let baseShape = RoundedRectangle(cornerRadius: 10)
let contentShape = Rectangle()
.frame(width: width, height: height) // << here !!
.foregroundColor(getContentColor(color: card.color))
baseShape.fill().foregroundColor(.white)
baseShape.strokeBorder(lineWidth: 3, antialiased: true)
contentShape
}

GeometryReader's child view overflowing width

I'm currently using GeometryReader and Image to have images in a 3-column grid. In this grid, I want when user taps the image, they will see the bigger version of it.
However, when I implemented this, GeometryReader is not functioning properly. The image within the GeometryReader is overflowing the frame size passed to the GeometryReader.
I have checked that the size of GeometryReader is 1:1 ratio, but when you see the screenshot of view hierarchy, the size of image is different.
Below is the code for my custom Image view.
GeometryReader { geo in
if isZoomable {
Image("Image_gallery_default")
.centerCropped(width: geo.size.width, height: geo.size.height)
.aspectRatio(1.0, contentMode: .fill)
.onTapGesture {
showImage = true
}
} else {
Image("Image_gallery_default")
.centerCropped(width: geo.size.width, height: geo.size.height)
}
}
And centerCropped modifier is just a set of resizable/scaletofill/frame/clipped to make image center cropped.
self
.resizable()
.scaledToFill()
.frame(width: width, height: height)
.clipped()
The image on screen is just fine picture.
But if you tap on the right side of first picture (turlip one), it will instead show the picture on right side.
Why is the Image view overflowing instead of following the width of GeometryReader?
I searched to find out the solution, but nothing similar came up.

SwiftUI content gets cut off on smaller iPhone screens

I'm new to SwiftUI, so I'm following a tutorial to get familiar with it. However, my app's content is getting cut off on smaller screens (both vertically and horizontally). How can I prevent this from happening?
Here's my code:
EDIT: I have added borders around my images and resized the images as suggested in your comments and answers, but as you can see, the images don't appear to be taking up any more space than they're supposed to.
struct ContentView: View {
var body: some View {
ZStack {
Image("background").ignoresSafeArea(.all)
VStack {
Spacer()
Image("logo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.border(Color.black)
Spacer()
HStack {
Spacer()
Image("card3").border(Color.black, width: 3)
Spacer()
Image("card4").border(Color.black, width: 3)
Spacer()
}
Spacer()
Image("dealbutton")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.border(Color.black)
Spacer()
HStack {
Spacer()
VStack {
Text("Player").padding(.bottom, 10)
Text("0").font(.largeTitle)
}
Spacer()
VStack {
Text("CPU").padding(.bottom, 10)
Text("0").font(.largeTitle)
}
Spacer()
}
.foregroundColor(.white)
Spacer()
}
}
}
}
And here's what the preview looks like on an iPod touch:
Try making Image("background") resizable or set it as the .background(:) of your ZStack. Currently the background image isn’t resizable and is larger than the screen, so it shows at its native size and stretches its parent ZStack beyond the bounds of the screen. Since your content is in that same ZStack, it also extends beyond the bounds of the screen
Your issue is related to your images that you have present on the view structure itself. Images are rendered at 100% their size, irrespective of their constraints. This will cause other views to be pushed away. The solution for that is to set a set size on the view itself that matches within the confines of your available space. Also you're resizing your ZStack which also resizes the content inside of the ZStack. For example.
Image("logo")
.resizeable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
Handling your image like this will ensure that it is set to the appropriate size when it is rendered. Then you can have the remaining views fall into place in a way that's expected. If you need it scaled on a % for the screen size you can use a GeometryReader to scale the view for different screen sizes.
GeometryReader { reader in
Image("logo")
.resizeable()
.aspectRatio(contentMode: .fit)
.frame(width: reader.size.width * 0.2)
}
Finally, remove your ZStack and set it up like this.
VStack {
//Your Content
}.background(
//Make sure to set the edgesIgnoring.. on this NOT the VStack
Image("background").edgesIgnoringSafeArea(.all)
)
Tip When Working with Stacks
Stacks, wether VStack, HStack, or ZStack's all ALWAYS have their frame set to the content that is held inside of them. If you had an object with a width of 100 and a height of 10,000 then the Stack would also have those dimensions, unless otherwise specified with a Modifier such as .frame(width...) or even, in your case, an Image that is resized.
Suppose your same stack then has a width: 10, height: 10 view added to it, it would still retain the same size as the largest content held within. This is of course handled differently with HStack and VStacks as they actually stack things in a 2D plane, whereas the ZStack works on the 3D plane.

SwiftUI Alignment Bottom

I have the following code:
struct CircleView: View {
var body: some View {
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle().frame(width: UIScreen.main.bounds.width/5)
}
}
}
}
Why is it not aligning the circle on the bottom? I have a spacer which should take up all free space in the ZStack?
Thanks in advance.
Because Circle is a Shape and does not have own content size, so consumes all provided. You limited width, but not height, so Circle consumed all height (blue rect is all circle, but drown only where fit).
So if you want to make it aligned, you have to limit it completely, like below
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle()
.frame(width: UIScreen.main.bounds.width/5, height: UIScreen.main.bounds.width/5)
}
}