Skip to content

Instantly share code, notes, and snippets.

@tobitech
Last active July 11, 2022 22:43
Show Gist options
  • Save tobitech/911600cfe873eb82321cc3328b4824cb to your computer and use it in GitHub Desktop.
Save tobitech/911600cfe873eb82321cc3328b4824cb to your computer and use it in GitHub Desktop.
A SwiftUI app using The Composable Architecture.
import ComposableArchitecture
import SwiftUI
let appReducer: Reducer<AppState, AppAction, AppEnvironment> = Reducer.combine(
onboardingReducer
.optional()
.pullback(
state: \AppState.onboarding,
action: /AppAction.onboarding,
environment: { _ in OnboardingEnvironment() }
),
Reducer { state, action, _ in
switch action {
case let .onboarding(.loginResponse(user)):
// not firing
state.loggedInUser = user
return .none
case .onboarding(.alert(.login)):
// not firing
return .none
case .onboarding(.alert(_)):
return .none
case .onboarding(.continueWithAppleButtonTapped):
// not firing
return .none
}
}
)
struct User: Equatable {
let email: String
}
struct AppState: Equatable {
var loggedInUser: User?
var onboarding: OnboardingState?
}
enum AppAction {
case onboarding(OnboardingAction)
}
struct AppEnvironment {}
struct AppView: View {
let store: Store<AppState, AppAction>
var body: some View {
WithViewStore(self.store) { viewStore in
if viewStore.loggedInUser != nil {
MainView()
} else {
OnboardingView(
store: Store(
initialState: OnboardingState(),
reducer: onboardingReducer,
environment: OnboardingEnvironment()
)
)
}
}
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView(
store: Store(
initialState: AppState(),
reducer: appReducer,
environment: AppEnvironment()
)
)
}
}
import ComposableArchitecture
import SwiftUI
let onboardingReducer = Reducer<OnboardingState, OnboardingAction, OnboardingEnvironment> { state, action, _ in
switch action {
case .continueWithAppleButtonTapped:
state.alert = .init(
title: TextState("Login"),
message: TextState("Are you sure you want to login?"),
primaryButton: .default(TextState("Login"), action: .send(.login)),
secondaryButton: .cancel(TextState("Cancel"))
)
return .none
case .alert(.dismiss):
state.alert = nil
return .none
case .alert(.login):
state.alert = nil
// TODO: Perform an async operation that will return user or error.
return Effect<User, Never>.future { callback in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let user = User(email: "user@gmail.com")
callback(.success(user))
}
}
.map { OnboardingAction.loginResponse($0) }
case let .loginResponse(user):
// handle on parent reducer
return .none
}
}
struct OnboardingState: Equatable {
var alert: AlertState<OnboardingAction.AlertAction>?
}
enum OnboardingAction {
case alert(AlertAction)
case continueWithAppleButtonTapped
case loginResponse(User)
enum AlertAction: Equatable {
case dismiss
case login
}
}
struct OnboardingEnvironment {}
struct OnboardingView: View {
let store: Store<OnboardingState, OnboardingAction>
var body: some View {
WithViewStore(self.store) { viewStore in
VStack(alignment: .leading, spacing: 0.0) {
// ...
VStack(alignment: .center) {
SignInWithAppleButton()
.onTapGesture {
viewStore.send(.continueWithAppleButtonTapped)
}
// ...
}
}
.alert(
self.store.scope(state: \.alert, action: OnboardingAction.alert),
dismiss: .dismiss
)
}
}
}
struct OnboardingView_Previews: PreviewProvider {
static var previews: some View {
OnboardingView(
store: Store(
initialState: OnboardingState(),
reducer: onboardingReducer,
environment: OnboardingEnvironment()
)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment