问题描述
该应用具有以下设置:
我的主视图使用 SwiftUI ForEach
循环创建标签云.ForEach
从名为 TagModel
的 ObservableObject
的 @Published
数组中获取其数据.使用 Timer
,ObservableObject
每三秒向数组添加一个新标签.通过添加标签,ForEach
再次被触发并创建另一个 TagView
.一旦超过三个标签被添加到数组中,ObservableObject
就会从数组中删除第一个(最旧的)标签,而 ForEach 会删除那个特定的 TagView
.
My main view creates a tag cloud using a SwiftUI ForEach
loop.The ForEach
gets its data from the @Published
array of an ObservableObject
called TagModel
. Using a Timer
, every three seconds the ObservableObject
adds a new tag to the array.By adding a tag the ForEach
gets triggered again and creates another TagView
. Once more than three tags have been added to the array, the ObservableObject
removes the first (oldest) tag from the array and the ForEach removes that particular TagView
.
遇到以下问题:
TagViews 的创建工作完美.它还使用 TagView
的动画和 .onAppear 修饰符以应有的方式制作动画.然而,当最旧的标签被移除时,它不会对其移除进行动画处理..onDisappear
中的代码会执行,但 TagView
会立即被移除.
The creation of the TagViews works perfect. It also animates the way it's supposed to with the animations and .onAppear modifiers of the TagView
. However when the oldest tag is removed it does not animate its removal. The code in .onDisappear
executes but the TagView
is removed immediately.
我尝试了以下方法来解决问题:
我试图通过使用重复然后自动反转的动画来让 TagView
的整个出现和消失动画在 .onAppear
内运行.它有点工作,但这样有两个问题.首先,如果动画的时间太短,一旦动画完成并且 TagView
被移除,将在没有应用任何修饰符的情况下显示一小段时间,然后将其移除.其次,如果我将动画持续时间设置得更长,TagView
将在动画完成之前被移除.为了让它起作用,我需要非常精确地对动画的移除和持续时间进行计时,这会使 TagView
非常依赖于 Timer
而这不会似乎不是一个好的解决方案.
I tried to have the whole appearing and disappearing animations of TagView
run inside the .onAppear
by using animations that repeat and then autoreverse.It sort of works but this way there are two issues.First, if the animation is timed too short, once the animation finishes and the TagView
is removed, will show up for a short moment without any modifiers applied, then it will be removed.Second, if I set the animation duration longer the TagView
will be removed before the animation has finished.In order for this to work I'd need to time the removal and the duration of the animation very precisely, which would make the TagView
very dependent on the Timer
and this doesn't seem to be a good solution.
我尝试的另一个解决方案是使用 @Environment(\.presentationMode)
变量找到类似于 self.presentationMode.wrappedValue.dismiss()
的东西,并以某种方式具有 TagView
在 .onAppear
动画完成后移除自身.但这仅适用于在导航堆栈中创建视图并且我找不到任何其他方法来让视图自行销毁的情况.此外,我认为一旦 TagModel
更新其数组,这会再次引起问题.
Another solution I tried was finding something similar to self.presentationMode.wrappedValue.dismiss()
using the @Environment(\.presentationMode)
variable and somehow have the TagView
remove itself after the .onAppear
animation has finished. But this only works if the view has been created in an navigation stack and I couldn't find any other way to have a view destroy itself. Also I assume that would again cause issue as soon as TagModel
updates its array.
我阅读了其他几篇 S.O.指向 ForEach 循环中数据枚举的解决方案.但是我将每个 TagView 创建为它自己的对象,我认为这不应该是问题,如果这是问题的一部分,我不确定我将如何实现它.
I read several other S.O. solution that pointed towards the enumeration of the data in the ForEach loop. But I'm creating each TagView as its own object, I'd assume this should not be the issue and I'm not sure how I'd have to implement this if this is part of the issue.
这是我的应用程序的简化代码,可以在 iOS 单视图 SwiftUI 项目中运行.
import SwiftUI
struct ContentView: View {
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
@ObservedObject var tagModel = TagModel()
var body: some View {
ZStack {
ForEach(tagModel.tags, id: \.self) { label in
TagView(label: label)
}
.onReceive(timer) { _ in
self.tagModel.addNextTag()
if tagModel.tags.count > 3 {
self.tagModel.removeOldestTag()
}
}
}
}
}
class TagModel: ObservableObject {
@Published var tags = [String]()
func addNextTag() {
tags.append(String( Date().timeIntervalSince1970 ))
}
func removeOldestTag() {
tags.remove(at: 0)
}
}
struct TagView: View {
@State private var show: Bool = false
@State private var position: CGPoint = CGPoint(x: Int.random(in: 50..<250), y: Int.random(in: 10..<25))
@State private var offsetY: CGFloat = .zero
let label: String
var body: some View {
let text = Text(label)
.opacity(show ? 1.0 : 0.0)
.scaleEffect(show ? 1.0 : 0.0)
.animation(Animation.easeInOut(duration: 6))
.position(position)
.offset(y: offsetY)
.animation(Animation.easeInOut(duration: 6))
.onAppear() {
show = true
offsetY = 100
}
.onDisappear() {
show = false
offsetY = 0
}
return text
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
推荐答案
目前尚不清楚您尝试实现哪种效果,但是在移除时您应该动画而不是视图内部,而是视图本身,即.在父级中,因为视图从那里移除并且作为一个整体.
It is not clear which effect do you try to achieve, but on remove you should animate not view internals, but view itself, ie. in parent, because view remove there and as-a-whole.
类似的东西(只是实验的方向):
Something like (just direction where to experiment):
var body: some View {
ZStack {
ForEach(tagModel.tags, id: \.self) { label in
TagView(label: label)
.transition(.move(edge: .leading)) // << here !! (maybe asymmetric needed)
}
.onReceive(timer) { _ in
self.tagModel.addNextTag()
if tagModel.tags.count > 3 {
self.tagModel.removeOldestTag()
}
}
}
.animation(Animation.easeInOut(duration: 1)) // << here !! (parent animates subview removing)
这篇关于如何动画移除使用 ForEach 循环创建的视图从 SwiftUI 中的 ObservableObject 获取其数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!