diff --git a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift index 92b39abe7793..916e05a5d069 100644 --- a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift +++ b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift @@ -3,10 +3,11 @@ import SwiftUI @Reducer struct DownloadComponent { + @ObservableState struct State: Equatable { - @PresentationState var alert: AlertState? + @Presents var alert: AlertState? let id: AnyHashable - var mode: Mode + var mode: Mode = .notDownloaded let url: URL } @@ -134,36 +135,49 @@ enum Mode: Equatable { } struct DownloadComponentView: View { - let store: StoreOf + @State var isVisible = false + @Bindable var store: StoreOf var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - Button { - viewStore.send(.buttonTapped) - } label: { - if viewStore.mode == .downloaded { - Image(systemName: "checkmark.circle") - .tint(.accentColor) - } else if viewStore.mode.progress > 0 { - ZStack { - CircularProgressView(value: viewStore.mode.progress) - .frame(width: 16, height: 16) - Rectangle() - .frame(width: 6, height: 6) - } - } else if viewStore.mode == .notDownloaded { - Image(systemName: "icloud.and.arrow.down") - } else if viewStore.mode == .startingToDownload { - ZStack { - ProgressView() - Rectangle() - .frame(width: 6, height: 6) - } + VStack { + if isVisible { + button + .alert($store.scope(state: \.alert, action: \.alert)) + } else { + button + } + } + .onAppear { isVisible = true } + .onDisappear { isVisible = false } + } + + @MainActor + private var button: some View { + Button { + store.send(.buttonTapped) + } label: { + if store.mode == .downloaded { + Image(systemName: "checkmark.circle") + .tint(.accentColor) + } else if store.mode.progress > 0 { + ZStack { + CircularProgressView(value: store.mode.progress) + .frame(width: 16, height: 16) + Rectangle() + .frame(width: 6, height: 6) + } + } else if store.mode == .notDownloaded { + Image(systemName: "icloud.and.arrow.down") + } else if store.mode == .startingToDownload { + ZStack { + ProgressView() + Rectangle() + .frame(width: 6, height: 6) } } - .foregroundStyle(.primary) - .alert(store: self.store.scope(state: \.$alert, action: \.alert)) } + .buttonStyle(.borderless) + .foregroundStyle(.primary) } } diff --git a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/ReusableComponents-Download.swift b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/ReusableComponents-Download.swift index 84f9a4621202..161129b62b38 100644 --- a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/ReusableComponents-Download.swift +++ b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ResuableOfflineDownloads/ReusableComponents-Download.swift @@ -16,28 +16,21 @@ private let readMe = """ @Reducer struct CityMap { + @ObservableState struct State: Equatable, Identifiable { var download: Download - var downloadAlert: AlertState? - var downloadMode: Mode + var downloadComponent: DownloadComponent.State + + init(download: Download) { + self.download = download + self.downloadComponent = DownloadComponent.State( + id: download.id, + url: download.downloadVideoUrl + ) + } var id: UUID { download.id } - var downloadComponent: DownloadComponent.State { - get { - DownloadComponent.State( - alert: downloadAlert, - id: download.id, - mode: downloadMode, - url: download.downloadVideoUrl - ) - } - set { - downloadAlert = newValue.alert - downloadMode = newValue.mode - } - } - struct Download: Equatable, Identifiable { var blurb: String var downloadVideoUrl: URL @@ -80,24 +73,16 @@ struct CityMapRowView: View { let store: StoreOf var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in + NavigationLink( + destination: CityMapDetailView(store: store) + ) { HStack { - NavigationLink( - destination: CityMapDetailView(store: store) - ) { - HStack { - Image(systemName: "map") - Text(viewStore.download.title) - } - .layoutPriority(1) - - Spacer() - - DownloadComponentView( - store: store.scope(state: \.downloadComponent, action: \.downloadComponent) - ) - .padding(.trailing, 8) - } + Image(systemName: "map") + Text(store.download.title) + Spacer() + DownloadComponentView( + store: store.scope(state: \.downloadComponent, action: \.downloadComponent) + ) } } } @@ -107,36 +92,35 @@ struct CityMapDetailView: View { let store: StoreOf var body: some View { - WithViewStore(store, observe: { $0 }) { viewStore in - VStack(spacing: 32) { - Text(viewStore.download.blurb) - - HStack { - if viewStore.downloadMode == .notDownloaded { - Text("Download for offline viewing") - } else if viewStore.downloadMode == .downloaded { - Text("Downloaded") - } else { - Text("Downloading \(Int(100 * viewStore.downloadComponent.mode.progress))%") - } - - Spacer() + Form { + Text(store.download.blurb) - DownloadComponentView( - store: store.scope(state: \.downloadComponent, action: \.downloadComponent) - ) + HStack { + switch store.downloadComponent.mode { + case .notDownloaded: + Text("Download for offline viewing") + case .downloaded: + Text("Downloaded") + case .downloading(progress: let progress): + Text("Downloading \(Int(100 * progress))%") + case .startingToDownload: + Text("Downloading…") } Spacer() + + DownloadComponentView( + store: store.scope(state: \.downloadComponent, action: \.downloadComponent) + ) } - .navigationTitle(viewStore.download.title) - .padding() } + .navigationTitle(store.download.title) } } @Reducer struct MapApp { + @ObservableState struct State: Equatable { var cityMaps: IdentifiedArrayOf = .mocks } @@ -160,9 +144,8 @@ struct CitiesView: View { Section { AboutView(readMe: readMe) } - ForEachStore(store.scope(state: \.cityMaps, action: \.cityMaps)) { cityMapStore in + ForEach(store.scope(state: \.cityMaps, action: \.cityMaps)) { cityMapStore in CityMapRowView(store: cityMapStore) - .buttonStyle(.borderless) } } .navigationTitle("Offline Downloads") @@ -182,8 +165,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!, id: UUID(), title: "New York, NY" - ), - downloadMode: .notDownloaded + ) ), CityMap.State( download: CityMap.State.Download( @@ -198,8 +180,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!, id: UUID(), title: "Los Angeles, LA" - ), - downloadMode: .notDownloaded + ) ), CityMap.State( download: CityMap.State.Download( @@ -212,8 +193,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!, id: UUID(), title: "Paris, France" - ), - downloadMode: .notDownloaded + ) ), CityMap.State( download: CityMap.State.Download( @@ -227,8 +207,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!, id: UUID(), title: "Tokyo, Japan" - ), - downloadMode: .notDownloaded + ) ), CityMap.State( download: CityMap.State.Download( @@ -243,8 +222,7 @@ extension IdentifiedArray where ID == CityMap.State.ID, Element == CityMap.State downloadVideoUrl: URL(string: "http://ipv4.download.thinkbroadband.com/50MB.zip")!, id: UUID(), title: "Buenos Aires, Argentina" - ), - downloadMode: .notDownloaded + ) ), ] } diff --git a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ReusableFavoriting.swift b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ReusableFavoriting.swift index 9280de1a323f..88fa91348688 100644 --- a/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ReusableFavoriting.swift +++ b/Examples/CaseStudies/SwiftUICaseStudies/05-HigherOrderReducers-ReusableFavoriting.swift @@ -17,8 +17,9 @@ private let readMe = """ favorite state and rendering an alert. """ +@ObservableState struct FavoritingState: Equatable { - @PresentationState var alert: AlertState? + @Presents var alert: AlertState? let id: ID var isFavorite: Bool } @@ -69,23 +70,22 @@ struct Favoriting { } struct FavoriteButton: View { - let store: Store, FavoritingAction> + @Bindable var store: Store, FavoritingAction> var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - Button { - viewStore.send(.buttonTapped) - } label: { - Image(systemName: "heart") - .symbolVariant(viewStore.isFavorite ? .fill : .none) - } - .alert(store: self.store.scope(state: \.$alert, action: \.alert)) + Button { + store.send(.buttonTapped) + } label: { + Image(systemName: "heart") + .symbolVariant(store.isFavorite ? .fill : .none) } + .alert($store.scope(state: \.alert, action: \.alert)) } } @Reducer struct Episode { + @ObservableState struct State: Equatable, Identifiable { var alert: AlertState? let id: UUID @@ -115,20 +115,19 @@ struct EpisodeView: View { let store: StoreOf var body: some View { - WithViewStore(self.store, observe: { $0 }) { viewStore in - HStack(alignment: .firstTextBaseline) { - Text(viewStore.title) + HStack(alignment: .firstTextBaseline) { + Text(store.title) - Spacer() + Spacer() - FavoriteButton(store: self.store.scope(state: \.favorite, action: \.favorite)) - } + FavoriteButton(store: store.scope(state: \.favorite, action: \.favorite)) } } } @Reducer struct Episodes { + @ObservableState struct State: Equatable { var episodes: IdentifiedArrayOf = [] } @@ -157,7 +156,8 @@ struct EpisodesView: View { Section { AboutView(readMe: readMe) } - ForEachStore(self.store.scope(state: \.episodes, action: \.episodes)) { rowStore in + + ForEach(store.scope(state: \.episodes, action: \.episodes)) { rowStore in EpisodeView(store: rowStore) } .buttonStyle(.borderless) diff --git a/Package.resolved b/Package.resolved index f55a6e549e16..0ce74c8a7bf8 100644 --- a/Package.resolved +++ b/Package.resolved @@ -93,7 +93,7 @@ { "identity" : "swift-docc-symbolkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-docc-symbolkit", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", "state" : { "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", "version" : "1.0.0"