iOS14.2中的SwiftUI PageTabView将多次调用ChildView onAppear方法

时间:2020-11-02 08:00:47

标签: ios ios14 swiftui uitabview

我在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
}

控制台打印的结果 enter image description here

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

它在ios14.2中有问题,14.1可以,您可以在Github中加载我的代码:https://github.com/werbhelius/TabViewBug

Xcode版本:12.1(12A7403)

设备:iPhone 6s iOS 14.2

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

我们期待您的帮助解决此问题。谢谢

3 个答案:

答案 0 :(得分:1)

View是由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()
    }
} 

答案 1 :(得分:1)

对于仍在努力寻找解决此问题的好方法的每个人。我设法使用了这个框架 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)")
            }
        }
        
    }
}

答案 2 :(得分:0)

由于各种 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

似乎有两个问题:

  1. .onAppear/.onDisappear 可能会被调用一次、多次或根本不调用,因此无法使用。
  2. 其他修饰符,如 .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
        }
      })
  }
}