Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace the method to show SFSafariViewController #45

Merged
merged 11 commits into from
Mar 21, 2024
21 changes: 11 additions & 10 deletions MyLibrary/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.9.1"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.2.0"),
.package(url: "https://github.com/zunda-pixel/LicenseProvider", from: "1.1.1"),
],
targets: [
Expand All @@ -39,11 +40,17 @@ let package = Package(
.process("Resources")
]
),
.target(
name: "DependencyExtra",
dependencies: [
.product(name: "Dependencies", package: "swift-dependencies"),
]
),
.target(
name: "GuidanceFeature",
dependencies: [
"DependencyExtra",
"MapKitClient",
"Safari",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]
),
Expand All @@ -54,17 +61,11 @@ let package = Package(
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]
),
.target(
name: "Safari",
dependencies: [
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
]
),
.target(
name: "ScheduleFeature",
dependencies: [
"DataClient",
"Safari",
"DependencyExtra",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]
),
Expand All @@ -73,15 +74,15 @@ let package = Package(
name: "SponsorFeature",
dependencies: [
"DataClient",
"Safari",
"DependencyExtra",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]
),
.target(
name: "trySwiftFeature",
dependencies: [
"DataClient",
"Safari",
"DependencyExtra",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
],
plugins: [
Expand Down
82 changes: 82 additions & 0 deletions MyLibrary/Sources/DependencyExtra/Safari.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Dependencies

#if canImport(SafariServices) && canImport(SwiftUI)
import SafariServices
import SwiftUI

extension DependencyValues {
/// A dependency that opens a URL in SFSafariViewController.
///
/// In iOS, use SFSafariViewController in UIKit context. Otherwise use openURL in environment values
///
/// - SeeAlso: https://sarunw.com/posts/sfsafariviewcontroller-in-swiftui/
@available(iOS 15, macOS 11, tvOS 14, watchOS 7, *)
public var safari: SafariEffect {
get { self[SafariKey.self] }
set { self[SafariKey.self] = newValue }
}
}

@available(iOS 15, macOS 11, tvOS 14, watchOS 7, *)
private enum SafariKey: DependencyKey {
static let liveValue = SafariEffect { url in
let stream = AsyncStream<Bool> { continuation in
let task = Task { @MainActor in
#if os(iOS)
let vc = SFSafariViewController(url: url)
UIApplication.shared.firstKeyWindow?.rootViewController?.present(vc, animated: true)
continuation.yield(true)
continuation.finish()
#else
EnvironmentValues().openURL(url)
continuation.yield(true)
continuation.finish()
#endif
}
continuation.onTermination = { @Sendable _ in
task.cancel()
}
}
return await stream.first(where: { _ in true }) ?? false
}
static let testValue = SafariEffect { _ in
XCTFail(#"Unimplemented: @Dependency(\.safari)"#)
return false
}
}

public struct SafariEffect: Sendable {
private let handler: @Sendable (URL) async -> Bool

public init(handler: @escaping @Sendable (URL) async -> Bool) {
self.handler = handler
}

@available(watchOS, unavailable)
@discardableResult
public func callAsFunction(_ url: URL) async -> Bool {
await self.handler(url)
}

@_disfavoredOverload
public func callAsFunction(_ url: URL) async {
_ = await self.handler(url)
}
}

#endif

#if canImport(UIKit)
import UIKit

extension UIApplication {
@available(iOS 15.0, *)
var firstKeyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.filter { $0.activationState == .foregroundActive }
.first?.keyWindow
}
}

#endif
4 changes: 2 additions & 2 deletions MyLibrary/Sources/GuidanceFeature/Guidance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CoreLocation
import Foundation
import MapKit
import MapKitClient
import Safari
import DependencyExtra
import SwiftUI

@Reducer
Expand Down Expand Up @@ -49,10 +49,10 @@ public struct Guidance {

@Reducer(state: .equatable)
public enum Destination {
case safari(Safari)
}

@Dependency(MapKitClient.self) var mapKitClient
@Dependency(\.safari) var safari

public init() {}

Expand Down
53 changes: 0 additions & 53 deletions MyLibrary/Sources/Safari/Safari.swift

This file was deleted.

26 changes: 3 additions & 23 deletions MyLibrary/Sources/ScheduleFeature/Detail.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ComposableArchitecture
import Foundation
import Safari
import DependencyExtra
import SharedModels
import SwiftUI

Expand All @@ -13,7 +13,6 @@ public struct ScheduleDetail {
var description: String
var requirements: String?
var speakers: [Speaker]
@Presents var destination: Destination.State?

public init(
title: String, description: String, requirements: String? = nil, speakers: [Speaker]
Expand All @@ -27,20 +26,14 @@ public struct ScheduleDetail {

public enum Action: ViewAction, BindableAction {
case binding(BindingAction<State>)
case destination(PresentationAction<Destination.Action>)
case view(View)

public enum View {
case snsTapped(URL)
}
}

@Reducer(state: .equatable)
public enum Destination {
case safari(Safari)
}

@Dependency(\.openURL) var openURL
@Dependency(\.safari) var safari

public init() {}

Expand All @@ -49,19 +42,11 @@ public struct ScheduleDetail {
Reduce { state, action in
switch action {
case let .view(.snsTapped(url)):
#if os(iOS) || os(macOS)
state.destination = .safari(.init(url: url))
return .none
#elseif os(visionOS)
return .run { _ in await openURL(url) }
#endif
case .destination:
return .none
return .run { _ in await safari(url) }
case .binding:
return .none
}
}
.ifLet(\.$destination, action: \.destination)
}
}

Expand Down Expand Up @@ -103,11 +88,6 @@ public struct ScheduleDetailView: View {
speakers
.frame(maxWidth: 700) // Readable content width for iPad
}
.sheet(item: $store.scope(state: \.destination?.safari, action: \.destination.safari)) {
sheetStore in
SafariViewRepresentation(url: sheetStore.url)
.ignoresSafeArea()
}
}

@ViewBuilder
Expand Down
2 changes: 0 additions & 2 deletions MyLibrary/Sources/ScheduleFeature/Schedule.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ComposableArchitecture
import DataClient
import Foundation
import Safari
import SharedModels
import SwiftUI
import TipKit
Expand Down Expand Up @@ -59,7 +58,6 @@ public struct Schedule {
public enum Destination {}

@Dependency(DataClient.self) var dataClient
@Dependency(\.openURL) var openURL

public init() {}

Expand Down
18 changes: 3 additions & 15 deletions MyLibrary/Sources/SponsorFeature/Sponsors.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ComposableArchitecture
import DataClient
import Foundation
import Safari
import DependencyExtra
import SharedModels
import SwiftUI

Expand Down Expand Up @@ -32,7 +32,7 @@ public struct SponsorsList {
public init() {}

@Dependency(DataClient.self) var dataClient
@Dependency(\.openURL) var openURL
@Dependency(\.safari) var safari

public var body: some ReducerOf<Self> {
BindingReducer()
Expand All @@ -44,12 +44,7 @@ public struct SponsorsList {

case let .view(.sponsorTapped(sponsor)):
guard let url = sponsor.link else { return .none }
#if os(iOS) || os(macOS)
state.destination = .safari(.init(url: url))
return .none
#elseif os(visionOS)
return .run { _ in await openURL(url) }
#endif
return .run { _ in await safari(url) }
case .binding:
return .none
case .destination:
Expand All @@ -61,7 +56,6 @@ public struct SponsorsList {

@Reducer(state: .equatable)
public enum Destination {
case safari(Safari)
}
}

Expand All @@ -76,12 +70,6 @@ public struct SponsorsListView: View {
public var body: some View {
NavigationView {
root
.fullScreenCover(
item: $store.scope(state: \.destination?.safari, action: \.destination.safari)
) { sheetStore in
SafariViewRepresentation(url: sheetStore.url)
.ignoresSafeArea()
}
.onAppear {
send(.onAppear)
}
Expand Down