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.
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:
Use a greedy view, such as a Color
or Shape
, as the base of the view for each letter. In fact, the rounded rectangle that you were showing in the background can be used as the base.
Use the modifier .aspectRatio
to set the required aspect ratio for the base.
Set the height as .maxHeight
on the base. This allows scaling to work.
Show the visible letter as an overlay over the base.
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:
GameView
, but KeyboardView
still adds padding.KeyboardView
is being scaled to fit.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
}
}