SwiftUI PageTabView in iOS14.2 will Recall ChildView onAppear method Many times

前端 未结 3 1573
轮回少年
轮回少年 2021-01-23 23:41

I use TabView PageTabViewStyle with SwiftUI to display a pageview, when I swipe this TabView I find child view will Recall onAppear method Many times, Can someone tell me why?

相关标签:
3条回答
  • 2021-01-23 23:48

    I've encountered similar problems due to bugs/features which have appeared with various 14.x releases. For example, on iOS 14.3 the code above prints this to the console at launch:

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

    and this when swiping to index "1":

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

    There appear to be two issues:

    1. .onAppear/.onDisappear may be called once, multiple times or not at all and therefore can't be used.
    2. Other modifiers like .onReceive and .onChange may be called repeatedly and therefore require debouncing.

    Here's a simplified example of a workaround that allows detection and debouncing of TabView page changes without using .onAppear.

    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
            }
          })
      }
    }
    
    0 讨论(0)
  • 2021-01-23 23:52

    For everyone who is still struggling to find a good workaround for this problem. I've managed to use this framework https://github.com/fermoya/SwiftUIPager which can be used to mimic the TabView .tabViewStyle(PageTabViewStyle()) style.

    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)")
                }
            }
            
        }
    }
    
    0 讨论(0)
  • 2021-01-24 00:08

    Views are preloaded at the discretion of SwiftUI. Sometimes more than others depending on the device's available resources. onAppear is called even if it has appeared out of view (pre-loaded)

    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()
        }
    } 
    
    0 讨论(0)
提交回复
热议问题