动画偏移正在破坏按钮 SwiftUI

问题描述 投票:0回答:2

我在堆栈中有一些带有动画偏移的按钮。由于某种原因,动画偏移按钮无法单击。当偏移量约为 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
            }
        })
    }
}   
swift animation swiftui
2个回答
2
投票

抵消如何运作?

首先,这是一种“预期”行为。因为当您使用 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)) } }

编辑:我添加了可自定义的时间戳以及自动反转。

它的样子是这样的:

您的视图正在运行,快去捕捉它 x)


0
投票

但是,这样的解决方案却不太好,只见树木,不见森林。

您只需要一件事,而不是使用复杂且低效的计时器解决方案:

使用

.position

代替

.offset
例如,

.position(x: xOffset, y: proxy.frame(in: .local).midY)

将视图向左或向右移动(包含在我们最喜欢的旧的

GeometryReader
中)。
    

© www.soinside.com 2019 - 2024. All rights reserved.