【问题标题】:A view extension that runs conditional code based on its GeometryReader results根据 GeometryReader 结果运行条件代码的视图扩展
【发布时间】:2021-01-17 17:39:51
【问题描述】:

我创建了一个 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 的闭包移动为逻辑与扩展代码共存。

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

【问题讨论】:

    标签: swift swiftui size offset geometryreader


    【解决方案1】:

    您只需要创建一个需要一些绑定的 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)
        }
    
    }
    
    
    

    【讨论】:

    • 谢谢瑞恩!我会调查一下然后回来。
    猜你喜欢
    • 2022-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多