Skip to content

Commit 6367410

Browse files
authored
Implement textContentType modifier (#129)
* Implement textContentType modifier * Fix doc comments * Add #available for macOS * address PR comments * correct add -> append * wrap in InputScopeName
1 parent 5e14f9f commit 6367410

File tree

6 files changed

+138
-0
lines changed

6 files changed

+138
-0
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,22 @@ public final class AppKitBackend: AppBackend {
642642
onChange(textField.stringValue)
643643
}
644644
textField.onSubmit = onSubmit
645+
646+
if #available(macOS 14, *) {
647+
textField.contentType =
648+
switch environment.textContentType {
649+
case .url:
650+
.URL
651+
case .phoneNumber:
652+
.telephoneNumber
653+
case .name:
654+
.name
655+
case .emailAddress:
656+
.emailAddress
657+
case .text, .digits(_), .decimal(_):
658+
nil
659+
}
660+
}
645661
}
646662

647663
public func getContent(ofTextField textField: Widget) -> String {

Sources/SwiftCrossUI/Environment/EnvironmentValues.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ public struct EnvironmentValues {
3838
/// The scale factor of the current window.
3939
public var windowScaleFactor: Double
4040

41+
/// The type of input that text fields represent.
42+
///
43+
/// This affects autocomplete suggestions, and on devices with no physical keyboard, which
44+
/// on-screen keyboard to use.
45+
///
46+
/// Do not use this in place of validation, even if you only plan on supporting mobile
47+
/// devices, as this does not restrict copy-paste and many mobile devices support bluetooth
48+
/// keyboards.
49+
public var textContentType: TextContentType
50+
4151
/// Called by view graph nodes when they resize due to an internal state
4252
/// change and end up changing size. Each view graph node sets its own
4353
/// handler when passing the environment on to its children, setting up
@@ -146,6 +156,7 @@ public struct EnvironmentValues {
146156
multilineTextAlignment = .leading
147157
colorScheme = .light
148158
windowScaleFactor = 1
159+
textContentType = .text
149160
window = nil
150161
extraValues = [:]
151162
listStyle = .default
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
extension View {
2+
/// Set the content type of text fields.
3+
///
4+
/// This controls autocomplete suggestions, and on mobile devices, which on-screen keyboard
5+
/// is shown.
6+
public func textContentType(_ type: TextContentType) -> some View {
7+
EnvironmentModifier(self) { environment in
8+
environment.with(\.textContentType, type)
9+
}
10+
}
11+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
public enum TextContentType {
2+
/// Plain text.
3+
///
4+
/// This is the default value.
5+
case text
6+
/// Just digits.
7+
///
8+
/// For numbers that may include decimals or negative numbers, see ``decimal(signed:)``.
9+
///
10+
/// If `ascii` is true, the user should only enter the ASCII digits 0-9. If `ascii` is
11+
/// false, on mobile devices they may see a different numeric keypad depending on their
12+
/// locale settings (for example, they may see the digits ० १ २ ३ ४ ५ ६ ७ ८ ९ instead
13+
/// if the language is set to Hindi).
14+
case digits(ascii: Bool)
15+
/// A URL.
16+
///
17+
/// On mobile devices, this type shows a keyboard with prominent buttons for "/" and ".com",
18+
/// and might not include a spacebar.
19+
case url
20+
/// A phone number.
21+
case phoneNumber
22+
/// A person's name.
23+
///
24+
/// This typically uses the default keyboard, but informs autocomplete to use contact
25+
/// names rather than regular words.
26+
case name
27+
/// A number.
28+
///
29+
/// If `signed` is false, on mobile devices it shows a numeric keypad with a decimal point,
30+
/// but not necessarily plus and minus signs. If `signed` is true then more punctuation can
31+
/// be entered.
32+
case decimal(signed: Bool)
33+
/// An email address.
34+
///
35+
/// This informs autocomplete that the input is an email address, and on mobile devices,
36+
/// displays a keyboard with prominent "@" and "." buttons.
37+
case emailAddress
38+
}

Sources/UIKitBackend/UIKitBackend+Control.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,36 @@ extension UIKitBackend {
240240
textFieldWidget.child.textColor = UIColor(color: environment.suggestedForegroundColor)
241241
textFieldWidget.onChange = onChange
242242
textFieldWidget.onSubmit = onSubmit
243+
244+
switch environment.textContentType {
245+
case .text:
246+
textFieldWidget.child.keyboardType = .default
247+
textFieldWidget.child.textContentType = nil
248+
case .digits(ascii: false):
249+
textFieldWidget.child.keyboardType = .numberPad
250+
textFieldWidget.child.textContentType = nil
251+
case .digits(ascii: true):
252+
textFieldWidget.child.keyboardType = .asciiCapableNumberPad
253+
textFieldWidget.child.textContentType = nil
254+
case .url:
255+
textFieldWidget.child.keyboardType = .URL
256+
textFieldWidget.child.textContentType = .URL
257+
case .phoneNumber:
258+
textFieldWidget.child.keyboardType = .phonePad
259+
textFieldWidget.child.textContentType = .telephoneNumber
260+
case .name:
261+
textFieldWidget.child.keyboardType = .namePhonePad
262+
textFieldWidget.child.textContentType = .name
263+
case .decimal(signed: false):
264+
textFieldWidget.child.keyboardType = .decimalPad
265+
textFieldWidget.child.textContentType = nil
266+
case .decimal(signed: true):
267+
textFieldWidget.child.keyboardType = .numbersAndPunctuation
268+
textFieldWidget.child.textContentType = nil
269+
case .emailAddress:
270+
textFieldWidget.child.keyboardType = .emailAddress
271+
textFieldWidget.child.textContentType = .emailAddress
272+
}
243273

244274
#if os(iOS)
245275
if let updateToolbar = environment.updateToolbar {

Sources/WinUIBackend/WinUIBackend.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,38 @@ public final class WinUIBackend: AppBackend {
909909
}
910910

911911
missing("text field font handling")
912+
913+
let inputScope: InputScopeNameValue =
914+
switch environment.textContentType {
915+
case .decimal(_):
916+
.number
917+
case .digits(_):
918+
.digits
919+
case .emailAddress:
920+
.emailSmtpAddress
921+
case .name:
922+
.personalFullName
923+
case .phoneNumber:
924+
.telephoneNumber
925+
case .text:
926+
.default
927+
case .url:
928+
.url
929+
}
930+
931+
setInputScope(for: textField, to: InputScopeName(inputScope))
932+
}
933+
934+
private func setInputScope(for textField: TextBox, to value: InputScopeName) {
935+
if let inputScope = textField.inputScope,
936+
inputScope.names.count == 1
937+
{
938+
inputScope.names[0] = value
939+
} else {
940+
let inputScope = InputScope()
941+
inputScope.names.append(value)
942+
textField.inputScope = inputScope
943+
}
912944
}
913945

914946
public func setContent(ofTextField textField: Widget, to content: String) {

0 commit comments

Comments
 (0)