8000 Implement textContentType modifier by bbrk24 · Pull Request #129 · stackotter/swift-cross-ui · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement textContentType modifier #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
8000
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Sources/AppKitBackend/AppKitBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@
rootEnvironment: EnvironmentValues
) -> EnvironmentValues {
// TODO: Record window scale factor in here
rootEnvironment

Check warning on line 285 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Record window scale factor in ...) (todo)
}

public func setWindowEnvironmentChangeHandler(
Expand All @@ -290,7 +290,7 @@
to action: @escaping () -> Void
) {
// TODO: Notify when window scale factor changes
}

Check warning on line 293 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Notify when window scale facto...) (todo)

public func setIncomingURLHandler(to action: @escaping (URL) -> Void) {
appDelegate. urls in
Expand Down Expand Up @@ -345,7 +345,7 @@
let container = container as! NSContainerView
guard container.children.indices.contains(index) else {
// TODO: Create proper logging system.
print("warning: Attempted to set position of non-existent container child")

Check warning on line 348 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Create proper logging system.) (todo)
return
}

Expand Down Expand Up @@ -421,7 +421,7 @@
var foundConstraint = false
for constraint in widget.constraints {
if constraint.firstAnchor === widget.widthAnchor {
constraint.constant = CGFloat(size.x)

Check warning on line 424 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Prefer For-Where Violation: `where` clauses are preferred over a single `if` inside a `for` (for_where)
foundConstraint = true
break
}
Expand All @@ -434,7 +434,7 @@
foundConstraint = false
for constraint in widget.constraints {
if constraint.firstAnchor === widget.heightAnchor {
constraint.constant = CGFloat(size.y)

Check warning on line 437 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Prefer For-Where Violation: `where` clauses are preferred over a single `if` inside a `for` (for_where)
foundConstraint = true
break
}
Expand Down Expand Up @@ -572,7 +572,7 @@
onChange: @escaping (Double) -> Void
) {
// TODO: Implement decimalPlaces
let slider = slider as! NSSlider

Check warning on line 575 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Implement decimalPlaces) (todo)
slider.minValue = minimum
slider.maxValue = maximum
slider. slider in
Expand Down Expand Up @@ -641,6 +641,22 @@
onChange(textField.stringValue)
}
textField.>

if #available(macOS 14, *) {
textField.contentType =
switch environment.textContentType {
case .url:
.URL
case .phoneNumber:
.telephoneNumber
case .name:
.name
case .emailAddress:
.emailAddress
case .text, .digits(_), .decimal(_):
nil

Check warning on line 657 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Empty Enum Arguments Violation: Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)

Check warning on line 657 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Empty Enum Arguments Violation: Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)
}
}
}

public func getContent(ofTextField textField: Widget) -> String {
Expand Down Expand Up @@ -749,7 +765,7 @@
}

