Skip to content
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

Alert with binding #145

Merged
merged 12 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
"state": {
"branch": null,
"revision": "bba1111185863c9288c5f047770f421c3b7793a4",
"version": "1.1.3"
"revision": "e593aba2c6222daad7c4f2732a431eed2c09bb07",
"version": "1.3.0"
}
},
{
Expand Down
73 changes: 3 additions & 70 deletions Sources/SwiftUINavigation/Alert.swift
Original file line number Diff line number Diff line change
@@ -1,73 +1,8 @@
#if canImport(SwiftUI)
import SwiftUI

extension View {
/// Presents an alert from a binding to an optional value.
///
/// SwiftUI's `alert` view modifiers are driven by two disconnected pieces of state: an
/// `isPresented` binding to a boolean that determines if the alert should be presented, and
/// optional alert `data` that is used to customize its actions and message.
///
/// Modeling the domain in this way unfortunately introduces a couple invalid runtime states:
///
/// * `isPresented` can be `true`, but `data` can be `nil`.
/// * `isPresented` can be `false`, but `data` can be non-`nil`.
///
/// On top of that, SwiftUI's `alert` modifiers take static titles, which means the title cannot
/// be dynamically computed from the alert data.
///
/// This overload addresses these shortcomings with a streamlined API. First, it eliminates the
/// invalid runtime states at compile time by driving the alert's presentation from a single,
/// optional binding. When this binding is non-`nil`, the alert will be presented. Further, the
/// title can be customized from the alert data.
///
/// ```swift
/// struct AlertDemo: View {
/// @State var randomMovie: Movie?
///
/// var body: some View {
/// Button("Pick a random movie", action: self.getRandomMovie)
/// .alert(
/// title: { Text($0.title) },
/// unwrapping: self.$randomMovie,
/// actions: { _ in
/// Button("Pick another", action: self.getRandomMovie)
/// },
/// message: { Text($0.summary) }
/// )
/// }
///
/// func getRandomMovie() {
/// self.randomMovie = Movie.allCases.randomElement()
/// }
/// }
/// ```
///
/// - Parameters:
/// - title: A closure returning the alert's title given the current alert state.
/// - value: A binding to an optional value that determines whether an alert should be
/// presented. When the binding is updated with non-`nil` value, it is unwrapped and passed
/// to the modifier's closures. You can use this data to populate the fields of an alert
/// that the system displays to the user. When the user presses or taps one of the alert's
/// actions, the system sets this value to `nil` and dismisses the alert.
/// - actions: A view builder returning the alert's actions given the current alert state.
/// - message: A view builder returning the message for the alert given the current alert
/// state.
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public func alert<Value, A: View, M: View>(
title: (Value) -> Text,
unwrapping value: Binding<Value?>,
@ViewBuilder actions: (Value) -> A,
@ViewBuilder message: (Value) -> M
) -> some View {
self.alert(
value.wrappedValue.map(title) ?? Text(verbatim: ""),
isPresented: value.isPresent(),
presenting: value.wrappedValue,
actions: actions,
message: message
)
}

@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
extension View {

/// Presents an alert from a binding to optional alert state.
///
Expand All @@ -81,7 +16,6 @@
/// dismisses the alert, and the action is fed to the `action` closure.
/// - handler: A closure that is called with an action from a particular alert button when
/// tapped.
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public func alert<Value>(
_ state: Binding<AlertState<Value>?>,
action handler: @escaping (Value?) -> Void = { (_: Never?) in }
Expand Down Expand Up @@ -114,7 +48,6 @@
/// dismisses the alert, and the action is fed to the `action` closure.
/// - handler: A closure that is called with an action from a particular alert button when
/// tapped.
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public func alert<Value>(
_ state: Binding<AlertState<Value>?>,
action handler: @escaping (Value?) async -> Void = { (_: Never?) async in }
Expand Down
21 changes: 1 addition & 20 deletions Sources/SwiftUINavigation/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,6 @@
self = base[default: DefaultSubscript(value)]
}

/// Creates a binding by projecting the current optional value to a boolean describing if it's
/// non-`nil`.
///
/// Writing `false` to the binding will `nil` out the base value. Writing `true` does nothing.
///
/// - Returns: A binding to a boolean. Returns `true` if non-`nil`, otherwise `false`.
public func isPresent<Wrapped>() -> Binding<Bool>
where Value == Wrapped? {
self._isPresent
}

/// Creates a binding that ignores writes to its wrapped value when equivalent to the new value.
///
/// Useful to minimize writes to bindings passed to SwiftUI APIs. For example, [`NavigationLink`
Expand Down Expand Up @@ -113,14 +102,6 @@
}

extension Optional {
fileprivate var _isPresent: Bool {
get { self != nil }
set {
guard !newValue else { return }
self = nil
}
}

fileprivate subscript(default defaultSubscript: DefaultSubscript<Wrapped>) -> Wrapped {
get {
defaultSubscript.value = self ?? defaultSubscript.value
Expand Down Expand Up @@ -175,4 +156,4 @@
}
}
}
#endif // canImport(SwiftUI)
#endif // canImport(SwiftUI)
73 changes: 0 additions & 73 deletions Sources/SwiftUINavigation/ConfirmationDialog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,6 @@
import SwiftUI

extension View {
/// Presents a confirmation dialog from a binding to an optional value.
///
/// SwiftUI's `confirmationDialog` view modifiers are driven by two disconnected pieces of
/// state: an `isPresented` binding to a boolean that determines if the dialog should be
/// presented, and optional dialog `data` that is used to customize its actions and message.
///
/// Modeling the domain in this way unfortunately introduces a couple invalid runtime states:
///
/// * `isPresented` can be `true`, but `data` can be `nil`.
/// * `isPresented` can be `false`, but `data` can be non-`nil`.
///
/// On top of that, SwiftUI's `confirmationDialog` modifiers take static titles, which means the
/// title cannot be dynamically computed from the dialog data.
///
/// This overload addresses these shortcomings with a streamlined API. First, it eliminates the
/// invalid runtime states at compile time by driving the dialog's presentation from a single,
/// optional binding. When this binding is non-`nil`, the dialog will be presented. Further, the
/// title can be customized from the dialog data.
///
/// ```swift
/// struct DialogDemo: View {
/// @State var randomMovie: Movie?
///
/// var body: some View {
/// Button("Pick a random movie", action: self.getRandomMovie)
/// .confirmationDialog(
/// title: { Text($0.title) },
/// titleVisibility: .always,
/// unwrapping: self.$randomMovie,
/// actions: { _ in
/// Button("Pick another", action: self.getRandomMovie)
/// },
/// message: { Text($0.summary) }
/// )
/// }
///
/// func getRandomMovie() {
/// self.randomMovie = Movie.allCases.randomElement()
/// }
/// }
/// ```
///
/// See <doc:AlertsDialogs> for more information on how to use this API.
///
/// - Parameters:
/// - title: A closure returning the dialog's title given the current dialog state.
/// - titleVisibility: The visibility of the dialog's title.
/// - value: A binding to an optional value that determines whether a dialog should be
/// presented. When the binding is updated with non-`nil` value, it is unwrapped and passed
/// to the modifier's closures. You can use this data to populate the fields of a dialog
/// that the system displays to the user. When the user presses or taps one of the dialog's
/// actions, the system sets this value to `nil` and dismisses the dialog.
/// - actions: A view builder returning the dialog's actions given the current dialog state.
/// - message: A view builder returning the message for the dialog given the current dialog
/// state.
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public func confirmationDialog<Value, A: View, M: View>(
title: (Value) -> Text,
titleVisibility: Visibility = .automatic,
unwrapping value: Binding<Value?>,
@ViewBuilder actions: (Value) -> A,
@ViewBuilder message: (Value) -> M
) -> some View {
self.confirmationDialog(
value.wrappedValue.map(title) ?? Text(verbatim: ""),
isPresented: value.isPresent(),
titleVisibility: titleVisibility,
presenting: value.wrappedValue,
actions: actions,
message: message
)
}

/// Presents a confirmation dialog from a binding to optional confirmation dialog state.
///
/// See <doc:AlertsDialogs> for more information on how to use this API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ struct SignInView: View {

### Dynamic case lookup

- ``SwiftUI/Binding/subscript(dynamicMember:)-9akk``
- ``SwiftUI/Binding/subscript(dynamicMember:)-9okch``
- ``SwiftUI/Binding/subscript(dynamicMember:)-9abgy``
- ``SwiftUI/Binding/subscript(dynamicMember:)-8vc80``

### Unwrapping bindings

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ instead.
### View modifiers

- ``SwiftUI/View/alert(title:unwrapping:case:actions:message:)``
- ``SwiftUI/View/alert(title:unwrapping:actions:message:)``
- ``SwiftUI/View/alert(unwrapping:action:)-7da26``
- ``SwiftUI/View/alert(unwrapping:action:)-6y2fk``
- ``SwiftUI/View/alert(unwrapping:action:)-867h5``
Expand Down
Loading