Created
March 20, 2025 00:27
-
-
Save cwalo/4314e24cb0591df1ac49f5533fa38cda to your computer and use it in GitHub Desktop.
A way to chain OpenURLAction handlers
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 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