视图更改后 SwiftUI 动画不连续
SwiftUI animation not continuous after view change
测试代码如下:
import SwiftUI
struct ContentView: View {
@State private var pad: Bool = false
@State private var showDot: Bool = true
var body: some View {
VStack {
Button {showDot.toggle()} label: {Text("Toggle Show Dot")}
Spacer().frame(height: pad ? 100 : 10)
Circ(showDot: showDot)
Spacer()
}.onAppear {
withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
}
}
}
struct Circ: View {
let showDot: Bool
var body: some View {
Circle().stroke().frame(height: 50).overlay {if showDot {Circle().frame(height: 20)}}
}
}
碰巧我切换showDot
后,点圈又不在笔划圈的中心了!我怎样才能解决这个问题?
Circ
视图已给出,我无法更改该视图!
编辑
如果只能隐藏视图,请参考方案一,推荐。如果您需要 re-build 视图,请参见解决方案 2。
解决方案 1
将 if
条件替换为当 showDot
为 true
时读取 1 的 .opacity()
修饰符。
这样,点就不会完全消失,它就在那里,只是你看不见而已。您将切换可见性,而不是视图本身。
像这样:
@State private var pad: Bool = false
@State private var showDot: Bool = true
var body: some View {
VStack {
Button {
showDot.toggle()
} label: {
Text("Toggle Show Dot")
}
Spacer()
.frame(height: pad ? 100 : 10)
Circle().stroke()
.frame(height: 50)
.overlay {
Circle()
.frame(height: 20)
.opacity(showDot ? 1 : 0) // <- Here
}
Spacer()
}
.onAppear {
withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
}
}
解决方案 2
您可以用计时器替换动画。每次触发时,它都会通过改变 Spacer()
.
的高度来移动整个视图
// These variables will track the position and moving direction of the dot
@State private var pos: CGFloat = 0
@State private var movingUp = false
@State private var showDot: Bool = true
// This variable will change the position
// This is a dummy iniatialization, the .onAppear modifier sets the real timer
@State private var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in }
var body: some View {
VStack {
Button {
showDot.toggle()
} label: {
Text("Toggle Show Dot")
}
Spacer()
.frame(height: pos)
Circle().stroke()
.frame(height: 50)
.overlay {
if showDot {
Circle().frame(height: 20)
}
}
Spacer()
}
.onAppear {
// The timer interval will define the speed
timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { _ in
moveCircle()
}
}
}
private func moveCircle() {
if movingUp {
if pos <= 0 {
pos = 0
movingUp = false
} else {
pos -= 1
}
} else {
if pos >= 100 {
pos = 100
movingUp = true
} else {
pos += 1
}
}
}
测试代码如下:
import SwiftUI
struct ContentView: View {
@State private var pad: Bool = false
@State private var showDot: Bool = true
var body: some View {
VStack {
Button {showDot.toggle()} label: {Text("Toggle Show Dot")}
Spacer().frame(height: pad ? 100 : 10)
Circ(showDot: showDot)
Spacer()
}.onAppear {
withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
}
}
}
struct Circ: View {
let showDot: Bool
var body: some View {
Circle().stroke().frame(height: 50).overlay {if showDot {Circle().frame(height: 20)}}
}
}
碰巧我切换showDot
后,点圈又不在笔划圈的中心了!我怎样才能解决这个问题?
Circ
视图已给出,我无法更改该视图!
编辑
如果只能隐藏视图,请参考方案一,推荐。如果您需要 re-build 视图,请参见解决方案 2。
解决方案 1
将 if
条件替换为当 showDot
为 true
时读取 1 的 .opacity()
修饰符。
这样,点就不会完全消失,它就在那里,只是你看不见而已。您将切换可见性,而不是视图本身。
像这样:
@State private var pad: Bool = false
@State private var showDot: Bool = true
var body: some View {
VStack {
Button {
showDot.toggle()
} label: {
Text("Toggle Show Dot")
}
Spacer()
.frame(height: pad ? 100 : 10)
Circle().stroke()
.frame(height: 50)
.overlay {
Circle()
.frame(height: 20)
.opacity(showDot ? 1 : 0) // <- Here
}
Spacer()
}
.onAppear {
withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
}
}
解决方案 2
您可以用计时器替换动画。每次触发时,它都会通过改变 Spacer()
.
// These variables will track the position and moving direction of the dot
@State private var pos: CGFloat = 0
@State private var movingUp = false
@State private var showDot: Bool = true
// This variable will change the position
// This is a dummy iniatialization, the .onAppear modifier sets the real timer
@State private var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in }
var body: some View {
VStack {
Button {
showDot.toggle()
} label: {
Text("Toggle Show Dot")
}
Spacer()
.frame(height: pos)
Circle().stroke()
.frame(height: 50)
.overlay {
if showDot {
Circle().frame(height: 20)
}
}
Spacer()
}
.onAppear {
// The timer interval will define the speed
timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { _ in
moveCircle()
}
}
}
private func moveCircle() {
if movingUp {
if pos <= 0 {
pos = 0
movingUp = false
} else {
pos -= 1
}
} else {
if pos >= 100 {
pos = 100
movingUp = true
} else {
pos += 1
}
}
}