UIKeyInput in SwiftUI not working on iPadOS

I have a project where I wanted to display a custom input field for a numeric code. UIKeyInput seems the right tool for this, so I implemented a UIViewRepresentable single digit view.

So far so good. on iOS this works nicely. On iPadOS however it doesn't ever become the first responder = gets focused.

Tested in iOS 15-17 and iPadOS 15-17.

Here is the entire code for testing. You can drop it into a blank iOS app and run it as is on iPad or iPhone simulators/devices. The field should get focus on tapping. On iOS it does - on iPad it doesn't.

The lines

print("DigitBoxView focused is \(focused)")
focused = true
print("DigitBoxView tap gesture sets focus to \(focused)")

print

DigitBoxView focused is false
DigitBoxView tap gesture sets focus to false

even though I unconditionally set it to true.

Am I doing something wrong? How can I achieve the KeyInput view getting focused on iPadOS?

//  Created by Thomas Krajacic on 13.11.23.
//

import SwiftUI

@main
struct KeyInputTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @State var char: Character?
    var body: some View {
        DigitBoxView(character: $char) { _ in
            //
        }
        .frame(width: 100)
        .padding()
    }
}

#Preview {
    ContentView()
}

struct DigitBoxView: View {
    /// The represented character (output)
    @Binding var character: Character?
    
    @FocusState var focused: Bool

    /// Triggered on pressing enter in any of the textfields
    var onSubmit: (String?) -> Void
    
    @State private var size: CGSize = .zero
    
    var body: some View {
        KeyInput { input in
            switch input {
            case let .text(text):
                guard let valid = text.first else { return }
                self.character = valid
            case .enter:
                self.onSubmit(nil)
            case .delete:
                self.character = nil
            case .tab:
                self.onSubmit(nil)
            }
        } hasText: {
            self.character != nil
        }
        .focused($focused)
        .aspectRatio(0.75, contentMode: .fit)
        .cornerRadius(10)
        .background {
            ZStack {
                // The outline
                RoundedRectangle(cornerRadius: 10)
                    .strokeBorder(Color.secondary, lineWidth: 1)
                
                // The digit
                Text(character.map(String.init) ?? "")
                    .minimumScaleFactor(0.5)
                    .font(.system(size: 72, weight: .regular, design: .monospaced))
            }
        }
        .overlay {
            ZStack {
                // The selection outline
                RoundedRectangle(cornerRadius: 10)
                    .strokeBorder(Color.primary, lineWidth: 2)
            }
            .opacity(focused ? 1 : 0)
        }
        // so the entire area is tappable
        .contentShape(RoundedRectangle(cornerRadius: 10))
        .onTapGesture {
            print("DigitBoxView focused is \(focused)")
            focused = true
            print("DigitBoxView tap gesture sets focus to \(focused)")
        }
    }
}

public struct KeyInput: UIViewRepresentable {
    public enum Input {
        case text(String)
        case delete
        case tab
        case enter
    }
    
    var onInput: (Input) -> Void
    var hasText: () -> Bool
    
    final public class InputView: UIView, UIKeyInput {
        var _onInput: (Input) -> Void
        var _hasText: () -> Bool
        
        init(onInput: @escaping (Input) -> Void, hasText: @escaping () -> Bool) {
            self._onInput = onInput
            self._hasText = hasText
            super.init(frame: .zero)
        }
        
        required init?(coder: NSCoder) { fatalError() }
        
        public var hasText: Bool { _hasText() }
        
        public func insertText(_ text: String) {
            if text == "\t" {
                _onInput(.tab)
            } else if text == "\n" {
                _onInput(.enter)
            } else {
                _onInput(.text(text))
            }
        }
        
        public func deleteBackward() { _onInput(.delete) }
        public override var canBecomeFirstResponder: Bool { true }
    }
    
    public func makeUIView(context: Context) -> InputView {
        InputView(
            onInput: onInput,
            hasText: hasText
        )
    }
    
    public func updateUIView(_ uiView: InputView, context: Context) {
        // Crickets…
    }
}
UIKeyInput in SwiftUI not working on iPadOS
 
 
Q