From 65cc9d8e7377289513517dce2f1dc86f920822cb Mon Sep 17 00:00:00 2001 From: danielamitay Date: Mon, 16 Sep 2024 16:58:00 -0400 Subject: [PATCH 1/5] Start TweakWindowGroup for SwiftUI support --- SwiftTweaks.xcodeproj/project.pbxproj | 12 +++++ SwiftTweaks/SwiftUI/TweakWindowGroup.swift | 58 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 SwiftTweaks/SwiftUI/TweakWindowGroup.swift diff --git a/SwiftTweaks.xcodeproj/project.pbxproj b/SwiftTweaks.xcodeproj/project.pbxproj index 150c145..04a59cd 100644 --- a/SwiftTweaks.xcodeproj/project.pbxproj +++ b/SwiftTweaks.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 07190479224D931A00D28728 /* HapticsPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939CB70E218CFE570041E3EA /* HapticsPlayer.swift */; }; + 6347E4112C98D1880099192C /* TweakWindowGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4102C98D1880099192C /* TweakWindowGroup.swift */; }; 930ECDB81DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */; }; 931472491BFFB0C800F66D20 /* UIColor+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */; }; 9314724C1BFFB41700F66D20 /* TweakWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A3AF311BF1677B00CAD43B /* TweakWindow.swift */; }; @@ -108,6 +109,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 6347E4102C98D1880099192C /* TweakWindowGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweakWindowGroup.swift; sourceTree = ""; }; 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TweakViewData+TweaksTests.swift"; sourceTree = ""; }; 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+TweaksTests.swift"; sourceTree = ""; }; 931A24711BFA77FB00E40192 /* TweakColorEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakColorEditViewController.swift; sourceTree = ""; }; @@ -187,6 +189,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6347E40F2C98D1740099192C /* SwiftUI */ = { + isa = PBXGroup; + children = ( + 6347E4102C98D1880099192C /* TweakWindowGroup.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; 93212CAE1CEE254900AA85D0 /* Shadow Template */ = { isa = PBXGroup; children = ( @@ -293,6 +303,7 @@ 93A84EFB1BEAE8A20022D2F3 /* Interface */, 93A84EFA1BEAE8940022D2F3 /* Model */, 93A84EFC1BEAE8AB0022D2F3 /* Utilities */, + 6347E40F2C98D1740099192C /* SwiftUI */, 93A84ED61BEAE86E0022D2F3 /* Info.plist */, 931A24751BFA7EEE00E40192 /* Media.xcassets */, ); @@ -505,6 +516,7 @@ AA356AF01EB3B5A90063F4E2 /* TweakAction.swift in Sources */, 93212CB01CEE255F00AA85D0 /* ShadowTweakTemplate.swift in Sources */, 93B058E31CC44D8900AB2759 /* Precision.swift in Sources */, + 6347E4112C98D1880099192C /* TweakWindowGroup.swift in Sources */, 9338787E1BF6A4C5007DF1B4 /* TweakTableCell.swift in Sources */, 933223541CB83F0C002D586B /* BasicAnimationTweakTemplate.swift in Sources */, 93E777041BED4BBD003F0DE2 /* TweakLibrary.swift in Sources */, diff --git a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift new file mode 100644 index 0000000..407692f --- /dev/null +++ b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift @@ -0,0 +1,58 @@ +// +// TweakWindowGroup.swift +// SwiftTweaks +// +// Created by Daniel Amitay on 9/16/24. +// Copyright © 2024 Khan Academy. All rights reserved. +// + +// Guarded by SwiftUI to prevent compilation errors when SwiftUI is not available. +#if canImport(SwiftUI) + +import SwiftUI + +@available(iOS 14.0, *) +/// A `Scene` that presents a `TweakStore` UI when a certain gesture is recognized. +/// Use this in place of the WindowGroup in your App's @main struct. +struct TweakWindowGroup: Scene { + + enum GestureType { + /// Shake the device, like you're trying to undo some text + case shake + /// Two-finger double-taps are not natively supported in SwiftUI yet + } + + /// The GestureType used to determine when to present the UI. + let gestureType: GestureType + /// The TweakStore to use for the UI. + let tweakStore: TweakStore + /// Your app's content. + let content: () -> Content + + /// Whether or not the Tweak UI is currently being shown. + @State private var showingTweaks: Bool = false + + public init( + gestureType: GestureType = .shake, + tweakStore: TweakStore, + @ViewBuilder content: @escaping () -> Content + ) { + self.gestureType = gestureType + self.tweakStore = tweakStore + self.content = content + } + + var body: some Scene { + WindowGroup { + VStack { + content() + } + .sheet(isPresented: $showingTweaks) { + // TODO: (Damitay) + Color.red + } + } + } +} + +#endif From c66909bfdcc8b745a74f4dada7d5ead6132dcf84 Mon Sep 17 00:00:00 2001 From: danielamitay Date: Mon, 16 Sep 2024 17:07:07 -0400 Subject: [PATCH 2/5] Create a TweaksViewRepresentable --- SwiftTweaks.xcodeproj/project.pbxproj | 4 ++ SwiftTweaks/SwiftUI/TweakWindowGroup.swift | 6 +- .../SwiftUI/TweaksViewRepresentable.swift | 62 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift diff --git a/SwiftTweaks.xcodeproj/project.pbxproj b/SwiftTweaks.xcodeproj/project.pbxproj index 04a59cd..2eeff4a 100644 --- a/SwiftTweaks.xcodeproj/project.pbxproj +++ b/SwiftTweaks.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 07190479224D931A00D28728 /* HapticsPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939CB70E218CFE570041E3EA /* HapticsPlayer.swift */; }; 6347E4112C98D1880099192C /* TweakWindowGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4102C98D1880099192C /* TweakWindowGroup.swift */; }; + 6347E4132C98D38B0099192C /* TweaksViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */; }; 930ECDB81DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */; }; 931472491BFFB0C800F66D20 /* UIColor+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */; }; 9314724C1BFFB41700F66D20 /* TweakWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A3AF311BF1677B00CAD43B /* TweakWindow.swift */; }; @@ -110,6 +111,7 @@ /* Begin PBXFileReference section */ 6347E4102C98D1880099192C /* TweakWindowGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweakWindowGroup.swift; sourceTree = ""; }; + 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweaksViewRepresentable.swift; sourceTree = ""; }; 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TweakViewData+TweaksTests.swift"; sourceTree = ""; }; 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+TweaksTests.swift"; sourceTree = ""; }; 931A24711BFA77FB00E40192 /* TweakColorEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakColorEditViewController.swift; sourceTree = ""; }; @@ -193,6 +195,7 @@ isa = PBXGroup; children = ( 6347E4102C98D1880099192C /* TweakWindowGroup.swift */, + 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */, ); path = SwiftUI; sourceTree = ""; @@ -508,6 +511,7 @@ 93AA34571BEBC654004B734B /* TweaksViewController.swift in Sources */, 93A84EF01BEAE88D0022D2F3 /* HashingUtilities.swift in Sources */, 937AA3711CB61BDE000928C5 /* FloatingTweakGroupViewController.swift in Sources */, + 6347E4132C98D38B0099192C /* TweaksViewRepresentable.swift in Sources */, 93C942591BFBDC550054811A /* TweakBinding.swift in Sources */, 9345EC0E1BF2B9100086AB5D /* TweakCollection.swift in Sources */, D5CE0BDC1DC7DFC200F79235 /* TweakBindingIdentifier.swift in Sources */, diff --git a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift index 407692f..4666f3d 100644 --- a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift +++ b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift @@ -48,8 +48,10 @@ struct TweakWindowGroup: Scene { content() } .sheet(isPresented: $showingTweaks) { - // TODO: (Damitay) - Color.red + TweaksViewRepresentable( + tweakStore: tweakStore, + showingTweaks: $showingTweaks + ) } } } diff --git a/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift new file mode 100644 index 0000000..a621f26 --- /dev/null +++ b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift @@ -0,0 +1,62 @@ +// +// TweaksViewRepresentable.swift +// SwiftTweaks +// +// Created by Daniel Amitay on 9/16/24. +// Copyright © 2024 Khan Academy. All rights reserved. +// + +#if canImport(SwiftUI) + +import SwiftUI + +@available(iOS 13.0, *) +/// A `UIViewControllerRepresentable` that presents the `TweaksViewController`. +struct TweaksViewRepresentable: UIViewControllerRepresentable { + let tweakStore: TweakStore + let showingTweaks: Binding + + init( + tweakStore: TweakStore, + showingTweaks: Binding + ) { + self.tweakStore = tweakStore + self.showingTweaks = showingTweaks + } + + func makeUIViewController(context: Context) -> TweaksViewController { + let delegate = RepresentableDelegate(showingTweaks: showingTweaks) + return TweaksViewController( + tweakStore: tweakStore, + delegate: delegate + ) + } + + func updateUIViewController(_ uiViewController: TweaksViewController, context: Context) { + // no-op + } +} + +@available(iOS 13.0, *) +class RepresentableDelegate: TweaksViewControllerDelegate { + @Binding var showingTweaks: Bool + + init(showingTweaks: Binding) { + self._showingTweaks = showingTweaks + } + + func tweaksViewControllerRequestsDismiss(_ tweaksViewController: TweaksViewController, completion: (() -> ())?) { + showingTweaks = false + completion?() + } +} + +@available(iOS 13.0, *) +#Preview { + TweaksViewRepresentable( + tweakStore: .init(tweaks: [], enabled: true), + showingTweaks: .constant(true) + ) +} + +#endif From 52f2cbaebbd73cee2b80fb505db22601998ee573 Mon Sep 17 00:00:00 2001 From: danielamitay Date: Mon, 16 Sep 2024 21:07:15 -0400 Subject: [PATCH 3/5] Add gated onshake behavior --- SwiftTweaks.xcodeproj/project.pbxproj | 4 + SwiftTweaks/SwiftUI/TweakWindowGroup.swift | 49 +++++++++++- .../SwiftUI/TweaksViewRepresentable.swift | 3 +- SwiftTweaks/SwiftUI/View+Tweaks.swift | 79 +++++++++++++++++++ 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 SwiftTweaks/SwiftUI/View+Tweaks.swift diff --git a/SwiftTweaks.xcodeproj/project.pbxproj b/SwiftTweaks.xcodeproj/project.pbxproj index 2eeff4a..66ab6a9 100644 --- a/SwiftTweaks.xcodeproj/project.pbxproj +++ b/SwiftTweaks.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 07190479224D931A00D28728 /* HapticsPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939CB70E218CFE570041E3EA /* HapticsPlayer.swift */; }; 6347E4112C98D1880099192C /* TweakWindowGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4102C98D1880099192C /* TweakWindowGroup.swift */; }; 6347E4132C98D38B0099192C /* TweaksViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */; }; + 6347E4152C98D59E0099192C /* View+Tweaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6347E4142C98D59E0099192C /* View+Tweaks.swift */; }; 930ECDB81DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */; }; 931472491BFFB0C800F66D20 /* UIColor+TweaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */; }; 9314724C1BFFB41700F66D20 /* TweakWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A3AF311BF1677B00CAD43B /* TweakWindow.swift */; }; @@ -112,6 +113,7 @@ /* Begin PBXFileReference section */ 6347E4102C98D1880099192C /* TweakWindowGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweakWindowGroup.swift; sourceTree = ""; }; 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweaksViewRepresentable.swift; sourceTree = ""; }; + 6347E4142C98D59E0099192C /* View+Tweaks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Tweaks.swift"; sourceTree = ""; }; 930ECDB71DA6EEB9001009B3 /* TweakViewData+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TweakViewData+TweaksTests.swift"; sourceTree = ""; }; 931472481BFFB0C800F66D20 /* UIColor+TweaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+TweaksTests.swift"; sourceTree = ""; }; 931A24711BFA77FB00E40192 /* TweakColorEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakColorEditViewController.swift; sourceTree = ""; }; @@ -196,6 +198,7 @@ children = ( 6347E4102C98D1880099192C /* TweakWindowGroup.swift */, 6347E4122C98D38B0099192C /* TweaksViewRepresentable.swift */, + 6347E4142C98D59E0099192C /* View+Tweaks.swift */, ); path = SwiftUI; sourceTree = ""; @@ -490,6 +493,7 @@ buildActionMask = 2147483647; files = ( 93AA344F1BEBBD2D004B734B /* TweakStore.swift in Sources */, + 6347E4152C98D59E0099192C /* View+Tweaks.swift in Sources */, 931A24721BFA77FB00E40192 /* TweakColorEditViewController.swift in Sources */, 939F2CD91CB810D300345E03 /* SpringAnimationTweakTemplate.swift in Sources */, 9338E9E71CB57068002A92BE /* UIImage+SwiftTweaks.swift in Sources */, diff --git a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift index 4666f3d..f738553 100644 --- a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift +++ b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift @@ -11,15 +11,13 @@ import SwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) /// A `Scene` that presents a `TweakStore` UI when a certain gesture is recognized. /// Use this in place of the WindowGroup in your App's @main struct. struct TweakWindowGroup: Scene { - enum GestureType { /// Shake the device, like you're trying to undo some text case shake - /// Two-finger double-taps are not natively supported in SwiftUI yet } /// The GestureType used to determine when to present the UI. @@ -31,6 +29,11 @@ struct TweakWindowGroup: Scene { /// Whether or not the Tweak UI is currently being shown. @State private var showingTweaks: Bool = false + /// Whether or not the device is currently being shaken. + @State private var shaking: Bool = false + + /// The amount of time you need to shake your device to bring up the Tweaks UI + private let shakeWindowTimeInterval: TimeInterval = 0.4 public init( gestureType: GestureType = .shake, @@ -53,8 +56,46 @@ struct TweakWindowGroup: Scene { showingTweaks: $showingTweaks ) } + .if(gestureType == .shake && tweakStore.enabled) { view in + view.onShake { phase in + switch phase { + case .began: + shaking = true + DispatchQueue.main.asyncAfter(deadline: .now() + shakeWindowTimeInterval) { + if self.shouldShakePresentTweaks { + self.showingTweaks = true + } + } + case .ended: + shaking = false + } + } + } + } + } +} + +@available(iOS 15.0, *) +extension TweakWindowGroup { + /// We need to know if we're running in the simulator (because shake gestures don't have a time duration in the simulator) + var runningInSimulator: Bool { +#if targetEnvironment(simulator) + return true +#else + return false +#endif + } + + /// We only want to present the Tweaks UI if we're shaking the device and the Tweaks UI is enabled + var shouldShakePresentTweaks: Bool { + if tweakStore.enabled { + switch gestureType { + case .shake: return shaking || runningInSimulator + } + } else { + return false } - } + } } #endif diff --git a/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift index a621f26..b9c7763 100644 --- a/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift +++ b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift @@ -6,7 +6,8 @@ // Copyright © 2024 Khan Academy. All rights reserved. // -#if canImport(SwiftUI) +// Guarded by SwiftUI to prevent compilation errors when SwiftUI is not available. +#if canImport(SwiftUI) import SwiftUI diff --git a/SwiftTweaks/SwiftUI/View+Tweaks.swift b/SwiftTweaks/SwiftUI/View+Tweaks.swift new file mode 100644 index 0000000..70058d8 --- /dev/null +++ b/SwiftTweaks/SwiftUI/View+Tweaks.swift @@ -0,0 +1,79 @@ +// +// View+Tweaks.swift +// SwiftTweaks +// +// Created by Daniel Amitay on 9/16/24. +// Copyright © 2024 Khan Academy. All rights reserved. +// + +// Guarded by SwiftUI to prevent compilation errors when SwiftUI is not available. +#if canImport(SwiftUI) + +import SwiftUI + +/// Whether the device began or ended shaking +enum ShakePhase { + case began + case ended +} + +@available(iOS 15.0, *) +/// `View` extension to add a shake gesture recognizer. +extension View { + func onShake(_ block: @escaping (_ phase: ShakePhase) -> Void) -> some View { + self.overlay { + ShakeViewRepresentable(onShake: block) + .allowsHitTesting(false) + .opacity(0.0) + } + } +} + +@available(iOS 13.0, *) +/// `View` extension to conditionally apply a transformation. +extension View { + @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { + if condition { + transform(self) + } else { + self + } + } +} + +@available(iOS 13.0, *) +/// Hook into the responder chain to detect shake gestures +struct ShakeViewRepresentable: UIViewControllerRepresentable { + let onShake: (ShakePhase) -> () + + class ShakeViewController: UIViewController { + let onShake: ((ShakePhase) -> ()) + init(onShake: @escaping (ShakePhase) -> Void) { + self.onShake = onShake + super.init(nibName: nil, bundle: nil) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { + if motion == .motionShake { + onShake(.began) + } + super.motionBegan(motion, with: event) + } + override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { + if motion == .motionShake { + onShake(.ended) + } + super.motionEnded(motion, with: event) + } + } + func makeUIViewController(context: Context) -> ShakeViewController { + return ShakeViewController(onShake: onShake) + } + func updateUIViewController(_ uiViewController: ShakeViewController, context: Context) { + // no-op + } +} + +#endif From 1317759854a55b611ddcf56c74c190dd86f1e212 Mon Sep 17 00:00:00 2001 From: danielamitay Date: Mon, 16 Sep 2024 21:19:01 -0400 Subject: [PATCH 4/5] Add public/internal/private visibility annotations --- SwiftTweaks/SwiftUI/TweakWindowGroup.swift | 8 ++++---- SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift | 8 ++++---- SwiftTweaks/SwiftUI/View+Tweaks.swift | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift index f738553..32284c9 100644 --- a/SwiftTweaks/SwiftUI/TweakWindowGroup.swift +++ b/SwiftTweaks/SwiftUI/TweakWindowGroup.swift @@ -14,8 +14,8 @@ import SwiftUI @available(iOS 15.0, *) /// A `Scene` that presents a `TweakStore` UI when a certain gesture is recognized. /// Use this in place of the WindowGroup in your App's @main struct. -struct TweakWindowGroup: Scene { - enum GestureType { +public struct TweakWindowGroup: Scene { + public enum GestureType { /// Shake the device, like you're trying to undo some text case shake } @@ -45,7 +45,7 @@ struct TweakWindowGroup: Scene { self.content = content } - var body: some Scene { + public var body: some Scene { WindowGroup { VStack { content() @@ -76,7 +76,7 @@ struct TweakWindowGroup: Scene { } @available(iOS 15.0, *) -extension TweakWindowGroup { +fileprivate extension TweakWindowGroup { /// We need to know if we're running in the simulator (because shake gestures don't have a time duration in the simulator) var runningInSimulator: Bool { #if targetEnvironment(simulator) diff --git a/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift index b9c7763..5b4b155 100644 --- a/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift +++ b/SwiftTweaks/SwiftUI/TweaksViewRepresentable.swift @@ -13,7 +13,7 @@ import SwiftUI @available(iOS 13.0, *) /// A `UIViewControllerRepresentable` that presents the `TweaksViewController`. -struct TweaksViewRepresentable: UIViewControllerRepresentable { +public struct TweaksViewRepresentable: UIViewControllerRepresentable { let tweakStore: TweakStore let showingTweaks: Binding @@ -25,7 +25,7 @@ struct TweaksViewRepresentable: UIViewControllerRepresentable { self.showingTweaks = showingTweaks } - func makeUIViewController(context: Context) -> TweaksViewController { + public func makeUIViewController(context: Context) -> TweaksViewController { let delegate = RepresentableDelegate(showingTweaks: showingTweaks) return TweaksViewController( tweakStore: tweakStore, @@ -33,13 +33,13 @@ struct TweaksViewRepresentable: UIViewControllerRepresentable { ) } - func updateUIViewController(_ uiViewController: TweaksViewController, context: Context) { + public func updateUIViewController(_ uiViewController: TweaksViewController, context: Context) { // no-op } } @available(iOS 13.0, *) -class RepresentableDelegate: TweaksViewControllerDelegate { +fileprivate class RepresentableDelegate: TweaksViewControllerDelegate { @Binding var showingTweaks: Bool init(showingTweaks: Binding) { diff --git a/SwiftTweaks/SwiftUI/View+Tweaks.swift b/SwiftTweaks/SwiftUI/View+Tweaks.swift index 70058d8..f38b443 100644 --- a/SwiftTweaks/SwiftUI/View+Tweaks.swift +++ b/SwiftTweaks/SwiftUI/View+Tweaks.swift @@ -12,14 +12,14 @@ import SwiftUI /// Whether the device began or ended shaking -enum ShakePhase { +internal enum ShakePhase { case began case ended } @available(iOS 15.0, *) /// `View` extension to add a shake gesture recognizer. -extension View { +internal extension View { func onShake(_ block: @escaping (_ phase: ShakePhase) -> Void) -> some View { self.overlay { ShakeViewRepresentable(onShake: block) @@ -31,7 +31,7 @@ extension View { @available(iOS 13.0, *) /// `View` extension to conditionally apply a transformation. -extension View { +internal extension View { @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) @@ -43,7 +43,7 @@ extension View { @available(iOS 13.0, *) /// Hook into the responder chain to detect shake gestures -struct ShakeViewRepresentable: UIViewControllerRepresentable { +internal struct ShakeViewRepresentable: UIViewControllerRepresentable { let onShake: (ShakePhase) -> () class ShakeViewController: UIViewController { From 71c70172223305290d140dcfea847c40f7017dc6 Mon Sep 17 00:00:00 2001 From: danielamitay Date: Mon, 16 Sep 2024 21:35:04 -0400 Subject: [PATCH 5/5] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6191795..2bfba62 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,10 @@ For more examples, check out the example project’s `ViewController.swift` file By default, SwiftTweaks uses a shake gesture to bring up the UI, but you can also use a custom gesture! +### Step Three (SwiftUI): Set TweakWindowGroup as your root scene in your App + +`TweakWindowGroup` is a drop-in replacement for `WindowGroup` in your `App` struct. By default, it uses a shake gesture to bring up the UI. Custom gestures/two finger double-tap is not yet supported for SwiftUI. + ## Installation #### Swift Package Manager