一段时间后,SwiftUI 设备运动会卡顿

SwiftUI device motion stutters after some time

我正在尝试使用 SwiftUI iOS 应用程序中的 CoreMotion 框架来移动屏幕上的对象(类似于 Measure 应用程序中的级别功能的想法)。

我有一个 MotionManager class 来处理设备运动更新:

class MotionManager: ObservableObject {

    private var motionManager: CMMotionManager

    private var pitch: Double = 0
    private var roll: Double = 0
    private var yaw: Double = 0

    @Published
    var x: Double = 0
    @Published
    var y: Double = 0

    init() {
        self.motionManager = CMMotionManager()

        self.motionManager.deviceMotionUpdateInterval = 1 / 60
        self.motionManager.startDeviceMotionUpdates(to: .main) { (deviceMotionData, error) in
            guard error == nil else {
                print(error!)
                return
            }
            
            if let attitudeData = deviceMotionData?.attitude {
                self.pitch = attitudeData.pitch
                self.roll = attitudeData.roll
                self.yaw = attitudeData.yaw
            }

            self.updatePositions()
        }
    }

    private func updatePositions() {
        let rollDegrees = self.roll * 180 / .pi
        let pitchDegrees = self.pitch * 180 / .pi

        self.x = convertAngleToRange(angle: rollDegrees, direction: .x)
        self.y = convertAngleToRange(angle: pitchDegrees, direction: .y)
    }
}

convertAngleToRange 方法在别处定义,但它本质上是将 pitch/roll 值从弧度映射到像素位置。

我有我的主要观点:

struct ContentView: View {

    @StateObject
    var motion: MotionManager = MotionManager()

    var body: some View {
        NavigationView {
            NavigationLink(destination: GuideView()) {
                EmptyView()
            }
        }
        .environmentObject(motion)
    }
}

然后,GuideView 视图:

struct GuideView: View {

    @EnvironmentObject
    var motion: MotionManager

    var body: some View {
        GeometryReader { geometry in
            Representation(x: $motion.x, y: $motion.y)
                .frame(width: geometry.size.width, height: geometry.size.height)
        }
        .statusBar(hidden: true)
        .navigationBarHidden(true)
    }
}

最后,包含移动圆的 Representation 视图:

struct Representation: View {

    @Binding
    var x: Double

    @Binding
    var y: Double

    var body: some View {
        GeometryReader { geometry in
            Circle()
                .fill(Color.gray)
                .frame(width: geometry.size.width * 0.5)
                .position(
                    x: geometry.size.width * 0.5 * (CGFloat(x) + 1),
                    y: geometry.size.height * 0.5 + geometry.size.width * 0.5 * CGFloat(y)
                )
        }
    }
}

这最初工作正常,但一段时间后圆圈的运动变得迟钝并且似乎结结巴巴。如果我四处移动设备,然后将其平放在 table 上,它需要几秒钟才能恢复并静止。

我的想法是 motion.xmotion.y 值的发布与刷新显示相结合导致事件累积,这就是它变得非常滞后的原因。我试过减少 MotionManager class 中的 motionManager.deviceMotionUpdateInterval 但即使在较低的值下它最终也会开始滞后。我想知道每次设备运动更新时是否做了太多事情,但我似乎无法弄清楚如何减少它,因为我仍然需要处理原始俯仰和滚动值以便将它们转换为坐标。

我可以使用一些技巧来避免这个问题,或者至少帮助诊断问题所在。

根据“the docs”,因为处理的事件可能会以很高的速率到达,不推荐使用主操作队列