Get onAppear behaviour from list in ScrollView in SwiftUI

前端 未结 2 1575
甜味超标
甜味超标 2021-02-20 18:06

When creating a List view onAppear triggers for elements in that list the way you would expect: As soon as you scroll to that element the onAppea

相关标签:
2条回答
  • 2021-02-20 18:15

    As per SwiftUI 2.0 (XCode 12 beta 1) this is finally natively solved: In a LazyHStack (or any other grid or stack with the Lazy prefix) elements will only initialise (and therefore trigger onAppear) when they appear on screen.

    0 讨论(0)
  • 2021-02-20 18:22

    Here is possible approach how to do this (tested/worked with Xcode 11.2 / iOS 13.2)

    Demo: (just show dynamically first & last visible cell in scrollview)

    demo

    A couple of important View extensions

    extension View {
        func rectReader(_ binding: Binding<CGRect>, in space: CoordinateSpace) -> some View {
            self.background(GeometryReader { (geometry) -> AnyView in
                let rect = geometry.frame(in: space)
                DispatchQueue.main.async {
                    binding.wrappedValue = rect
                }
                return AnyView(Rectangle().fill(Color.clear))
            })
        }
    }
    
    extension View {
        func ifVisible(in rect: CGRect, in space: CoordinateSpace, execute: @escaping (CGRect) -> Void) -> some View {
            self.background(GeometryReader { (geometry) -> AnyView in
                let frame = geometry.frame(in: space)
                if frame.intersects(rect) {
                    execute(frame)
                }
                return AnyView(Rectangle().fill(Color.clear))
            })
        }
    }
    

    And a demo view of how to use them with cell views being in scroll view

    struct TestScrollViewOnVisible: View {
        @State private var firstVisible: Int = 0
        @State private var lastVisible: Int = 0
        @State private var visibleRect: CGRect = .zero
        var body: some View {
            VStack {
                HStack {
                    Text("<< \(firstVisible)")
                    Spacer()
                    Text("\(lastVisible) >> ")
                }
                Divider()
                band()
            }
        }
    
        func band() -> some View {
            ScrollView(.horizontal) {
                HStack(spacing: 10) {
                    ForEach(0..<50) { i in
                        self.cell(for: i)
                            .ifVisible(in: self.visibleRect, in: .named("my")) { rect in
                                print(">> become visible [\(i)]")
    
                                // do anything needed with visible rects, below is simple example
                                // (w/o taking into account spacing)
                                if rect.minX <= self.visibleRect.minX && self.firstVisible != i {
                                    DispatchQueue.main.async {
                                        self.firstVisible = i
                                    }
                                } else
                                if rect.maxX >= self.visibleRect.maxX && self.lastVisible != i {
                                    DispatchQueue.main.async {
                                        self.lastVisible = i
                                    }
                                }
                            }
                    }
                }
            }
            .coordinateSpace(name: "my")
            .rectReader(self.$visibleRect, in: .named("my"))
        }
    
        func cell(for idx: Int) -> some View {
            RoundedRectangle(cornerRadius: 10)
                .fill(Color.yellow)
                .frame(width: 80, height: 60)
                .overlay(Text("\(idx)"))
        }
    }
    
    0 讨论(0)
提交回复
热议问题