Search code examples
iosswiftswiftuizstack

How can I show in SwiftUI a screen in different screen sizes with the same measures?


I’m having problems with my app with different screen sizes. I made a little example to show the problem. I have a screen with a text and a button on the left and an image on the right. Behind this, in zstack, I have the same image bigger and a layer over this image with opacity 0.5.

The problem is that I’m using measures relative to the screen and the result is different. In the image attached you can see the differences.

This is the result

This is the code.

struct Screen2: View {
var body: some View {
    VStack {
        ZStack {
            ZStack {
                Image(uiImage: resizeImage(image: UIImage.init(named: "cover.jpeg")!, targetSize: .init(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.width)))
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 2.9, alignment: .center)
                    .cornerRadius(0)
                Color.black
                    .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 2.9, alignment: .center)
                    .opacity(0.5)
            }
            .edgesIgnoringSafeArea(.all)
            .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 5, alignment: .top)
            HStack(alignment: .top) {
                VStack {
                    Text("The Suicide Squad")
                        .font(.title)
                        .shadow(color: .white, radius: 5)
                    Button("Publish") {
                        print("Published")
                    }
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .padding()
                }
                .padding([.leading], 10)
                Spacer()
                Image(uiImage: resizeImage(image: UIImage.init(named: "cover.jpeg")!, targetSize: CGSize.init(width: UIScreen.main.bounds.width / 4, height: UIScreen.main.bounds.height / 4)))
            }
        }
        HStack {
            Spacer()
            Text("TESTING")
            Spacer()
        }
        .background(Color.red)
        Spacer()
    }
}

func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
    let size = image.size
    let widthRatio = targetSize.width / size.width
    let heightRatio = targetSize.height / size.height
    var newSize:CGSize
    if (widthRatio > heightRatio)
    {
        newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
    }
    else
    {
        newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
    }
    let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
    UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
    image.draw(in: rect)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage!
}

}

Anyone can help me to show the result in the same way in every screen size?

Thank you


Solution

    1. You're resizing your cover to one targetSize, and then setting an other size using .frame, and then adding one more frame to container ZStack. Why would you do that?
    2. When image frame is changed and contentMode set to .fill, it's gonna be centered in its frame but rest of the image will be visible out of the frame. You can see this in this example:
    let targetSize = CGSize(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 2.9)
    
    Image("cover")
        .resizable()
        .aspectRatio(contentMode: .fill)
        .cornerRadius(0)
        .frame(width: targetSize.width, height: targetSize.height, alignment: .center)
        .overlay(Rectangle().stroke(lineWidth: 5).foregroundColor(.red))
    

    This can be fixed with .clipped() modifier. It's same as clipsToBounds on UIKit if you're familiar with that

    1. When you're applying edgesIgnoringSafeArea to one item in VStack, it works like offset modifier, which means that next item won't be right after. You need to apply edgesIgnoringSafeArea to whole VStack, and, if needed, you can than disable if .edgesIgnoringSafeArea([]) for items that needs safe area inset
    2. There's no need to resize images before passing it to Image, especially you don't want to do that inside view builder function, because this gonna be re-calculated each time you update this view. Usually applying needed modifiers is totally enough to get needed result, let SwiftUI optimize it for your:
    VStack(spacing: 0) {
        ZStack(alignment: .bottom) {
            let targetSize = CGSize(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height / 2.9)
    
            ZStack {
                Image("cover")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .cornerRadius(0)
                    .frame(width: targetSize.width, height: targetSize.height, alignment: .center)
                    .clipped()
                Color.black
                    .opacity(0.5)
            }
            .frame(width: targetSize.width, height: targetSize.height, alignment: .center)
            HStack(alignment: .top) {
                VStack {
                    Text("The Suicide Squad")
                        .font(.title)
                        .shadow(color: .white, radius: 5)
                    Button("Publish") {
                        print("Published")
                    }
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .padding()
                }
                .padding([.leading], 10)
                Spacer()
                Image("cover")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: UIScreen.main.bounds.width / 4)
            }
            .edgesIgnoringSafeArea([])
        }
        HStack {
            Spacer()
            Text("TESTING")
            Spacer()
        }
        .background(Color.red)
        Spacer()
    }
    .edgesIgnoringSafeArea(.all)
    

    Result: