Skip to content

Instantly share code, notes, and snippets.

@scotteg
Last active October 11, 2024 16:54
Show Gist options
  • Save scotteg/e2ac712f1793828d97bfdbe968aa4e68 to your computer and use it in GitHub Desktop.
Save scotteg/e2ac712f1793828d97bfdbe968aa4e68 to your computer and use it in GitHub Desktop.
A SwiftUI modifier to present a tip based on dynamic conditions.
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