Created
April 4, 2023 08:44
-
-
Save simme/cff23d521653f380310c04fc785364c5 to your computer and use it in GitHub Desktop.
UIKit helpers for presenting alerts and confirmation dialogs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Example usage: | |
confirmationDialog( | |
store: store.scope(state: \.destination, action: UserRecipesListReducer.Action.destination), | |
state: /UserRecipesListReducer.Destination.State.confirmation, | |
action: UserRecipesListReducer.Destination.Action.confirmation | |
).store(in: &subscriptions) | |
alert( | |
store: store.scope(state: \.destination, action: UserRecipesListReducer.Action.destination), | |
state: /UserRecipesListReducer.Destination.State.alert, | |
action: UserRecipesListReducer.Destination.Action.alert | |
).store(in: &subscriptions) | |
*/ | |
extension StoreViewController { | |
func alert<DestinationState, DestinationAction, Action>( | |
store: Store<DestinationState?, PresentationAction<DestinationAction>>, | |
state toAlertState: @escaping (DestinationState) -> AlertState<Action>?, | |
action fromAlertAction: @escaping (Action) -> DestinationAction | |
) -> any Cancellable { | |
self.alert( | |
store: store.scope( | |
state: { $0.flatMap(toAlertState) }, | |
action: { | |
switch $0 { | |
case .dismiss: return .dismiss | |
case let .presented(action): return .presented(fromAlertAction(action)) | |
} | |
} | |
) | |
) | |
} | |
func alert<Action>( | |
store: Store<AlertState<Action>?, PresentationAction<Action>> | |
) -> any Cancellable { | |
let viewStore = ViewStore(store, observe: { $0 }, removeDuplicates: { ($0 != nil) == ($1 != nil) }) | |
return viewStore.publisher.sink { alertState in | |
if let alertState { | |
let alertController = UIAlertController(state: alertState) { action in | |
if let action { | |
viewStore.send(.presented(action)) | |
} | |
} | |
self.present(alertController, animated: true) | |
} else { | |
self.dismiss(animated: true) | |
} | |
} | |
} | |
func confirmationDialog<DestinationState, DestinationAction, Action>( | |
store: Store<DestinationState?, PresentationAction<DestinationAction>>, | |
state toConfirmationDialogState: @escaping (DestinationState) -> ConfirmationDialogState<Action>?, | |
action fromConfirmationAction: @escaping (Action) -> DestinationAction | |
) -> any Cancellable { | |
self.confirmationDialog( | |
store: store.scope( | |
state: { $0.flatMap(toConfirmationDialogState) }, | |
action: { | |
switch $0 { | |
case .dismiss: return .dismiss | |
case let .presented(action): return .presented(fromConfirmationAction(action)) | |
} | |
} | |
) | |
) | |
} | |
func confirmationDialog<Action>( | |
store: Store<ConfirmationDialogState<Action>?, PresentationAction<Action>> | |
) -> any Cancellable { | |
let viewStore = ViewStore(store, observe: { $0 }, removeDuplicates: { ($0 != nil) == ($1 != nil) }) | |
return viewStore.publisher.sink { confirmationState in | |
if let confirmationState { | |
let alertController = UIAlertController(state: confirmationState) { action in | |
if let action { | |
viewStore.send(.presented(action)) | |
} | |
} | |
self.present(alertController, animated: true) | |
} else { | |
self.dismiss(animated: true) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@simme that's a really good point...I created my own navigation controller and I had to override the popViewController like this:
Then in your StoreViewController you'd do something like this:
Finally, in your reducer you nil out state:
An absolute boilerplate party, which I wish I didn't have to attend but I want to support iOS 15 (which means I can't override
navigationItem.backButtonAction
), and I don't like how I lose the original back button design when creating my own back button. It's an ugly workaround :(