Skip to content

Commit 3367625

Browse files
committed
Implement textContentType modifier
1 parent ca20994 commit 3367625

File tree

6 files changed

+128
-0
lines changed

6 files changed

+128
-0
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,20 @@ public final class AppKitBackend: AppBackend {
641641
onChange(textField.stringValue)
642642
}
643643
textField.onSubmit = onSubmit
644+
645+
textField.contentType =
646+
switch environment.textContentType {
647+
case .url:
648+
.URL
649+
case .phoneNumber:
650+
.telephoneNumber
651+
case .name:
652+
.name
653+
case .emailAddress:
654+
.emailAddress
655+
default:
656+
nil
657+
}
644658
}
645659

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

Sources/SwiftCrossUI/Environment/EnvironmentValues.swift

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

41+
/// The type of on-screen keyboard to show when a text field is focused.
42+
public var textContentType: TextContentType
43+
4144
/// Called by view graph nodes when they resize due to an internal state
4245
/// change and end up changing size. Each view graph node sets its own
4346
/// handler when passing the environment on to its children, setting up
@@ -143,6 +146,7 @@ public struct EnvironmentValues {
143146
multilineTextAlignment = .leading
144147
colorScheme = .light
145148
windowScaleFactor = 1
149+
textContentType = .text
146150
window = nil
147151
extraValues = [:]
148152
}
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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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, they may see a different numeric keypad depending on their locale settings
12+
/// (for example, they may see the digits ० १ २ ३ ४ ५ ६ ७ ८ ९ instead if the language
13+
/// is set to Hindi).
14+
case digits(ascii: Bool)
15+
/// A URL.
16+
///
17+
/// This keyboard type typically has prominent buttons for "/" and ".com", and might not
18+
/// include a spacebar.
19+
case url
20+
/// A phone number.
21+
///
22+
/// This input type has buttons for the digits 0-9 and the symbols "#" and "*", and may
23+
/// include other symbols or even letters.
24+
case phoneNumber
25+
/// A person's name.
26+
case name
27+
/// A number.
28+
///
29+
/// This keyboard type is guaranteed to have at least digits and a decimal separator. If
30+
/// `signed` is true, the input will also have a plus sign and minus sign. Other characters
31+
/// may also appear.
32+
case decimal(signed: Bool)
33+
/// An email address.
34+
///
35+
/// This keyboard type typically has prominent buttons for "@", ".", and/or ".com".
36+
case emailAddress
37+
}

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
@@ -801,6 +801,38 @@ public final class WinUIBackend: AppBackend {
801801
}
802802

803803
missing("text field font handling")
804+
805+
let inputScope: InputScopeNameValue =
806+
switch environment.textContentType {
807+
case .decimal(_):
808+
.number
809+
case .digits(_):
810+
.digits
811+
case .emailAddress:
812+
.emailSmtpAddress
813+
case .name:
814+
.personalFullName
815+
case .phoneNumber:
816+
.telephoneNumber
817+
case .text:
818+
.default
819+
case .url:
820+
.url
821+
}
822+
823+
setInputScope(for: textField, to: inputScope)
824+
}
825+
826+
private func setInputScope(for textField: TextBox, to value: InputScopeNameValue) {
827+
if let inputScope = textField.inputScope,
828+
inputScope.names.count == 1
829+
{
830+
inputScope.names[0] = value
831+
} else {
832+
let inputScope = InputScope()
833+
inputScope.names.add(value)
834+
textField.inputScope = inputScope
835+
}
804836
}
805837

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

0 commit comments

Comments
 (0)