从SwiftUI的ScrollView中的列表获取onAppear行为

时间:2019-07-28 14:20:01

标签: swiftui

在创建List视图时,该列表中元素的onAppear触发器将按照您期望的方式:一旦滚动到该元素,onAppear触发器就会触发。但是,我正在尝试实现这样的水平列表

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

使用此方法,onAppear会立即为所有项目触发,即:立即触发,但是我想要与List视图相同的行为。我将如何去做呢?是否有手动方式来触发onAppear或控制何时加载视图?

为什么要实现此目的:我创建了一个自定义Image视图,该视图仅在出现时才从URL加载图像(并同时替换一个占位符),这对于{{ 1}}视图,但我希望它也适用于我的水平“列表”。

2 个答案:

答案 0 :(得分:1)

这里是可能的方法(通过Xcode 11.2 / iOS 13.2测试/工作)

演示:(只需在滚动视图中动态显示第一个和最后一个可见的单元格即可)

demo

几个重要的View扩展名

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

以及如何将其与滚动视图中的单元格视图一起使用的演示视图

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

答案 1 :(得分:0)

根据SwiftUI 2.0(XCode 12 beta 1),这最终得以解决: 在LazyHStack(或任何其他带有Lazy前缀的网格或堆栈)中,元素只会在它们出现在屏幕上时初始化(并因此触发onAppear)。