Search code examples
iosswiftui

How can I make an Image with half of screen height in SwiftUI?


I would like to centre an image on the screen but make its height half of the screen height? How is this possible?


Solution

  • You can use a GeometryReader in SwiftUI to get details of a view's container geometry; Note that this approach is different to using the screen size, but this is a more flexible as you can use it inside other views.

    struct RatioImage: View {
        let image: Image
        let heightRatio:CGFloat = 0.5
        
        var body: some View {
            GeometryReader { geo in
                VStack {
                    Spacer()
                    image.resizable().aspectRatio(contentMode: .fit)
                    .frame(width: geo.size.width, height: geo.size.height*heightRatio, 
                           alignment: .center)
                    Spacer()
                }
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            HalfHeight {
                RatioImage(image: Image(systemName:"photo.fill"))
            }
        }
    }
    

    By using SwiftUI's ViewBuilder capability you can create a generic container that can resize arbitrary content:

    struct RatioContainer<Content: View>: View {
        let heightRatio:CGFloat
        let content: Content
    
        init(heightRatio:CGFloat = 0.5,@ViewBuilder content: () -> Content) {
                self.heightRatio = heightRatio
                self.content = content()
        }
    
        var body: some View {
            GeometryReader { geo in
                VStack {
                    Spacer()
                content.frame(width: geo.size.width, height: geo.size.height*heightRatio, alignment: .center)
                    Spacer()
                }
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            RatioContainer {
                VStack {
                    Image(systemName: "photo").resizable().aspectRatio(contentMode: .fit)
                    Text("Some caption text").font(.caption)
                }
            }
        }
    }
    

    You can even nest instances of the container, which won't work correctly if you use UIScreen dimensions:

    struct ContentView: View {
        var body: some View {
            VStack {
                RatioContainer {
                    VStack {
                        Image(systemName: "photo").resizable().aspectRatio(contentMode: .fit)
                        Text("Some caption text").font(.caption)
                        RatioContainer(heightRatio:0.3) {
                            VStack {
                                Image(systemName: "photo.fill").resizable().aspectRatio(contentMode: .fit)
                                Text("Some other caption text").font(.caption)
                            }
                        }
                        
                    }
                }
                
            }
        }
    }