Hướng dẫn cài đặt XenseAR SDK cho Flutter iOS
- Copy XenseAR SDK vào thư mục
<flutter root>/ios/unityLibrary/.
- File SDK có thể lấy từ Trang tải xuống.
- Thêm package vào
<flutter root>/pubspec.yaml.
- Thêm các package cần thiết:
dependencies:
# Thêm hai package này vào danh sách
flutter_embed_unity: ^1.4.0- Sau khi đã thêm, thực hiện download qua terminal:
flutter pub get- Mở file
xcworkspacetại<your flutter project>/ios/Runner.xcworkspace.
- Việc mở file
xcworkspacemà không phải filexcprojlà cực kỳ quan trọng. Filexcworkspaceđược dùng để kết hợp nhiều projects (Bao gồm Project Runner của Flutter và XenseAR Library).
- Thêm
XenseARvàoworkspace.
- Từ trên Menu chọn
File -> Add Files to "Runner".... - Chọn
xcodeprojcủa XenseAR Library, thường được đặt tại<your flutter project>/ios/UnityLibrary/Unity-Iphone.xcodeproj. - Đảm bảo XenseAR Library nằm cùng cấp với Runner.
- Chỉnh sửa file
<your flutter app>/ios/Podfile:
- XenseAR Library cần hệ điều hành tối thiểu iOS 13+ để hoạt động
# XenseAR Library requires iOS 13+
platform :ios, '13.0'- Nếu không thấy file
Podfile, thực hiện lại bước cài lại flutter:
flutter clean
flutter pub get- Cài đặt pod:
cd ios & pod install- Copy Framework
- Chọn project
Runner - Đến phần
Target -> Runner -> General -> Frameworks, Libraries, and Embedded Content - Copy framework thư mục
Unity-iPhone/Frameworks/Silver Tau/vàoFrameworks, Libraries, and Embedded Content - Chọn chế độ
EmbedlàEmbed & Sign
- Bổ sung quyền trong
Info.plist
- Chọn project
Runner - Đến phần
Target -> Runner -> Info -> Custom iOS Target Properties - Bổ sung hai key:
Privacy - Camera Usage DescriptionPrivacy - Microphone Usage Description
- Sửa lỗi đen màn hình
- Do xung đột giữa cách flutter và một package trong XenseAR Library hoạt động,
cần thêm một vài đoạn code vào file
ios/Runner/AppDelegate.swift- Nhận tín hiệu có thay đổi window trong
application - Đưa webview vào UnityView
- Nhận tín hiệu có thay đổi window trong
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// 1️⃣ Observe when new windows appear or become active
NotificationCenter.default.addObserver(
self,
selector: #selector(onWindowChange),
name: UIWindow.didBecomeVisibleNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(onWindowChange),
name: UIWindow.didBecomeKeyNotification,
object: nil
)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 🔁 Called when any window becomes visible or key
@objc func onWindowChange(_ notification: Notification) {
print("🪟 Window change detected — re-checking WKWebView position.")
// Delay slightly to let UIKit attach new layers
DispatchQueue.main.async {
self.moveWKWebViewToFrontmostWindow()
}
}
func moveWKWebViewToFrontmostWindow() {
// 1️⃣ Collect all visible windows from active scenes
let allWindows = UIApplication.shared.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.windows }
.flatMap { $0 }
.filter { !$0.isHidden } // ignore hidden windows
guard !allWindows.isEmpty else {
print("❌ No visible windows found.")
return
}
// 2️⃣ Locate the UnityView inside any visible window
guard
let unityView = allWindows
.compactMap({ findSubview(in: $0, nameContains: "UnityView") })
.first
else {
print("⚠️ UnityView not found in any window.")
return
}
// 3️⃣ Locate the Vuplex fullscreen WKWebView
guard
let wkWebView = allWindows
.compactMap({ findSubview(in: $0, nameContains: "VXFullScreenWKWebView") })
.first
else {
print("⚠️ WKWebView not found in any window.")
return
}
// 4️⃣ If WKWebView is already in the UnityView, no action needed
if wkWebView.superview === unityView {
print("✅ WKWebView is already inside the UnityView.")
return
}
// 5️⃣ Move WKWebView from its current parent to UnityView
let previousSuperview = wkWebView.superview
wkWebView.removeFromSuperview()
unityView.addSubview(wkWebView)
// 6️⃣ Make WKWebView fill the UnityView entirely
wkWebView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
wkWebView.leadingAnchor.constraint(equalTo: unityView.leadingAnchor),
wkWebView.trailingAnchor.constraint(equalTo: unityView.trailingAnchor),
wkWebView.topAnchor.constraint(equalTo: unityView.topAnchor),
wkWebView.bottomAnchor.constraint(equalTo: unityView.bottomAnchor),
])
// 7️⃣ Hide and clean up the old window if it’s different from UnityView's window
if let prevWindow = previousSuperview?.window,
prevWindow !== unityView.window {
prevWindow.isHidden = true
}
// 8️⃣ Remove window observers (no longer needed)
NotificationCenter.default.removeObserver(
self,
name: UIWindow.didBecomeKeyNotification,
object: nil
)
NotificationCenter.default.removeObserver(
self,
name: UIWindow.didBecomeVisibleNotification,
object: nil
)
print("✅ WKWebView moved from \(String(describing: previousSuperview)) to UnityView \(unityView).")
}
private func findSubview(in view: UIView, nameContains keyword: String) -> UIView? {
if String(describing: type(of: view)).contains(keyword) {
return view
}
for subview in view.subviews {
if let match = findSubview(in: subview, nameContains: keyword) {
return match
}
}
return nil
}
}Last updated on