diff --git a/Bitkit/Extensions/UINavigationController+SwipeBack.swift b/Bitkit/Extensions/UINavigationController+SwipeBack.swift new file mode 100644 index 000000000..ee62b34c5 --- /dev/null +++ b/Bitkit/Extensions/UINavigationController+SwipeBack.swift @@ -0,0 +1,25 @@ +import UIKit + +/// Shared state for swipe-back gesture. Views can set this via the `.allowSwipeBack(_:)` modifier +/// to disable the gesture on screens that don't show a back button (e.g. SheetHeader without back). +enum SwipeBackState { + /// When false, the interactive pop gesture is disabled. Set by views that hide the back button. + static var allowSwipeBack: Bool = true +} + +/// Re-enables the interactive swipe-back gesture when the navigation bar is hidden +/// (e.g. when using a custom NavigationBar with `.navigationBarHidden(true)`). +/// Without this, the system disables the gesture when the bar is hidden. +/// Use `.allowSwipeBack(false)` on views that don't show a back button to disable the gesture there. +extension UINavigationController: @retroactive UIGestureRecognizerDelegate { + override open func viewDidLoad() { + super.viewDidLoad() + interactivePopGestureRecognizer?.delegate = self + } + + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + // Only allow swipe-back when not at root — avoids iOS 17+ freeze when re-pushing after swiping to root + guard viewControllers.count > 1 else { return false } + return SwipeBackState.allowSwipeBack + } +} diff --git a/Bitkit/Extensions/View+AllowSwipeBack.swift b/Bitkit/Extensions/View+AllowSwipeBack.swift new file mode 100644 index 000000000..81949857a --- /dev/null +++ b/Bitkit/Extensions/View+AllowSwipeBack.swift @@ -0,0 +1,21 @@ +import SwiftUI + +extension View { + /// Controls whether the interactive swipe-back gesture is enabled on this screen. + /// Use `.allowSwipeBack(false)` on screens that use a custom header without a back button + /// (e.g. `SheetHeader` with default `showBackButton: false`) so users can't swipe to dismiss. + /// Default is `true`; only apply this modifier when you want to disable the gesture. + func allowSwipeBack(_ allowed: Bool) -> some View { + modifier(AllowSwipeBackModifier(allowed: allowed)) + } +} + +private struct AllowSwipeBackModifier: ViewModifier { + let allowed: Bool + + func body(content: Content) -> some View { + content + .onAppear { SwipeBackState.allowSwipeBack = allowed } + .onDisappear { SwipeBackState.allowSwipeBack = true } + } +} diff --git a/Bitkit/Views/Backup/BackupMnemonic.swift b/Bitkit/Views/Backup/BackupMnemonic.swift index e0190ceac..2b417adcf 100644 --- a/Bitkit/Views/Backup/BackupMnemonic.swift +++ b/Bitkit/Views/Backup/BackupMnemonic.swift @@ -99,6 +99,7 @@ struct BackupMnemonicView: View { .padding(.horizontal, 16) } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/Bitkit/Views/Backup/BackupPassphrase.swift b/Bitkit/Views/Backup/BackupPassphrase.swift index 7347369dc..7846aca1b 100644 --- a/Bitkit/Views/Backup/BackupPassphrase.swift +++ b/Bitkit/Views/Backup/BackupPassphrase.swift @@ -7,7 +7,7 @@ struct BackupPassphrase: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - SheetHeader(title: t("security__pass_your")) + SheetHeader(title: t("security__pass_your"), showBackButton: true) VStack(spacing: 0) { BodyMText(t("security__pass_text")) diff --git a/Bitkit/Views/Gift/GiftError.swift b/Bitkit/Views/Gift/GiftError.swift index a07b40175..111dbedfa 100644 --- a/Bitkit/Views/Gift/GiftError.swift +++ b/Bitkit/Views/Gift/GiftError.swift @@ -30,6 +30,7 @@ struct GiftFailed: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .accessibilityIdentifier("GiftError") diff --git a/Bitkit/Views/Gift/GiftUsed.swift b/Bitkit/Views/Gift/GiftUsed.swift index 7878e1fb5..e287c051e 100644 --- a/Bitkit/Views/Gift/GiftUsed.swift +++ b/Bitkit/Views/Gift/GiftUsed.swift @@ -30,6 +30,7 @@ struct GiftUsed: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .accessibilityIdentifier("GiftUsed") diff --git a/Bitkit/Views/Gift/GiftUsedUp.swift b/Bitkit/Views/Gift/GiftUsedUp.swift index 14b077de7..a0b521df2 100644 --- a/Bitkit/Views/Gift/GiftUsedUp.swift +++ b/Bitkit/Views/Gift/GiftUsedUp.swift @@ -30,6 +30,7 @@ struct GiftUsedUp: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .accessibilityIdentifier("GiftUsedUp") diff --git a/Bitkit/Views/Settings/Security/SecuritySheet/SecurityBiometrics.swift b/Bitkit/Views/Settings/Security/SecuritySheet/SecurityBiometrics.swift index 904b9337f..9816dd1a0 100644 --- a/Bitkit/Views/Settings/Security/SecuritySheet/SecurityBiometrics.swift +++ b/Bitkit/Views/Settings/Security/SecuritySheet/SecurityBiometrics.swift @@ -60,6 +60,7 @@ struct SecurityBiometrics: View { .padding(.horizontal, 16) } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .alert( diff --git a/Bitkit/Views/Settings/Security/SecuritySheet/SecurityPin.swift b/Bitkit/Views/Settings/Security/SecuritySheet/SecurityPin.swift index 0d5215ced..4497c1cca 100644 --- a/Bitkit/Views/Settings/Security/SecuritySheet/SecurityPin.swift +++ b/Bitkit/Views/Settings/Security/SecuritySheet/SecurityPin.swift @@ -52,6 +52,7 @@ struct SecurityPin: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .sheetBackground() } diff --git a/Bitkit/Views/Settings/Security/SecuritySheet/SecuritySuccess.swift b/Bitkit/Views/Settings/Security/SecuritySheet/SecuritySuccess.swift index a3be8a792..7a57c1bbc 100644 --- a/Bitkit/Views/Settings/Security/SecuritySheet/SecuritySuccess.swift +++ b/Bitkit/Views/Settings/Security/SecuritySheet/SecuritySuccess.swift @@ -60,6 +60,7 @@ struct SecuritySuccess: View { .padding(.horizontal, 16) } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() } diff --git a/Bitkit/Views/Transfer/FundManualSuccessView.swift b/Bitkit/Views/Transfer/FundManualSuccessView.swift index f356f2962..2e0e8bb44 100644 --- a/Bitkit/Views/Transfer/FundManualSuccessView.swift +++ b/Bitkit/Views/Transfer/FundManualSuccessView.swift @@ -47,7 +47,7 @@ struct FundManualSuccessView: View { .accessibilityIdentifier("ExternalSuccess") } .navigationBarHidden(true) - .interactiveDismissDisabled() + .allowSwipeBack(false) .padding(.horizontal, 16) } } diff --git a/Bitkit/Views/Transfer/SavingsProgressView.swift b/Bitkit/Views/Transfer/SavingsProgressView.swift index 99aed9fd4..06182ad66 100644 --- a/Bitkit/Views/Transfer/SavingsProgressView.swift +++ b/Bitkit/Views/Transfer/SavingsProgressView.swift @@ -42,7 +42,7 @@ struct SavingsProgressContentView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - NavigationBar(title: navTitle) + NavigationBar(title: navTitle, showBackButton: false) .padding(.bottom, 16) DisplayText(title, accentColor: .brandAccent) @@ -113,9 +113,9 @@ struct SavingsProgressContentView: View { .accessibilityIdentifierIfPresent(progressState == .success ? "TransferSuccess-button" : nil) } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .bottomSafeAreaPadding() - .interactiveDismissDisabled() } } diff --git a/Bitkit/Views/Transfer/SettingUpView.swift b/Bitkit/Views/Transfer/SettingUpView.swift index c75aa6515..fdfe4483d 100644 --- a/Bitkit/Views/Transfer/SettingUpView.swift +++ b/Bitkit/Views/Transfer/SettingUpView.swift @@ -194,9 +194,9 @@ struct SettingUpView: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .bottomSafeAreaPadding() - .interactiveDismissDisabled() .onAppear { Logger.debug("View appeared - TransferViewModel is handling order updates") diff --git a/Bitkit/Views/Wallets/Send/SendFailure.swift b/Bitkit/Views/Wallets/Send/SendFailure.swift index 52f0b679e..fa4580f77 100644 --- a/Bitkit/Views/Wallets/Send/SendFailure.swift +++ b/Bitkit/Views/Wallets/Send/SendFailure.swift @@ -31,6 +31,7 @@ struct SendFailure: View { .padding(.horizontal, 16) } .navigationBarHidden(true) + .allowSwipeBack(false) .sheetBackground() } } diff --git a/Bitkit/Views/Wallets/Send/SendPendingScreen.swift b/Bitkit/Views/Wallets/Send/SendPendingScreen.swift index 8f66cf67a..5af85d10d 100644 --- a/Bitkit/Views/Wallets/Send/SendPendingScreen.swift +++ b/Bitkit/Views/Wallets/Send/SendPendingScreen.swift @@ -70,6 +70,7 @@ struct SendPendingScreen: View { } } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/Bitkit/Views/Wallets/Send/SendQuickpay.swift b/Bitkit/Views/Wallets/Send/SendQuickpay.swift index b2d837d06..24e0fdbcd 100644 --- a/Bitkit/Views/Wallets/Send/SendQuickpay.swift +++ b/Bitkit/Views/Wallets/Send/SendQuickpay.swift @@ -72,6 +72,7 @@ struct SendQuickpay: View { DisplayText(t("wallet__send_quickpay__title"), accentColor: .purpleAccent) } .navigationBarHidden(true) + .allowSwipeBack(false) .padding(.horizontal, 16) .sheetBackground() .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/Bitkit/Views/Wallets/Send/SendSuccess.swift b/Bitkit/Views/Wallets/Send/SendSuccess.swift index 1672d998a..3ee1421a2 100644 --- a/Bitkit/Views/Wallets/Send/SendSuccess.swift +++ b/Bitkit/Views/Wallets/Send/SendSuccess.swift @@ -77,6 +77,7 @@ struct SendSuccess: View { .padding(.horizontal, 16) } .navigationBarHidden(true) + .allowSwipeBack(false) .sheetBackground() } .task {