【问题标题】:How can I be sure that a View did completely rendered until last line of code in SwiftUI?我如何确定视图在 SwiftUI 中的最后一行代码之前完全呈现?
【发布时间】:2021-01-26 03:22:07
【问题描述】:

我有一些代码,它们必须在视图完全渲染和绘制后被触发,因为我们知道我们为视图获取了 onAppear 或 onChange,它们对于了解视图是否已经出现或正在更改很有用,但它们对于确保 View 已 100% 渲染没有用处,例如,如果我们在 View 中有 ForEach、List 或 Form,您可以看到 View,但由于层次结构,ForEach 仍然可以在 View 上工作,这意味着视图没有完全加载,如果你给了一个动画修改器,它可能会变得更加明显,然后你也可以看到它,所以我的目标是知道我如何能 100% 确保我的视图被渲染到里面的所有内容,并且渲染视图将不再需要运行代码?

感谢阅读和帮助


例子:

import SwiftUI


struct ContentView: View {

    var body: some View {

        HierarchyView()
            .onAppear() { print("HierarchyView"); print("- - - - - - - - - - -") }

    }

}



struct HierarchyView: View {

    var body: some View {

        ZStack {

            Color
                .blue
                .ignoresSafeArea()
                .onAppear() { print("blue"); print("- - - - - - - - - - -")  }

            VStack {
                
                Text("Top text")
                    .onAppear() { print("Top Text") }
                
                Spacer()
                    .onAppear() { print("Top Spacer") }
                
            }
            .onAppear() { print("Top VStack"); print("- - - - - - - - - - -")  }

            VStack {

                VStack {

                    VStack {

                        ForEach(0..<3, id:\..self) { index in
                            
                            Text(index.description)
                                .onAppear() { print(index.description); if (index == 0) { print("Last of second ForEach!!!") } }
                            
                        }
  
                    }
                    .onAppear() { print("Middle VStack Inside DEEP"); print("- - - - - - - - - - -")  }

                }
                .onAppear() { print("Middle VStack Inside"); print("- - - - - - - - - - -")  }

            }
            .onAppear() { print("Middle VStack Outside"); print("- - - - - - - - - - -")  }
            
            
            
            VStack {
                
                Spacer()
                    .onAppear() { print("Bottom Spacer") }
                
                
                ForEach(0..<3, id:\..self) { index in
                    
                    Text(index.description)
                        .onAppear() { print(index.description); if (index == 0) { print("Last of first ForEach!!!") } }
                    
                }

                
                
                
                Text("Bottom text")
                    .onAppear() { print("Bottom Text") }
                
            }
            .onAppear() { print("Bottom VStack"); print("- - - - - - - - - - -")  }
            
            
        }
        .onAppear() { print("ZStack of HierarchyView"); print("- - - - - - - - - - -") }
        
        
    }
 
}

【问题讨论】:

  • .onAppear of parent 在其后代初始化并计算其body 后被触发
  • 我是这么想的,直到我观察到 ForEach 的行为!在这种情况下,父级的子级可以在其父级之后渲染,子级是 ForEach,您放入 View 或 VStack 或其他任何内容,它将最后渲染!具有从下到上的层次结构,最后一个索引到第一个索引!这使得很难找到真正完成的渲染,另一方面,我不想在 ForEach 中设置一个条件来检查这是否是索引零,因为它会使应用程序运行繁重,这只是 ForEach,还有其他改变我所说的几何的可能方法,这就是为什么我想知道,当视图是 . .
  • 已完全呈现,不再需要运行行代码。我们在 View 中使用过什么,可能是 ForEach、几何或简单的 Text,我需要找出一般的了解方式
  • 我建议你提供一个最小的例子来说明你的意思以及它偏离你的期望的地方。或者,甚至在此之前 - 您希望动画如何工作以及在哪里不工作
  • 我以前没有遇到过你的问题,因为我不完全理解你的问题。我告诉过你父级的.onAppear 何时相对于其子级触发。它还取决于“渲染”的含义——一般来说,视图可能会“出现”,但实际上稍后会渲染其内容。 SwiftUI 也发生了很多幕后的事情,还有很多反最佳实践。从您的期望偏离现实以减少歧义的最小示例(或行为不同的两个示例)开始,然后从那里开始。

标签: swiftui


【解决方案1】:

AFAIK,没有通用的方法来判断所有可能的后代视图何时“出现”(即会解雇他们的 onAppear)。

根据您的想法,“出现”与“呈现”不同。一般来说,每个视图决定何时渲染其子视图。例如,LazyVStack 只会在需要渲染时创建它的元素。符合UIViewControllerRepresentable 的自定义视图可以决定为所欲为。

考虑到这一点(假设 render == 出现),您可以采取的一种方法是“跟踪”那些您关心的“出现”视图,并在它们全部出现时触发回调。

您可以创建一个视图修改器来跟踪每个视图“渲染”状态,并使用PreferenceKey 收集所有这些数据:

struct RenderedPreferenceKey: PreferenceKey {
    static var defaultValue: Int = 0
    static func reduce(value: inout Int, nextValue: () -> Int) {
        value = value + nextValue() // sum all those remain to-be-rendered
    }
}

struct MarkRender: ViewModifier {
    @State private var toBeRendered = 1
    func body(content: Content) -> some View {
        content
            .preference(key: RenderedPreferenceKey.self, value: toBeRendered)
            .onAppear { toBeRendered = 0 }
    }
}

然后,在View 上创建便捷方法以简化其使用:

extension View {
    func trackRendering() -> some View {
        self.modifier(MarkRender())
    }

    func onRendered(_ perform: @escaping () -> Void) -> some View {
        self.onPreferenceChange(RenderedPreferenceKey.self) { toBeRendered in
           // invoke the callback only when all tracked have been set to 0,
           // which happens when all of their .onAppear are called
           if toBeRendered == 0 { perform() }
        }
    }
}

用法如下:

VStack {
    ForEach(0..<3, id:\..self) { index in
        Text("\(index)").trackRendering()
    }
}
.onRendered {
    print("Rendered")
}

【讨论】:

  • @swiftPunk,你能详细说明一下这个问题吗? (我修正了一个小的复制粘贴错字)
  • 很好的解决方案。一旦网格完成渲染其内容,我将其调整为 LazyVGrid 以使其滚动到特定项目。当 LazyVGrid 多次重新构建其项目时,它很有用。在这种情况下,LazyVGrid 的 .onAppear 修饰符不能多次使用。请参阅stackoverflow.com/a/70517757/1249407
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-16
相关资源
最近更新 更多