Skip to content

Instantly share code, notes, and snippets.

@cwalo
Created March 20, 2025 00:27
Show Gist options
  • Save cwalo/4314e24cb0591df1ac49f5533fa38cda to your computer and use it in GitHub Desktop.
Save cwalo/4314e24cb0591df1ac49f5533fa38cda to your computer and use it in GitHub Desktop.
A way to chain OpenURLAction handlers
import Foundation
import PlaygroundSupport
import SwiftUI
import UIKit
protocol Scheme {
var url: URL { get }
}
enum ActionScheme: Scheme {
case one
case two
var url: URL { URL(string: "action://\(self)")! }
}
struct ContentView: View {
let storage: LinkModifierStorage<ActionScheme> = .init()
var one: AttributedString {
var attributedString = AttributedString("One")
attributedString.foregroundColor = .red
attributedString.link = ActionScheme.one.url
return attributedString
}
var two: AttributedString {
var attributedString = AttributedString("\nTwo")
attributedString.foregroundColor = .blue
attributedString.link = ActionScheme.two.url
return attributedString
}
var text: Text {
Text(one) + Text(two)
}
var body: some View {
VStack {
text
}
.onLinkTapped(ActionScheme.one, storage: storage) { url in
print("1: \(url)")
}
.onLinkTapped(ActionScheme.two, storage: storage) { url in
print("2: \(url)")
}
}
}
@MainActor
class LinkModifierStorage<S: Scheme> {
var storage: [URL: (URL) -> Void] = [:]
func store(scheme: S, _ action: @escaping (URL) -> Void) {
storage[scheme.url] = action
}
func action(for url: URL) -> ((URL) -> Void)? {
storage[url]
}
func remove(scheme: S) {
storage.removeValue(forKey: scheme.url)
}
}
private struct LinkURLModifier<S: Scheme>: ViewModifier {
let scheme: S
let storage: LinkModifierStorage<S>
let action: (_ url: URL) -> Void
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
if let action = storage.action(for: url) {
action(url)
return .handled
}
return .discarded
})
.task {
storage.store(scheme: scheme, action)
}
.onDisappear {
storage.remove(scheme: scheme)
}
}
}
extension View {
func onLinkTapped<S: Scheme>(_ scheme: S, storage: LinkModifierStorage<S>, perform action: @escaping (URL) -> Void) -> some View {
modifier(LinkURLModifier<S>(scheme: scheme, storage: storage, action: action))
}
}
let controller = UIHostingController(
rootView: ContentView()
.frame(width: 350, height: 800)
)
PlaygroundPage.current.liveView = controller
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment