Files
TrackingDemo/NearbyDemo/ContentView.swift

118 lines
3.8 KiB
Swift

import SwiftUI
import ARKit
struct ContentView: View {
@EnvironmentObject var ble: BLEManager
@EnvironmentObject var ni: NIManager
@EnvironmentObject var ar: ARManager
@EnvironmentObject var estimator: AnchorEstimator
var body: some View {
ZStack {
ARViewContainer(arManager: ar, estimator: estimator)
.ignoresSafeArea()
if let angle = estimator.offScreenAngle {
Image(systemName: "location.north.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.foregroundColor(.red)
.shadow(radius: 5)
// The image points "Up".
// SwiftUI rotates clockwise. Angle is mathematically CCW.
// If angle = 0 (right), we need it to point Right, so rotate 90 deg clockwise.
// Actually, if image points UP, we rotate by PI/2 - angle.
.rotationEffect(.radians(.pi / 2 - angle))
// Offset moves it towards the edge
.offset(x: cos(angle) * 140, y: -sin(angle) * 140)
.animation(.interactiveSpring(), value: angle)
}
VStack {
HUDView(ble: ble, ni: ni, estimator: estimator)
.padding()
Spacer()
Button("Reset Estimate") {
estimator.reset()
}
.buttonStyle(.borderedProminent)
.tint(.red.opacity(0.8))
.padding(.bottom, 40)
}
}
}
}
// MARK: - HUD overlay
private struct HUDView: View {
@ObservedObject var ble: BLEManager
@ObservedObject var ni: NIManager
@ObservedObject var estimator: AnchorEstimator
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HUDRow(label: "BLE", value: bleStateText)
HUDRow(label: "NI", value: niStateText)
HUDRow(label: "Range", value: rangeText)
HUDRow(label: "Measurements", value: "\(estimator.measurementCount)")
HUDRow(label: "Residual", value: String(format: "%.3f m", estimator.residualError))
}
.padding(12)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
.frame(maxWidth: .infinity, alignment: .leading)
}
private var bleStateText: String {
switch ble.connectionState {
case .disconnected: return "Disconnected"
case .scanning: return "Scanning…"
case .connecting: return "Connecting…"
case .connected: return "Connected"
}
}
private var niStateText: String {
switch ni.sessionState {
case .idle: return "Idle"
case .waitingForAccessory: return "Waiting for board…"
case .configuring: return "Configuring…"
case .ranging: return "Ranging"
case .error(let msg): return "Error: \(msg)"
}
}
private var rangeText: String {
if let r = ni.lastRange {
return String(format: "%.2f m", r)
}
return ""
}
}
private struct HUDRow: View {
let label: String
let value: String
var body: some View {
HStack {
Text(label)
.font(.caption.bold())
.foregroundStyle(.secondary)
.frame(width: 100, alignment: .leading)
Text(value)
.font(.caption)
.foregroundStyle(.primary)
}
}
}
#Preview {
ContentView()
.environmentObject(BLEManager())
.environmentObject(NIManager())
.environmentObject(ARManager())
.environmentObject(AnchorEstimator())
}