Get onAppear behaviour from list in ScrollView in SwiftUI

若如初见. 提交于 2021-02-18 10:31:49

问题


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 onAppear triggers. However, I'm trying to implement a horizontal list like this

ScrollView(.horizontal) { 
   HStack(spacing: mySpacing) {
      ForEach(items) { item in 
         MyView(item: item)
            .onAppear { \\do something }
      } 
   }
}

Using this method the onAppear triggers for all items at once, that is to say: immediately, but I want the same behavior as for a List view. How would I go about doing this? Is there a manual way to trigger onAppear, or control when views load?

Why I want to achieve this: I have made a custom Image view that loads an image from an URL only when it appears (and substitutes a placeholder in the mean time), this works fine for a List view, but I'd like it to also work for my horizontal 'list'.


回答1:


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)"))
    }
}



回答2:


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.



来源:https://stackoverflow.com/questions/57241783/get-onappear-behaviour-from-list-in-scrollview-in-swiftui

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!