@webspatial/platform-visionos 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/web-spatial/EventEmitter.swift +11 -11
  3. package/web-spatial/JSBCommand.swift +15 -3
  4. package/web-spatial/WebMsgCommand.swift +7 -3
  5. package/web-spatial/WebSpatialApp.swift +10 -10
  6. package/web-spatial/Window.swift +2 -2
  7. package/web-spatial/manager/AttachmentManager.swift +81 -0
  8. package/web-spatial/manager/JSBManager.swift +1 -2
  9. package/web-spatial/manifest.swift +1 -1
  10. package/web-spatial/model/SpatialApp.swift +59 -55
  11. package/web-spatial/model/SpatialScene.swift +97 -14
  12. package/web-spatial/model/Spatialized2DElement.swift +4 -5
  13. package/web-spatial/model/SpatializedStatic3DElement.swift +1 -1
  14. package/web-spatial/model/dynamic3d/SpatialComponent.swift +27 -27
  15. package/web-spatial/model/dynamic3d/SpatialEntity.swift +2 -2
  16. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +15 -15
  17. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +10 -10
  18. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +1 -1
  19. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +8 -8
  20. package/web-spatial/view/SpatialNavView.swift +52 -47
  21. package/web-spatial/view/SpatializedDynamic3DView.swift +68 -4
  22. package/web-spatial/view/SpatializedElementView.swift +28 -13
  23. package/web-spatial/view/SpatializedStatic3DView.swift +4 -6
  24. package/web-spatial/view/view-modifier/HideViewModifier.swift +2 -2
  25. package/web-spatial/webview/SpatialWebController.swift +27 -24
  26. package/web-spatial/webview/SpatialWebView.swift +5 -1
  27. package/web-spatial/webview/SpatialWebViewModel.swift +13 -7
  28. package/web-spatial.xcodeproj/project.pbxproj +13 -0
  29. package/web-spatialTests/NavigationCleanupTests.swift +33 -0
@@ -21,7 +21,6 @@ struct NavDivider: View {
21
21
  }
22
22
  }
23
23
 
24
-
25
24
  struct NavButton: View {
26
25
  var action: () -> Void
27
26
  var children: Image
@@ -55,21 +54,18 @@ struct NavButton: View {
55
54
  }
56
55
  }
57
56
 
