From dbd2726bda227ff1b8eac32c043668c3b390d6b5 Mon Sep 17 00:00:00 2001 From: Steffan Andrews Date: Mon, 2 Oct 2023 04:34:42 -0700 Subject: [PATCH] Added custom SettingsLink pre/post action init (#2) --- .../xcschemes/SettingsAccess.xcscheme | 2 +- Demo/Demo.xcodeproj/project.pbxproj | 4 ++ Demo/Demo/DemoApp.swift | 31 ++++++++- README.md | 34 ++++++++-- .../PrePostActionsButtonStyle.swift | 65 +++++++++++-------- Sources/SettingsAccess/SettingsLink.swift | 48 ++++++++++++++ 6 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 Sources/SettingsAccess/SettingsLink.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SettingsAccess.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SettingsAccess.xcscheme index c01f1ca..392f98e 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SettingsAccess.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SettingsAccess.xcscheme @@ -1,6 +1,6 @@ Note that this will not work when applied to a `Button` or `SettingsLink` contained +/// > within a menu or menu-based MenuBarExtra. @available(macOS 10.15, *) @available(iOS, unavailable) @available(tvOS, unavailable) @available(watchOS, unavailable) public struct PrePostActionsButtonStyle: PrimitiveButtonStyle { - public let preTapAction: (() -> Void)? - public let postTapAction: (() -> Void)? + public let preAction: (() -> Void)? + public let postAction: (() -> Void)? @Binding public var performAction: () -> Void /// Initialize with an optional pre-action and post-action. Also optionally supply a binding to /// expose a method to programmatically call the button's action. /// /// - Parameters: - /// - preTapAction: Closure to execute before the button's action. - /// - postTapAction: Closure to execute after the button's action. + /// - preAction: Closure to execute before the button's action. + /// - postAction: Closure to execute after the button's action. /// - performAction: Binding to expose a method to programmatically call the button's action. public init( - preTapAction: (() -> Void)?, - postTapAction: (() -> Void)?, + preAction: (() -> Void)?, + postAction: (() -> Void)?, performAction: Binding<(() -> Void)> = .constant { } ) { - self.preTapAction = preTapAction - self.postTapAction = postTapAction + self.preAction = preAction + self.postAction = postAction self._performAction = performAction } + // note: this never gets called when used in a menu instead of a View public func makeBody(configuration: Configuration) -> some View { // capture the button action DispatchQueue.main.async { @@ -45,14 +49,23 @@ public struct PrePostActionsButtonStyle: PrimitiveButtonStyle { } } - return configuration.label - .contentShape(Rectangle()) - .allowsHitTesting(true) - .onTapGesture { - preTapAction?() + if #available(macOS 12.0, *) { // role is macOS 12+ + return Button(role: configuration.role) { + preAction?() configuration.trigger() - postTapAction?() + postAction?() + } label: { + configuration.label } + } else { + return Button { + preAction?() + configuration.trigger() + postAction?() + } label: { + configuration.label + } + } } } @@ -61,25 +74,25 @@ public struct PrePostActionsButtonStyle: PrimitiveButtonStyle { @available(tvOS, unavailable) @available(watchOS, unavailable) private struct PrePostActionsButtonStyleModifier: ViewModifier { - let preTapAction: (() -> Void)? - let postTapAction: (() -> Void)? + let preAction: (() -> Void)? + let postAction: (() -> Void)? @Binding var performAction: () -> Void init( - preTapAction: (() -> Void)?, - postTapAction: (() -> Void)?, + preAction: (() -> Void)?, + postAction: (() -> Void)?, performAction: Binding<(() -> Void)> = .constant { } ) { - self.preTapAction = preTapAction - self.postTapAction = postTapAction + self.preAction = preAction + self.postAction = postAction self._performAction = performAction } func body(content: Content) -> some View { content.buttonStyle( PrePostActionsButtonStyle( - preTapAction: preTapAction, - postTapAction: postTapAction, + preAction: preAction, + postAction: postAction, performAction: $performAction ) ) @@ -95,14 +108,14 @@ extension View { /// Allows execution of code before and/or after user clicks a button. /// Also provides a binding to a method which can programmatically call the button's action. public func prePostActionsButtonStyle( - preTapAction: (() -> Void)? = nil, - postTapAction: (() -> Void)? = nil, + preAction: (() -> Void)? = nil, + postAction: (() -> Void)? = nil, performAction: Binding<(() -> Void)> = .constant { } ) -> some View { modifier( PrePostActionsButtonStyleModifier( - preTapAction: preTapAction, - postTapAction: postTapAction, + preAction: preAction, + postAction: postAction, performAction: performAction) ) } diff --git a/Sources/SettingsAccess/SettingsLink.swift b/Sources/SettingsAccess/SettingsLink.swift new file mode 100644 index 0000000..e4591d7 --- /dev/null +++ b/Sources/SettingsAccess/SettingsLink.swift @@ -0,0 +1,48 @@ +// +// SettingsLink.swift +// SettingsAccess • https://github.com/orchetect/SettingsAccess +// © 2023 Steffan Andrews • Licensed under MIT License +// + +#if os(macOS) + +import Foundation +import SwiftUI + +/// Create a `SettingsLink` that optionally executes code before or after the Settings scene is opened. +/// This is suitable for use in a menu, a menu-based `MenuBarExtra`, or any standard `View` where a button +/// is needed. This is also backwards-compatible to macOS 11. +/// +/// - Parameters: +/// - label: A view to use as the label for this settings link. +/// - preAction: Closure to execute before the button's action. +/// - postAction: Closure to execute after the button's action. +@available(macOS 10.15, *) +@available(iOS, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +@ViewBuilder +public func SettingsLink( + @ViewBuilder label: () -> Label, + preAction: @escaping () -> Void, + postAction: @escaping () -> Void +) -> some View { + if #available(macOS 14.0, *) { + SettingsLink(label: label) + .prePostActionsButtonStyle( + preAction: preAction, + postAction: postAction + ) + } else { + Button( + action: { + preAction() + openSettingsLegacyOS() + postAction() + }, + label: label + ) + } +} + +#endif