diff --git a/.github/workflows/build-test-and-docs.yml b/.github/workflows/build-test-and-docs.yml index 58351d9ce9..287d40c417 100644 --- a/.github/workflows/build-test-and-docs.yml +++ b/.github/workflows/build-test-and-docs.yml @@ -80,12 +80,14 @@ jobs: - name: Build run: | - set -uexo pipefail + set -uxo pipefail device_type=${{ matrix.device-type }} set +e deviceid=$(xcrun simctl list devices $device_type available | grep -v -- -- | tail -n 1 | grep -oE '[0-9A-F\-]{36}') if [ $? -eq 0 ]; then ( + set -e + buildtarget () { # Use the same derived data path as DocC compilation so that we don't duplicate work. xcodebuild -derivedDataPath /tmp/data -skipMacroValidation -scheme "$1" -destination "id=$deviceid" build | xcbeautify --renderer github-actions @@ -116,6 +118,7 @@ jobs: ) else echo "No $device_type simulators found" >&2 + false fi - name: Extract UIKitBackend symbol graphs diff --git a/Examples/Package.resolved b/Examples/Package.resolved index 9af6aab45d..3842945e2a 100644 --- a/Examples/Package.resolved +++ b/Examples/Package.resolved @@ -185,8 +185,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/thebrowsercompany/swift-cwinrt", "state" : { - "branch" : "main", - "revision" : "ad451d31fc7c1c60041c146d177cdaf5aaf1cf47" + "revision" : "eb46cdb66f770a1e006f9fcfebbf9e99a0fba811" } }, { @@ -284,7 +283,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stackotter/swift-uwp", "state" : { - "revision" : "c9d3fc079aaaa5113cde9a0132278fb83e808599" + "revision" : "e3ff9c195775e16b404b82cf6886c5e81d73b6c1" } }, { @@ -292,7 +291,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stackotter/swift-webview2core", "state" : { - "revision" : "9afd97424f844478914ca4512c8ca0a2d3a2bb67" + "revision" : "c9911ca23455b9fcdb2429e98baa6f4d003b381c" } }, { @@ -300,16 +299,15 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stackotter/swift-windowsappsdk", "state" : { - "revision" : "ed938db0b9790b36391dc91b20cee81f2410309f" + "revision" : "ba6f0ec377b70d8be835d253102ff665a0e47d99" } }, { "identity" : "swift-windowsfoundation", "kind" : "remoteSourceControl", - "location" : "https://github.com/thebrowsercompany/swift-windowsfoundation", + "location" : "https://github.com/stackotter/swift-windowsfoundation", "state" : { - "branch" : "main", - "revision" : "ba7a8b5000ed3f9f077000d1a31f2a0b19907657" + "revision" : "4ad57d20553514bcb23724bdae9121569b19f172" } }, { @@ -317,7 +315,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stackotter/swift-winui", "state" : { - "revision" : "927e2c46430cfb1b6c195590b9e65a30a8fd98a2" + "revision" : "1695ee3ea2b7a249f6504c7f1759e7ec7a38eb86" } }, { diff --git a/Sources/SwiftCrossUI/Views/WebView.swift b/Sources/SwiftCrossUI/Views/WebView.swift index 65049264b7..0b9e1dae4e 100644 --- a/Sources/SwiftCrossUI/Views/WebView.swift +++ b/Sources/SwiftCrossUI/Views/WebView.swift @@ -1,5 +1,6 @@ import Foundation +@available(tvOS, unavailable) public struct WebView: ElementaryView { @State var currentURL: URL? @Binding var url: URL diff --git a/Sources/UIKitBackend/UIKitBackend+Control.swift b/Sources/UIKitBackend/UIKitBackend+Control.swift index 9179075d20..68816b02dd 100644 --- a/Sources/UIKitBackend/UIKitBackend+Control.swift +++ b/Sources/UIKitBackend/UIKitBackend+Control.swift @@ -59,6 +59,17 @@ final class TextFieldWidget: WrapperWidget, UITextFieldDelegate { final class TextEditorWidget: WrapperWidget, UITextViewDelegate { var onChange: ((String) -> Void)? + var isEditable: Bool = true { + didSet { + #if os(tvOS) + if !isEditable { + child.resignFirstResponder() + } + #else + child.isEditable = isEditable + #endif + } + } init() { super.init(child: UITextView()) @@ -68,6 +79,10 @@ final class TextEditorWidget: WrapperWidget, UITextViewDelegate { func textViewDidChange(_: UITextView) { onChange?(child.text ?? "") } + + func textViewShouldBeginEditing(_: UITextView) -> Bool { + return isEditable + } } #if os(tvOS) @@ -172,32 +187,36 @@ final class TappableWidget: ContainerWidget { @available(tvOS, unavailable) final class HoverableWidget: ContainerWidget { - private var hoverGestureRecognizer: UIHoverGestureRecognizer? - - var hoverChangesHandler: ((Bool) -> Void)? { - didSet { - if hoverChangesHandler != nil && hoverGestureRecognizer == nil { - let gestureRecognizer = UIHoverGestureRecognizer( - target: self, - action: #selector(hoveringChanged(_:))) - child.view.addGestureRecognizer(gestureRecognizer) - self.hoverGestureRecognizer = gestureRecognizer - } else if hoverChangesHandler == nil, let hoverGestureRecognizer { - child.view.removeGestureRecognizer(hoverGestureRecognizer) - self.hoverGestureRecognizer = nil + // So much as attempting to reference UIHoverGestureRecognizer here results in a linker error on tvOS. + #if !os(tvOS) + private var hoverGestureRecognizer: UIHoverGestureRecognizer? + + var hoverChangesHandler: ((Bool) -> Void)? { + didSet { + if hoverChangesHandler != nil && hoverGestureRecognizer == nil { + let gestureRecognizer = UIHoverGestureRecognizer( + target: self, + action: #selector(hoveringChanged(_:))) + child.view.addGestureRecognizer(gestureRecognizer) + self.hoverGestureRecognizer = gestureRecognizer + } else if hoverChangesHandler == nil, let hoverGestureRecognizer { + child.view.removeGestureRecognizer(hoverGestureRecognizer) + self.hoverGestureRecognizer = nil + } } } - } - @objc - func hoveringChanged(_ recognizer: UIHoverGestureRecognizer) { - switch recognizer.state { - case .began: hoverChangesHandler?(true) - case .ended: hoverChangesHandler?(false) - default: break + @objc + func hoveringChanged(_ recognizer: UIHoverGestureRecognizer) { + switch recognizer.state { + case .began: hoverChangesHandler?(true) + case .ended: hoverChangesHandler?(false) + default: break + } } - } + #endif } + @available(tvOS, unavailable) final class SliderWidget: WrapperWidget { var onChange: ((Double) -> Void)? @@ -332,7 +351,7 @@ extension UIKitBackend { ) { let textEditorWidget = textEditor as! TextEditorWidget - textEditorWidget.child.isEditable = environment.isEnabled + textEditorWidget.isEditable = environment.isEnabled textEditorWidget.child.font = environment.resolvedFont.uiFont textEditorWidget.child.textColor = UIColor(color: environment.suggestedForegroundColor) textEditorWidget.onChange = onChange @@ -448,20 +467,20 @@ extension UIKitBackend { } } - public func createHoverTarget(wrapping child: Widget) -> Widget { - HoverableWidget(child: child) - } + #if os(iOS) || os(visionOS) || targetEnvironment(macCatalyst) + public func createHoverTarget(wrapping child: Widget) -> Widget { + HoverableWidget(child: child) + } - public func updateHoverTarget( - _ hoverTarget: any WidgetProtocol, - environment: EnvironmentValues, - action: @escaping (Bool) -> Void - ) { - let wrapper = hoverTarget as! HoverableWidget - wrapper.hoverChangesHandler = action - } + public func updateHoverTarget( + _ hoverTarget: any WidgetProtocol, + environment: EnvironmentValues, + action: @escaping (Bool) -> Void + ) { + let wrapper = hoverTarget as! HoverableWidget + wrapper.hoverChangesHandler = action + } - #if os(iOS) || os(visionOS) || targetEnvironment(macCatalyst) public func createSlider() -> Widget { SliderWidget() } diff --git a/Sources/UIKitBackend/UIKitBackend+WebView.swift b/Sources/UIKitBackend/UIKitBackend+WebView.swift index b4b74c062f..637ca4aff5 100644 --- a/Sources/UIKitBackend/UIKitBackend+WebView.swift +++ b/Sources/UIKitBackend/UIKitBackend+WebView.swift @@ -1,43 +1,45 @@ -import SwiftCrossUI -import WebKit +#if !os(tvOS) + import SwiftCrossUI + import WebKit -extension UIKitBackend { - public func createWebView() -> Widget { - WebViewWidget() - } + extension UIKitBackend { + public func createWebView() -> Widget { + WebViewWidget() + } - public func updateWebView( - _ webView: Widget, - environment: EnvironmentValues, - onNavigate: @escaping (URL) -> Void - ) { - let webView = webView as! WebViewWidget - webView.onNavigate = onNavigate - } + public func updateWebView( + _ webView: Widget, + environment: EnvironmentValues, + onNavigate: @escaping (URL) -> Void + ) { + let webView = webView as! WebViewWidget + webView.onNavigate = onNavigate + } - public func navigateWebView(_ webView: Widget, to url: URL) { - let webView = webView as! WebViewWidget - let request = URLRequest(url: url) - webView.child.load(request) + public func navigateWebView(_ webView: Widget, to url: URL) { + let webView = webView as! WebViewWidget + let request = URLRequest(url: url) + webView.child.load(request) + } } -} -/// A wrapper for WKWebView. Acts as the web view's delegate as well. -final class WebViewWidget: WrapperWidget, WKNavigationDelegate { - var onNavigate: ((URL) -> Void)? + /// A wrapper for WKWebView. Acts as the web view's delegate as well. + final class WebViewWidget: WrapperWidget, WKNavigationDelegate { + var onNavigate: ((URL) -> Void)? - init() { - super.init(child: WKWebView()) + init() { + super.init(child: WKWebView()) - child.navigationDelegate = self - } - - func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - guard let url = webView.url else { - print("warning: Web view has no URL") - return + child.navigationDelegate = self } - onNavigate?(url) + func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + guard let url = webView.url else { + print("warning: Web view has no URL") + return + } + + onNavigate?(url) + } } -} +#endif