58
-
59
57
  struct SpatialNavView: View {
60
58
  static let navHeight: CGFloat = SpatialScene.navHeight
61
59
  static let minWidth: CGFloat = 400
62
60
  var spatialScene: SpatialScene
63
- var model:SpatialWebViewModel? {
64
- get {
65
- return spatialScene.spatialWebViewModel
66
- }
61
+ var model: SpatialWebViewModel? {
62
+ return spatialScene.spatialWebViewModel
67
63
  }
64
+
68
65
  var url: String {
69
- get {
70
- return spatialScene.url
71
- }
66
+ return spatialScene.url
72
67
  }
68
+
73
69
  @State var navWidth: CGFloat = 0
74
70
  @State private var showCopyTip = false
75
71
  @State private var contentHeight: CGFloat = 60
@@ -81,36 +77,40 @@ struct SpatialNavView: View {
81
77
  @State private var showNav: Double = 0
82
78
  @State private var showUrl: Double = 0
83
79
  @Namespace var hoverNamespace
84
-
85
- func checkButtonState(){
80
+
81
+ func checkButtonState() {
86
82
  canGoBack = model!.getController().webview!.canGoBack
87
83
  canGoForward = model!.getController().webview!.canGoForward
88
84
  }
89
-
85
+
90
86
  func goBack() {
87
+ spatialScene.resetForNavigation()
91
88
  model?.getController().webview?.goBack()
92
89
  }
93
-
90
+
94
91
  func goForward() {
92
+ spatialScene.resetForNavigation()
95
93
  model?.getController().webview?.goForward()
96
94
  }
97
-
95
+
98
96
  func reload() {
97
+ spatialScene.resetForNavigation()
99
98
  model?.getController().webview?.reload()
100
99
  }
101
-
100
+
102
101
  func navigateToURL(url: URL) {
102
+ spatialScene.resetForNavigation()
103
103
  model?.load(url.absoluteString)
104
104
  }
105
-
105
+
106
106
  func getURL() -> URL? {
107
107
  return model?.getController().webview?.url
108
108
  }
109
-
109
+
110
110
  @State var canGoBack: Bool = false
111
-
111
+
112
112
  @State var canGoForward: Bool = false
113
-
113
+
114
114
  var navHoverGroup: HoverEffectGroup {
115
115
  HoverEffectGroup(hoverNamespace)
116
116
  }
@@ -126,13 +126,15 @@ struct SpatialNavView: View {
126
126
  action: { self.goBack()
127
127
  },
128
128
  children: Image("arrow_left"),
129
- clearBackGround: true)
129
+ clearBackGround: true
130
+ )
130
131
  .disabled(!(self.canGoBack))
131
132
  NavButton(
132
133
  action: { self.goForward()
133
134
  },
134
135
  children: Image("arrow_right"),
135
- clearBackGround: true)
136
+ clearBackGround: true
137
+ )
136
138
  .disabled(!(self.canGoBack))
137
139
  NavButton(action: { self.reload() }, children: Image("refresh"), clearBackGround: true)
138
140
  NavDivider()
@@ -156,12 +158,14 @@ struct SpatialNavView: View {
156
158
  NavButton(
157
159
  action: { self.goBack()
158
160
  },
159
- children: Image("arrow_left"))
161
+ children: Image("arrow_left")
162
+ )
160
163
  .disabled(!(self.canGoBack))
161
164
  NavButton(
162
165
  action: { self.goForward()
163
166
  },
164
- children: Image("arrow_right"))
167
+ children: Image("arrow_right")
168
+ )
165
169
  .disabled(!(self.canGoBack))
166
170
  NavButton(action: { self.reload() }, children: Image("refresh"))
167
171
  NavDivider()
@@ -187,14 +191,14 @@ struct SpatialNavView: View {
187
191
  self.getURL()?.absoluteString ?? ""
188
192
  )
189
193
  )
190
- .lineLimit(1)
191
- .textSelection(.enabled)
192
- .padding(12)
193
- .frame(minWidth: 200)
194
- .frame(maxWidth: 500)
195
- .frame(height: 44)
196
- .background(.black)
197
- .cornerRadius(100)
194
+ .lineLimit(1)
195
+ .textSelection(.enabled)
196
+ .padding(12)
197
+ .frame(minWidth: 200)
198
+ .frame(maxWidth: 500)
199
+ .frame(height: 44)
200
+ .background(.black)
201
+ .cornerRadius(100)
198
202
  NavButton(action: {
199
203
  UIPasteboard.general.string = self.getURL()?.absoluteString ?? ""
200
204
  showCopyTip = true
@@ -204,21 +208,22 @@ struct SpatialNavView: View {
204
208
  withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
205
209
  }, children: Image("copy"))
206
210
  NavButton(
207
- action: {
208
- print("open browser")
209
- UIApplication.shared
210
- .open(
211
- URL(
212
- string: url.count > 0 ? url : (
213
- self.getURL()?.absoluteString ?? ""
211
+ action: {
212
+ print("open browser")
213
+ UIApplication.shared
214
+ .open(
215
+ URL(
216
+ string: url.count > 0 ? url : (
217
+ self.getURL()?.absoluteString ?? ""
218
+ )
219
+ )!,
220
+ options: [:],
221
+ completionHandler: nil
222
+ )
223
+ withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
224
+ },
225
+ children: Image("browser")
214
226
  )
215
- )!,
216
- options: [:],
217
- completionHandler: nil
218
- )
219
- withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 }
220
- },
221
- children: Image("browser"))
222
227
  NavButton(action: { withAnimation(.easeInOut(duration: 0.5)) { showUrl = 0; showNav = 1 } }, children: Image("close"))
223
228
  }
