UIScreen.main is now deprecated; what's the proper way to get screen width upfront? - swift

I have the following code:
ZStack {
AsyncImage(url: URL(string: url)) { image in
image
} placeholder: {
EmptyView()
}
.frame(maxWidth: UIScreen.main.bounds.width)
buttons
}
The image extends off the screen, but this is something I want so I don't resize it. However, I have buttons in a ZStack on top of the image that pin to the corners of the view. Because the image extends past the screen, I need to limit the max width of the view the image is in so that the ZStack doesn't extend off the screen. I am using the UIScreen method right now which works, but Xcode is telling me UIScreen.main is deprecated. I can't use GeometryReader since I need to know the size up front, so what would be the best method to do this?

You can use GeometryReader like this;
GeometryReader { geo in
//Your code here
}
.frame(width: geo.size.width, height: geo.size.height)

Related

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 Image scaledToFill layout behaviour

In my iOS 14 Widget I want to display several circular images in a row.
When using scaledToFit() the image stretches weirdly to fit the circle. scaledToFill() gives me the desired output like so:
Image("Person")
.resizable()
.scaledToFill()
.clipShape(Circle())
But this changes the behaviour of the view to ignore it's parent and expand beyond it. Setting a fixed frame prevents this, but I need these Images to resize dynamically. When I place this View inside an HStack in my Widget the Images are way too large.
How can I get the image to scale like scaledToFill() and still respect the parent view.
You can try using geometry reader. This is an example code of what I have used in my Widget so you have to adjust it for your project, however, you will get the idea.
GeometryReader { geo in
ZStack(alignment: .bottom) {
HStack {
Image("Person")
.resizable()
.frame(width: geo.size.width, height: geo.size.height)
.aspectRatio(contentMode: .fit)
}
}
}
I found a solution in this post, the first answer works great in the Widget:
stackoverflow.com/questions/58290963/clip-image-to-square-in-swiftui
However I am reusing the view within my app and it caused some weird behaviour there. Instead I am now clipping the Image to a Circle before the View is created. This allows me to use .scaledToFit() and still maintain the original aspect ratio of the image.
I found it works best when wrapping the GeometryReader directly into the Image
HStack {
GeometryReader { geo in
Image("Person")
.resizable()
.scaledToFill()
.frame(width: geo.size.width, height: geo.size.height)
}
}

SwiftUI Zstack – Make element ignore safe area and another one don't

I have a Zstack like this:
ZStack {
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
VStack {
// with a lot of stuff
}
}
I want the image to ignore the safe area, but the Vstack must respect the safe area.
The image is a background image that should cover all the area.
This code I have is making the VStack full screen too and I don't want that.
Why everything is not respecting the safe area is a mystery, because the respective modifier is applied to the image only.
Put your image in the .background:
struct ContentView: View {
var body: some View {
VStack {
// lots of stuff
Color.red
Color.blue
}
.background(
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
)
}
}
The reason why it doesn't work with your example is that you are using scaledToFill() modifier on image, and here it matters in this particular case.
First you declared ZStack which is itself doesn't ignore safe area. After you put Image and resizable. What resizable does, is it stretches the image to fit its view, in your case it is the ZStack.
Let's see what we have until now.We have an image which stretches till the safe area.
ZStack {
Image("Breakfast").resizable()
}
So from now on you put edgesIgnoringSafeArea on Image, which lets the image cover all the area(ignore safe area).
Now you have Zstack which respects safe area, and Image which ignores safe area. This let you put VStack in ZStack and add staff inside it. VStack will fill its parent view, which is ZStack, so it too will respect safe area(See code and image below).
ZStack {
Image("Breakfast").resizable().edgesIgnoringSafeArea(.all)
VStack {
Text("hello")
Spacer()
}
}
And here at last you add .scaledToFill() modifier, which stretches the image to contain all the area, and by doing this it makes ZStack view to become the hole area, as fitting view's (ZStack, HStack, VStack) calculates its size based on its content.
Useful link:
Fitting and filling views in SwiftUI
How to resize a SwiftUI Image and keep its aspect ratio
Another way that worked for me is:
struct ContentView: View {
var body: some View {
ZStack {
Color.clear
.background(
Image("beach")
.resizable()
.ignoresSafeArea()
.scaledToFill()
)
VStack {
// lots of stuff
}
}
}
}
Note: In my case, when trying the other solution of putting .background on VStack the image did not fill the entire screen, instead it shrank to fit the size of what was in the VStack.

How to use GeometryReader within a LazyVGrid

I'm building a grid with cards which have an image view at the top and some text at the bottom. Here is the swift UI code for the component:
struct Main: View {
var body: some View {
ScrollView {
LazyVGrid(columns: .init(repeating: .init(.flexible()), count: 2)) {
ForEach(0..<6) { _ in
ZStack {
Rectangle()
.foregroundColor(Color(UIColor.random))
VStack {
Rectangle()
.frame(minHeight: 72)
Text(ipsum)
.fixedSize(horizontal: false, vertical: true)
.padding()
}
}.clipShape(RoundedRectangle(cornerRadius: 10))
}
}.padding()
}.frame(width: 400, height: 600)
}
}
This component outputs the following layout:
This Looks great, but I want to add a Geometry reader into the Card component in order to scale the top image view according to the width of the enclosing grid column. As far as I know, that code should look like the following:
struct Main: View {
var body: some View {
ScrollView {
LazyVGrid(columns: .init(repeating: .init(.flexible()), count: 2)) {
ForEach(0..<6) { _ in
ZStack {
Rectangle()
.foregroundColor(Color(UIColor.random))
VStack {
GeometryReader { geometry in
Rectangle()
.frame(minHeight: 72)
Text(ipsum)
.fixedSize(horizontal: false, vertical: true)
.padding()
}
}
}.clipShape(RoundedRectangle(cornerRadius: 10))
}
}.padding()
}.frame(width: 400, height: 600)
}
}
The trouble is that this renders as the following:
As you can see, I'm not even trying to use the GeometryReader, I've just added it. If I add the geometry reader at the top level, It will render the grid correctly, however this is not of great use to me because I plan to abstract the components into other View conforming structs. Additionally, GeometryReader seems to be contextually useful, and it wouldn't make sense to do a bunch of math to cut the width value in half and then make my calculations from there considering the geometry would be from the top level (full width).
Am I using geometry reader incorrectly? My understanding is that it can be used anywhere in the component tree, not just at the top level.
Thanks for taking a look!
I had the same problem as you, but I've worked it out. Here's some key point.
If you set GeometryReader inside LazyVGrid and Foreach, according to SwiftUI layout rule, GeometryReader will get the suggested size (may be just 10 point). More importantly, No matter what subview inside GeometryReader, it wouldn't affect the size of GeometryReader and GeometryReader's parent view.
For this reason, your view appears as a long strip of black. You can control height by setting GeometryReader { subView }.frame(some size),
Generally, we need two GeometryReader to implement this. The first one can get size and do some Computing operations, then pass to second one.
(Since my original code contains Chinese, it may be hard for you to read, so I can only give a simple structure for you.)
GeometryReader { firstGeo in
LazyVGrid(columns: rows) {
ForEach(dataList) { data in
GeometryReader { secondGeo in
// subview
}
.frame(width: widthYouWantSubViewGet)
}
}
}
I just started to learn swift for a week. There may be some mistakes in my understanding. You are welcome to help correct it.