Files
RuView/integrations/iphone-lidar/native/RuViewLiDAR/ContentView.swift
rUv 0df48df7b2 feat: add native iPhone LiDAR sensor and web viewer (#1684)
* feat(ios): add RuView LiDAR frame protocol

* feat(ios): capture ARKit scene depth for RuView

* feat(ios): stream compact LiDAR frames over websocket

* feat(ios): add native LiDAR capture UI

* feat(ios): add RuView LiDAR app entrypoint

* feat(web): add LiDAR bridge web package

* feat(web): decode RuView LiDAR wire frames

* feat(web): add local LiDAR websocket relay

* feat(web): add LiDAR browser viewer

* feat(web): render live LiDAR point cloud

* fix(ios): use wall clock time for LiDAR provenance

* feat(web): style LiDAR viewer

* test(web): add LiDAR codec tests

* docs: add iPhone LiDAR integration guide

* docs(adr): define iPhone LiDAR sensor bridge

* fix(ios): harden and validate LiDAR bridge

* fix(ios): qualify LiDAR wire depth type
2026-08-22 18:06:16 -04:00

122 lines
4.3 KiB
Swift

import SwiftUI
struct ContentView: View {
@StateObject private var capture = LiDARCaptureManager()
@State private var endpoint = "ws://HOST:8787/ws/lidar?token=TOKEN"
@State private var streaming = false
@State private var status = "Idle"
private let streamer = WebSocketStreamer()
var body: some View {
NavigationStack {
Form {
Section("Sensor") {
HStack {
Text("State")
Spacer()
Text(stateText)
.foregroundStyle(stateColor)
}
HStack {
Text("Capture FPS")
Spacer()
Text(capture.framesPerSecond.formatted(.number.precision(.fractionLength(1))))
}
if let frame = capture.lastFrame {
HStack {
Text("Depth")
Spacer()
Text("\(frame.depth.width) x \(frame.depth.height)")
}
HStack {
Text("Sequence")
Spacer()
Text("\(frame.provenance.sequence)")
}
}
Button("Start LiDAR") {
capture.start(smoothed: false)
}
.disabled(capture.state == .running)
Button("Stop") {
capture.stop()
}
.disabled(capture.state != .running)
}
Section("RuView Stream") {
TextField("ws://host:port/ws/lidar", text: $endpoint)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
Toggle("Stream geometry", isOn: $streaming)
.onChange(of: streaming) { _, enabled in
Task {
if enabled {
do {
try await streamer.connect(to: endpoint)
status = "Connected"
} catch {
streaming = false
status = error.localizedDescription
}
} else {
await streamer.disconnect()
status = "Disconnected"
}
}
}
Text(status)
.font(.caption)
.foregroundStyle(.secondary)
}
Section("Privacy") {
Text("This implementation transmits depth geometry, confidence, camera intrinsics, and device pose. RGB camera frames are not transmitted.")
.font(.footnote)
}
}
.navigationTitle("RuView LiDAR")
.onAppear {
capture.onFrame = { frame in
guard streaming else { return }
Task {
do {
try await streamer.send(frame, maxFPS: 15, sampleStep: 2)
} catch {
await MainActor.run {
status = error.localizedDescription
}
}
}
}
}
.onDisappear {
capture.stop()
Task { await streamer.disconnect() }
}
}
}
private var stateText: String {
switch capture.state {
case .idle: return "Idle"
case .unsupported: return "No LiDAR"
case .running: return "Live"
case .failed(let message): return "Error: \(message)"
}
}
private var stateColor: Color {
switch capture.state {
case .running: return .green
case .failed, .unsupported: return .red
case .idle: return .secondary
}
}
}