diff --git a/Sources/Showcase/EnvironmentPlayground.swift b/Sources/Showcase/EnvironmentPlayground.swift index 1f4e8a0..19f5017 100644 --- a/Sources/Showcase/EnvironmentPlayground.swift +++ b/Sources/Showcase/EnvironmentPlayground.swift @@ -16,8 +16,8 @@ struct EnvironmentPlayground: View { .environment(\.environmentPlaygroundCustomKey, "Custom!") } } - .onChange(of: tapCountObservable.tapCount) { - logger.log("onChange(of: tapCountObservable.tapCount): \($0)") + .onChange(of: tapCountObservable.tapCount) { oldTapCount, newTapCount in + logger.log("onChange(of: tapCountObservable.tapCount): \(newTapCount)") } .toolbar { PlaygroundSourceLink(file: "EnvironmentPlayground.swift") diff --git a/Sources/Showcase/PlaygroundListView.swift b/Sources/Showcase/PlaygroundListView.swift index 8187b4b..5ce5f3e 100644 --- a/Sources/Showcase/PlaygroundListView.swift +++ b/Sources/Showcase/PlaygroundListView.swift @@ -81,6 +81,7 @@ enum PlaygroundType: CaseIterable, View { case videoPlayer case viewThatFits case webAuthenticationSession + case webBrowser case webView case zIndex @@ -242,6 +243,8 @@ enum PlaygroundType: CaseIterable, View { return LocalizedStringResource("Video Player") case .webAuthenticationSession: return LocalizedStringResource("Web Authentication Session") + case .webBrowser: + return LocalizedStringResource("WebBrowser") case .webView: return LocalizedStringResource("WebView") case .zIndex: @@ -411,6 +414,8 @@ enum PlaygroundType: CaseIterable, View { VideoPlayerPlayground() case .webAuthenticationSession: WebAuthenticationSessionPlayground() + case .webBrowser: + WebBrowserPlayground() case .webView: WebViewPlayground() case .zIndex: diff --git a/Sources/Showcase/PlaygroundSourceLink.swift b/Sources/Showcase/PlaygroundSourceLink.swift index 2195090..fbdec72 100644 --- a/Sources/Showcase/PlaygroundSourceLink.swift +++ b/Sources/Showcase/PlaygroundSourceLink.swift @@ -1,8 +1,12 @@ // Copyright 2023–2025 Skip +import Foundation import SwiftUI +import SkipWeb /// Displays a link to the source code for the given playground type. struct PlaygroundSourceLink : View { + @AppStorage("openLinksExternally") var openLinksExternally = false + @State var showSource = false private let destination: URL init(file: String) { @@ -10,6 +14,8 @@ struct PlaygroundSourceLink : View { } var body: some View { - Link("Source", destination: destination) + Button("Source") { + showSource = true + }.openWebBrowser(isPresented: $showSource, url: destination, mode: openLinksExternally ? .launchBrowser : .embeddedBrowser(params: nil)) } } diff --git a/Sources/Showcase/Resources/Localizable.xcstrings b/Sources/Showcase/Resources/Localizable.xcstrings index 6ccc284..8e42cc9 100644 --- a/Sources/Showcase/Resources/Localizable.xcstrings +++ b/Sources/Showcase/Resources/Localizable.xcstrings @@ -672,6 +672,9 @@ }, "Accessibility" : { + }, + "Actions" : { + }, "Add" : { @@ -711,6 +714,9 @@ }, "alignment: .topLeading" : { + }, + "All Options" : { + }, "Also see the `Transition` playground for view enter/exit animations" : { @@ -1127,6 +1133,9 @@ }, "Custom" : { + }, + "Custom Actions" : { + }, "Custom Cancel" : { @@ -1291,6 +1300,9 @@ }, "Email" : { + }, + "Embedded Browser" : { + }, "Enter text" : { @@ -1801,6 +1813,9 @@ }, "Last: %@" : { + }, + "Launch Browser" : { + }, "Lazy Item %lld" : { @@ -2134,6 +2149,15 @@ } } } + }, + "Open in Embedded Browser" : { + + }, + "Open in System Browser" : { + + }, + "Open with No Params" : { + }, "Option" : { @@ -2354,6 +2378,9 @@ }, "Present with item binding" : { + }, + "Presentation Mode" : { + }, "Primary" : { @@ -2770,6 +2797,9 @@ }, "Sheet" : { + }, + "Sheet / Navigation" : { + }, "Short" : { @@ -3553,6 +3583,9 @@ }, "Web Authentication Session" : { + }, + "WebBrowser" : { + }, "WebView" : { @@ -3708,6 +3741,9 @@ }, "width: 100, height: 100" : { + }, + "With Custom Action" : { + }, "With prompt" : { diff --git a/Sources/Showcase/ScenePhasePlayground.swift b/Sources/Showcase/ScenePhasePlayground.swift index dc6361b..8029526 100644 --- a/Sources/Showcase/ScenePhasePlayground.swift +++ b/Sources/Showcase/ScenePhasePlayground.swift @@ -13,9 +13,9 @@ struct ScenePhasePlayground: View { } } } - .onChange(of: scenePhase) { phase in - logger.log("onChange(of: schenePhase): \(String(describing: phase))") - history.append(phase) + .onChange(of: scenePhase) { oldPhase, newPhase in + logger.log("onChange(of: schenePhase): \(String(describing: newPhase))") + history.append(newPhase) } .toolbar { diff --git a/Sources/Showcase/StatePlayground.swift b/Sources/Showcase/StatePlayground.swift index fc5c5df..cb2d2ca 100644 --- a/Sources/Showcase/StatePlayground.swift +++ b/Sources/Showcase/StatePlayground.swift @@ -79,14 +79,14 @@ struct StatePlayground: View { } } } - .onChange(of: tapCount) { - logger.log("onChange(of: tapCount): \($0)") + .onChange(of: tapCount) { oldValue, newValue in + logger.log("onChange(of: tapCount): \(newValue)") } - .onChange(of: tapCountObservable.tapCount) { - logger.log("onChange(of: tapCountObservable.tapCount): \($0)") + .onChange(of: tapCountObservable.tapCount) { oldValue, newValue in + logger.log("onChange(of: tapCountObservable.tapCount): \(newValue)") } - .onChange(of: tapCountStruct.tapCount) { - logger.log("onChange(of: tapCountStruct.tapCount): \($0)") + .onChange(of: tapCountStruct.tapCount) { oldValue, newValue in + logger.log("onChange(of: tapCountStruct.tapCount): \(newValue)") } .toolbar { PlaygroundSourceLink(file: "StatePlayground.swift") diff --git a/Sources/Showcase/TextFieldPlayground.swift b/Sources/Showcase/TextFieldPlayground.swift index 8b33063..f613b38 100644 --- a/Sources/Showcase/TextFieldPlayground.swift +++ b/Sources/Showcase/TextFieldPlayground.swift @@ -54,7 +54,7 @@ struct TextFieldPlayground: View { #if !os(macOS) || os(Android) .keyboardType(UIKeyboardType.phonePad) #endif - .onChange(of: phone) { newValue in + .onChange(of: phone) { oldValue, newValue in phone = formatPhone(newValue) } TextField("Email", text: $text) diff --git a/Sources/Showcase/VideoPlayerPlayground.swift b/Sources/Showcase/VideoPlayerPlayground.swift index 1866f6b..2ef1e1a 100644 --- a/Sources/Showcase/VideoPlayerPlayground.swift +++ b/Sources/Showcase/VideoPlayerPlayground.swift @@ -120,7 +120,7 @@ struct LoopingPlayerView: View { } Slider(value: $rate, in: 0.0...10.0, label: { Text("Rate") }) - .onChange(of: rate) { newValue in + .onChange(of: rate) { oldValue, newValue in player.rate = Float(newValue) } } diff --git a/Sources/Showcase/WebAuthenticationSessionPlayground.swift b/Sources/Showcase/WebAuthenticationSessionPlayground.swift index 5f025c6..bc41f84 100644 --- a/Sources/Showcase/WebAuthenticationSessionPlayground.swift +++ b/Sources/Showcase/WebAuthenticationSessionPlayground.swift @@ -76,7 +76,7 @@ struct WebAuthenticationSessionPlayground: View { PlaygroundSourceLink(file: "WebAuthenticationSessionPlayground.swift") } } - + private func signIn() { errorMessage = nil cancelled = false diff --git a/Sources/Showcase/WebBrowserPlayground.swift b/Sources/Showcase/WebBrowserPlayground.swift new file mode 100644 index 0000000..ef42910 --- /dev/null +++ b/Sources/Showcase/WebBrowserPlayground.swift @@ -0,0 +1,78 @@ +// Copyright 2023–2025 Skip +import SwiftUI +import SkipWeb + +struct WebBrowserPlayground: View { + let skipURL = URL(string: "https://skip.dev")! + @State var showEmbedded = false + @State var showEmbeddedNoParams = false + @State var showLaunchBrowser = false + @State var showCustomActions = false + @State var showAllOptions = false + @State var useNavigationPresentation = false + + var presentationMode: WebBrowserPresentationMode { + useNavigationPresentation ? .navigation : .sheet + } + + var body: some View { + List { + Section("Presentation Mode") { + Toggle("Sheet / Navigation", isOn: $useNavigationPresentation) + } + + Section("Embedded Browser") { + Button("Open in Embedded Browser") { + showEmbedded = true + } + .openWebBrowser(isPresented: $showEmbedded, url: skipURL, mode: .embeddedBrowser(params: EmbeddedParams(presentationMode: presentationMode))) + + Button("Open with No Params") { + showEmbeddedNoParams = true + } + .openWebBrowser(isPresented: $showEmbeddedNoParams, url: skipURL.appendingPathComponent("docs"), mode: .embeddedBrowser(params: EmbeddedParams(presentationMode: presentationMode))) + } + + Section("Launch Browser") { + Button("Open in System Browser") { + showLaunchBrowser = true + } + .openWebBrowser(isPresented: $showLaunchBrowser, url: skipURL, mode: .launchBrowser) + } + + Section("Custom Actions") { + Button("With Custom Action") { + showCustomActions = true + } + .openWebBrowser(isPresented: $showCustomActions, url: skipURL, mode: .embeddedBrowser(params: EmbeddedParams( + presentationMode: presentationMode, + customActions: [ + WebBrowserAction(label: "Log URL") { url in + logger.info("Custom action URL: \(url)") + } + ] + ))) + } + + Section("All Options") { + Button("Actions") { + showAllOptions = true + } + .openWebBrowser(isPresented: $showAllOptions, url: skipURL, mode: .embeddedBrowser(params: EmbeddedParams( + presentationMode: presentationMode, + customActions: [ + WebBrowserAction(label: "Share URL") { url in + logger.info("Share: \(url)") + }, + WebBrowserAction(label: "Bookmark") { url in + logger.info("Bookmark: \(url)") + } + ] + ))) + } + } + .toolbar { + PlaygroundSourceLink(file: "WebBrowserPlayground.swift") + } + } +} diff --git a/Sources/Showcase/WebViewPlayground.swift b/Sources/Showcase/WebViewPlayground.swift index eaa38d6..5d7c6f6 100644 --- a/Sources/Showcase/WebViewPlayground.swift +++ b/Sources/Showcase/WebViewPlayground.swift @@ -13,7 +13,7 @@ struct WebViewPlayground: View { var body: some View { VStack { - WebView(configuration: config, navigator: navigator, url: URL(string: "https://skip.tools")!, state: $state) + WebView(configuration: config, navigator: navigator, url: URL(string: "https://skip.dev")!, state: $state) } .toolbar { Button {