Created
October 2, 2025 01:05
-
-
Save leok7v/55326e84344293cfd173cac3ab745f2e to your computer and use it in GitHub Desktop.
Platform Independent unifying Platform[Web]View templates using ViewRepresentable to minimize/reduce #if #else #endif in SwiftUI code
This file contains hidden or 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 | |
| /// Platform Independent unifying ViewRepresentable template to minimize #if #else #endif in SwiftUI code | |
| #if canImport(UIKit) | |
| import UIKit | |
| public typealias _BaseView = UIView | |
| public typealias PlatformViewRepresentable = UIViewRepresentable | |
| #else | |
| import AppKit | |
| public typealias _BaseView = NSView | |
| public typealias PlatformViewRepresentable = NSViewRepresentable | |
| #endif | |
| public struct PlatformView<V: _BaseView>: PlatformViewRepresentable { | |
| public init(make: @escaping (Context) -> V, | |
| update: @escaping (V, Context) -> Void = { _, _ in }) { | |
| self.make = make | |
| self.update = update | |
| } | |
| private let make: (Context) -> V | |
| private let update: (V, Context) -> Void | |
| public typealias Coordinator = Void | |
| #if canImport(UIKit) | |
| public typealias Context = UIViewRepresentableContext<PlatformView<V>> | |
| public typealias UIViewType = V | |
| public func makeUIView(context: Context) -> V { make(context) } | |
| public func updateUIView(_ v: V, context: Context) { update(v, context) } | |
| #else | |
| public typealias Context = NSViewRepresentableContext<PlatformView<V>> | |
| public typealias NSViewType = V | |
| public func makeNSView(context: Context) -> V { make(context) } | |
| public func updateNSView(_ v: V, context: Context) { update(v, context) } | |
| #endif | |
| } | |
| public struct PlatformViewWithCoordinator<V: _BaseView, C>: PlatformViewRepresentable { | |
| public init(make_coordinator: @escaping () -> C, | |
| make: @escaping (Context, C) -> V, | |
| update: @escaping (V, Context, C) -> Void = { _, _, _ in }) { | |
| self.make_coordinator = make_coordinator | |
| self.make = make | |
| self.update = update | |
| } | |
| private let make_coordinator: () -> C | |
| private let make: (Context, C) -> V | |
| private let update: (V, Context, C) -> Void | |
| public typealias Coordinator = C | |
| public func makeCoordinator() -> C { make_coordinator() } | |
| #if canImport(UIKit) | |
| public typealias Context = | |
| UIViewRepresentableContext<PlatformViewWithCoordinator<V, C>> | |
| public typealias UIViewType = V | |
| public func makeUIView(context: Context) -> V { | |
| make(context, context.coordinator) | |
| } | |
| public func updateUIView(_ v: V, context: Context) { | |
| update(v, context, context.coordinator) | |
| } | |
| #else | |
| public typealias Context = | |
| NSViewRepresentableContext<PlatformViewWithCoordinator<V, C>> | |
| public typealias NSViewType = V | |
| public func makeNSView(context: Context) -> V { | |
| make(context, context.coordinator) | |
| } | |
| public func updateNSView(_ v: V, context: Context) { | |
| update(v, context, context.coordinator) | |
| } | |
| #endif | |
| } | |
| /// No coordinator | |
| /// let anyView = PlatformView<_BaseView>( | |
| /// make: { _ in _BaseView(frame: .zero) }, | |
| /// update: { v, _, _ in v.isHidden = false } | |
| /// ) | |
| /// With coordinator | |
| /// final class MyCoord { var tag = 0 } | |
| /// let withCoord = PlatformViewWithCoordinator<_BaseView, MyCoord>( | |
| /// make_coordinator: { MyCoord() }, | |
| /// make: { _, c in let v = _BaseView(frame: .zero); c.tag = 42; return v }, | |
| /// update: { v, _, c in _ = (v, c) } | |
| /// ) | |
This file contains hidden or 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 WebKit | |
| /// Platform Independent unifying WebView template to reduce #if #else #endif in SwiftUI code | |
| public struct PlatformWebView: View { | |
| public typealias Created = (WKWebView) -> Void | |
| public typealias Configuration = (WKWebViewConfiguration) -> Void | |
| public typealias DidFinish = (WKWebView) -> Void | |
| public typealias Policy = (WKWebView, WKNavigationAction, | |
| @escaping (WKNavigationActionPolicy) -> Void) -> Void | |
| public init(configuration: Configuration? = nil, | |
| created: Created? = nil, | |
| policy: Policy? = nil, | |
| didFinish: DidFinish? = nil) { | |
| self.created = created | |
| self.configuration = configuration | |
| self.policy = policy | |
| self.didFinish = didFinish | |
| } | |
| private let created: Created? | |
| private let configuration: Configuration? | |
| private let didFinish: DidFinish? | |
| private let policy: Policy? | |
| public final class Coordinator: NSObject, WKNavigationDelegate { | |
| var parent: PlatformWebView | |
| init(_ parent: PlatformWebView) { self.parent = parent } | |
| public func webView(_ v: WKWebView, didFinish n: WKNavigation!) { | |
| parent.didFinish?(v) | |
| } | |
| public func webView(_ v: WKWebView, | |
| decidePolicyFor a: WKNavigationAction, | |
| decisionHandler d: @escaping | |
| (WKNavigationActionPolicy) -> Void) { | |
| if let f = parent.policy { | |
| f(v, a, d) | |
| } else { | |
| d(.allow) | |
| } | |
| } | |
| } | |
| public var body: some View { | |
| PlatformViewWithCoordinator<WKWebView, Coordinator>( | |
| make_coordinator: { Coordinator(self) }, | |
| make: { _, context in | |
| let c = WKWebViewConfiguration() | |
| configuration?(c) | |
| let v = WKWebView(frame: .zero, configuration: c) | |
| v.navigationDelegate = context | |
| created?(v) | |
| return v | |
| }, | |
| update: { _, _, _ in } | |
| ) | |
| } | |
| } | |
| #Preview("Basic") { | |
| /// usage example: | |
| PlatformWebView( | |
| configuration: { c in | |
| c.preferences.shouldPrintBackgrounds = false | |
| if #available(macOS 11.0, *) { | |
| } else { // before MacOS 11.0 | |
| c.preferences.javaScriptEnabled = true | |
| } | |
| }, | |
| created: { w in | |
| #if os(iOS) | |
| w.isOpaque = false | |
| w.backgroundColor = .clear | |
| w.allowsBackForwardNavigationGestures = false | |
| w.translatesAutoresizingMaskIntoConstraints = true | |
| w.autoresizingMask = [.flexibleWidth] | |
| w.scrollView.backgroundColor = .clear | |
| w.scrollView.isScrollEnabled = false | |
| w.scrollView.contentInsetAdjustmentBehavior = .never | |
| w.scrollView.delaysContentTouches = false | |
| w.scrollView.pinchGestureRecognizer?.isEnabled = false | |
| #elseif os(macOS) | |
| w.setValue(false, forKey: "drawsBackground") | |
| w.translatesAutoresizingMaskIntoConstraints = true | |
| w.autoresizingMask = [.width] | |
| #endif | |
| let html = """ | |
| <html><head><meta name='viewport' content='width=device-width, \ | |
| initial-scale=1'></head><body style='font:16px -apple-system'> | |
| <h2>PlatformWebView</h2><p>Hello, preview.</p><p id='progress'>Loading...</p></body></html> | |
| """ | |
| w.loadHTMLString(html, baseURL: nil) | |
| }, | |
| policy: { view, action, decision in | |
| if action.navigationType == .linkActivated, | |
| let url = action.request.url { | |
| #if os(iOS) | |
| UIApplication.shared.open(url, options: [:], | |
| completionHandler: nil) | |
| #elseif os(macOS) | |
| NSWorkspace.shared.open(url) | |
| #endif | |
| decision(.cancel) | |
| } else { | |
| decision(.allow) | |
| } | |
| }, | |
| didFinish: { w in | |
| w.evaluateJavaScript(""" | |
| document.getElementById('progress').textContent = 'Loaded.' | |
| """) | |
| } | |
| ) | |
| .frame(width: 360, height: 240) | |
| .background(Color.white) | |
| .overlay(Color.orange.opacity(0.25).blendMode(.multiply)) | |
| } |
Author
leok7v
commented
Oct 2, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment