在 SwiftUI 中实时获取移动视图的坐标
Get coordinates in real time of a moving view in SwiftUI
我正在移动一个名为 Point
的视图(基本上是一个点),该点每 2 秒在屏幕中移动一次,我需要实时更新坐标才能准确知道在哪个位置位置正在过渡。基本上,我告诉点从 (X, Y) 移动到 (newX, newY) 并在 0.2 秒内完成,但我正在尝试读取点的位置(我需要实时知道它,所以与此同时点在移动,我需要知道那个移动的位置,大约每秒 30 次是可以的(甚至 15 次),但坐标不会更新!
我尝试使用 GeometryReader 获取坐标并创建一个带有位置的 ObservableObject,然后执行坐标更新我尝试在 onChange()
方法中进行,但我无法使其工作(实际上,它根本不会在任何时候执行)。我还在我的模型中实现了 Equatable 协议,以便能够使用方法 onChange()
有人知道为什么 onChange()
没有被调用吗?实时显示移动点坐标的正确方案是什么?
我的SwiftUI代码(演示)是:
struct Point: View {
var body: some View {
ZStack{
Circle()
.frame(width: 40, height: 40, alignment: .center)
.foregroundColor(.green)
Circle()
.frame(width: 5, height: 5, alignment: .center)
.foregroundColor(.black)
}
}
}
struct Page: View {
@ObservedObject var P: Position = Position()
var body: some View {
VStack {
GeometryReader() { geo in
Point()
.position(x: CGFloat(P.xObjective), y: CGFloat(P.yObjective))
.animation(.linear(duration: 0.2))
.onChange(of: P) { Equatable in
P.xRealtime = geo.frame(in: .global).midX
P.yRealTime = geo.frame(in: .global).midY
print("This should had been executed!")
}
}
Text("X: \(P.xRealtime), Y: \(P.yRealTime)")
}.onAppear() {
P.startMovement()
}
}
}
我的 Swift 代码(型号)是:
class Position: ObservableObject, Equatable {
@Published var xObjective: CGFloat = 0.0
@Published var yObjective: CGFloat = 0.0
@Published var xRealtime: CGFloat = 0.0
@Published var yRealTime: CGFloat = 0.0
private var mainTimer: Timer = Timer()
private var executedTimes: Int = 0
private var coordinatesPoints: [(x: CGFloat, y: CGFloat)] {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
return [(screenWidth / 24 * 12 , screenHeight / 24 * 12),
(screenWidth / 24 * 7 , screenHeight / 24 * 7),
(screenWidth / 24 * 7 , screenHeight / 24 * 17)
]
}
// Conform to Equatable protocol
static func == (lhs: Position, rhs: Position) -> Bool {
if lhs.xRealtime == rhs.xRealtime && lhs.yRealTime == rhs.yRealTime && lhs.xObjective == rhs.xObjective && lhs.yObjective == rhs.yObjective {
return true
}
return false
}
func startMovement() {
mainTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(movePoint), userInfo: nil, repeats: true)
}
@objc func movePoint() {
if (executedTimes == coordinatesPoints.count) {
mainTimer.invalidate()
return
}
self.xObjective = coordinatesPoints[executedTimes].x
self.yObjective = coordinatesPoints[executedTimes].y
executedTimes += 1
}
}
您无法在 SwiftUI 中访问实时动画值。
相反,您可以通过计算每一帧的位置自己制作动画。 CADisplayLink
将帮助您:它是一个 Timer
模拟,但在每个帧渲染时调用,因此您可以更新您的值。
struct Page: View {
@ObservedObject var P: Position = Position()
var body: some View {
VStack {
Point()
.position(x: P.realtimePosition.x, y: P.realtimePosition.y)
Text("X: \(P.realtimePosition.x), Y: \(P.realtimePosition.y)")
}.onAppear() {
P.startMovement()
}
}
}
class Position: ObservableObject {
struct AnimationInfo {
let startDate: Date
let duration: TimeInterval
let startPoint: CGPoint
let endPoint: CGPoint
func point(at date: Date) -> (point: CGPoint, finished: Bool) {
let progress = CGFloat(max(0, min(1, date.timeIntervalSince(startDate) / duration)))
return (
point: CGPoint(
x: startPoint.x + (endPoint.x - startPoint.x) * progress,
y: startPoint.y + (endPoint.y - startPoint.y) * progress
),
finished: progress == 1
)
}
}
@Published var realtimePosition = CGPoint.zero
private var mainTimer: Timer = Timer()
private var executedTimes: Int = 0
private lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(displayLinkAction))
displayLink.add(to: .main, forMode: .default)
return displayLink
}()
private let animationDuration: TimeInterval = 0.1
private var animationInfo: AnimationInfo?
private var coordinatesPoints: [CGPoint] {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
return [CGPoint(x: screenWidth / 24 * 12, y: screenHeight / 24 * 12),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 7),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 17)
]
}
func startMovement() {
mainTimer = Timer.scheduledTimer(timeInterval: 2.5,
target: self,
selector: #selector(movePoint),
userInfo: nil,
repeats: true)
}
@objc func movePoint() {
if (executedTimes == coordinatesPoints.count) {
mainTimer.invalidate()
return
}
animationInfo = AnimationInfo(
startDate: Date(),
duration: animationDuration,
startPoint: realtimePosition,
endPoint: coordinatesPoints[executedTimes]
)
displayLink.isPaused = false
executedTimes += 1
}
@objc func displayLinkAction() {
guard
let (point, finished) = animationInfo?.point(at: Date())
else {
displayLink.isPaused = true
return
}
realtimePosition = point
if finished {
displayLink.isPaused = true
animationInfo = nil
}
}
}
我正在移动一个名为 Point
的视图(基本上是一个点),该点每 2 秒在屏幕中移动一次,我需要实时更新坐标才能准确知道在哪个位置位置正在过渡。基本上,我告诉点从 (X, Y) 移动到 (newX, newY) 并在 0.2 秒内完成,但我正在尝试读取点的位置(我需要实时知道它,所以与此同时点在移动,我需要知道那个移动的位置,大约每秒 30 次是可以的(甚至 15 次),但坐标不会更新!
我尝试使用 GeometryReader 获取坐标并创建一个带有位置的 ObservableObject,然后执行坐标更新我尝试在 onChange()
方法中进行,但我无法使其工作(实际上,它根本不会在任何时候执行)。我还在我的模型中实现了 Equatable 协议,以便能够使用方法 onChange()
有人知道为什么 onChange()
没有被调用吗?实时显示移动点坐标的正确方案是什么?
我的SwiftUI代码(演示)是:
struct Point: View {
var body: some View {
ZStack{
Circle()
.frame(width: 40, height: 40, alignment: .center)
.foregroundColor(.green)
Circle()
.frame(width: 5, height: 5, alignment: .center)
.foregroundColor(.black)
}
}
}
struct Page: View {
@ObservedObject var P: Position = Position()
var body: some View {
VStack {
GeometryReader() { geo in
Point()
.position(x: CGFloat(P.xObjective), y: CGFloat(P.yObjective))
.animation(.linear(duration: 0.2))
.onChange(of: P) { Equatable in
P.xRealtime = geo.frame(in: .global).midX
P.yRealTime = geo.frame(in: .global).midY
print("This should had been executed!")
}
}
Text("X: \(P.xRealtime), Y: \(P.yRealTime)")
}.onAppear() {
P.startMovement()
}
}
}
我的 Swift 代码(型号)是:
class Position: ObservableObject, Equatable {
@Published var xObjective: CGFloat = 0.0
@Published var yObjective: CGFloat = 0.0
@Published var xRealtime: CGFloat = 0.0
@Published var yRealTime: CGFloat = 0.0
private var mainTimer: Timer = Timer()
private var executedTimes: Int = 0
private var coordinatesPoints: [(x: CGFloat, y: CGFloat)] {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
return [(screenWidth / 24 * 12 , screenHeight / 24 * 12),
(screenWidth / 24 * 7 , screenHeight / 24 * 7),
(screenWidth / 24 * 7 , screenHeight / 24 * 17)
]
}
// Conform to Equatable protocol
static func == (lhs: Position, rhs: Position) -> Bool {
if lhs.xRealtime == rhs.xRealtime && lhs.yRealTime == rhs.yRealTime && lhs.xObjective == rhs.xObjective && lhs.yObjective == rhs.yObjective {
return true
}
return false
}
func startMovement() {
mainTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(movePoint), userInfo: nil, repeats: true)
}
@objc func movePoint() {
if (executedTimes == coordinatesPoints.count) {
mainTimer.invalidate()
return
}
self.xObjective = coordinatesPoints[executedTimes].x
self.yObjective = coordinatesPoints[executedTimes].y
executedTimes += 1
}
}
您无法在 SwiftUI 中访问实时动画值。
相反,您可以通过计算每一帧的位置自己制作动画。 CADisplayLink
将帮助您:它是一个 Timer
模拟,但在每个帧渲染时调用,因此您可以更新您的值。
struct Page: View {
@ObservedObject var P: Position = Position()
var body: some View {
VStack {
Point()
.position(x: P.realtimePosition.x, y: P.realtimePosition.y)
Text("X: \(P.realtimePosition.x), Y: \(P.realtimePosition.y)")
}.onAppear() {
P.startMovement()
}
}
}
class Position: ObservableObject {
struct AnimationInfo {
let startDate: Date
let duration: TimeInterval
let startPoint: CGPoint
let endPoint: CGPoint
func point(at date: Date) -> (point: CGPoint, finished: Bool) {
let progress = CGFloat(max(0, min(1, date.timeIntervalSince(startDate) / duration)))
return (
point: CGPoint(
x: startPoint.x + (endPoint.x - startPoint.x) * progress,
y: startPoint.y + (endPoint.y - startPoint.y) * progress
),
finished: progress == 1
)
}
}
@Published var realtimePosition = CGPoint.zero
private var mainTimer: Timer = Timer()
private var executedTimes: Int = 0
private lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(displayLinkAction))
displayLink.add(to: .main, forMode: .default)
return displayLink
}()
private let animationDuration: TimeInterval = 0.1
private var animationInfo: AnimationInfo?
private var coordinatesPoints: [CGPoint] {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
return [CGPoint(x: screenWidth / 24 * 12, y: screenHeight / 24 * 12),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 7),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 17)
]
}
func startMovement() {
mainTimer = Timer.scheduledTimer(timeInterval: 2.5,
target: self,
selector: #selector(movePoint),
userInfo: nil,
repeats: true)
}
@objc func movePoint() {
if (executedTimes == coordinatesPoints.count) {
mainTimer.invalidate()
return
}
animationInfo = AnimationInfo(
startDate: Date(),
duration: animationDuration,
startPoint: realtimePosition,
endPoint: coordinatesPoints[executedTimes]
)
displayLink.isPaused = false
executedTimes += 1
}
@objc func displayLinkAction() {
guard
let (point, finished) = animationInfo?.point(at: Date())
else {
displayLink.isPaused = true
return
}
realtimePosition = point
if finished {
displayLink.isPaused = true
animationInfo = nil
}
}
}