Darren
Darren

Reputation: 10398

SwiftUI view won't expand to full height - iOS 15 Xcode 13 beta 5

I'm trying to create my own keyboard view (I need a decimal keyboard, with a guaranteed . and not , which is shown in Europe).

I have it working great except in landscape one of the keys is not the correct size. (Colours to show frame sizes)

enter image description here enter image description here

The problem seems to happen on iOS 15 when the height of the full view shrinks to around 200px. On iOS 14 you can see it's causing issue as it has padding on the bottom row. Removing the .resizable() form the Image seems to fix that so not sure if it's related.

I've simplified the code here to a view that shows the issue.

Here is the code that builds each key:

enum KeyboardKey: String, CaseIterable {
    
    case one = "1"
    case two = "2"
    case three = "3"
    case four = "4"
    case five = "5"
    case six = "6"
    case seven = "7"
    case eight = "8"
    case nine = "9"
    case zero = "0"
    case dot = "."
    case backspace = "backspace"
    
    @ViewBuilder
    func view() -> some View {
        switch self {
        case .one, .two, .three, .four, .five, .six, .seven,
                .eight, .nine, .zero:
            Text(self.rawValue)
                .font(.title)
                .foregroundColor(.black)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.white)
                .cornerRadius(6)
                .shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
        case .dot:
            Text(self.rawValue)
                .font(.title)
                .foregroundColor(.black)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.red)
                .cornerRadius(6)
        case .backspace:
            HStack(alignment: .center, spacing: 0) {
                Image(systemName: "delete.left")
                    .resizable()
                    .frame(width: 20, height: 20)
                    .background(Color.green)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.red)
            .cornerRadius(6)
        }
    }
}

and the keyboard view is built up with this:

struct IPKeyboardButtons: View {
    
    let rows: [[KeyboardKey]] = [
        [.one, .two, .three],
        [.four, .five, .six],
        [.seven, .eight, .nine],
        [.dot, .zero, .backspace]
    ]
    
    var body: some View {
        VStack {
            ForEach(0..<rows.count, id: \.self) { rowIndex in
                
                let row = rows[rowIndex]
                HStack(spacing: 5) {
                    ForEach(row, id: \.self) { key in
                        Button(action: {
                            print("Tapped: \(key)")
                        }) {
                            key.view()
                        }
                    }
                }
            }
        }
        .padding(5)
        .padding(.bottom, 40)
        .background(Color.keyboardBackground)
    }
}

You can view it in a SwiftUI preview with this:

struct App_Previews: PreviewProvider {
    static var previews: some View {
        IPKeyboardButtons()
            .frame(height: 200)
    }
}

Removing the .frame(height: 200) make it look perfect.

I've tried using a ZStack, VStack, AnyView() you name it, to wrap the key, but they all have the same effect. I want the image to be smaller than the button, so I set it to be 20x20 then set the container view to .infinity but it seems to be ignored.

Upvotes: 1

Views: 1480

Answers (1)

Yrb
Yrb

Reputation: 9675

I would use a LazyVGrid to handle this. The solution you are trying to use was really an iOS 13 workaround for making grids. LazyVGrid handles this elegantly.

struct IPKeyboardButtons: View {
    
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        LazyVGrid(columns: columns, alignment: .center, spacing: 20) {
            ForEach(KeyboardKey.allCases, id: \.self) { key in
                Button(action: {
                    print("Tapped: \(key)")
                }) {
                    key.view()
                }
            }
        }
        .padding(5)
        .padding()
        .background(Color.gray)
    }
}

I changed the enums view() function slightly to add some padding around the symbols(number or SF Font). Also, you can treat an SF Font like text. They are designed to be used right along with text and using text modifiers like .font:

enum KeyboardKey: String, CaseIterable {
    
    case one = "1"
    case two = "2"
    case three = "3"
    case four = "4"
    case five = "5"
    case six = "6"
    case seven = "7"
    case eight = "8"
    case nine = "9"
    case zero = "0"
    case dot = "."
    case backspace = "backspace"
    
    @ViewBuilder
    func view() -> some View {
        switch self {
        case .one, .two, .three, .four, .five, .six, .seven,
                .eight, .nine, .zero:
            Text(self.rawValue)
                .font(.title)
                .foregroundColor(Color.black)
                .padding() // optional, but this will increase the size of the button itself
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.white)
                .cornerRadius(6)
                .shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
        case .dot:
            Text(self.rawValue)
                .font(.title)
                .foregroundColor(Color.black)
                .padding() // optional, but this will increase the size of the button itself
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.red)
                .cornerRadius(6)
        case .backspace:
            Image(systemName: "delete.left")
                .font(.title)
                .background(Color.green)
                .padding() // optional, but this will increase the size of the button itself
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.red)
                .cornerRadius(6)
        }
    }
}

This is the view I put it in so it would behave in a real world test case. I would stay away from forcing frames in the preview as that will give artificial constraints and does not respect safe areas:

struct KeyboardView: View {
    
    @State var textBinding = ""
    
    var body: some View {
        VStack {
            TextField("TextField", text: $textBinding)
            Spacer()
            IPKeyboardButtons()
        }
    }
}

This code is much simpler and easier to maintain.

Upvotes: 1

Related Questions