如何动画移除使用 ForEach 循环创建的视图从 SwiftUI 中的 ObservableObject 获取其数据 [英] How to animate the removal of a view created with a ForEach loop getting its data from an ObservableObject in SwiftUI

查看:39
本文介绍了如何动画移除使用 ForEach 循环创建的视图从 SwiftUI 中的 ObservableObject 获取其数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

该应用具有以下设置:

我的主视图使用 SwiftUI ForEach 循环创建标签云.ForEach 从名为 TagModelObservableObject@Published 数组中获取其数据.使用 TimerObservableObject 每三秒向数组添加一个新标签.通过添加标签,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 获取其数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