SwiftUI:带动画栏的步进器
SwiftUI: stepper with animated bar
我想创建一个带动画栏的步进器组件。这是我得到的结果:
我的想法是该条应始终居中,而且我想在值更改时为蓝色条设置动画,但我无法使其正常工作。
这是我的代码:
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
}
}
}
以及我如何在容器视图中调用它:
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")
})
}
}
}
你能帮我理解为什么动画不起作用吗?
另外,有没有更好的布局方式 UI?
谢谢!
最简单的解决方案是像这样更改 withAnimation
块中的 currentIndex
:
Button(action: {
withAnimation {
currentIndex += 1
}
}, label: {
Text("INCREMENT")
})
这里是固定的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
}
}
此处修复了步进器与动画居中对齐的问题。还添加了对 INCREMENT - DECREMENT 值的验证。您现在可以在没有更新功能的情况下以您的方式使用它。目前,在您的代码中,这一行出现一个警告,这也已解决。
.frame(width: .infinity, height: 18)
最终代码:
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")
})
}
}
}
我想创建一个带动画栏的步进器组件。这是我得到的结果:
我的想法是该条应始终居中,而且我想在值更改时为蓝色条设置动画,但我无法使其正常工作。
这是我的代码:
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
}
}
}
以及我如何在容器视图中调用它:
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")
})
}
}
}
你能帮我理解为什么动画不起作用吗? 另外,有没有更好的布局方式 UI?
谢谢!
最简单的解决方案是像这样更改 withAnimation
块中的 currentIndex
:
Button(action: {
withAnimation {
currentIndex += 1
}
}, label: {
Text("INCREMENT")
})
这里是固定的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
}
}
此处修复了步进器与动画居中对齐的问题。还添加了对 INCREMENT - DECREMENT 值的验证。您现在可以在没有更新功能的情况下以您的方式使用它。目前,在您的代码中,这一行出现一个警告,这也已解决。
.frame(width: .infinity, height: 18)
最终代码:
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")
})
}
}
}