简体   繁体   English

SwiftUI:带动画条的步进器

[英]SwiftUI: stepper with animated bar

I would like to create a stepper component with an animated bar.我想创建一个带有动画条的步进器组件。 Here is the result I get:这是我得到的结果:

在此处输入图像描述

The idea is that the bar should always be centered, and also I would like to animate the blue bar when the value changes, but I can't get it working.这个想法是该条应始终居中,并且我想在值更改时为蓝色条设置动画,但我无法使其正常工作。

Here is my code:这是我的代码:

struct Stepper: View {
    @Binding var currentIndex: Int
    var total: Int
    
    var body: some View {
        ZStack(alignment: .center) {
            ZStack(alignment: .leading) {
                Color.gray.opacity(0.4)
                Color.blue
                    .frame(width: 175.5 / CGFloat(total) * CGFloat(currentIndex))
            }
            .frame(width: 175.5, height: 2)
            Text("\(currentIndex)")
                .foregroundColor(.black)
                .offset(x: -113)
            Text("\(total)")
                .foregroundColor(.black)
                .offset(x: 113)
        }
        .frame(width: .infinity, height: 18)
    }
    
    init(withTotal total: Int,
         andCurrentIndex currentIndex: Binding<Int>) {
        self._currentIndex = currentIndex
        self.total = total
    }
    
    func update(to value: Int) {
        guard value >= 0, value <= total else {
            return
        }
        withAnimation {
            currentIndex = value
        }
    }
}

And how I call this in a container view:以及我如何在容器视图中调用它:

struct StepperVC: View {
    @State private var currentIndex: Int = 1
    
    var body: some View {
        VStack(spacing: 32) {
            Stepper(withTotal: 8, andCurrentIndex: $currentIndex)
            Button(action: {
                currentIndex += 1
            }, label: {
                Text("INCREMENT")
            })
            Button(action: {
                currentIndex -= 1
            }, label: {
                Text("DECREMENT")
            })
        }
    }
}

Could you help me understanding why the animation doesn't work?你能帮我理解为什么 animation 不起作用吗? Also, is there a better way to layout the UI?另外,有没有更好的方式来布局 UI?

Thank you!谢谢!

The simplest solution is to change currentIndex in a withAnimation block like this:最简单的解决方案是在withAnimation块中更改currentIndex ,如下所示:

Button(action: {
    withAnimation {
        currentIndex += 1
    }
}, label: {
    Text("INCREMENT")
})

Here is fixed Stepper (tested with Xcode 12.1 / iOS 14.1)这是固定Stepper器(使用 Xcode 12.1 / iOS 14.1 测试)

演示

struct Stepper: View {
    @Binding var currentIndex: Int
    var total: Int
    
    var body: some View {
        ZStack(alignment: .center) {
            ZStack(alignment: .leading) {
                Color.gray.opacity(0.4)
                Color.blue
                    .frame(width: 175.5 / CGFloat(total) * CGFloat(currentIndex))
            }
            .frame(width: 175.5, height: 2)
            .animation(.default, value: currentIndex)     // << here !!
            Text("\(currentIndex)")
                .foregroundColor(.black)
                .offset(x: -113)
            Text("\(total)")
                .foregroundColor(.black)
                .offset(x: 113)
        }
        .frame(width: .infinity, height: 18)
    }
    
    init(withTotal total: Int,
         andCurrentIndex currentIndex: Binding<Int>) {
        self._currentIndex = currentIndex
        self.total = total
    }
}

Here is fixed Stepper centered alignment with animation.这是固定的步进器中心 alignment 和 animation。 Also added validation for INCREMENT - DECREMENT value.还添加了对 INCREMENT - DECREMENT 值的验证。 You can now use it in your way without the update function.您现在可以在不更新 function 的情况下以您的方式使用它。 Currently, in your code, one warning appears for this line this is also solved.目前,在您的代码中,此行出现一个警告,这也已解决。

 .frame(width: .infinity, height: 18)

Final code:最终代码:

struct Stepper: View {
    @Binding var currentIndex: Int
    
    private var total: Int
    
    private var mainIndex: Int {
        if currentIndex >= 0 && currentIndex <= total {
            return currentIndex
            
        } else if currentIndex < 0 {
            DispatchQueue.main.async {
                self.currentIndex = 0
            }
            return 0
            
        } else {
            DispatchQueue.main.async {
                self.currentIndex = total
            }
            return total
        }
    }
    
    var body: some View {
        GeometryReader { geometry in
            HStack() {
                Text("\(mainIndex)")
                    .foregroundColor(.black)
                    .frame(width: 30)
                
                ZStack(alignment: .leading) {
                    Color.gray.opacity(0.4).frame(width: geometry.size.width - 60)
                    Color.blue.frame(width: (geometry.size.width - 60) / CGFloat(total) * CGFloat(mainIndex))
                }
                .animation(.default, value: mainIndex)
                .frame(width: geometry.size.width - 60, height: 2)
                
                Text("\(total)")
                    .foregroundColor(.black)
                    .frame(width: 30)
            }
            .frame(width: geometry.size.width, height: geometry.size.height)
            .background(Color.yellow)
            
        }
    }
    
    init(withTotal total: Int,
         andCurrentIndex currentIndex: Binding<Int>) {
        self._currentIndex = currentIndex
        self.total = total
    }
    
    func update(to value: Int) {
        guard value >= 0, value <= total else {
            return
        }
        withAnimation {
            currentIndex = value
        }
    }
}

struct StepperVC: View {
    @State private var currentIndex: Int = 1
    
    var body: some View {
        VStack( alignment: .center,spacing: 32) {
            Stepper(withTotal: 8, andCurrentIndex: $currentIndex)
                .frame(width: UIScreen.main.bounds.size.width - 50, height: 50, alignment: .center)
            Button(action: {
                currentIndex += 1
            }, label: {
                Text("INCREMENT")
            })
            Button(action: {
                currentIndex -= 1
            }, label: {
                Text("DECREMENT")
            })
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM