iOS14.2 SwiftUI PageTabView 会多次调用 ChildView onAppear 方法

Posted

技术标签:

【中文标题】iOS14.2 SwiftUI PageTabView 会多次调用 ChildView onAppear 方法【英文标题】:SwiftUI PageTabView in iOS14.2 will Recall ChildView onAppear method Many times 【发布时间】:2020-11-02 08:00:47 【问题描述】:

我在 SwiftUI 中使用 TabView PageTabViewStyle 来显示一个页面视图,当我滑动这个 TabView 时,我发现子视图会调用 onAppear 方法很多次,有人能告诉我为什么吗?

这是我的代码

import SwiftUI

struct Pageview: View 
    
    @StateObject var vm = PageViewModel()
    
    var body: some View 
        VStack 
            
            DragViewBar().padding(.top, 14)
            
            TabView(selection: $vm.selectTabIndex) 
                
                TextView(index: "0").tag(0)
                TextView(index: "1").tag(1)
                TextView(index: "2").tag(2)
                TextView(index: "3").tag(3)
                TextView(index: "4").tag(4)
                TextView(index: "5").tag(5)
                
            
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            
        

    


struct TextView: View 
    
    let index: String
    
    var body: some View 
        VStack 
            Text(index)
        
        .onAppear  print(index) 
        
    


struct DragViewBar: View 
    var body: some View 
        Rectangle()
            .frame(width:36.0,height:5.0).foregroundColor(Color.blue)
            .cornerRadius(100)
    


class PageViewModel: ObservableObject 
    @Published var selectTabIndex = 0


控制台打印的结果

正确的情况是每次滑动只打印一次

只是ios14.2有问题,14.1就可以了,你可以在Github上加载我的代码:https://github.com/werbhelius/TabViewBug

Xcode 版本:12.1 (12A7403)

设备:iPhone 6s iOS 14.2

我认为您可以在 iOS 14.2 的任何设备上重现此问题

我期待您的帮助来解决这个问题。谢谢

【问题讨论】:

【参考方案1】:

Views 由 SwiftUI 自行决定预加载。有时比其他更多取决于设备的可用资源。 onAppear 被调用,即使它已经出现在视野之外(预加载)

import SwiftUI

struct PageView: View 
    
    @StateObject var vm = PageViewModel()
    
    var body: some View 
        VStack 
            
            DragViewBar().padding(.top, 14)
            
            TabView(selection: $vm.selectTabIndex) 
                
                TextView(index: "0").tag(0)
                TextView(index: "1").tag(1)
                TextView(index: "2").tag(2)
                TextView(index: "3").tag(3)
                TextView(index: "4").tag(4)
                TextView(index: "5").tag(5)
                
            
            //This lets you perform an operation when the value has changed
            .onReceive(vm.$selectTabIndex, perform:  idx in
                print("PageView :: body :: onReceive" + idx.description)
            )
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            
        

    


struct TextView: View 
    
    let index: String
    
    var body: some View 
        VStack 
            Text(index)
        
        //Views are pre-loaded at the discretion of SwiftUI
        .onAppear  print(index) 
        
        .onReceive(index.publisher, perform:  idx in
            print("TextView :: body :: onReceive" + idx.description)
        )
        
    


struct DragViewBar: View 
    var body: some View 
        Rectangle()
            .frame(width:36.0,height:5.0).foregroundColor(Color.blue)
            .cornerRadius(100)
    


class PageViewModel: ObservableObject 
    @Published var selectTabIndex = 0



struct PageView_Previews: PreviewProvider 
    static var previews: some View 
        PageView()
    
 

【讨论】:

但是我只在 14.2 上重复调用 onAppear 才发现这个问题,这对于 14.1 版本来说是正常的,我怀疑这是 ios 14.2 上 TabView 中的一个错误 我也有同样的问题!我的应用程序依赖于 PageTabViewStyle,因此它在 iOS 14.2 上完全崩溃了。|你找到解决办法了吗? 我不确定,但PageTabViewStyle 可能只是出于“效率”目的而采用了List 的这种预加载行为。一种流行的解决方法是struct LazyView<Content: View>: View 我还没有为PageTabViewStyle 尝试过,但如果onReceive 不适用于您的用例***.com/questions/58357414/…,那么这可能是值得的努力@ 你们有没有发现这个问题? @MaximeHeckel 我在 14.3 中仍然面临这个问题。 上面的答案对我有用,这就是我最终要做的:twitter.com/MaximeHeckel/status/1328381063287042048/photo/2【参考方案2】:

