์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
Tags
- visionOS
- Xcode
- BOJ
- SnapKit
- MVVM
- ios
- TCA
- ํ๋ก๊ทธ๋๋จธ์ค
- Kuring
- CollectionView
- SwiftUI
- XCTest
- arkit
- tableView
- ํจ์คํธ์บ ํผ์ค
- realm
- raywenderlich
- rxcocoa
- Lv2
- ๋ฐฑ์ค
- Flutter
- swift
- reactorkit
- UIKit
- designpattern
- Swfit
- BFS
- RxSwift
- combine
- node.js
Archives
- Today
- Total
lgvv98
[TCA] Tutorial #5 (Multiple presentation destinations) ๋ณธ๋ฌธ
apple/๐ฆฅ TCA
[TCA] Tutorial #5 (Multiple presentation destinations)
๐ฅ ์บ๋ฟ๋งจ 2023. 9. 24. 12:12
[TCA] Tutorial #5 (Multiple presentation destinations)
// MARK: - Contact
import Foundation
import ComposableArchitecture
struct Contact: Equatable, Identifiable {
let id: UUID
var name: String
}
struct ContactsFeature: Reducer {
struct State: Equatable {
var contacts: IdentifiedArrayOf<Contact> = []
@PresentationState var destination: Destination.State? // ํ๋ฉด์ ํ์ ์ด๋ ๊ฒ ๋ฌถ์ด์ ๋ถ๋ฆฌํด์ ์ฒ๋ฆฌ
}
enum Action: Equatable {
case addButtonTapped
case deleteButtonTapped(id: Contact.ID)
case destination(PresentationAction<Destination.Action>) // ํ๋ฉด์ ํ ์ก์
enum Alert: Equatable {
case confirmDeletion(id: Contact.ID)
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .addButtonTapped:
state.destination = .addContact(
AddContactFeature.State(
contact: Contact(id: UUID(), name: "")
)
)
return .none
case let .destination(.presented(.addContact(.delegate(.saveContact(contact))))):
// delegate๋ฅผ ํตํด์ ๋ค๋ฅธ ๋ชฉ์ ์ง์ action์ผ๋ก ์ฐ๊ฒฐ
state.contacts.append(contact)
return .none
case let .destination(.presented(.alert(.confirmDeletion(id: id)))):
// alert๋ฅผ ์ฐ๊ฒฐ
state.contacts.remove(id: id)
return .none
case .destination:
return .none
case let .deleteButtonTapped(id: id):
state.destination = .alert(
// AlertState ์์คํ
์๋ฟ์ cancel์ด ๋ํดํธ๋ก ๋ค์ด๊ฐ ์์
AlertState {
TextState("Are you sure?")
} actions: {
ButtonState(role: .destructive, action: .confirmDeletion(id: id)) {
TextState("Delete")
}
}
)
return .none
}
}
.ifLet(\.$destination, action: /Action.destination) {
// ํ๋ฉด์ด๋์ ๋ํ ๋ฆฌ๋์๋ฅผ ๊ฒฐํฉ
Destination()
}
}
}
extension ContactsFeature {
struct Destination: Reducer {
// ํ๋ฉด์ด๋์ ๋ํ ๋ถ๋ถ์ extension์ผ๋ก ์ฒ๋ฆฌ
enum State: Equatable {
case addContact(AddContactFeature.State)
case alert(AlertState<ContactsFeature.Action.Alert>)
}
enum Action: Equatable {
case addContact(AddContactFeature.Action)
case alert(ContactsFeature.Action.Alert)
}
var body: some ReducerOf<Self> {
// ํ๋์ Reducer๋ฐ์ ์์ด์ ์ด๋ ๊ฒ ์ฒ๋ฆฌ
// ์๋ฟ์ ๊ฒฝ์ฐ์๋ ๋ณ๋๋ก Reducer๊ฐ ํ์์๊ธฐ ๋๋ฌธ.
Scope(state: /State.addContact, action: /Action.addContact) {
AddContactFeature()
}
}
}
}
import SwiftUI
struct ContactsView: View {
let store: StoreOf<ContactsFeature>
var body: some View {
NavigationStack {
WithViewStore(self.store, observe: \.contacts) { viewStore in
List {
ForEach(viewStore.state) { contact in
HStack {
Text(contact.name)
Spacer()
Button {
viewStore.send(.deleteButtonTapped(id: contact.id))
} label: {
Image(systemName: "trash")
.foregroundColor(.red)
}
}
}
}
.navigationTitle("Contacts")
.toolbar {
ToolbarItem {
Button {
viewStore.send(.addButtonTapped)
} label: {
Image(systemName: "plus")
}
}
}
}
}
.sheet(store: self.store.scope(state: \.$destination, action: { .destination($0) }),
state: /ContactsFeature.Destination.State.addContact, // case path๋ก ์ฌ์ฉํ๋๋ฐ ์ด ๋ฌธ๋ฒ์ ์ ๋ฆฌ๊ฐ ํ์ํ ๋ฏ ์ถ๋ค.
action: ContactsFeature.Destination.Action.addContact
) { addContactStore in
NavigationStack {
AddContactView(store: addContactStore)
}
}
.alert(
// ์๋ฟ๋ ๋์ผํ๊ฒ ์ฌ์ฉํ๋ค. action์ ์ฃผ๊ณ ํ์ฒ๋ฆฌ๋ reducer์์ ๋ด๋นํ๋ค. View ๋ก์ง์ด ๋งค์ฐ ๊น๋ํด์ง
store: self.store.scope(state: \.$destination, action: { .destination($0) }),
state: /ContactsFeature.Destination.State.alert,
action: ContactsFeature.Destination.Action.alert
)
}
}
import ComposableArchitecture
struct AddContactFeature: Reducer {
struct State: Equatable {
var contact: Contact
}
enum Action: Equatable {
case cancelButtonTapped
case delegate(Delegate) // ๋ถ๋ชจ ์์๊ฐ์ delegate๋ก ์ฐ๊ฒฐ
case saveButtonTapped
case setName(String)
enum Delegate: Equatable {
// ๋ถ๋ชจ ์์๊ฐ์ delegate๋ก ์ฐ๊ฒฐ
// case cancel
case saveContact(Contact)
}
}
@Dependency(\.dismiss) var dismiss
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .cancelButtonTapped:
// dismiss๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ํ๋๋ฏ๋ก Efeect์์ ํธ์ถํ๋๊ฒ ์ ์ ํจ.
return .run { _ in await self.dismiss() }
case .delegate:
// ๋๋ฆฌ๊ฒ์ดํธ๋ ์ค์ ๋ก ์ด๋ค ๋ก์ง์ ์ฒ๋ฆฌํด์๋ ์๋๋ค. ๋ฐ๋์ .none!
return .none
case .saveButtonTapped:
return .run { [contact = state.contact] send in
await send(.delegate(.saveContact(contact))) // ๋๋ฆฌ๊ฒ์ดํธ๋ก ์ ๋ฌ!
await self.dismiss()
}
case let .setName(name):
state.contact.name = name
return .none
}
}
}
struct AddContactView: View {
let store: StoreOf<AddContactFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
Form {
TextField("Name", text: viewStore.binding(get: \.contact.name, send: { .setName($0) })) // bindingํ๋ ๋ฐฉ๋ฒ
Button("Save") {
viewStore.send(.saveButtonTapped)
}
}
.toolbar {
ToolbarItem {
Button("Cancel") {
viewStore.send(.cancelButtonTapped)
}
}
}
}
}
}
'apple > ๐ฆฅ TCA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TCA] SharedState (1) | 2023.09.27 |
---|---|
[TCA] OptionalState (IfLetCase) (0) | 2023.09.27 |
[TCA] FocusState (0) | 2023.09.27 |
[TCA] Binding (0) | 2023.09.27 |
[TCA] ๊ณต๋ถ๊ธฐ๋ก #1 (ReducerProtocol) (0) | 2023.01.16 |
Comments