如何在 swiftUI 中以高性能显示来自 Observable Object 的数据?

How to display data from Observable Object with high performances in swiftUI?

在我的指南针应用程序中,我显示航向但性能不高:当我快速转动设备时,所有角度都没有显示。我搜索了与 Apple 的 natif 指南针应用程序相同的性能。 例如,当角度从 359 变为 0 度时,我会发出振动以通知用户。但有时振动不出现。

我的位置提供者class:

import SwiftUI
import CoreLocation
import Combine

public class LocationProvider: NSObject, CLLocationManagerDelegate, ObservableObject {

  private let locationManager: CLLocationManager
  public let heading = PassthroughSubject<CGFloat, Never>()

  @Published var currentHeading: CGFloat {
    willSet {
      heading.send(newValue)
    }
  }

  public override init() {
    currentHeading = 0
    locationManager = CLLocationManager()
    super.init()
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.startUpdatingHeading()
    locationManager.requestWhenInUseAuthorization()
  }

  public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
    self.currentHeading = CGFloat(newHeading.trueHeading)
  }
}

我的内容视图:

import SwiftUI
import CoreLocation

struct ContentView: View {

  @ObservedObject var location: LocationProvider = LocationProvider()

  @State var angle: CGFloat = 0

  var body: some View {
    VStack {
      Text(String(Double(-self.location.currentHeading + 360).stringWithoutZeroFraction) + "°")
        .font(.system(size: 80))
    }
    .onReceive(self.location.heading) { heading in
      compassTapticFeedback(-heading + 360)
    }
  }
}

public extension Double {
  var stringWithoutZeroFraction: String {
    return String(format: "%.0f", self)
  }
}

// Taptic feednack
func tapticFeedback(_ type: String) {
  switch type {
    case "heavy":
      let tapticFeedback = UIImpactFeedbackGenerator(style: .heavy)
      tapticFeedback.prepare()
      tapticFeedback.impactOccurred()
    case "medium":
      let tapticFeedback = UIImpactFeedbackGenerator(style: .medium)
      tapticFeedback.prepare()
      tapticFeedback.impactOccurred()
    case "light":
      let tapticFeedback = UIImpactFeedbackGenerator(style: .light)
      tapticFeedback.prepare()
      tapticFeedback.impactOccurred()
    default:
      let tapticFeedback = UIImpactFeedbackGenerator(style: .medium)
      tapticFeedback.prepare()
      tapticFeedback.impactOccurred()
  }
}

func compassTapticFeedback(_ angle: CGFloat) {
  switch Int(angle) {
    case 0:
      tapticFeedback("heavy")
    case 30:
      tapticFeedback("heavy")
    case 60:
      tapticFeedback("heavy")
    case 90:
      tapticFeedback("heavy")
    case 120:
      tapticFeedback("heavy")
    case 250:
      tapticFeedback("heavy")
    case 180:
      tapticFeedback("heavy")
    case 210:
      tapticFeedback("heavy")
    case 240:
      tapticFeedback("heavy")
    case 270:
      tapticFeedback("heavy")
    case 300:
      tapticFeedback("heavy")
    case 330:
      tapticFeedback("heavy")
    default:
      return
  }
}

可能是因为我对角度做了很多计算得到一个没有零分数的值?我不知道。即使我不确定这是原因。

问题是您正在检查 当前 角度是否是 30 的倍数。问题在于,如果您旋转设备的速度太快,该数字将被跳过,因此不会被检测到。

按照下面进一步提示的第二点所示完成 compassTapticFeedback(_:) 的缩短版本后,我添加了更多代码以使该方法检测到跳过的中间角度:

enum TapticSync {
    static var lastAngle: CGFloat = 0
}


func compassTapticFeedback(_ angle: CGFloat) {
    // If the angle is 30°, carry on. Otherwise do more checks
    if !Int(angle).isMultiple(of: 30) {
        // If there was an angle at a multiple of 30° skipped, carry on. Otherwise do not trigger haptic feedback.
        let minVal = min(angle, TapticSync.lastAngle)
        let maxVal = max(angle, TapticSync.lastAngle)
        let roundUpToNextMultiple = ceil(minVal / 30) * 30
        guard maxVal > roundUpToNextMultiple else { return }
    }

    // If the checks were passed, trigger the haptic feedback and update the last angle
    tapticFeedback("heavy")
    TapticSync.lastAngle = angle
}

更多提示

1:触觉反馈.prepare()

你的情况 tapticFeedback.prepare() 没有意义。正如 .prepare() 的文档中所述:

Calling prepare() and then immediately triggering feedback (without any time in between) does not improve latency.

你应该改为:

Think about when you can best prepare your generators. Call prepare() before the event that triggers feedback. The system needs time to prepare the Taptic Engine for minimal latency.

有关完整信息,请参阅 documentation

2:检查角度

在您的情况下,您不需要 switch 来检查每个 30 的倍数的角度。相反,将 compassTapticFeedback(_:) 更改为如下所示:

func compassTapticFeedback(_ angle: CGFloat) {
    if Int(angle).isMultiple(of: 30) {
        tapticFeedback("heavy")
    }
}

3:你的罗盘方向反了

由于这些部分(删除了不必要的 self.),以度为单位的方向被颠倒了:

-location.currentHeading + 360

compassTapticFeedback(-heading + 360)

相反,度数顺时针增加,使用:

location.currentHeading

compassTapticFeedback(heading)