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…
}
}
Post
Replies
Boosts
Views
Activity
I am using runtime loaded dynamic libraries (in an NSBundle) as a plugin system in my app.I would like to switch over to using XPC Services though, for various reasons. Is it possible to load plugins which are really xpc services from a user-specified folder? I have not yet figured out how I would go about dicovering that a plugin is present or put in the PlugIns folder.That folder would most likely be either under ~/Documents/MyApp/PlugIns or something else user-accessible.(I may also ship some plugins with the app, which are inside the app's bundle, but discovering/loading those seems trivial)Thank you for your help.Thomas
I am trying to understand how an NSTextField reacts to changes of its cell's backgroundStyle.The reason is that I want to use the automatic handling of the text color be used when I set the backgroundStyle of the text field that's inside a table cell.From what I have gathered so far, the textfield's cell has a backgroundStyle property and when this is set, and the textColor is `labelColor` (or any of the other control colors) the text color should be automatically updated to be fitting for the backgroundStyle.Given a tableView with a custom row class and a custom cell:class RowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
if isSelected {
NSColor.white.set()
var rects: UnsafePointer<NSRect>? = nil
var count: Int = 0
self.getRectsBeingDrawn(&rects, count: &count)
for i in 0..<count {
let rect = NSIntersectionRect(bounds, rects![i])
__NSRectFillUsingOperation(rect, NSCompositingOperation.sourceOver)
}
}
}
override var interiorBackgroundStyle: NSView.BackgroundStyle {
return isSelected ? .light : .dark
}
}andclass CustomCellView: NSTableCellView {
@IBOutlet weak var label: NSTextField!
override var backgroundStyle: NSView.BackgroundStyle {
didSet {
Swift.print("Style set to \(backgroundStyle == .light ? "light" : "dark")")
Swift.print("Label Style set to \(label.cell!.backgroundStyle == .light ? "light" : "dark")")
}
}
}I would expect the label to be drawn in black, when I select the row.I can see that the backgroundStyle is forwarded correctly when I select the row, but the label doesn't change color at all. In fact, being seemingly blended with my white selection, the label disappears entirely.Why is the label color not changing?P.S.: I know that I can manually set the label's color based on the background style of the cell. That's not what I am asking. I want to use the convenience of the `controlTextColor`s or at least understand why they are not working in this case. (They do seem to work if I don't draw my custom selection in the row and use the standard blue selection)