【发布时间】:2022-07-30 02:27:45
【问题描述】:
我预计会发生什么
我有一个冥想视图,它有一个带有绑定属性 inhaling 的动画子视图,当按下按钮时应该会出现。
当动画子视图出现时,它应该从头开始动画。它基本上是苹果冥想呼吸动画:它从一个小球开始,当吸气为真时变大,然后当吸气为假时变小。
当用户再次按下按钮时,动画应该会消失。
当用户再次按下按钮,第二次,它应该启动动画子视图与绑定清洁。这意味着子视图是一个小球,然后又变大了。和第一次一样。
struct Meditation: View {
@State var startBreathingAnimation = false
@State var inhaling = false
@State var infoText = "Start a mediation"
var body: some View {
VStack(spacing: 20) {
ZStack {
if startBreathingAnimation {
BreathAnimation(inhaling: $inhaling)
.onChange(of: inhaling) { newValue in
if newValue {
infoText = "Breath in..."
} else {
infoText = "Breath out..."
} }
.onDisappear {
infoText = "Start your meditation" // Never executed?
}
} else {
Circle()
.frame(height: 100)
.foregroundColor(.blue)
}
}
Text(infoText)
Button("Toggle") {
startBreathingAnimation.toggle()
}
}
.padding()
}
}
实际发生的情况
具有绑定的动画子视图不会重置,而是重新初始化,而是在按下按钮“关闭”后从中断处开始。
当我没有在子视图中添加绑定属性时,它实际上按预期工作:它每次都会重置并给我一个“新鲜”的子视图。但我确实需要观察动画子视图属性inhaling 的变化,以便更新主视图中的infoText 属性。
可重现的示例代码,准备复制到 Xcode 中
非常感谢任何帮助!
// Can be copied to Xcode directly
struct Meditation: View {
@State var startBreathingAnimation = false
@State var inhaling = false
@State var infoText = "Start a mediation"
var body: some View {
VStack(spacing: 20) {
ZStack {
if startBreathingAnimation {
BreathAnimation(inhaling: $inhaling)
.onChange(of: inhaling) { newValue in
if newValue {
infoText = "Breath in..."
} else {
infoText = "Breath out..."
} }
.onDisappear {
infoText = "Start your meditation" // Never executed?
}
} else {
Circle()
.frame(height: 100)
.foregroundColor(.blue)
}
}
Text(infoText)
Button("Toggle") {
startBreathingAnimation.toggle()
}
}
.padding()
}
}
private let gradientStart = Color.accentColor.opacity(0.9)
private let gradientEnd = Color.accentColor.opacity(1.0)
private let gradient = LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]), startPoint: .top, endPoint: .bottom)
private let maskGradient = LinearGradient(gradient: Gradient(colors: [.black]), startPoint: .top, endPoint: .bottom)
private let maxSize: CGFloat = 150
private let minSize: CGFloat = 30
private let inhaleTime: Double = 8
private let exhaleTime: Double = 8
private let pauseTime: Double = 1.5
private let numberOfPetals = 4
private let bigAngle = 360 / numberOfPetals
private let smallAngle = bigAngle / 2
private let ghostMaxSize: CGFloat = maxSize * 0.99
private let ghostMinSize: CGFloat = maxSize * 0.95
private struct Petals: View {
let size: CGFloat
let inhaling: Bool
var isMask = false
var body: some View {
let petalsGradient = isMask ? maskGradient : gradient
ZStack {
ForEach(0..<numberOfPetals) { index in
petalsGradient
.frame(maxWidth: .infinity, maxHeight: .infinity)
.mask(
Circle()
.frame(width: size, height: size)
.offset(x: inhaling ? size * 0.5 : 0)
.rotationEffect(.degrees(Double(bigAngle * index)))
)
.blendMode(isMask ? .normal : .screen)
}
}
}
}
struct BreathAnimation: View {
@State private var size = minSize
@Binding var inhaling: Bool
@State private var ghostSize = ghostMaxSize
@State private var ghostBlur: CGFloat = 0
@State private var ghostOpacity: Double = 0
var body: some View {
ZStack {
// Color.black
// .edgesIgnoringSafeArea(.all)
ZStack {
// ghosting for exhaling
Petals(size: ghostSize, inhaling: inhaling)
.blur(radius: ghostBlur)
.opacity(ghostOpacity)
// the mask is important, otherwise there is a color
// 'jump' when exhaling
Petals(size: size, inhaling: inhaling, isMask: true)
// overlapping petals
Petals(size: size, inhaling: inhaling)
Petals(size: size, inhaling: inhaling)
.rotationEffect(.degrees(Double(smallAngle)))
.opacity(inhaling ? 0.8 : 0.6)
}
.rotationEffect(.degrees(Double(inhaling ? bigAngle : -smallAngle)))
.drawingGroup()
}
.onAppear {
performAnimations()
}
.onDisappear {
size = minSize
inhaling = false
ghostSize = ghostMaxSize
ghostBlur = 0
ghostOpacity = 0
}
}
func performAnimations() {
withAnimation(.easeInOut(duration: inhaleTime)) {
inhaling = true
size = maxSize
}
Timer.scheduledTimer(withTimeInterval: inhaleTime + pauseTime, repeats: false) { _ in
ghostSize = ghostMaxSize
ghostBlur = 0
ghostOpacity = 0.8
Timer.scheduledTimer(withTimeInterval: exhaleTime * 0.2, repeats: false) { _ in
withAnimation(.easeOut(duration: exhaleTime * 0.6)) {
ghostBlur = 30
ghostOpacity = 0
}
}
withAnimation(.easeInOut(duration: exhaleTime)) {
inhaling = false
size = minSize
ghostSize = ghostMinSize
}
}
Timer.scheduledTimer(withTimeInterval: inhaleTime + pauseTime + exhaleTime + pauseTime, repeats: false) { _ in
// endless animation!
performAnimations()
}
}
private func performAnimations2() {
withAnimation(.easeInOut(duration: inhaleTime)) {
inhaling = true
size = maxSize
}
Timer.scheduledTimer(withTimeInterval: inhaleTime + pauseTime, repeats: false) { _ in
ghostSize = ghostMaxSize
ghostBlur = 0
ghostOpacity = 0.8
Timer.scheduledTimer(withTimeInterval: exhaleTime * 0.2, repeats: false) { _ in
withAnimation(.easeOut(duration: exhaleTime * 0.6)) {
ghostBlur = 30
ghostOpacity = 0
}
}
withAnimation(.easeInOut(duration: exhaleTime)) {
inhaling = false
size = minSize
ghostSize = ghostMinSize
}
}
Timer.scheduledTimer(withTimeInterval: inhaleTime + pauseTime + exhaleTime + pauseTime, repeats: false) { _ in
// endless animation!
performAnimations()
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
Meditation()
}
}
【问题讨论】:
-
Xcode 13.4 / iOS 15.5 未观察到问题,即。如果在关闭动画从初始状态重新开始后打开。你是什么环境?还是我错过了什么?
-
或者这取决于你把冥想视图放在哪里。我将它直接放入 ContentView 正文中(周围什么都没有)。
-
顺便说一句,以前的帖子stackoverflow.com/questions/73144662/… 确实有可重现的问题,但我在当前变体中看不到它。 (最好去掉之前的帖子,不要有同样的问题帖子,可能会混淆以后搜索的人。)
-
我的环境是 Xcode 14 beta 3 和 iOS 16 beta 3。我也将它直接放到 ContentView 中。周围什么都没有。所以也许它与测试版有关。虽然下面的答案似乎和我一样。
-
好吧,我认为这一切都与计时器有关 - 两个许多不受管理且未明确停止,因此它们可以在不幸的中断时刻重叠。您必须保留对计时器的引用并在停止活动时明确取消它们。
标签: swiftui