iosswiftswiftui

How to scale a view to fit its parent?


Code that reproduces the issue

import SwiftUI

@main
struct KeyboardLayoutProblemApp: App {
    var body: some Scene {
        WindowGroup {
            iOSTabView()
        }
    }
}

struct iOSTabView: View {
    var body: some View {
        TabView {
            GameView()
                .tabItem {
                    Label("Play", systemImage: "gamecontroller.fill")
                }
        }
    }
}

struct GameView: View {
    var body: some View {
        VStack {
            Text("Play")
            Spacer()
            KeyboardView()
        }
        .padding()
    }
}

struct KeyboardView: View {
    let firstRowLetters = "qwertyuiop".map { $0 }
    let secondRowLetters = "asdfghjkl".map { $0 }
    let thirdRowLetters = "zxcvbnm".map { $0 }
    
    var body: some View {
        VStack {
            HStack {
                ForEach(firstRowLetters, id: \.self) {
                    LetterKeyView(character: $0)
                }
            }
            HStack {
                ForEach(secondRowLetters, id: \.self) {
                    LetterKeyView(character: $0)
                }
            }
            HStack {
                ForEach(thirdRowLetters, id: \.self) {
                    LetterKeyView(character: $0)
                }
            }
        }
        .padding()
    }
}

struct LetterKeyView: View {
    let character: Character
    
    var width: CGFloat { height*0.8 }
    @ScaledMetric(relativeTo: .title3) private var height = 35
    
    var body: some View {
        Button {
            print("\(character) pressed")
        } label: {
            Text(String(character).capitalized)
                .font(.title3)
                .frame(width: self.width, height: self.height)
                .background {
                    RoundedRectangle(cornerRadius: min(width, height)/4, style: .continuous)
                        .stroke(.gray)
                }
        }
        .buttonStyle(PlainButtonStyle())
    }
}

#Preview {
    iOSTabView()
}

Problem

GameView doesn't fit its parent view:

Question

How do I make GameView be at most as big as its parent view?

What I've tried and didn't work

GameView()
  .frame(maxWidth: .infinity, maxHeight: .infinity)
GeometryReader { geometry in
  GameView()
    .frame(maxWidth: geometry.size.width, maxHeight: geometry.size.height)
}
GameView()
  .clipped()
GameView()
  .layoutPriority(1)
GameView()
  .scaledToFit()
GameView()
  .minimumScaleFactor(0.01)
GameView()
  .scaledToFill()
  .minimumScaleFactor(0.5)

I'm not using UIScreen.main.bounds.width because I'm trying to build a multi-platform app.


Solution

  • If you're talking about the issue that the rows of letters overflow the screen width, it's happening because of the .frame with fixed width and height that you are setting on each letter. This prevents scaling to fit.

    Instead of fixing both the width and the height of each letter, it works with the following changes:

    Btw, you are not allowing much space for the rows, because there is padding around both KeyboardView and GameView. Also, it might make more sense to scale just KeyboardView to fit, instead of the full GameView.

    Here is the updated example to show it working. Some notes:

    struct LetterKeyView: View {
        let character: Character
        @ScaledMetric(relativeTo: .title3) private var height = 35
    
        var body: some View {
            Button {
                print("\(character) pressed")
            } label: {
                RoundedRectangle(cornerRadius: 7, style: .continuous)
                    .stroke(.gray)
                    .aspectRatio(0.8, contentMode: .fit)
                    .frame(maxHeight: height)
                    .overlay {
                        Text(String(character).capitalized)
                            .font(.title3)
                    }
            }
            .buttonStyle(.plain)
        }
    }
    
    struct GameView: View {
        var body: some View {
            VStack {
                Text("Play")
                Spacer()
                KeyboardView()
                    .minimumScaleFactor(0.2) // 👈 added
                    .scaledToFit() // 👈 added
            }
            .padding(.vertical) // 👈 vertical padding only
        }
    }
    

    Animation