224
229
  .popover(isPresented: $showCopyTip) {
@@ -232,8 +237,8 @@ action: {
232
237
  .cornerRadius(100)
233
238
  .opacity(showUrl)
234
239
  }
235
- .onAppear(){
236
- model?.addStateListener(.didFinishLoad){
240
+ .onAppear {
241
+ model?.addStateListener(.didFinishLoad) {
237
242
  self.checkButtonState()
238
243
  }
239
244
  }
@@ -16,10 +16,21 @@ struct SpatializedDynamic3DView: View {
16
16
  SpatialTapGesture(count: 1).targetedToAnyEntity()
17
17
  .onEnded { value in
18
18
  if let entity = value.entity as? SpatialEntity {
19
- spatialScene.sendWebMsg(entity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D)))
19
+ // Convert local gesture coordinates into world (global) coordinates via RealityKit.
20
+ let globalLocation3D = entity.convert(position: SIMD3<Float>(Float(value.location3D.x), Float(value.location3D.y), Float(value.location3D.z)), to: nil)
21
+ let globalPoint3D = Point3D(x: Double(globalLocation3D.x), y: Double(globalLocation3D.y), z: Double(globalLocation3D.z))
22
+
23
+ spatialScene.sendWebMsg(entity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D, globalLocation3D: globalPoint3D)))
20
24
  } else {
21
25
  if let spatialEntity = SpatialEntity.findNearestParent(entity: value.entity) {
22
- spatialScene.sendWebMsg(spatialEntity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D)))
26
+ // Convert using the hit entity's coordinate space, then forward to the nearest SpatialEntity.
27
+ let globalLocation3D = value.entity.convert(
28
+ position: SIMD3<Float>(Float(value.location3D.x), Float(value.location3D.y), Float(value.location3D.z)),
29
+ to: nil
30
+ )
31
+ let globalPoint3D = Point3D(x: Double(globalLocation3D.x), y: Double(globalLocation3D.y), z: Double(globalLocation3D.z))
32
+
33
+ spatialScene.sendWebMsg(spatialEntity.spatialId, WebSpatialTapGuestureEvent(detail: WebSpatialTapGuestureEventDetail(location3D: value.location3D, globalLocation3D: globalPoint3D)))
23
34
  }
24
35
  }
25
36
  }
