从 SwiftUI 的 ScrollView 列表中获取 onAppear 行为
Get onAppear behaviour from list in ScrollView in SwiftUI
在创建 List
视图时,onAppear
会以您期望的方式触发该列表中的元素:只要滚动到该元素,onAppear
就会触发。但是,我正在尝试实现这样的水平列表
ScrollView(.horizontal) {
HStack(spacing: mySpacing) {
ForEach(items) { item in
MyView(item: item)
.onAppear { \do something }
}
}
}
使用此方法,onAppear
会立即触发所有项目,也就是说:立即触发,但我想要与 List
视图相同的行为。我该怎么做呢?是否有手动触发 onAppear 或控制视图加载时间的方法?
我为什么要实现这一点:我制作了一个自定义 Image
视图,它仅在 URL 出现时从 URL 加载图像(同时替换占位符),这对于 List
视图工作正常,但我希望它也适用于我的水平 'list'.
这里是可能的方法如何做到这一点(tested/worked with Xcode 11.2 / iOS 13.2)
演示:(仅在滚动视图中动态显示第一个和最后一个可见单元格)
一些重要的 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)"))
}
}
根据 SwiftUI 2.0 (XCode 12 beta 1),这终于在本地解决了:
在 LazyHStack
(或任何其他带有 Lazy 前缀的网格或堆栈)中,元素只会在出现在屏幕上时初始化(并因此触发 onAppear
)。
相信LazyHStack可以实现你想要的效果。
ScrollView {
LazyVStack {
ForEach(1...100, id: \.self) { value in
Text("Row \(value)")
.onAppear {
// Write your code for onAppear here.
}
}
}
}
在创建 List
视图时,onAppear
会以您期望的方式触发该列表中的元素:只要滚动到该元素,onAppear
就会触发。但是,我正在尝试实现这样的水平列表
ScrollView(.horizontal) {
HStack(spacing: mySpacing) {
ForEach(items) { item in
MyView(item: item)
.onAppear { \do something }
}
}
}
使用此方法,onAppear
会立即触发所有项目,也就是说:立即触发,但我想要与 List
视图相同的行为。我该怎么做呢?是否有手动触发 onAppear 或控制视图加载时间的方法?
我为什么要实现这一点:我制作了一个自定义 Image
视图,它仅在 URL 出现时从 URL 加载图像(同时替换占位符),这对于 List
视图工作正常,但我希望它也适用于我的水平 'list'.
这里是可能的方法如何做到这一点(tested/worked with Xcode 11.2 / iOS 13.2)
演示:(仅在滚动视图中动态显示第一个和最后一个可见单元格)
一些重要的 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)"))
}
}
根据 SwiftUI 2.0 (XCode 12 beta 1),这终于在本地解决了:
在 LazyHStack
(或任何其他带有 Lazy 前缀的网格或堆栈)中,元素只会在出现在屏幕上时初始化(并因此触发 onAppear
)。
相信LazyHStack可以实现你想要的效果。
ScrollView {
LazyVStack {
ForEach(1...100, id: \.self) { value in
Text("Row \(value)")
.onAppear {
// Write your code for onAppear here.
}
}
}
}