@webspatial/platform-visionos 1.0.3 → 1.0.5
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.
- package/package.json +2 -2
- package/web-spatial/EventEmitter.swift +56 -0
- package/web-spatial/JSBCommand.swift +348 -0
- package/web-spatial/SpatialObject.swift +108 -0
- package/web-spatial/WebMsgCommand.swift +119 -0
- package/web-spatial/WebSpatialApp.swift +111 -0
- package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
- package/web-spatial/manager/Dynamic3DManager.swift +114 -0
- package/web-spatial/manager/JSBManager.swift +148 -0
- package/web-spatial/manager/WKWebViewManager.swift +39 -0
- package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
- package/web-spatial/model/SpatialApp.swift +190 -0
- package/web-spatial/model/SpatialScene.swift +1042 -0
- package/web-spatial/model/Spatialized2DElement.swift +128 -0
- package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
- package/web-spatial/model/SpatializedElement.swift +95 -0
- package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
- package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
- package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
- package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
- package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
- package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
- package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
- package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
- package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
- package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
- package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
- package/web-spatial/protocol/WebMsgSender.swift +5 -0
- package/web-spatial/types/Vec2.swift +12 -0
- package/web-spatial/types/Vec3.swift +7 -0
- package/web-spatial/view/SceneHandlerUIView.swift +92 -0
- package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
- package/web-spatial/view/SpatialSceneContentView.swift +218 -0
- package/web-spatial/view/SpatialSceneView.swift +53 -0
- package/web-spatial/view/Spatialized2DElementView.swift +96 -0
- package/web-spatial/view/SpatializedDynamic3DView.swift +104 -0
- package/web-spatial/view/SpatializedElementView.swift +178 -0
- package/web-spatial/view/SpatializedStatic3DView.swift +70 -0
- package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
- package/web-spatial/webview/SpatialWebController.swift +300 -0
- package/web-spatial/webview/SpatialWebView.swift +34 -0
- package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
- package/web-spatial.xcodeproj/project.pbxproj +126 -207
- package/web-spatial/libs/EventEmitter.swift +0 -25
- package/web-spatial/libs/SpatialComponent.swift +0 -24
- package/web-spatial/libs/SpatialEntity.swift +0 -172
- package/web-spatial/libs/SpatialInputComponent.swift +0 -19
- package/web-spatial/libs/SpatialMeshResource.swift +0 -12
- package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
- package/web-spatial/libs/SpatialModelComponent.swift +0 -25
- package/web-spatial/libs/SpatialObject.swift +0 -140
- package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
- package/web-spatial/libs/SpatialViewComponent.swift +0 -8
- package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
- package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
- package/web-spatial/libs/Utils/CommandManager.swift +0 -823
- package/web-spatial/libs/Utils/PerfClock.swift +0 -43
- package/web-spatial/libs/Utils/SceneManager.swift +0 -101
- package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
- package/web-spatial/libs/json/JsonParser.swift +0 -45
- package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
- package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
- package/web-spatial/views/ImmersiveView.swift +0 -17
- package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
- package/web-spatial/views/PlainWindowContainerView.swift +0 -132
- package/web-spatial/views/SpatialModel3DView.swift +0 -187
- package/web-spatial/views/SpatialViewUI.swift +0 -168
- package/web-spatial/views/SpatialWebViewUI.swift +0 -179
- package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
- package/web-spatial/web_spatialApp.swift +0 -141
- /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
- /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
- /package/web-spatial/{views → view}/LoadingView.swift +0 -0
- /package/web-spatial/{views → view/view-modifier}/HideViewModifier.swift +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import RealityKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
struct SpatializedStatic3DView: View {
|
|
5
|
+
@Environment(SpatializedElement.self) var spatializedElement: SpatializedElement
|
|
6
|
+
@Environment(SpatialScene.self) var spatialScene: SpatialScene
|
|
7
|
+
|
|
8
|
+
private var spatializedStatic3DElement: SpatializedStatic3DElement {
|
|
9
|
+
return spatializedElement as! SpatializedStatic3DElement
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func onLoadSuccess() {
|
|
13
|
+
spatialScene.sendWebMsg(spatializedElement.id, ModelLoadSuccess())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func onLoadFailure() {
|
|
17
|
+
spatialScene.sendWebMsg(spatializedElement.id, ModelLoadFailure())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@ViewBuilder
|
|
21
|
+
var body: some View {
|
|
22
|
+
let transform = spatializedStatic3DElement.modelTransform
|
|
23
|
+
let translation = transform.translation
|
|
24
|
+
let scale = transform.scale
|
|
25
|
+
let rotation = transform.rotation!
|
|
26
|
+
let x = translation.x
|
|
27
|
+
let y = translation.y
|
|
28
|
+
let z = translation.z
|
|
29
|
+
|
|
30
|
+
let enableGesture = spatializedElement.enableGesture
|
|
31
|
+
if let url = URL(string: spatializedStatic3DElement.modelURL) {
|
|
32
|
+
Model3D(url: url) { newPhase in
|
|
33
|
+
switch newPhase {
|
|
34
|
+
case .empty:
|
|
35
|
+
ProgressView()
|
|
36
|
+
|
|
37
|
+
case let .success(resolvedModel3D):
|
|
38
|
+
resolvedModel3D
|
|
39
|
+
.resizable(true)
|
|
40
|
+
.aspectRatio(
|
|
41
|
+
nil,
|
|
42
|
+
contentMode: .fit
|
|
43
|
+
)
|
|
44
|
+
.onAppear {
|
|
45
|
+
self.onLoadSuccess()
|
|
46
|
+
}
|
|
47
|
+
.if(enableGesture) { view in view.hoverEffect()}
|
|
48
|
+
case .failure:
|
|
49
|
+
Text("").onAppear {
|
|
50
|
+
self.onLoadFailure()
|
|
51
|
+
}
|
|
52
|
+
@unknown default:
|
|
53
|
+
EmptyView()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
.scaleEffect(
|
|
57
|
+
x: scale.width,
|
|
58
|
+
y: scale.height,
|
|
59
|
+
z: scale.depth
|
|
60
|
+
)
|
|
61
|
+
.rotation3DEffect(
|
|
62
|
+
rotation
|
|
63
|
+
)
|
|
64
|
+
.offset(x: x, y: y)
|
|
65
|
+
.offset(z: z)
|
|
66
|
+
} else {
|
|
67
|
+
EmptyView()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -49,10 +49,17 @@ struct CornerRadius: Codable {
|
|
|
49
49
|
struct MaterialWithBorderCornerModifier: ViewModifier {
|
|
50
50
|
let backgroundMaterial: BackgroundMaterial
|
|
51
51
|
let cornerRadius: CornerRadius
|
|
52
|
+
let windowStyle: SpatialScene.WindowStyle
|
|
52
53
|
|
|
53
|
-
init(
|
|
54
|
+
init(
|
|
55
|
+
_ backgroundMaterial: BackgroundMaterial,
|
|
56
|
+
_ cornerRadius: CornerRadius,
|
|
57
|
+
_ windowStyle: SpatialScene
|
|
58
|
+
.WindowStyle
|
|
59
|
+
) {
|
|
54
60
|
self.backgroundMaterial = backgroundMaterial
|
|
55
61
|
self.cornerRadius = cornerRadius
|
|
62
|
+
self.windowStyle = windowStyle
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
func body(content: Content) -> some View {
|
|
@@ -60,11 +67,20 @@ struct MaterialWithBorderCornerModifier: ViewModifier {
|
|
|
60
67
|
|
|
61
68
|
switch backgroundMaterial {
|
|
62
69
|
case .GlassMaterial:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
if windowStyle == .volume {
|
|
71
|
+
content
|
|
72
|
+
.glassBackgroundEffect(
|
|
73
|
+
in: .rect(cornerRadii: radii),
|
|
74
|
+
displayMode: .always
|
|
75
|
+
)
|
|
76
|
+
} else {
|
|
77
|
+
content
|
|
78
|
+
.glassBackgroundEffect(
|
|
79
|
+
in: .rect(cornerRadii: radii),
|
|
80
|
+
displayMode: .always
|
|
81
|
+
)
|
|
82
|
+
.frame(depth: 0)
|
|
83
|
+
}
|
|
68
84
|
|
|
69
85
|
case .RegularMaterial:
|
|
70
86
|
content
|
|
@@ -89,9 +105,13 @@ struct MaterialWithBorderCornerModifier: ViewModifier {
|
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
extension View {
|
|
92
|
-
func materialWithBorderCorner(_ backgroundMaterial: BackgroundMaterial, _ cornerRadius: CornerRadius) -> some View {
|
|
108
|
+
func materialWithBorderCorner(_ backgroundMaterial: BackgroundMaterial, _ cornerRadius: CornerRadius, _ windowStyle: SpatialScene.WindowStyle) -> some View {
|
|
93
109
|
return modifier(
|
|
94
|
-
MaterialWithBorderCornerModifier(
|
|
110
|
+
MaterialWithBorderCornerModifier(
|
|
111
|
+
backgroundMaterial,
|
|
112
|
+
cornerRadius,
|
|
113
|
+
windowStyle
|
|
114
|
+
)
|
|
95
115
|
)
|
|
96
116
|
}
|
|
97
117
|
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
@preconcurrency import WebKit
|
|
3
|
+
|
|
4
|
+
class SpatialWebController: NSObject, WKNavigationDelegate, WKScriptMessageHandlerWithReply, WKUIDelegate, UIScrollViewDelegate, WKURLSchemeHandler {
|
|
5
|
+
weak var model: SpatialWebViewModel?
|
|
6
|
+
var webview: WKWebView?
|
|
7
|
+
private var isObserving = false
|
|
8
|
+
private var navigationInvoke: ((_ data: URL) -> Bool)?
|
|
9
|
+
private var openWindowInvoke: ((_ data: URL) -> WebViewElementInfo?)?
|
|
10
|
+
private var webviewStateChangeInvoke: ((_ type: SpatialWebViewState) -> Void)?
|
|
11
|
+
private var scorllUpdateInvoke: ((_ type: ScrollState, _ point: CGPoint) -> Void)?
|
|
12
|
+
private var webviewTitle: String? = nil
|
|
13
|
+
private var firstLoad = true
|
|
14
|
+
private var jsbManager = JSBManager()
|
|
15
|
+
|
|
16
|
+
override init() {
|
|
17
|
+
WKWebView.enableFileScheme() // ensure the handler is usable
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
deinit {}
|
|
21
|
+
|
|
22
|
+
func registerNavigationInvoke(invoke: @escaping (_ data: URL) -> Bool) {
|
|
23
|
+
navigationInvoke = invoke
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func registerOpenWindowInvoke(invoke: @escaping (_ data: URL) -> WebViewElementInfo?) {
|
|
27
|
+
openWindowInvoke = invoke
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func registeJSBHandler<T: CommandDataProtocol>(_ type: T.Type, _ event: @escaping (T, @escaping JSBManager.ResolveHandler<Encodable>) -> Void) {
|
|
31
|
+
jsbManager.register(type, event)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func registeJSBHandler<T: CommandDataProtocol>(_ type: T.Type, _ event: @escaping (@escaping JSBManager.ResolveHandler<Encodable>) -> Void) {
|
|
35
|
+
jsbManager.register(type, event)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func unregisterJSBHandler<T: CommandDataProtocol>(_ type: T.Type) {
|
|
39
|
+
jsbManager.remove(type)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func clearJSBHandler() {
|
|
43
|
+
jsbManager.clear()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func mockJSB(_ command: String) {
|
|
47
|
+
jsbManager.handlerMessage(command)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func registerWebviewStateChangeInvoke(invoke: @escaping (_ type: SpatialWebViewState) -> Void) {
|
|
51
|
+
webviewStateChangeInvoke = invoke
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func registerScrollUpdateInvoke(invoke: @escaping (_ type: ScrollState, _ point: CGPoint) -> Void) {
|
|
55
|
+
scorllUpdateInvoke = invoke
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func setWebViewTitle(_ title: String) {
|
|
59
|
+
webviewTitle = title
|
|
60
|
+
if webview != nil {
|
|
61
|
+
callJS("document.title='\(title)'")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// navigation request
|
|
66
|
+
// SpatialDiv/forcestyle/normal web link protocol
|
|
67
|
+
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
|
|
68
|
+
let deciside = navigationInvoke?(navigationAction.request.url!)
|
|
69
|
+
if deciside == true {
|
|
70
|
+
if !firstLoad{
|
|
71
|
+
webviewStateChangeInvoke?(.didUnload)
|
|
72
|
+
}
|
|
73
|
+
firstLoad = false
|
|
74
|
+
}
|
|
75
|
+
var needAllow = deciside ?? false
|
|
76
|
+
|
|
77
|
+
if !needAllow{
|
|
78
|
+
UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
|
|
79
|
+
}
|
|
80
|
+
decisionHandler(needAllow ? .allow : .cancel)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// open window request
|
|
84
|
+
func webView(
|
|
85
|
+
_ webView: WKWebView,
|
|
86
|
+
createWebViewWith configuration: WKWebViewConfiguration,
|
|
87
|
+
for navigationAction: WKNavigationAction,
|
|
88
|
+
windowFeatures: WKWindowFeatures
|
|
89
|
+
) -> WKWebView? {
|
|
90
|
+
if let modelInfo = openWindowInvoke?(navigationAction.request.url!) {
|
|
91
|
+
modelInfo.element.load(configuration, modelInfo.id)
|
|
92
|
+
return modelInfo.element.getController().webview
|
|
93
|
+
}
|
|
94
|
+
print("no webview")
|
|
95
|
+
return nil
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// invoke jsb
|
|
99
|
+
func userContentController(
|
|
100
|
+
_ userContentController: WKUserContentController,
|
|
101
|
+
didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void
|
|
102
|
+
) {
|
|
103
|
+
// let promise = JSBManager.Promise(replyHandler)
|
|
104
|
+
jsbManager.handlerMessage(message.body as! String, replyHandler)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// custom scheme request
|
|
108
|
+
func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
|
|
109
|
+
print("urlSchemeTask")
|
|
110
|
+
let url = urlSchemeTask.request.url
|
|
111
|
+
if url!.absoluteString.starts(with: "file://") {
|
|
112
|
+
let urlRequest = urlSchemeTask.request
|
|
113
|
+
|
|
114
|
+
let session = URLSession(configuration: URLSessionConfiguration.default)
|
|
115
|
+
let dataTask = session.dataTask(with: urlRequest) { [task = urlSchemeTask as AnyObject] data, response, _ in
|
|
116
|
+
guard let task = task as? WKURLSchemeTask else { return }
|
|
117
|
+
|
|
118
|
+
task.didReceive(response!)
|
|
119
|
+
task.didReceive(data!)
|
|
120
|
+
task.didFinish()
|
|
121
|
+
}
|
|
122
|
+
dataTask.resume()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) {}
|
|
127
|
+
func webView(_ webView: WKWebView, didStartProvisionalNavigation: WKNavigation!) {
|
|
128
|
+
webviewStateChangeInvoke?(.didStartLoad)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
|
132
|
+
webviewStateChangeInvoke?(.didReceive)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
136
|
+
webviewStateChangeInvoke?(.didFinishLoad)
|
|
137
|
+
if webviewTitle != nil {
|
|
138
|
+
callJS("document.title='\(webviewTitle!)'")
|
|
139
|
+
}
|
|
140
|
+
// flush pending calljs comand
|
|
141
|
+
isPageLoaded = true
|
|
142
|
+
flushJSQueue()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
|
|
146
|
+
decisionHandler(.allow)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
150
|
+
if let urlError = (error as? URLError) {
|
|
151
|
+
if urlError.code == .cannotConnectToHost {
|
|
152
|
+
webviewStateChangeInvoke?(.didFailLoad)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func webViewDidClose(_ webView: WKWebView) {
|
|
158
|
+
webviewStateChangeInvoke?(.didClose)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
|
162
|
+
guard let serverTrust = challenge.protectionSpace.serverTrust else { return completionHandler(.useCredential, nil) }
|
|
163
|
+
let exceptions = SecTrustCopyExceptions(serverTrust)
|
|
164
|
+
SecTrustSetExceptions(serverTrust, exceptions)
|
|
165
|
+
completionHandler(.useCredential, URLCredential(trust: serverTrust))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
169
|
+
scorllUpdateInvoke?(.start, scrollView.contentOffset)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
173
|
+
scorllUpdateInvoke?(.update, scrollView.contentOffset)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
177
|
+
scorllUpdateInvoke?(.end, scrollView.contentOffset)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
|
181
|
+
if !decelerate {
|
|
182
|
+
scorllUpdateInvoke?(.end, scrollView.contentOffset)
|
|
183
|
+
} else {
|
|
184
|
+
scorllUpdateInvoke?(.release, scrollView.contentOffset)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func startObserving() {
|
|
189
|
+
guard !isObserving else { return }
|
|
190
|
+
webview?.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)
|
|
191
|
+
isObserving = true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func stopObserving() {
|
|
195
|
+
guard isObserving else { return }
|
|
196
|
+
webview?.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
|
|
197
|
+
isObserving = false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
override func observeValue(
|
|
201
|
+
forKeyPath keyPath: String?,
|
|
202
|
+
of object: Any?,
|
|
203
|
+
change: [NSKeyValueChangeKey: Any]?,
|
|
204
|
+
context: UnsafeMutableRawPointer?
|
|
205
|
+
) {
|
|
206
|
+
if keyPath == #keyPath(WKWebView.url),
|
|
207
|
+
let url = (object as? WKWebView)?.url?.absoluteString
|
|
208
|
+
{
|
|
209
|
+
DispatchQueue.main.async {
|
|
210
|
+
// print("url change", url)
|
|
211
|
+
self.model?.url = url
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func destroy() {
|
|
217
|
+
destroyView()
|
|
218
|
+
navigationInvoke = nil
|
|
219
|
+
openWindowInvoke = nil
|
|
220
|
+
webviewStateChangeInvoke = nil
|
|
221
|
+
scorllUpdateInvoke = nil
|
|
222
|
+
model = nil
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private var state:SpatialWebViewState?
|
|
226
|
+
|
|
227
|
+
func destroyView() {
|
|
228
|
+
stopObserving()
|
|
229
|
+
if webview != nil {
|
|
230
|
+
webview?.stopLoading()
|
|
231
|
+
webview?.configuration.userContentController.removeScriptMessageHandler(forName: "bridge")
|
|
232
|
+
webview?.uiDelegate = nil
|
|
233
|
+
webview?.navigationDelegate = nil
|
|
234
|
+
webview?.scrollView.delegate = nil
|
|
235
|
+
webview = nil
|
|
236
|
+
webviewStateChangeInvoke?(.didDestroyView)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private var isPageLoaded = false
|
|
241
|
+
|
|
242
|
+
private var jsQueue: [String] = []
|
|
243
|
+
|
|
244
|
+
private func enqueueJS(_ js: String) {
|
|
245
|
+
jsQueue.append(js)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private func flushJSQueue() {
|
|
249
|
+
guard !jsQueue.isEmpty else { return }
|
|
250
|
+
let combined = jsQueue.joined(separator: ";")
|
|
251
|
+
callJS(combined)
|
|
252
|
+
jsQueue.removeAll()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func callJS(_ js: String) {
|
|
256
|
+
if webview != nil && isPageLoaded {
|
|
257
|
+
webview!.evaluateJavaScript(js)
|
|
258
|
+
} else {
|
|
259
|
+
enqueueJS(js)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
enum ScrollState {
|
|
265
|
+
case start
|
|
266
|
+
case update
|
|
267
|
+
case release
|
|
268
|
+
case end
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// extend webview to support file://
|
|
272
|
+
@available(iOS 11.0, *)
|
|
273
|
+
extension WKWebView {
|
|
274
|
+
/// WKWebView, Support setting file scheme in configuration
|
|
275
|
+
public private(set) static var isEnableFileSupport = false
|
|
276
|
+
public static func enableFileScheme() {
|
|
277
|
+
/// This method supports adapting supported files through Configuration, but cannot be cancelled (Configuration is immutable).
|
|
278
|
+
if !isEnableFileSupport {
|
|
279
|
+
switchHandlesURLScheme()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private static func switchHandlesURLScheme() {
|
|
284
|
+
if
|
|
285
|
+
case let cls = WKWebView.self,
|
|
286
|
+
let m1 = class_getClassMethod(cls, NSSelectorFromString("handlesURLScheme:")),
|
|
287
|
+
let m2 = class_getClassMethod(cls, #selector(WKWebView.wrapHandles(urlScheme:)))
|
|
288
|
+
{
|
|
289
|
+
method_exchangeImplementations(m1, m2)
|
|
290
|
+
isEnableFileSupport = !isEnableFileSupport
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// 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 {
|
|
297
|
+
if urlScheme == "file" { return false }
|
|
298
|
+
return wrapHandles(urlScheme: urlScheme)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
@preconcurrency import WebKit
|
|
3
|
+
|
|
4
|
+
struct SpatialWebView: UIViewRepresentable {
|
|
5
|
+
weak var model: SpatialWebViewModel? = nil
|
|
6
|
+
var url: URL = .init(filePath: "/")
|
|
7
|
+
private var webviewStateChangeInvoke: ((_ type: SpatialWebViewState) -> Void)?
|
|
8
|
+
|
|
9
|
+
func makeUIView(context: Context) -> WKWebView {
|
|
10
|
+
webviewStateChangeInvoke?(.didMakeView)
|
|
11
|
+
return model!.getController().webview!
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func makeCoordinator() -> SpatialWebController {
|
|
15
|
+
return model!.getController()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func updateUIView(_ webView: WKWebView, context: Context) {
|
|
19
|
+
webviewStateChangeInvoke?(.didUpdateView)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
mutating func registerWebviewStateChangeInvoke(invoke: @escaping (_ type: SpatialWebViewState) -> Void) {
|
|
23
|
+
webviewStateChangeInvoke = invoke
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
mutating func destroy() {
|
|
27
|
+
webviewStateChangeInvoke = nil
|
|
28
|
+
model = nil
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static func dismantleUIView(_ uiView: WKWebView, coordinator: SpatialWebController) {
|
|
32
|
+
// print("dismantleUIView", coordinator.model?.id)
|
|
33
|
+
}
|
|
34
|
+
}
|