美文网首页SwiftUISwiftUI
SwiftUI如何获取ScrollView的contentOff

SwiftUI如何获取ScrollView的contentOff

作者: Xu___ | 来源:发表于2020-12-23 13:26 被阅读0次

    不喜欢使用****UIViewRepresentable****的方式去实现一个UIScrollview然后获取偏移量。这一点都不SwiftUI...

    可是目前SwiftUI中的ScrollView还没有类似的功能。😭苹果大大给点力啊!

    思路:

    上层容器由ScrollView承载,底层容器由任意View承载,ScrollView滑动时上层容器的位置相对于底层容器的位置即是偏移量。

    所以我写了以下代码

    var body: some View {
      ZStack(alignment: .topLeading) {
        HStack {
            Spacer()
            Text("底层")
                .font(.title)
            Spacer()
        }
        .background(Color.blue)
        .zIndex(1)
        ScrollView(.vertical) {
            HStack {
                Spacer()
                Text("上层")
                    .background(Color.orange)
                Spacer()
            }
        }
        .zIndex(1000)
      }
     }
    
    image.png

    但是如何实时获取上层和底层的实时位置?

    SwiftUI:GeometryReader

    掘金上对于GeometryReader的一篇介绍文章。

    机翻叫做几何读取器

    @frozen public struct GeometryReader<Content> : View where Content : View {
        
        public var content: (GeometryProxy) -> Content
    
        @inlinable public init(@ViewBuilder content: @escaping (GeometryProxy) -> Content)
    
        /// The type of view representing the body of this view.
        ///
        /// When you create a custom view, Swift infers this type from your
        /// implementation of the required `body` property.
        public typealias Body = Never
    }
    
    public struct GeometryProxy {
    
        /// The size of the container view.
        public var size: CGSize { get }
    
        /// Resolves the value of `anchor` to the container view.
        public subscript<T>(anchor: Anchor<T>) -> T { get }
    
        /// The safe area inset of the container view.
        public var safeAreaInsets: EdgeInsets { get }
    
        // 我们将用它,来获取控件的实时位置。
        public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
    } 
    

    简单来说,GeometryReader也是View, 只不过这个view可以让你做更多事。

    这样一来,我们替换掉刚刚的底层和上层视图,统统换为GeometryReader:

    var body: some View {
      // 底层
      GeometryReader { outProxy in
        ScrollView(.vertical) {
            // 上层
            GeometryReader { inProxy in
                // 底层minY - 上层minY
                Text("\(outProxy.frame(in: .global).minY - inProxy.frame(in: .global).minY)")
            }
        }
      }
    }
    

    如果运行程序,Text的文本将会正确的显示出当前ScrollView的偏移量。

    封装:

    现在要把这个功能点集成进一个ScrollView中,方便我们在任何地方获取offset。

    1. 实现的ScrollView应该提供纵向和横向的offset。

    2. 通过绑定使外部View能够获取offset。

    3. 使用preference将子View的变化传递给父View。

    //
    //  InspectingScrollView.swift
    //  Memory
    //
    //  Created by Xu on 2020/12/22.
    //
    
    import SwiftUI
    
    
    // MARK: PreferenceKey
    /*
     使用PreferenceKey沿View树向上传递:
     子View->父View
     */
    
    struct ScrollOffsetPreferenceKey: PreferenceKey{
        typealias Value = [CGFloat]
        
        static var defaultValue: [CGFloat] = [0]
        
        static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
            value.append(contentsOf: nextValue())
        }
    }
    
    struct InspectingScrollView<Content>: View where Content: View {
        
        /// 方向
        let axes: Axis.Set
        /// 指示器
        let showsIndicators: Bool
        /// 偏移量
        @Binding var contentOffset: CGFloat
    
        let content: Content
        
        init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, contentOffset: Binding<CGFloat>, @ViewBuilder content: ()-> Content) {
            self.axes = axes
            self.showsIndicators = showsIndicators
            self._contentOffset = contentOffset
            self.content = content()
        }
        
        var body: some View {
            GeometryReader { outsideProxy in
                ScrollView(self.axes, showsIndicators: self.showsIndicators, content: {
                    ZStack(alignment: self.axes == .vertical ? .top : .leading) {
                        GeometryReader { insideProxy in
                            Color.clear
                                .preference(
                                    key: ScrollOffsetPreferenceKey.self,
                                    value: [
                                        calculateContentoffset(from: insideProxy,
                                                               outsideProxy: outsideProxy)
                                    ])
                        }
                        VStack {
                            self.content
                        }
                    }
                })
                .onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: { value in
                    self.contentOffset = value.first ?? 0
                })
                
            }
        }
        
        func calculateContentoffset(from insideProxy: GeometryProxy, outsideProxy: GeometryProxy)-> CGFloat {
            /*
             Notice: frame(in: CoordinateSpace)
             Returns the container view's bounds rectangle, converted to a defined
             coordinate space.
             返回容器视图的边界矩形,将其转换为定义的坐标空间。
             
             CoordinateSpace: 坐标空间
             {
                .global
                .local
                .named("自定义坐标空间")
             }
             */
            if axes == .vertical {
                return outsideProxy.frame(in: .global).minY - insideProxy.frame(in: .global).minY
            }else {
                return outsideProxy.frame(in: .global).minX - insideProxy.frame(in: .global).minX
            }
        }
    }
    

    如何使用:

    struct DemoView: View {
      @State private var contentOffset: CGFloat = 0
      var body: some View {
        InspectingScrollView(.vertical, showsIndicators: false, contentOffset: $contentOffset) {
          // some views
        }
      }
    }
    

    至此大功告成。

    参考文章

    1. https://medium.com/@maxnatchanon/swiftui-how-to-get-content-offset-from-scrollview-5ce1f84603ec

    2. SwiftUI编程思想

    3. https://juejin.cn/post/6844904004133060615

    相关文章

      网友评论

        本文标题:SwiftUI如何获取ScrollView的contentOff

        本文链接:https://www.haomeiwen.com/subject/ozbynktx.html