我在堆栈中有一些带有动画偏移的按钮。由于某种原因,动画偏移按钮无法单击。当偏移量约为 250 左右时,按钮似乎可以单击一秒钟,然后在偏移量低于该值时再次变得不可单击...非常感谢任何帮助!
struct ContentView: View {
@State var offset: CGFloat = -300
var body: some View {
HStack {
Button(action: {
print("clickable")
}, label: {
Text("Click me")
})
Button(action: {
print("clickable2")
}, label: {
Text("Click me2")
})
Button(action: {
print("clickable3")
}, label: {
Text("Click me3")
})
}.offset(x: offset)
.onAppear(perform: {
withAnimation(.linear(duration: 10).repeatForever()) {
offset = 300
}
})
}
}
首先,这是一种“预期”行为。因为当您使用 offset
时,
SwiftUI
会移动显示的内容。简而言之,这意味着
SwiftUI
改变了
View
本身
因为onTapGesture
仅识别视图上的触摸,这也解释了为什么您可以单击到偏移视图
offsetting
您的
View
,然后应用动画。当您使用 withAnimation
时,SwiftUI 会使用提供的动画重新计算视图的主体,但请记住,它不会更改事先应用于
View
的任何内容。Click Me
变得可点击。发生这种情况是因为红色矩形表示
Click Me
按钮的最终偏移量。 (所以它只是一个占位符)因此 View
本身和
offset
必须匹配,因为当您首先偏移视图时,SwiftUI
需要您的视图there来触发点击手势。 可能的解决方案
。 因此,如果这没有帮助,一种可能的解决方案可能是使用动画更改周期内的偏移量(例如,我每个周期使用 0.1 秒),因为这会导致 SwiftUI 在每次更改偏移量时重新定位视图,所以我们奇怪的错误不应该发生。
代码:
struct ContentView: View {
@State private var increment : CGFloat = 1
@State private var offset : CGFloat = 0
var body: some View {
ZStack {
Button("Click Me") {
print("Click")
}
.fontWeight(.black)
}
.tappableOffsetAnimation(offset: $offset, animation: .linear, duration: 5, finalOffsetAmount: 300)
}
}
struct TappableAnimationModifier : ViewModifier {
@Binding var offset : CGFloat
var duration : Double
var finalOffsetAmount : Double
var animation : Animation
var timerPublishInSeconds : TimeInterval = 0.1
let timer : Publishers.Autoconnect<Timer.TimerPublisher>
var autoreverses : Bool = false
@State private var decreasing = false
public init(offset: Binding<CGFloat>, duration: Double, finalOffsetAmount: Double, animation: Animation, autoreverses: Bool) {
self._offset = offset
self.duration = duration
self.finalOffsetAmount = finalOffsetAmount
self.animation = animation
self.autoreverses = autoreverses
self.timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
}
public init(offset: Binding<CGFloat>, duration: Double, finalOffsetAmount: Double, animation: Animation, timerPublishInSeconds: TimeInterval) {
self._offset = offset
self.duration = duration
self.finalOffsetAmount = finalOffsetAmount
self.animation = animation
self.timerPublishInSeconds = timerPublishInSeconds
self.timer = Timer.publish(every: timerPublishInSeconds, on: .main, in: .common).autoconnect()
}
public init(offset: Binding<CGFloat>, duration: Double, finalOffsetAmount: Double, animation: Animation) {
self._offset = offset
self.duration = duration
self.finalOffsetAmount = finalOffsetAmount
self.animation = animation
self.timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
}
func body(content: Content) -> some View {
content
.animation(animation, value: offset)
.offset(x: offset)
.onReceive(timer) { input in
/*
* a simple math here, we're dividing duration by 0.1 because our timer gets triggered
* in every 0.1 seconds, so dividing this result will always produce the
* proper value to finish offset animation in `x` seconds
* example: 300 / (5 / 0.1) = 300 / 50 = 6 increment per 0.1 second
*/
if (offset >= finalOffsetAmount) {
// you could implement autoReverses by not canceling the timer here
// and substracting finalOffsetAmount / (duration / 0.1) until it reaches zero
// then you can again start incrementing it.
if autoreverses {
self.decreasing = true
}
else {
timer.upstream.connect().cancel()
return
}
}
if offset <= 0 {
self.decreasing = false
}
if decreasing {
offset -= finalOffsetAmount / (duration / timerPublishInSeconds)
}
else {
offset += finalOffsetAmount / (duration / timerPublishInSeconds)
}
}
}
}
extension View {
func tappableOffsetAnimation(offset: Binding<CGFloat>, animation: Animation, duration: Double, finalOffsetAmount: Double) -> some View {
modifier(TappableAnimationModifier(offset: offset, duration: duration, finalOffsetAmount: finalOffsetAmount, animation: animation))
}
}
编辑:我添加了可自定义的时间戳以及自动反转。
它的样子是这样的:
但是,这样的解决方案却不太好,只见树木,不见森林。
您只需要一件事,而不是使用复杂且低效的计时器解决方案:
使用
.position
代替
.offset
。例如,.position(x: xOffset, y: proxy.frame(in: .local).midY)
将视图向左或向右移动(包含在我们最喜欢的旧的
GeometryReader
中)。