@@ -76,9 +87,16 @@ struct SpatializedDynamic3DView: View {
76
87
  // Always forward drag gesture events to JS
77
88
  if let entity = value.entity as? SpatialEntity {
78
89
  if !isDrag {
90
+ let globalStartLocation3D = value.entity.convert(
91
+ position: SIMD3<Float>(Float(value.startLocation3D.x), Float(value.startLocation3D.y), Float(value.startLocation3D.z)),
92
+ to: nil
93
+ )
94
+ let globalStartPoint3D = Point3D(x: Double(globalStartLocation3D.x), y: Double(globalStartLocation3D.y), z: Double(globalStartLocation3D.z))
95
+
79
96
  let startEvent = WebSpatialDragStartGuestureEvent(
80
97
  detail: .init(
81
- startLocation3D: value.startLocation3D
98
+ startLocation3D: value.startLocation3D,
99
+ globalLocation3D: globalStartPoint3D
82
100
  )
83
101
  )
84
102
  spatialScene.sendWebMsg(entity.spatialId, startEvent)
@@ -101,13 +119,59 @@ struct SpatializedDynamic3DView: View {
101
119
  }
102
120
 
103
121
  var body: some View {
104
- RealityView(make: { content in
122
+ RealityView(make: { content, attachments in
105
123
  let rootEntity = spatializedDynamic3DElement.getRoot()
106
124
  content.add(rootEntity)
125
+
126
+ // Add existing attachments on initial creation
127
+ for (_, info) in spatialScene.attachmentManager.attachments {
128
+ if let attachmentEntity = attachments.entity(for: info.id) {
129
+ attachmentEntity.position = info.position
130
+ if let parentEntity = findSpatialEntity(info.parentEntityId) {
131
+ parentEntity.addChild(attachmentEntity)
132
+ } else {
133
+ rootEntity.addChild(attachmentEntity)
134
+ }
135
+ }
136
+ }
137
+ }, update: { _, attachments in
138
+ let rootEntity = spatializedDynamic3DElement.getRoot()
139
+ // Update attachment positions and parenting
140
+ for (_, info) in spatialScene.attachmentManager.attachments {
141
+ if let attachmentEntity = attachments.entity(for: info.id) {
142
+ attachmentEntity.position = info.position
143
+ // Re-parent if not already under the correct parent
144
+ if let parentEntity = findSpatialEntity(info.parentEntityId) {
145
+ if attachmentEntity.parent != parentEntity {
146
+ parentEntity.addChild(attachmentEntity)
147
+ }
148
+ } else {
149
+ // Parent entity might have been destroyed; fall back to root.
150
+ if attachmentEntity.parent != rootEntity {
151
+ rootEntity.addChild(attachmentEntity)
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }, attachments: {
157
+ ForEach(Array(spatialScene.attachmentManager.attachments.values)) { info in
158
+ Attachment(id: info.id) {
159
+ info.webViewModel.getView()
160
+ .frame(
161
+ width: info.size.width,
162
+ height: info.size.height
163
+ )
164
+ }
165
+ }
107
166
  })
108
167
  .simultaneousGesture(spatialTapEvent)
109
168
  .simultaneousGesture(rotate3dEvent)
110
169
  .simultaneousGesture(dragEvent)
111
170
  .simultaneousGesture(magnifyEvent)
112
171
  }
172
+
173
+ private func findSpatialEntity(_ spatialId: String) -> SpatialEntity? {
174
+ // Look up the SpatialEntity from the SpatialScene's spatial object registry
175
+ return spatialScene.findSpatialObject(spatialId)
176
+ }
113
177
  }
@@ -1,12 +1,17 @@
1
+ import CoreGraphics
1
2
  import SwiftUI
2
3
 
3
- // zIndex() have some bug, so use zOrderBias to simulate zIndex effect
4
+ /// zIndex() have some bug, so use zOrderBias to simulate zIndex effect
4
5
  let zOrderBias = 0.001
5
6
 
6
7
  final class GestureFlags {
7
8
  var isDrag = false
8
9
  }
9
10
 
11
+ final class TransformHolder {
12
+ var transform: AffineTransform3D = .identity
13
+ }
14
+
10
15
  struct SpatializedElementView<Content: View>: View {
11
16
  @Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
12
17
  @Environment(SpatialScene.self) var spatialScene: SpatialScene
@@ -15,13 +20,15 @@ struct SpatializedElementView<Content: View>: View {
15
20
  var content: Content
16
21
 
17
22
  @State private var gestureFlags = GestureFlags()
23
+ @State private var transformHolder = TransformHolder()
24
+ @State private var currentTransform: AffineTransform3D = .identity
18
25
 
19
26
  init(parentScrollOffset: Vec2, @ViewBuilder content: () -> Content) {
20
27
  self.parentScrollOffset = parentScrollOffset
21
28
  self.content = content()
22
29
  }
23
30
 
24
- // Begin Interaction
31
+ /// Begin Interaction
25
32
  var gesture: some Gesture {
26
33
  DragGesture(minimumDistance: 10)
27
34
  .onChanged(onDragging)
@@ -29,17 +36,14 @@ struct SpatializedElementView<Content: View>: View {
29
36
  .simultaneously(with:
30
37
  RotateGesture3D()
31
38
  .onChanged(onRotateGesture3D)
32
- .onEnded(onRotateGesture3DEnd)
33
- )
39
+ .onEnded(onRotateGesture3DEnd))
34
40
  .simultaneously(with:
35
41
  MagnifyGesture()
36
42
  .onChanged(onMagnifyGesture)
37
- .onEnded(onMagnifyGestureEnd)
38
- )
43
+ .onEnded(onMagnifyGestureEnd))
39
44
  .simultaneously(with:
40
45
  SpatialTapGesture(count: 1)
41
- .onEnded(onTapEnded)
42
- )
46
+ .onEnded(onTapEnded))
43
47
  }
44
48
 
45
49
  private func onRotateGesture3D(_ event: RotateGesture3D.Value) {
@@ -64,8 +68,13 @@ struct SpatializedElementView<Content: View>: View {
64
68
 
65
69
  private func onDragging(_ event: DragGesture.Value) {
66
70
  if spatializedElement.enableDragStartGesture, !gestureFlags.isDrag {
71
+ let localPoint = SIMD4<Double>(event.startLocation3D.x, event.startLocation3D.y, event.startLocation3D.z, 1.0)
72
+ let transformedPoint = transformHolder.transform.matrix * localPoint
73
+ let globalPoint3D = Point3D(x: transformedPoint.x, y: transformedPoint.y, z: transformedPoint.z)
74
+
67
75
  let gestureEvent = WebSpatialDragStartGuestureEvent(detail: .init(
68
- startLocation3D: event.startLocation3D
76
+ startLocation3D: event.startLocation3D,
77
+ globalLocation3D: globalPoint3D
69
78
  ))
70
79
 
71
80
  spatialScene.sendWebMsg(spatializedElement.id, gestureEvent)
@@ -92,7 +101,11 @@ struct SpatializedElementView<Content: View>: View {
92
101
 
93
102
  private func onTapEnded(_ event: SpatialTapGesture.Value) {
94
103
  if spatializedElement.enableTapGesture {
95
- spatialScene.sendWebMsg(spatializedElement.id, WebSpatialTapGuestureEvent(detail: .init(location3D: event.location3D)))
104
+ let localPoint = SIMD4<Double>(event.location3D.x, event.location3D.y, event.location3D.z, 1.0)
105
+ let transformedPoint = transformHolder.transform.matrix * localPoint
106
+ let globalPoint3D = Point3D(x: transformedPoint.x, y: transformedPoint.y, z: transformedPoint.z)
107
+
108
+ spatialScene.sendWebMsg(spatializedElement.id, WebSpatialTapGuestureEvent(detail: .init(location3D: event.location3D, globalLocation3D: globalPoint3D)))
96
109
  }
97
110
  }
98
111
 
@@ -101,7 +114,8 @@ struct SpatializedElementView<Content: View>: View {
101
114
  let gestureEvent = WebSpatialMagnifyGuestureEvent(
102
115
  detail: .init(
103
116
  magnification: event.magnification
104
- ))
117
+ )
118
+ )
105
119
  spatialScene.sendWebMsg(spatializedElement.id, gestureEvent)
106
120
  }
107
121
  }
@@ -114,7 +128,6 @@ struct SpatializedElementView<Content: View>: View {
114
128
 
115
129
  // End Interaction
116
130
 
117
- @ViewBuilder
118
131
  var body: some View {
119
132
  let transform = spatializedElement.transform
120
133
  let translation = transform.translation
@@ -146,7 +159,9 @@ struct SpatializedElementView<Content: View>: View {
146
159
  .onGeometryChange3D(for: AffineTransform3D.self) { proxy in
147
160
  let rect3d = proxy.frame(in: .named("SpatialScene"))
148
161
  spatialScene.sendWebMsg(spatializedElement.id, SpatiaizedContainerClientCube(origin: rect3d.origin, size: rect3d.size))
149
- return proxy.transform(in: .named("SpatialScene"))!
162
+ let transform = proxy.transform(in: .named("SpatialScene"))!
163
+ transformHolder.transform = transform
164
+ return transform
150
165
  } action: { _, new in
151
166
  spatialScene.sendWebMsg(spatializedElement.id, SpatiaizedContainerTransform(detail: new))
152
167
  }
@@ -8,7 +8,7 @@ struct SpatializedStatic3DView: View {
8
8
  private var spatializedStatic3DElement: SpatializedStatic3DElement {
9
9
  return spatializedElement as! SpatializedStatic3DElement
10
10
  }
11
-
11
+
12
12
  func onLoadSuccess() {
13
13
  spatialScene.sendWebMsg(spatializedElement.id, ModelLoadSuccess())
14
14
  }
@@ -17,7 +17,6 @@ struct SpatializedStatic3DView: View {
17
17
  spatialScene.sendWebMsg(spatializedElement.id, ModelLoadFailure())
18
18
  }
19
19
 
20
- @ViewBuilder
21
20
  var body: some View {
22
21
  let depth = spatializedElement.depth
23
22
  let transform = spatializedStatic3DElement.modelTransform
@@ -27,14 +26,13 @@ struct SpatializedStatic3DView: View {
27
26
  let x = translation.x
28
27
  let y = translation.y
29
28
  let z = translation.z
30
-
29
+
31
30
  let enableGesture = spatializedElement.enableGesture
32
31
  if let url = URL(string: spatializedStatic3DElement.modelURL) {
33
32
  Model3D(url: url) { newPhase in
34
33
  switch newPhase {
35
34
  case .empty:
36
35
  ProgressView()
37
-
38
36
  case let .success(resolvedModel3D):
39
37
  resolvedModel3D
40
38
  .resizable(true)
@@ -42,11 +40,11 @@ struct SpatializedStatic3DView: View {
42
40
  nil,
43
41
  contentMode: .fit
44
42
  )
45
- .if(!depth.isZero){ view in view.scaledToFit3D()}
43
+ .if(!depth.isZero) { view in view.scaledToFit3D() }
46
44
  .onAppear {
47
45
  self.onLoadSuccess()
48
46
  }
49
- .if(enableGesture) { view in view.hoverEffect()}
47
+ .if(enableGesture) { view in view.hoverEffect() }
50
48
  case .failure:
51
49
  Text("").onAppear {
52
50
  self.onLoadFailure()
@@ -2,14 +2,14 @@ import SwiftUI
2
2
 
3
3
  struct HideViewModifier: ViewModifier {
4
4
  let isHidden: Bool
5
- @ViewBuilder func body(content: Content) -> some View {
5
+ func body(content: Content) -> some View {
6
6
  content
7
7
  .opacity(isHidden ? 0 : 1)
8
8
  .disabled(isHidden)
9
9
  }
10
10
  }
11
11
 
12
- // Extending on View to apply to all Views
12
+ /// Extending on View to apply to all Views
13
13
  extension View {
14
14
  func hidden(_ isHidden: Bool) -> some View {
15
15
  modifier(HideViewModifier(isHidden: isHidden))
@@ -9,7 +9,7 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
9
9
  private var openWindowInvoke: ((_ data: URL) -> WebViewElementInfo?)?
10
10
  private var webviewStateChangeInvoke: ((_ type: SpatialWebViewState) -> Void)?
11
11
  private var scorllUpdateInvoke: ((_ type: ScrollState, _ point: CGPoint) -> Void)?
12
- private var webviewTitle: String? = nil
12
+ private var webviewTitle: String?
13
13
  private var firstLoad = true
14
14
  private var jsbManager = JSBManager()
15
15
 
@@ -62,25 +62,29 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
62
62
  }
63
63
  }
64
64
 
65
- // navigation request
66
- // SpatialDiv/forcestyle/normal web link protocol
65
+ /// navigation request
66
+ /// SpatialDiv/forcestyle/normal web link protocol
67
67
  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
68
68
  let deciside = navigationInvoke?(navigationAction.request.url!)
69
69
  if deciside == true {
70
- if !firstLoad{
70
+ if !firstLoad {
71
71
  webviewStateChangeInvoke?(.didUnload)
72
72
  }
73
73
  firstLoad = false
74
74
  }
75
75
  var needAllow = deciside ?? false
76
-
77
- if !needAllow{
78
- UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
76
+
77
+ if !needAllow {
78
+ // webspatial:// is an internal scheme for in-app routing between main and attachment windows.
79
+ // Only open non-webspatial URLs externally via the system.
80
+ if navigationAction.request.url?.scheme != "webspatial" {
81
+ UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
82
+ }
79
83
  }
80
84
  decisionHandler(needAllow ? .allow : .cancel)
81
85
  }
82
86
 
83
- // open window request
87
+ /// open window request
84
88
  func webView(
85
89
  _ webView: WKWebView,
86
90
  createWebViewWith configuration: WKWebViewConfiguration,
@@ -95,7 +99,7 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
95
99
  return nil
96
100
  }
97
101
 
98
- // invoke jsb
102
+ /// invoke jsb
99
103
  func userContentController(
100
104
  _ userContentController: WKUserContentController,
101
105
  didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void
@@ -104,7 +108,7 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
104
108
  jsbManager.handlerMessage(message.body as! String, replyHandler)
105
109
  }
106
110
 
107
- // custom scheme request
111
+ /// custom scheme request
108
112
  func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
109
113
  print("urlSchemeTask")
110
114
  let url = urlSchemeTask.request.url
@@ -221,8 +225,8 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
221
225
  scorllUpdateInvoke = nil
222
226
  model = nil
223
227
  }
224
-
225
- private var state:SpatialWebViewState?
228
+
229
+ private var state: SpatialWebViewState?
226
230
 
227
231
  func destroyView() {
228
232
  stopObserving()
@@ -236,15 +240,15 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
236
240
  webviewStateChangeInvoke?(.didDestroyView)
237
241
  }
238
242
  }
239
-
243
+
240
244
  private var isPageLoaded = false
241
-
245
+
242
246
  private var jsQueue: [String] = []
243
-
247
+
244
248
  private func enqueueJS(_ js: String) {
245
249
  jsQueue.append(js)
246
250
  }
247
-
251
+
248
252
  private func flushJSQueue() {
249
253
  guard !jsQueue.isEmpty else { return }
250
254
  let combined = jsQueue.joined(separator: ";")
@@ -253,7 +257,7 @@ class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandl
253
257
  }
254
258
 
255
259
  func callJS(_ js: String) {
256
- if webview != nil && isPageLoaded {
260
+ if webview != nil, isPageLoaded {
257
261
  webview!.evaluateJavaScript(js)
258
262
  } else {
259
263
  enqueueJS(js)
@@ -268,13 +272,13 @@ enum ScrollState {
268
272
  case end
269
273
  }
270
274
 
271
- // extend webview to support file://
272
- @available(iOS 11.0, *)
273
- extension WKWebView {
275
+ /// extend webview to support file://
276
+ @available(iOS 11.0, *)
277
+ extension WKWebView {
274
278
  /// WKWebView, Support setting file scheme in configuration
275
279
  public private(set) static var isEnableFileSupport = false
276
280
  public static func enableFileScheme() {
277
- /// This method supports adapting supported files through Configuration, but cannot be cancelled (Configuration is immutable).
281
+ // This method supports adapting supported files through Configuration, but cannot be cancelled (Configuration is immutable).
278
282
  if !isEnableFileSupport {
279
283
  switchHandlesURLScheme()
280
284
  }
@@ -292,9 +296,8 @@ enum ScrollState {
292
296
  }
293
297
 
294
298
  /// Return true if WKWebview supports handling this protocol, but WKWebview supports HTTP by default, so return false to support using custom HTTP Handler
295
- @objc private dynamic
296
- static func wrapHandles(urlScheme: String) -> Bool {
299
+ @objc private dynamic static func wrapHandles(urlScheme: String) -> Bool {
297
300
  if urlScheme == "file" { return false }
298
301
  return wrapHandles(urlScheme: urlScheme)
299
302
  }
300
- }
303
+ }
@@ -8,7 +8,11 @@ struct SpatialWebView: UIViewRepresentable {
8
8
 
9
9
  func makeUIView(context: Context) -> WKWebView {
10
10
  webviewStateChangeInvoke?(.didMakeView)
11
- return model!.getController().webview!
11
+ let controller = model?.getController()
12
+ if controller?.webview == nil {
13
+ model?.load()
14
+ }
15
+ return controller?.webview ?? WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
12
16
  }
13
17
 
14
18
  func makeCoordinator() -> SpatialWebController {