0

我创建了一个 View 扩展来读取它的偏移量(灵感来自https://fivestars.blog/swiftui/swiftui-share-layout-information.html):

func readOffset(in coordinateSpace: String? = nil, onChange: @escaping (CGFloat) -> Void) -> some View {
    background(
        GeometryReader { 
            Color.clear.preference(key: ViewOffsetKey.self,
               value: -$0.frame(in: coordinateSpace == nil ? .global : .named(coordinateSpace)).origin.y)
    })
    .onPreferenceChange(ViewOffsetKey.self, perform: onChange)
}

我也在使用 Federico 的 readSize 函数:

func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
    background(
        GeometryReader { geo in
            Color.clear
                .preference(key: SizePreferenceKey.self, value: geo.size)
    })
    .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}

两者一起帮助我确定滚动视图中的子视图是否在屏幕上/在屏幕外:

struct TestInfinityList: View {
    
    @State var visibleItems: Set<Int> = []
    @State var items: [Int] = Array(0...20)
    @State var size: CGSize = .zero
    
    var body: some View {
        ScrollView(.vertical) {
            ForEach(items, id: \.self) { item in
                
                GeometryReader { geo in
                    VStack {
                        Text("Item \(item)")
                    }.id(item)
                    .readOffset(in: "scroll") { newOffset in
                        if !isOffscreen(when: newOffset, in: size.height) {
                            visibleItems.insert(item)
                        }
                        else {
                            visibleItems.remove(item)
                        }
                    }
                }.frame(height: 300)
                
            }
        }.coordinateSpace(name: "scroll")
    }
    .readSize { newSize in
        self.size = newSize
    }
}

这是检查可见性的 isOffscreen 函数:

func isOffscreen(when offset: CGFloat, in height: CGFloat) -> Bool {
    if offset <= 0 && offset + height >= 0 {
        return false
    }
    return true
} 

一切正常。但是,我想将代码进一步优化为一个单独的扩展,该扩展根据输入的偏移量和 size.height 检查可见性,并且还接收关于可见时做什么的参数,即移动 readOffset 的闭包成为逻辑与扩展代码共存。

我不知道这是否可行,但认为值得一问。

4

1 回答 1

0

您只需要创建一个需要一些绑定的 View 或 ViewModifier。请注意,下面的代码只是您可以使用的一些模式的示例(例如,可选绑定、转义内容闭包),但采用 Stack 样式包装而不是 ViewModifier 的形式(基于您知道的博客如何设置)。


struct ScrollableVStack<Content: View>: View {

    let content: Content
    @Binding var useScrollView: Bool
    @Binding var scroller: ScrollViewProxy?
    @State private var staticGeo = ViewGeometry()
    @State private var scrollContainerGeo = ViewGeometry()
    let topFade: CGFloat
    let bottomFade: CGFloat

    init(_ useScrollView: Binding<Bool>,
         topFade: CGFloat = 0.09,
         bottomFade: CGFloat = 0.09,
         _ scroller: Binding<ScrollViewProxy?> = .constant(nil),
         @ViewBuilder _ content: @escaping () -> Content ) {
        _useScrollView = useScrollView
        _scroller = scroller
        self.content = content()
        self.topFade = topFade
        self.bottomFade = bottomFade
    }

    var body: some View {
        if useScrollView { scrollView }
        else { VStack { staticContent } }
    }

    var scrollView: some View {
        ScrollViewReader { scroller in
            ScrollView(.vertical, showsIndicators: false) {
                staticContent
                    .onAppear { self.scroller = scroller }
            }
            .geometry($scrollContainerGeo)
            .fadeInOut(topFade: staticGeo.size.height * topFade,
                       bottomFade: staticGeo.size.height * bottomFade)
        }
        .onChange(of: staticGeo.size.height) { newStaticHeight in
            useScrollView = newStaticHeight > scrollContainerGeo.size.height * 0.85
        }

    }

    var staticContent: some View {
        content
            .geometry($staticGeo)
            .padding(.top, staticGeo.size.height * topFade * 1.25)
            .padding(.bottom, staticGeo.size.height * bottomFade)
    }

}


于 2021-01-17T18:44:21.023 回答