public func updateImageView(
_ imageView: Widget,

Check warning on line 768 in Sources/AppKitBackend/AppKitBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Function Parameter Count Violation: Function should have 5 parameters or less: it currently has 7 (function_parameter_count)
rgbaData: [UInt8],
width: Int,
height: Int,
Expand Down
11 changes: 11 additions & 0 deletions Sources/SwiftCrossUI/Environment/EnvironmentValues.swift
< 8000 tr data-hunk="c92bab4e8674ffff8bce80a073323101536475cd019fd443d467b27fa4319fa7" class="show-top-border">
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public struct EnvironmentValues {
/// The scale factor of the current window.
public var windowScaleFactor: Double

/// The type of input that text fields represent.
///
/// This affects autocomplete suggestions, and on devices with no physical keyboard, which
/// on-screen keyboard to use.
///
/// Do not use this in place of validation, even if you only plan on supporting mobile
/// devices, as this does not restrict copy-paste and many mobile devices support bluetooth
/// keyboards.
public var textContentType: TextContentType

/// Called by view graph nodes when they resize due to an internal state
/// change and end up changing size. Each view graph node sets its own
/// handler when passing the environment on to its children, setting up
Expand Down Expand Up @@ -143,6 +153,7 @@ public struct EnvironmentValues {
multilineTextAlignment = .leading
colorScheme = .light
windowScaleFactor = 1
textContentType = .text
window = nil
extraValues = [:]
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/SwiftCrossUI/Modifiers/TextContentTypeModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extension View {
/// Set the content type of text fields.
///
/// This controls autocomplete suggestions, and on mobile devices, which on-screen keyboard
/// is shown.
public func textContentType(_ type: TextContentType) -> some View {
EnvironmentModifier(self) { environment in
environment.with(\.textContentType, type)
}
}
}
38 changes: 38 additions & 0 deletions Sources/SwiftCrossUI/TextContentType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
public enum TextContentType {
/// Plain text.
///
/// This is the default value.
case text
/// Just digits.
///
/// For numbers that may include decimals or negative numbers, see ``decimal(signed:)``.
///
/// If `ascii` is true, the user should only enter the ASCII digits 0-9. If `ascii` is
/// false, on mobile devices they may see a different numeric keypad depending on their
/// locale settings (for example, they may see the digits ० १ २ ३ ४ ५ ६ ७ ८ ९ instead
/// if the language is set to Hindi).
case digits(ascii: Bool)
/// A URL.
///
/// On mobile devices, this type shows a keyboard with prominent buttons for "/" and ".com",
/// and might not include a spacebar.
case url
/// A phone number.
case phoneNumber
/// A person's name.
///
/// This typically uses the default keyboard, but informs autocomplete to use contact
/// names rather than regular words.
case name
/// A number.
///
/// If `signed` is false, on mobile devices it shows a numeric keypad with a decimal point,
/// but not necessarily plus and minus signs. If `signed` is true then more punctuation can
/// be entered.
case decimal(signed: Bool)
/// An email address.
///
/// This informs autocomplete that the input is an email address, and on mobile devices,
/// displays a keyboard with prominent "@" and "." buttons.
case emailAddress
}
30 changes: 30 additions & 0 deletions Sources/UIKitBackend/UIKitBackend+Control.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,36 @@ extension UIKitBackend {
textFieldWidget.child.textColor = UIColor(color: environment.suggestedForegroundColor)
textFieldWidget.>
textFieldWidget.>

switch environment.textContentType {
case .text:
textFieldWidget.child.keyboardType = .default
textFieldWidget.child.textContentType = nil
case .digits(ascii: false):
textFieldWidget.child.keyboardType = .numberPad
textFieldWidget.child.textContentType = nil
case .digits(ascii: true):
textFieldWidget.child.keyboardType = .asciiCapableNumberPad
textFieldWidget.child.textContentType = nil
case .url:
textFieldWidget.child.keyboardType = .URL
textFieldWidget.child.textContentType = .URL
case .phoneNumber:
textFieldWidget.child.keyboardType = .phonePad
textFieldWidget.child.textContentType = .telephoneNumber
case .name:
textFieldWidget.child.keyboardType = .namePhonePad
textFieldWidget.child.textContentType = .name
case .decimal(signed: false):
textFieldWidget.child.keyboardType = .decimalPad
textFieldWidget.child.textContentType = nil
case .decimal(signed: true):
textFieldWidget.child.keyboardType = .numbersAndPunctuation
textFieldWidget.child.textContentType = nil
case .emailAddress:
textFieldWidget.child.keyboardType = .emailAddress
textFieldWidget.child.textContentType = .emailAddress
}

#if os(iOS)
if let updateToolbar = environment.updateToolbar {
Expand Down
32 changes: 32 additions & 0 deletions Sources/WinUIBackend/WinUIBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,38 @@ public final class WinUIBackend: AppBackend {
}

missing("text field font handling")

let inputScope: InputScopeNameValue =
switch environment.textContentType {
case .decimal(_):
.number
case .digits(_):
.digits
case .emailAddress:
.emailSmtpAddress
case .name:
.personalFullName
case .phoneNumber:
.telephoneNumber
case .text:
.default
case .url:
.url
}

setInputScope(for: textField, to: inputScope)
}

private func setInputScope(for textField: TextBox, to value: InputScopeNameValue) {
if let inputScope = textField.inputScope,
inputScope.names.count == 1
{
inputScope.names[0] = value
} else {
let inputScope = InputScope()
inputScope.names.add(value)
textField.inputScope = inputScope
}
}

public func setContent(ofTextField textField: Widget, to content: String) {
Expand Down
Loading
0