Fix In-App Safari (#945)

* Fix In-App Safari

* Open SFSafariViewController in a separate window
This commit is contained in:
David Walter 2023-02-21 07:23:23 +01:00 committed by GitHub
parent 53f364b232
commit 94d50fafc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -14,20 +14,20 @@ private struct SafariRouter: ViewModifier {
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@EnvironmentObject private var routerPath: RouterPath @EnvironmentObject private var routerPath: RouterPath
@State private var presentedURL: URL? @StateObject private var safariManager = InAppSafariManager()
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.environment(\.openURL, OpenURLAction { url in .environment(\.openURL, OpenURLAction { url in
// Open internal URL. // Open internal URL.
routerPath.handle(url: url) routerPath.handle(url: url)
}) })
.onOpenURL(perform: { url in .onOpenURL { url in
// Open external URL (from icecubesapp://) // Open external URL (from icecubesapp://)
let urlString = url.absoluteString.replacingOccurrences(of: "icecubesapp://", with: "https://") let urlString = url.absoluteString.replacingOccurrences(of: "icecubesapp://", with: "https://")
guard let url = URL(string: urlString), url.host != nil else { return } guard let url = URL(string: urlString), url.host != nil else { return }
_ = routerPath.handle(url: url) _ = routerPath.handle(url: url)
}) }
.onAppear { .onAppear {
routerPath.urlHandler = { url in routerPath.urlHandler = { url in
if url.absoluteString.contains("@twitter.com"), url.absoluteString.hasPrefix("mailto:") { if url.absoluteString.contains("@twitter.com"), url.absoluteString.hasPrefix("mailto:") {
@ -45,31 +45,104 @@ private struct SafariRouter: ViewModifier {
guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else { guard let scheme = url.scheme, ["https", "http"].contains(scheme.lowercased()) else {
return .systemAction return .systemAction
} }
return safariManager.open(url)
presentedURL = url }
return .handled }
.background {
WindowReader { window in
self.safariManager.windowScene = window.windowScene
} }
} }
.fullScreenCover(item: $presentedURL, content: { url in }
SafariView(url: url, inAppBrowserReaderView: preferences.inAppBrowserReaderView) }
.edgesIgnoringSafeArea(.all)
}) private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate {
} var windowScene: UIWindowScene?
let viewController: UIViewController = UIViewController()
struct SafariView: UIViewControllerRepresentable { var window: UIWindow?
let url: URL
let inAppBrowserReaderView: Bool @MainActor
func open(_ url: URL) -> OpenURLAction.Result {
func makeUIViewController(context _: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController { guard let windowScene = windowScene else { return .systemAction }
let configuration = SFSafariViewController.Configuration()
configuration.entersReaderIfAvailable = inAppBrowserReaderView self.window = setupWindow(windowScene: windowScene)
let safari = SFSafariViewController(url: url, configuration: configuration) let configuration = SFSafariViewController.Configuration()
safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor) configuration.entersReaderIfAvailable = UserPreferences.shared.inAppBrowserReaderView
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
return safari let safari = SFSafariViewController(url: url, configuration: configuration)
} safari.preferredBarTintColor = UIColor(Theme.shared.primaryBackgroundColor)
safari.preferredControlTintColor = UIColor(Theme.shared.tintColor)
func updateUIViewController(_: SFSafariViewController, context _: UIViewControllerRepresentableContext<SafariView>) {} safari.delegate = self
DispatchQueue.main.async { [weak self] in
self?.viewController.present(safari, animated: true)
}
return .handled
}
func setupWindow(windowScene: UIWindowScene) -> UIWindow {
let window = self.window ?? UIWindow(windowScene: windowScene)
window.rootViewController = viewController
window.makeKeyAndVisible()
switch Theme.shared.selectedScheme {
case .dark:
window.overrideUserInterfaceStyle = .dark
case .light:
window.overrideUserInterfaceStyle = .light
}
self.window = window
return window
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
window?.resignKey()
window?.isHidden = false
window = nil
}
}
private struct WindowReader: UIViewRepresentable {
var onUpdate: (UIWindow) -> Void
func makeUIView(context: Context) -> InjectView {
InjectView(onUpdate: onUpdate)
}
func updateUIView(_ uiView: InjectView, context: Context) {
}
class InjectView: UIView {
var onUpdate: (UIWindow) -> Void
init(onUpdate: @escaping (UIWindow) -> Void) {
self.onUpdate = onUpdate
super.init(frame: .zero)
isHidden = true
isUserInteractionEnabled = false
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
if let window = newWindow {
onUpdate(window)
} else {
DispatchQueue.main.async { [weak self] in
if let window = self?.window {
self?.onUpdate(window)
}
}
}
}
} }
} }