不喜歡使用****UIViewRepresentable****的方式去實(shí)現(xiàn)一個(gè)UIScrollview然后獲取偏移量。這一點(diǎn)都不SwiftUI...
可是目前SwiftUI中的ScrollView還沒有類似的功能。??蘋果大大給點(diǎn)力?。?/strong>
思路:
上層容器由ScrollView承載,底層容器由任意View承載,ScrollView滑動(dòng)時(shí)上層容器的位置相對(duì)于底層容器的位置即是偏移量。
所以我寫了以下代碼
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
但是如何實(shí)時(shí)獲取上層和底層的實(shí)時(shí)位置?
掘金上對(duì)于GeometryReader的一篇介紹文章。
機(jī)翻叫做幾何讀取器
@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 }
// 我們將用它,來獲取控件的實(shí)時(shí)位置。
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
}
簡(jiǎn)單來說,GeometryReader也是View, 只不過這個(gè)view可以讓你做更多事。
這樣一來,我們替換掉剛剛的底層和上層視圖,統(tǒng)統(tǒng)換為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)")
}
}
}
}
如果運(yùn)行程序,Text的文本將會(huì)正確的顯示出當(dāng)前ScrollView的偏移量。
封裝:
現(xiàn)在要把這個(gè)功能點(diǎn)集成進(jìn)一個(gè)ScrollView中,方便我們?cè)谌魏蔚胤将@取offset。
實(shí)現(xiàn)的ScrollView應(yīng)該提供縱向和橫向的offset。
通過綁定使外部View能夠獲取offset。
使用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.
返回容器視圖的邊界矩形,將其轉(zhuǎn)換為定義的坐標(biāo)空間。
CoordinateSpace: 坐標(biāo)空間
{
.global
.local
.named("自定義坐標(biāo)空間")
}
*/
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
}
}
}
至此大功告成。
參考文章: