Last active
October 11, 2024 16:54
-
-
Save scotteg/e2ac712f1793828d97bfdbe968aa4e68 to your computer and use it in GitHub Desktop.
A SwiftUI modifier to present a tip based on dynamic conditions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import TipKit | |
/// Encapsulate tip conditions with specified values. | |
protocol TipCondition { | |
func evaluate() -> (Binding<Bool>, is: Bool) | |
} | |
/// Conform `Binding<Bool>` to `TipCondition` with a default value of `true`. | |
extension Binding: TipCondition where Value == Bool { | |
func evaluate() -> (Binding<Bool>, is: Bool) { | |
return (self, true) | |
} | |
} | |
/// Conform tuples `(Binding<Bool>, Bool)` to `TipCondition`. | |
struct TupleCondition: TipCondition { | |
let binding: Binding<Bool> | |
let value: Bool | |
func evaluate() -> (Binding<Bool>, is: Bool) { | |
return (binding, value) | |
} | |
} | |
/// A modifier to present a tip based on dynamic conditions. | |
struct PopoverTipModifier<Tip: TipKit.Tip>: ViewModifier { | |
var tip: Tip | |
var conditions: [TipCondition] | |
@ViewBuilder | |
func body(content: Content) -> some View { | |
let shouldShowTip = conditions.allSatisfy { condition in | |
let (binding, expectedValue) = condition.evaluate() | |
return binding.wrappedValue == expectedValue | |
} | |
if shouldShowTip { | |
content.popoverTip(tip) | |
} else { | |
content | |
} | |
} | |
} | |
extension View { | |
/// Presents a tip when one or more conditions are met. | |
/// - Parameters: | |
/// - tip: The tip to present. | |
/// - conditions: A variadic list of conditions that can be either `Binding<Bool>` or a tuple `(Binding<Bool>, Bool)`. | |
/// - Returns: A view with the tip modifier applied. | |
/// - Examples: | |
/// 1. Use a single condition (defaults to `true`): | |
/// ```swift | |
/// .showPopoverTip(SheetMusicTip(), when: $presentTip) | |
/// ``` | |
/// 2. Use a single tuple condition: | |
/// ```swift | |
/// .showPopoverTip(SheetMusicTip(), when: ($someValue, is: false)) | |
/// ``` | |
/// 3. Use multiple conditions, including tuples: | |
/// ```swift | |
/// .showPopoverTip(SheetMusicTip(), | |
/// when: $presentTip, | |
/// ($someOtherValue, is: false)) | |
/// ``` | |
func showPopoverTip<Tip: TipKit.Tip>(_ tip: Tip, when conditions: Any...) -> some View { | |
// Convert the variadic arguments into an array of `TipCondition`. | |
let parsedConditions: [TipCondition] = conditions.map { condition in | |
if let binding = condition as? Binding<Bool> { | |
return binding | |
} else if let tuple = condition as? (Binding<Bool>, Bool) { | |
return TupleCondition(binding: tuple.0, value: tuple.1) | |
} else { | |
fatalError("Unsupported condition type.") | |
} | |
} | |
return modifier(PopoverTipModifier(tip: tip, conditions: parsedConditions)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment