From 2cd48b81862023b7c75dc7ecce23267eb2bd0421 Mon Sep 17 00:00:00 2001 From: Will Brandin Date: Thu, 12 Jan 2023 12:20:12 -0700 Subject: [PATCH] Demo form controls --- .../CustomTcaAlert.xcodeproj/project.pbxproj | 16 ++++ Examples/Shared/AlertDemoView.swift | 40 ++++---- Examples/Shared/DemoFormView.swift | 92 +++++++++++++++++++ Examples/Shared/DemoReducer.swift | 78 ++++++++++++++++ Sources/TCACustomAlert/CustomAlert.swift | 17 ++-- 5 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 Examples/Shared/DemoFormView.swift create mode 100644 Examples/Shared/DemoReducer.swift diff --git a/Examples/CustomTcaAlert.xcodeproj/project.pbxproj b/Examples/CustomTcaAlert.xcodeproj/project.pbxproj index 192fa46..e9328a3 100644 --- a/Examples/CustomTcaAlert.xcodeproj/project.pbxproj +++ b/Examples/CustomTcaAlert.xcodeproj/project.pbxproj @@ -25,6 +25,12 @@ D2A11A32297082950002E8FD /* AlertDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A11A31297082950002E8FD /* AlertDemoView.swift */; }; D2A11A33297082A40002E8FD /* AlertDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A11A31297082950002E8FD /* AlertDemoView.swift */; }; D2A11A34297082A40002E8FD /* AlertDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A11A31297082950002E8FD /* AlertDemoView.swift */; }; + D2BB9510297094A0007CCC6D /* DemoReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB950F297094A0007CCC6D /* DemoReducer.swift */; }; + D2BB9511297094A0007CCC6D /* DemoReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB950F297094A0007CCC6D /* DemoReducer.swift */; }; + D2BB9512297094A0007CCC6D /* DemoReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB950F297094A0007CCC6D /* DemoReducer.swift */; }; + D2BB9514297094C6007CCC6D /* DemoFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB9513297094C6007CCC6D /* DemoFormView.swift */; }; + D2BB9515297094C6007CCC6D /* DemoFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB9513297094C6007CCC6D /* DemoFormView.swift */; }; + D2BB9516297094C6007CCC6D /* DemoFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2BB9513297094C6007CCC6D /* DemoFormView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -42,6 +48,8 @@ D2A11A2429707CE10002E8FD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D2A11A2729707CE10002E8FD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; D2A11A31297082950002E8FD /* AlertDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDemoView.swift; sourceTree = ""; }; + D2BB950F297094A0007CCC6D /* DemoReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoReducer.swift; sourceTree = ""; }; + D2BB9513297094C6007CCC6D /* DemoFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoFormView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +171,8 @@ isa = PBXGroup; children = ( D2A11A31297082950002E8FD /* AlertDemoView.swift */, + D2BB950F297094A0007CCC6D /* DemoReducer.swift */, + D2BB9513297094C6007CCC6D /* DemoFormView.swift */, ); path = Shared; sourceTree = ""; @@ -313,6 +323,8 @@ buildActionMask = 2147483647; files = ( D2A11A32297082950002E8FD /* AlertDemoView.swift in Sources */, + D2BB9514297094C6007CCC6D /* DemoFormView.swift in Sources */, + D2BB9510297094A0007CCC6D /* DemoReducer.swift in Sources */, D2A119D3297078320002E8FD /* CustomTcaAlertApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -322,6 +334,8 @@ buildActionMask = 2147483647; files = ( D2A11A33297082A40002E8FD /* AlertDemoView.swift in Sources */, + D2BB9515297094C6007CCC6D /* DemoFormView.swift in Sources */, + D2BB9511297094A0007CCC6D /* DemoReducer.swift in Sources */, D2A11A0B29707C410002E8FD /* CustomAlertTVDemoApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -331,6 +345,8 @@ buildActionMask = 2147483647; files = ( D2A11A34297082A40002E8FD /* AlertDemoView.swift in Sources */, + D2BB9516297094C6007CCC6D /* DemoFormView.swift in Sources */, + D2BB9512297094A0007CCC6D /* DemoReducer.swift in Sources */, D2A11A2129707CE00002E8FD /* CustomAlertWatchDemoApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Examples/Shared/AlertDemoView.swift b/Examples/Shared/AlertDemoView.swift index 714f329..7f3fb66 100644 --- a/Examples/Shared/AlertDemoView.swift +++ b/Examples/Shared/AlertDemoView.swift @@ -3,35 +3,37 @@ import SwiftUI import TCACustomAlert struct AlertDemoView: View { - private struct AlertDemoReducer: ReducerProtocol { - struct State: Equatable { - var alert: CustomTcaAlert.State = .init() - } - - enum Action: Equatable { - case alert(CustomTcaAlert.Action) - } - - var body: some ReducerProtocol { - Scope(state: \.alert, action: /Action.alert) { - CustomTcaAlert() - } - } - } private let store = Store( initialState: .init(), - reducer: AlertDemoReducer() + reducer: AlertDemoReducer()._printChanges() ) var body: some View { WithViewStore(store) { viewStore in - VStack { - Button("Present", action: { viewStore.send(.alert(.present)) }) + ZStack { + FormView( + store: self.store.scope( + state: \.form, + action: AlertDemoReducer.Action.form + ) + ) + + VStack { + Spacer() + HStack { + Spacer() + Button("Present", action: { viewStore.send(.alert(.present)) }) + .padding() + Spacer() + } + .background(.thinMaterial) + } + .frame(maxWidth: .infinity) } .customTcaAlert( store.scope( - state: \.alert, + state: \.alertState, action: AlertDemoReducer.Action.alert ), content: { diff --git a/Examples/Shared/DemoFormView.swift b/Examples/Shared/DemoFormView.swift new file mode 100644 index 0000000..8a76b61 --- /dev/null +++ b/Examples/Shared/DemoFormView.swift @@ -0,0 +1,92 @@ +import ComposableArchitecture +import SwiftUI + +struct FormView: View { + let store: StoreOf + + var body: some View { + WithViewStore(store) { formViewStore in + Form { + Section { + Text("Scrim Settings") + Toggle("Dismiss on Scrim Tap", isOn: formViewStore.binding(\.$dismissOnScrimTap)) + HStack { + Text("Opacity: \(formViewStore.scrimPercentage)") + Slider( + value: formViewStore.binding(\.$scrimOpacity), + in: 0.1...1, + step: 0.1 + ) + } + } + + Section { + Text("Alert presentation starting position") + HStack { + Text("X: \(Int(formViewStore.alertStartX))") + .frame(width: 80, alignment: .leading) + Slider( + value: formViewStore.binding(\.$alertStartX), + in: -1000...1000, + step: 100 + ) + } + HStack { + Text("Y: \(Int(formViewStore.alertStartY))") + .frame(width: 80, alignment: .leading) + Slider( + value: formViewStore.binding(\.$alertStartY), + in: -1000...1000, + step: 100 + ) + } + } + + Section { + Text("Alert presented position") + HStack { + Text("X: \(Int(formViewStore.alertPresentedX))") + .frame(width: 80, alignment: .leading) + + Slider( + value: formViewStore.binding(\.$alertPresentedX), + in: -300...300, + step: 100 + ) + } + HStack { + Text("Y: \(Int(formViewStore.alertPresentedY))") + .frame(width: 80, alignment: .leading) + Slider( + value: formViewStore.binding(\.$alertPresentedY), + in: -300...300, + step: 100 + ) + } + } + + Section { + Text("Alert dismissal ending position") + HStack { + Text("X: \(Int(formViewStore.alertEndX))") + .frame(width: 80, alignment: .leading) + Slider( + value: formViewStore.binding(\.$alertEndX), + in: -1000...1000, + step: 100 + ) + } + HStack { + Text("Y: \(Int(formViewStore.alertEndY))") + .frame(width: 80, alignment: .leading) + Slider( + value: formViewStore.binding(\.$alertEndY), + in: -1000...1000, + step: 100 + ) + } + } + } + } + } +} diff --git a/Examples/Shared/DemoReducer.swift b/Examples/Shared/DemoReducer.swift new file mode 100644 index 0000000..e1debf7 --- /dev/null +++ b/Examples/Shared/DemoReducer.swift @@ -0,0 +1,78 @@ +import ComposableArchitecture +import SwiftUI +import TCACustomAlert + +struct AlertDemoReducer: ReducerProtocol { + + struct State: Equatable { + + var alertState: CustomTcaAlert.State = .init() + + var form: Form.State { + get { + .init( + dismissOnScrimTap: alertState.dismissOnScrimTap, + scrimOpacity: alertState.endScrimOpacity, + alertStartY: alertState.alertStartPosition.height, + alertStartX: alertState.alertStartPosition.width, + alertPresentedY: alertState.alertPresentedPosition.height, + alertPresentedX: alertState.alertPresentedPosition.width, + alertEndY: alertState.alertEndPosition.height, + alertEndX: alertState.alertEndPosition.width + ) + } + set { + self.alertState.dismissOnScrimTap = newValue.dismissOnScrimTap + self.alertState.endScrimOpacity = newValue.scrimOpacity + self.alertState.alertStartPosition = .init(width: newValue.alertStartX, height: newValue.alertStartY) + self.alertState.alertPresentedPosition = .init(width: newValue.alertPresentedX, height: newValue.alertPresentedY) + self.alertState.alertEndPosition = .init(width: newValue.alertEndX, height: newValue.alertEndY) + + } + } + } + + enum Action: Equatable { + case alert(CustomTcaAlert.Action) + case form(Form.Action) + } + + var body: some ReducerProtocol { + Scope(state: \.form, action: /Action.form) { + Form() + } + + Scope(state: \.alertState, action: /Action.alert) { + CustomTcaAlert() + } + } + + struct Form: ReducerProtocol { + struct State: Equatable { + @BindableState var dismissOnScrimTap = true + @BindableState var scrimOpacity = 0.6 + + @BindableState var alertStartY: CGFloat = 500 + @BindableState var alertStartX: CGFloat = 500 + + @BindableState var alertPresentedY: CGFloat = 0 + @BindableState var alertPresentedX: CGFloat = 0 + + @BindableState var alertEndY: CGFloat = -500 + @BindableState var alertEndX: CGFloat = -500 + + var scrimPercentage: String { + // Simple formatting for this use case. + String(format: "%.f%%", scrimOpacity * 100) + } + } + + enum Action: Equatable, BindableAction { + case binding(BindingAction) + } + + var body: some ReducerProtocol { + BindingReducer() + } + } +} diff --git a/Sources/TCACustomAlert/CustomAlert.swift b/Sources/TCACustomAlert/CustomAlert.swift index 0770f5a..5fca980 100644 --- a/Sources/TCACustomAlert/CustomAlert.swift +++ b/Sources/TCACustomAlert/CustomAlert.swift @@ -5,22 +5,25 @@ public struct CustomTcaAlert: ReducerProtocol { public struct State: Equatable { /// X, Y postion where the alert will appear from. /// Defaults to bottom of the screen. - public let alertStartPosition: CGSize + public var alertStartPosition: CGSize /// X, Y position where the alert will be when shown to the user. /// Defaults to center of the screen. - public let alertPresentedPosition: CGSize + public var alertPresentedPosition: CGSize /// X, Y position where the alert will dismiss towards. /// Defaults to top of screen. - public let alertEndPosition: CGSize + public var alertEndPosition: CGSize /// Opacity of the scrim view behind the modal. /// Defaults to 60%. - public let endScrimOpacity: CGFloat - /// Boolean reflecting if the alert is currently presented. - public internal(set) var isPresented: Bool + public var endScrimOpacity: CGFloat /// Boolean reflecting if tapping the scrim view will dismiss the alert. /// Defaults to `true`. /// Dismissal requires sending ``RealtimeAlert.Action.dismiss`` into the reducer. - public let dismissOnScrimTap: Bool + public var dismissOnScrimTap: Bool + /// Boolean reflecting if the alert is currently presented. + public internal(set) var isPresented: Bool + + // Animated propertes should not be available outside + // the scope of the Reducer internal var modalOffset: CGSize internal var modalOpacity: CGFloat