适合所有仍在努力寻找解决此问题的好方法的人。我已经设法使用了这个框架https://github.com/fermoya/SwiftUIPager,它可以用来模仿TabView .tabViewStyle(PageTabViewStyle()) 风格。

import SwiftUIPager
struct Dummy: View 
    @StateObject var page: Page = .first()
    let numberOfPages : Int = 3
    var body: some View 
        
        Pager(page: self.page,
              data: Array(0..<numberOfPages),
              id: \.self,
              content:  index in
                
                switch (index) 
                case 0:
                    ZStack
                        Color.white
                        Text("Test")
                    
                case 1:
                    ZStack
                        Color.white
                        Text("Test2")
                    
                default:
                    ZStack
                        Color.white
                        Text("Test3")
                    
                
                
              
        )
        .pagingPriority(.simultaneous)
        .onPageWillChange  (newIndex) in
            print("Page \(self.page.index) will change to \(newIndex) i.e. onDisappear")
            switch (self.page.index) 
            case 0:
                print("onDisappear of \(self.page.index)")
            case 1:
                print("onDisappear of \(self.page.index)")
            default:
                print("onDisappear of \(self.page.index)")
            
        
        .onPageChanged  (newIndex) in
            print("Has changed to page \(self.page.index) i.e. onAppear")
            switch (newIndex) 
            case 0:
                print("onAppear of \(newIndex)")
            case 1:
                print("onAppear of \(newIndex)")
            default:
                print("onAppear of \(newIndex)")
            
        
        
    

【讨论】:

【参考方案3】:

由于各种 14.x 版本中出现的错误/功能,我遇到了类似的问题。例如,在 iOS 14.3 上,上面的代码在启动时将其打印到控制台:

PageView :: body :: onReceive0 TextView :: body :: onReceive0 0 0 0 0 0 0 0 0 0 0 0

当滑动到索引“1”时:

TextView :: body :: onReceive1 1 TextView :: body :: onReceive2 2 PageView :: body :: onReceive1 1 1 1 1 1 1

似乎有两个问题:

    .onAppear/.onDisappear 可能会被调用一次、多次或根本不调用,因此无法使用。 .onReceive 和 .onChange 等其他修饰符可能会重复调用,因此需要去抖动。

这是一个解决方法的简化示例,它允许在不使用 .onAppear 的情况下检测和消除 TabView 页面更改。

import SwiftUI

struct ContentView: View 
  @State private var currentView: Int = 0
  
  var body: some View 
    
    TabView(selection: $currentView) 
      ChildView1(currentView: $currentView).tag(1)
      ChildView2(currentView: $currentView).tag(2)
    
    .tabViewStyle(PageTabViewStyle())
  


struct ChildView1: View 
  @Binding var currentView: Int
  @State private var debouncer = 0
  let thisViewTag = 1
  
  var body: some View 
    Text("View 1")
      
      .onChange(of: currentView, perform:  value in
        if value == thisViewTag 
          debouncer += 1
          if debouncer == 1 
            print ("view 1 appeared")
          
         else 
          debouncer = 0
        
      )
  


struct ChildView2: View 
  @Binding var currentView: Int
  @State private var debouncer = 0
  let thisViewTag = 2
  
  var body: some View 
    Text("View 2")
      
      .onChange(of: currentView, perform:  value in
        if value == thisViewTag 
          debouncer += 1
          if debouncer == 1 
            print ("view 2 appeared")
          
         else 
          debouncer = 0
        
      )
  

【讨论】:

以上是关于iOS14.2 SwiftUI PageTabView 会多次调用 ChildView onAppear 方法的主要内容,如果未能解决你的问题,请参考以下文章

将表单附加到边缘[iOS/SwiftUI]

iOS 15.3.1中SwiftUI toolbar中按钮不响应点击动作等若干不兼容问题的解决

iOS 15.3.1中SwiftUI toolbar中按钮不响应点击动作等若干不兼容问题的解决

iOS 14.2 Beta - AVPlayer 无法播放 [关闭]

iOS 14.2 中引入 AudioToolback 崩溃

在 Safari iOS 14.2 上自动播放视频 HTML