Adds backports for onSubmit and submitLabel
This commit is contained in:
parent
b092912f24
commit
e4d699b678
|
@ -20,9 +20,6 @@ struct EnvironmentOutputModifier: ViewModifier {
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.onAppear {
|
|
||||||
print(environment.debugDescription)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extension View {
|
extension View {
|
||||||
|
|
|
@ -38,7 +38,7 @@ internal extension PlatformView {
|
||||||
for subview in views.reversed() {
|
for subview in views.reversed() {
|
||||||
if let typed = subview as? ViewType {
|
if let typed = subview as? ViewType {
|
||||||
return typed
|
return typed
|
||||||
} else if let typed = subview.child(ofType: type) {
|
} else if let typed = subview.descendent(ofType: type) {
|
||||||
return typed
|
return typed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,11 +46,11 @@ internal extension PlatformView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func child<ViewType: PlatformView>(ofType type: ViewType.Type) -> ViewType? {
|
func descendent<ViewType: PlatformView>(ofType type: ViewType.Type) -> ViewType? {
|
||||||
for subview in subviews {
|
for subview in subviews {
|
||||||
if let typed = subview as? ViewType {
|
if let typed = subview as? ViewType {
|
||||||
return typed
|
return typed
|
||||||
} else if let typed = subview.child(ofType: type) {
|
} else if let typed = subview.descendent(ofType: type) {
|
||||||
return typed
|
return typed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,12 @@ internal struct Inspector {
|
||||||
var sourceView: PlatformView
|
var sourceView: PlatformView
|
||||||
var sourceController: PlatformViewController
|
var sourceController: PlatformViewController
|
||||||
|
|
||||||
|
func `any`<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
|
||||||
|
ancestor(ofType: ViewType.self)
|
||||||
|
?? sibling(ofType: ViewType.self)
|
||||||
|
?? descendent(ofType: ViewType.self)
|
||||||
|
}
|
||||||
|
|
||||||
func ancestor<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
|
func ancestor<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
|
||||||
hostView.ancestor(ofType: ViewType.self)
|
hostView.ancestor(ofType: ViewType.self)
|
||||||
}
|
}
|
||||||
|
@ -72,8 +78,8 @@ internal struct Inspector {
|
||||||
hostView.sibling(ofType: ViewType.self)
|
hostView.sibling(ofType: ViewType.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func child<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
|
func descendent<ViewType: PlatformView>(ofType: ViewType.Type) -> ViewType? {
|
||||||
hostView.child(ofType: ViewType.self)
|
hostView.descendent(ofType: ViewType.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ final class PreviewController<Items>: UIViewController, UIAdaptivePresentationCo
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentController() {
|
private func presentController() {
|
||||||
print("Present")
|
|
||||||
let controller = QLPreviewController(nibName: nil, bundle: nil)
|
let controller = QLPreviewController(nibName: nil, bundle: nil)
|
||||||
controller.dataSource = self
|
controller.dataSource = self
|
||||||
controller.delegate = self
|
controller.delegate = self
|
||||||
|
|
|
@ -53,8 +53,6 @@ final class PreviewController<Items>: NSViewController, QLPreviewPanelDataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
private func present() {
|
private func present() {
|
||||||
print("Present")
|
|
||||||
|
|
||||||
NSApp.mainWindow?.nextResponder = self
|
NSApp.mainWindow?.nextResponder = self
|
||||||
|
|
||||||
if isVisible {
|
if isVisible {
|
||||||
|
@ -88,18 +86,15 @@ final class PreviewController<Items>: NSViewController, QLPreviewPanelDataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
|
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
|
||||||
print("Accept")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
|
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
|
||||||
print("Begin")
|
|
||||||
panel.dataSource = self
|
panel.dataSource = self
|
||||||
panel.reloadData()
|
panel.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
|
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
|
||||||
print("End")
|
|
||||||
panel.dataSource = nil
|
panel.dataSource = nil
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
#if os(iOS)
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftBackports
|
|
||||||
|
|
||||||
/*
|
|
||||||
WIP
|
|
||||||
*/
|
|
||||||
|
|
||||||
@available(iOS, deprecated: 16)
|
|
||||||
private extension Backport where Wrapped: View {
|
|
||||||
func scrollContentBackground(_ visibility: Backport<Any>.Visibility) -> some View {
|
|
||||||
wrapped.inspect { inspector in
|
|
||||||
inspector.sibling(ofType: PlatformScrollView.self)
|
|
||||||
} customize: { view in
|
|
||||||
let isHidden = visibility == .hidden
|
|
||||||
print(isHidden)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftBackports
|
||||||
|
|
||||||
|
extension Backport where Wrapped: View {
|
||||||
|
func textInputAutocapitalization(_ autocapitalization: Backport<Any>.TextInputAutocapitalization?) -> some View {
|
||||||
|
wrapped.modifier(
|
||||||
|
AutoCapitalizationModifier(
|
||||||
|
capitalization: autocapitalization?.capitalization ?? .none
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS, introduced: 13, deprecated: 15)
|
||||||
|
@available(macOS, unavailable)
|
||||||
|
@available(tvOS, unavailable)
|
||||||
|
@available(watchOS, unavailable)
|
||||||
|
extension Backport<Any> {
|
||||||
|
public struct TextInputAutocapitalization {
|
||||||
|
internal let capitalization: UITextAutocapitalizationType
|
||||||
|
|
||||||
|
fileprivate init(capitalization: UITextAutocapitalizationType) {
|
||||||
|
self.capitalization = capitalization
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines an autocapitalizing behavior that will not capitalize anything.
|
||||||
|
public static var never: TextInputAutocapitalization { .init(capitalization: .none) }
|
||||||
|
|
||||||
|
/// Defines an autocapitalizing behavior that will capitalize the first
|
||||||
|
/// letter of every word.
|
||||||
|
public static var words: TextInputAutocapitalization { .init(capitalization: .words) }
|
||||||
|
|
||||||
|
/// Defines an autocapitalizing behavior that will capitalize the first
|
||||||
|
/// letter in every sentence.
|
||||||
|
public static var sentences: TextInputAutocapitalization { .init(capitalization: .sentences) }
|
||||||
|
|
||||||
|
/// Defines an autocapitalizing behavior that will capitalize every letter.
|
||||||
|
public static var characters: TextInputAutocapitalization { .init(capitalization: .allCharacters) }
|
||||||
|
|
||||||
|
public init?(_ type: UITextAutocapitalizationType) {
|
||||||
|
self.capitalization = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct AutoCapitalizationModifier: ViewModifier {
|
||||||
|
let capitalization: UITextAutocapitalizationType
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.inspect { inspector in
|
||||||
|
inspector.ancestor(ofType: UITextField.self)
|
||||||
|
} customize: { view in
|
||||||
|
view.autocapitalizationType = capitalization
|
||||||
|
}
|
||||||
|
.inspect { inspector in
|
||||||
|
inspector.ancestor(ofType: UITextView.self)
|
||||||
|
} customize: { view in
|
||||||
|
view.autocapitalizationType = capitalization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,11 +50,11 @@ extension Backport where Wrapped: View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
inspector.sibling(ofType: UIScrollView.self)
|
inspector.sibling(ofType: UIScrollView.self)
|
||||||
?? inspector.ancestor(ofType: UIScrollView.self)
|
?? inspector.ancestor(ofType: UIScrollView.self)
|
||||||
?? inspector.child(ofType: UIScrollView.self)
|
?? inspector.descendent(ofType: UIScrollView.self)
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
inspector.sibling(ofType: NSScrollView.self)
|
inspector.sibling(ofType: NSScrollView.self)
|
||||||
?? inspector.ancestor(ofType: NSScrollView.self)
|
?? inspector.ancestor(ofType: NSScrollView.self)
|
||||||
?? inspector.child(ofType: NSScrollView.self)
|
?? inspector.descendent(ofType: NSScrollView.self)
|
||||||
#endif
|
#endif
|
||||||
} customize: { scrollView in
|
} customize: { scrollView in
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftBackports
|
||||||
|
|
||||||
|
public extension Backport where Wrapped: View {
|
||||||
|
/// Adds an action to perform when the user submits a value to this view.
|
||||||
|
///
|
||||||
|
/// Different views may have different triggers for the provided action. A TextField, or SecureField will trigger this action when the user hits the hardware or software return key. This modifier may also bind this action to a default action keyboard shortcut. You may set this action on an individual view or an entire view hierarchy.
|
||||||
|
///
|
||||||
|
/// TextField("Username", text: $username)
|
||||||
|
/// .onSubmit {
|
||||||
|
/// guard viewModel.validate() else { return }
|
||||||
|
/// viewModel.login()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
@ViewBuilder
|
||||||
|
func onSubmit(_ action: @escaping () -> Void) -> some View {
|
||||||
|
Group {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
wrapped
|
||||||
|
.onSubmit(action)
|
||||||
|
} else {
|
||||||
|
wrapped
|
||||||
|
.modifier(SubmitModifier())
|
||||||
|
.environment(\.backportSubmit, .init(submit: action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A semantic label describing the label of submission within a view hierarchy.
|
||||||
|
///
|
||||||
|
/// A submit label is a description of a submission action provided to a
|
||||||
|
/// view hierarchy using the ``View/onSubmit(of:_:)`` modifier.
|
||||||
|
@ViewBuilder
|
||||||
|
func submitLabel(_ label: Backport<Any>.SubmitLabel) -> some View {
|
||||||
|
Group {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
wrapped
|
||||||
|
.submitLabel(.init(label))
|
||||||
|
} else {
|
||||||
|
wrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modifier(SubmitModifier())
|
||||||
|
.environment(\.backportSubmitLabel, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Backport where Wrapped == Any {
|
||||||
|
/// A semantic label describing the label of submission within a view hierarchy.
|
||||||
|
///
|
||||||
|
/// A submit label is a description of a submission action provided to a
|
||||||
|
/// view hierarchy using the ``View/onSubmit(of:_:)`` modifier.
|
||||||
|
struct SubmitLabel: Equatable {
|
||||||
|
internal let returnKeyType: UIReturnKeyType
|
||||||
|
fileprivate init(_ type: UIReturnKeyType) {
|
||||||
|
returnKeyType = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 15, *)
|
||||||
|
private extension SwiftUI.SubmitLabel {
|
||||||
|
init(_ label: Backport<Any>.SubmitLabel) {
|
||||||
|
switch label {
|
||||||
|
case .continue: self = .continue
|
||||||
|
case .done: self = .done
|
||||||
|
case .go: self = .go
|
||||||
|
case .join: self = .join
|
||||||
|
case .next: self = .next
|
||||||
|
case .return: self = .return
|
||||||
|
case .route: self = .route
|
||||||
|
case .search: self = .search
|
||||||
|
case .send: self = .send
|
||||||
|
default: self = .return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Backport.SubmitLabel {
|
||||||
|
/// Defines a submit label with text of "Continue".
|
||||||
|
static var `continue`: Self { .init(.continue) }
|
||||||
|
/// Defines a submit label with text of "Done".
|
||||||
|
static var done: Self { .init(.done) }
|
||||||
|
/// Defines a submit label with text of "Go".
|
||||||
|
static var go: Self { .init(.go) }
|
||||||
|
/// Defines a submit label with text of "Join".
|
||||||
|
static var join: Self { .init(.join) }
|
||||||
|
/// Defines a submit label with text of "Next".
|
||||||
|
static var next: Self { .init(.next) }
|
||||||
|
/// Defines a submit label with text of "Return".
|
||||||
|
static var `return`: Self { .init(.default) }
|
||||||
|
/// Defines a submit label with text of "Route".
|
||||||
|
static var route: Self { .init(.route) }
|
||||||
|
/// Defines a submit label with text of "Search".
|
||||||
|
static var search: Self { .init(.search) }
|
||||||
|
/// Defines a submit label with text of "Send".
|
||||||
|
static var send: Self { .init(.send) }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct SubmitAction {
|
||||||
|
let submit: () -> Void
|
||||||
|
func callAsFunction() { submit() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SubmitEnvironmentKey: EnvironmentKey {
|
||||||
|
static var defaultValue: SubmitAction = .init(submit: { })
|
||||||
|
}
|
||||||
|
|
||||||
|
internal extension EnvironmentValues {
|
||||||
|
var backportSubmit: SubmitAction {
|
||||||
|
get { self[SubmitEnvironmentKey.self] }
|
||||||
|
set { self[SubmitEnvironmentKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SubmitLabelEnvironmentKey: EnvironmentKey {
|
||||||
|
static var defaultValue: Backport.SubmitLabel = .return
|
||||||
|
}
|
||||||
|
|
||||||
|
internal extension EnvironmentValues {
|
||||||
|
var backportSubmitLabel: Backport<Any>.SubmitLabel {
|
||||||
|
get { self[SubmitLabelEnvironmentKey.self] }
|
||||||
|
set { self[SubmitLabelEnvironmentKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SubmitModifier: ViewModifier {
|
||||||
|
@Environment(\.backportSubmit) private var submit
|
||||||
|
@Environment(\.backportSubmitLabel) private var label
|
||||||
|
|
||||||
|
@Backport.StateObject private var coordinator: Coordinator = .init()
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.inspect { inspector in
|
||||||
|
inspector.any(ofType: UITextView.self)
|
||||||
|
} customize: { view in
|
||||||
|
view.returnKeyType = label.returnKeyType
|
||||||
|
}
|
||||||
|
.inspect { inspector in
|
||||||
|
inspector.any(ofType: UITextField.self)
|
||||||
|
} customize: { view in
|
||||||
|
view.returnKeyType = label.returnKeyType
|
||||||
|
coordinator.onReturn = { submit() }
|
||||||
|
coordinator.observe(view: view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Coordinator: NSObject, ObservableObject {
|
||||||
|
private(set) weak var field: UITextField?
|
||||||
|
|
||||||
|
var onReturn: () -> Void = { }
|
||||||
|
@objc private func didReturn() { onReturn() }
|
||||||
|
|
||||||
|
override init() { }
|
||||||
|
|
||||||
|
func observe(view: UITextField) {
|
||||||
|
view.addTarget(self, action: #selector(didReturn), for: .editingDidEndOnExit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,7 +95,6 @@ extension OffsetShapeStyle<SystemColorsStyle> {
|
||||||
case 3: color = .quaternaryLabel
|
case 3: color = .quaternaryLabel
|
||||||
default: color = .label
|
default: color = .label
|
||||||
}
|
}
|
||||||
print(offset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +214,6 @@ func resolveColorProvider(_ provider: Any) -> ColorProvider? {
|
||||||
|
|
||||||
func printMirror(_ value: Any) {
|
func printMirror(_ value: Any) {
|
||||||
let mirror = Mirror(reflecting: value)
|
let mirror = Mirror(reflecting: value)
|
||||||
print(mirror.subjectType)
|
|
||||||
for child in mirror.children {
|
for child in mirror.children {
|
||||||
print(child)
|
print(child)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,16 +96,16 @@ extension Backport where Wrapped == Any {
|
||||||
|
|
||||||
view.delegate = self
|
view.delegate = self
|
||||||
view.adjustsFontForContentSizeCategory = true
|
view.adjustsFontForContentSizeCategory = true
|
||||||
view.autocapitalizationType = .allCharacters
|
view.autocapitalizationType = .sentences
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
|
view.dataDetectorTypes = []
|
||||||
|
view.returnKeyType = parent.environment.backportSubmitLabel.returnKeyType
|
||||||
|
|
||||||
switch parent.environment.disableAutocorrection {
|
switch parent.environment.autocorrectionDisabled {
|
||||||
case .some(true):
|
case true:
|
||||||
view.autocorrectionType = .yes
|
view.autocorrectionType = .yes
|
||||||
case .some(false):
|
case false:
|
||||||
view.autocorrectionType = .no
|
view.autocorrectionType = .no
|
||||||
case nil:
|
|
||||||
view.autocorrectionType = .default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = NSMutableParagraphStyle()
|
let style = NSMutableParagraphStyle()
|
||||||
|
|
Loading…
Reference in New Issue