SwiftUIBackports/Sources/SwiftUIBackports/iOS/Submit/Submit.swift

165 lines
5.5 KiB
Swift

import SwiftUI
import SwiftBackports
#if os(iOS)
@available(iOS, deprecated: 15)
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)
}
}
}
#endif