Skip to content

Commit 8f5703d

Browse files
carson-katrisupernintendo
authored andcommitted
Add LiveViewHost initializers
1 parent 307d322 commit 8f5703d

File tree

3 files changed

+123
-17
lines changed

3 files changed

+123
-17
lines changed

Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public class LiveSessionCoordinator<R: RootRegistry>: ObservableObject {
6363

6464
var isMounted: Bool = false
6565

66+
public convenience init(_ host: some LiveViewHost, config: LiveSessionConfiguration = .init(), customRegistryType: R.Type = R.self) {
67+
self.init(host.url, config: config, customRegistryType: customRegistryType)
68+
}
69+
6670
/// Creates a new coordinator with a custom registry.
6771
/// - Parameter url: The URL of the page to establish the connection to.
6872
/// - Parameter config: The configuration for this coordinator.

Sources/LiveViewNative/LiveView.swift

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Foundation
99
import SwiftUI
10+
import Combine
1011

1112
/// The SwiftUI root view for a Phoenix LiveView.
1213
///
@@ -23,7 +24,9 @@ import SwiftUI
2324
/// - ``LiveViewModel``
2425
public struct LiveView<R: RootRegistry>: View {
2526
@State private var hasAppeared = false
26-
@ObservedObject var session: LiveSessionCoordinator<R>
27+
28+
@StateObject var storage: LiveSessionCoordinatorStorage
29+
2730
@State private var hasSetupNavigationControllerDelegate = false
2831

2932
@ObservedObject private var rootCoordinator: LiveViewCoordinator<R>
@@ -32,18 +35,39 @@ public struct LiveView<R: RootRegistry>: View {
3235
///
3336
/// - Note: Changing coordinators after the `LiveView` is setup and connected is forbidden.
3437
public init(session: LiveSessionCoordinator<R>) {
35-
self._session = .init(wrappedValue: session)
38+
self._storage = .init(wrappedValue: .init(session: session))
3639
self.rootCoordinator = session.rootCoordinator
3740
}
41+
42+
public init(_ host: some LiveViewHost, configuration: LiveSessionConfiguration = .init()) {
43+
self.init(session: .init(host.url, config: configuration))
44+
}
45+
46+
public init(url: URL, configuration: LiveSessionConfiguration = .init()) {
47+
self.init(session: .init(url, config: configuration))
48+
}
49+
50+
final class LiveSessionCoordinatorStorage: ObservableObject {
51+
var session: LiveSessionCoordinator<R>
52+
53+
var cancellable: AnyCancellable?
54+
55+
init(session: LiveSessionCoordinator<R>) {
56+
self.session = session
57+
self.cancellable = session.objectWillChange.sink { [weak self] _ in
58+
self?.objectWillChange.send()
59+
}
60+
}
61+
}
3862

3963
public var body: some View {
4064
SwiftUI.VStack {
41-
switch session.state {
65+
switch storage.session.state {
4266
case .connected:
4367
rootNavEntry
4468
default:
4569
if R.LoadingView.self == Never.self {
46-
switch session.state {
70+
switch storage.session.state {
4771
case .connected:
4872
fatalError()
4973
case .notConnected:
@@ -80,18 +104,18 @@ public struct LiveView<R: RootRegistry>: View {
80104
}
81105
}
82106
} else {
83-
R.loadingView(for: session.url, state: session.state)
107+
R.loadingView(for: storage.session.url, state: storage.session.state)
84108
}
85109
}
86110
}
87111
.task {
88-
await session.connect()
112+
await storage.session.connect()
89113
}
90114
}
91115

92116
@ViewBuilder
93117
private var rootNavEntry: some View {
94-
switch session.config.navigationMode {
118+
switch storage.session.config.navigationMode {
95119
case .enabled:
96120
navigationStack
97121
case .splitView:
@@ -105,7 +129,7 @@ public struct LiveView<R: RootRegistry>: View {
105129

106130
@ViewBuilder
107131
private var navigationStack: some View {
108-
NavigationStack(path: $session.navigationPath) {
132+
NavigationStack(path: $storage.session.navigationPath) {
109133
navigationRoot
110134
.navigationDestination(for: LiveNavigationEntry<R>.self) { entry in
111135
NavStackEntryView(entry)
@@ -118,16 +142,16 @@ public struct LiveView<R: RootRegistry>: View {
118142
navigationRoot
119143
} detail: {
120144
NavigationStack(path: Binding<[LiveNavigationEntry<R>]> {
121-
Array(session.navigationPath.dropFirst())
145+
Array(storage.session.navigationPath.dropFirst())
122146
} set: { value in
123147
var result = value
124-
if let first = session.navigationPath.first {
148+
if let first = storage.session.navigationPath.first {
125149
result.insert(first, at: 0)
126150
}
127-
session.navigationPath = result
151+
storage.session.navigationPath = result
128152
}) {
129153
SwiftUI.Group {
130-
if let entry = session.navigationPath.first {
154+
if let entry = storage.session.navigationPath.first {
131155
NavStackEntryView(entry)
132156
}
133157
}
@@ -143,8 +167,8 @@ public struct LiveView<R: RootRegistry>: View {
143167
private func tabView(_ tabs: [LiveSessionConfiguration.NavigationMode.Tab]) -> some View {
144168
SwiftUI.TabView(selection: $selectedTab) {
145169
ForEach(tabs) { tab in
146-
NavigationStack(path: $session.navigationPath) {
147-
NavStackEntryView(.init(url: selectedTab ?? session.url, coordinator: rootCoordinator))
170+
NavigationStack(path: $storage.session.navigationPath) {
171+
NavStackEntryView(.init(url: selectedTab ?? storage.session.url, coordinator: rootCoordinator))
148172
.navigationDestination(for: LiveNavigationEntry<R>.self) { entry in
149173
NavStackEntryView(entry)
150174
}
@@ -158,13 +182,23 @@ public struct LiveView<R: RootRegistry>: View {
158182
.onChange(of: selectedTab) { newValue in
159183
guard let newValue else { return }
160184
Task {
161-
session.rootCoordinator.url = newValue
162-
await session.rootCoordinator.reconnect()
185+
storage.session.rootCoordinator.url = newValue
186+
await storage.session.rootCoordinator.reconnect()
163187
}
164188
}
165189
}
166190

167191
private var navigationRoot: some View {
168-
NavStackEntryView(.init(url: session.url, coordinator: rootCoordinator))
192+
NavStackEntryView(.init(url: storage.session.url, coordinator: rootCoordinator))
193+
}
194+
}
195+
196+
extension LiveView where R == EmptyRegistry {
197+
public init(_ host: some LiveViewHost, configuration: LiveSessionConfiguration = .init()) {
198+
self.init(session: .init(host.url, config: configuration))
199+
}
200+
201+
public init(url: URL, configuration: LiveSessionConfiguration = .init()) {
202+
self.init(session: .init(url, config: configuration))
169203
}
170204
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// LiveViewHost.swift
3+
//
4+
//
5+
// Created by Carson Katri on 6/29/23.
6+
//
7+
8+
import Foundation
9+
10+
/// The information needed to locate a server hosting a LiveView.
11+
public protocol LiveViewHost {
12+
var url: URL { get }
13+
}
14+
15+
public struct LocalhostLiveViewHost: LiveViewHost {
16+
public let url: URL
17+
18+
init(port: Int) {
19+
self.url = .init(string: "http://localhost:\(port)")!
20+
}
21+
}
22+
23+
public extension LiveViewHost where Self == LocalhostLiveViewHost {
24+
/// A server at `http://localhost:4000`.
25+
static var localhost: Self { .init(port: 4000) }
26+
/// A server at `localhost` on the given port.
27+
static func localhost(port: Int) -> Self { .init(port: port) }
28+
}
29+
30+
public struct CustomLiveViewHost: LiveViewHost {
31+
public let url: URL
32+
33+
init(_ url: URL) {
34+
self.url = url
35+
}
36+
}
37+
38+
public extension LiveViewHost where Self == CustomLiveViewHost {
39+
/// A server at a custom `URL`.
40+
static func custom(_ url: URL) -> Self { .init(url) }
41+
}
42+
43+
public struct AutomaticLiveViewHost: LiveViewHost {
44+
public let url: URL
45+
46+
init(
47+
development: some LiveViewHost,
48+
production: some LiveViewHost
49+
) {
50+
#if DEBUG
51+
self.url = development.url
52+
#else
53+
self.url = production.url
54+
#endif
55+
}
56+
}
57+
58+
public extension LiveViewHost where Self == AutomaticLiveViewHost {
59+
/// Automatically select a host based on the `DEBUG` compiler directive.
60+
static func automatic(development: some LiveViewHost, production: some LiveViewHost) -> Self {
61+
.init(development: development, production: production)
62+
}
63+
64+
/// Automatically select a host based on the `DEBUG` compiler directive.
65+
static func automatic(_ url: URL) -> Self {
66+
.init(development: .localhost, production: .custom(url))
67+
}
68+
}

0 commit comments

Comments
 (0)