省流
代码完成:FrameGetter
需要获取 Frame 的场景
一般咱们运用 SwiftUI 布局 UI 时,利用 VStack、HStack 等即可约束不同 View 之间的位置联系,因而不会再需要获取指定 View 的 Frame。但是有些时候为了完成杂乱的页面,或者为了一些布局相关的核算时,就不得不获取 Frame。
比方下面这个场景,为了完成下拉时扩大标题的效果,必需要获取 ScrollView 的 minY,从而核算出顶部区域的扩大倍数:
初尝运用 GeometryReader
GeometryReader – Apple doc
A container view that defines its content as a function of its own size and coordinate space.
运用 GeometryReader 能够在布局时获取到对应 View 在指定坐标系的布局信息,咱们能够利用这个特点将 frame 信息读取出来。
@State var frame: CGRect = .zero
var body: some View {
GeometryReader { geometry in
self.frame = geometry.frame(in: .global)
MainContent()
}
}
当然咱们不能直接设置,因为 block 内的内容必需要遵循 View 协议,直接这样写编译器无法揣度。
View – Apple doc
You create custom views by declaring types that conform to the
View
protocol. Implement the requiredbody
computed property to provide the content for your custom view.
咱们将布局代码提取出来,明确返回值即可:
var body: some View {
GeometryReader { geometry in
makeView(geometry)
}
}
func makeView(_ geometry: GeometryProxy) -> some View {
print(geometry.size.width, geometry.size.height)
self.frame = geometry.frame(in: .global)
return MainContent()
}
不过现在有新的问题产生:在布局阶段,不能够更改 @State 关键字修饰的属性。
这个问题的原因在于,@State 关键字的意义简略来说,其实便是 View 的状态;而如果在核算 View 的时候更改 @State,那就有可能造成时序上的混乱,导致布局错误,所以必需要将 @State 变量的修正拖延一个周期。
于是现在的完好代码变成了:
struct ContentView: View {
@State var frame: CGRect = .zero
var body: some View {
GeometryReader { (geometry) in
self.makeView(geometry)
}
}
func makeView(_ geometry: GeometryProxy) -> some View {
print(geometry.size.width, geometry.size.height)
DispatchQueue.main.async { self.frame = geometry.frame(in: .global) }
return MainContent()
}
}
终究完成方便的 Extension: FrameGetter
首要,咱们将已有的部分提取到 ViewModifier 中,当然需要必定的改造 —— 将 GeometryReader 的部分放入 background 中运用。这样做既不会影响布局的获取,又能方便优雅,并且避免 body 直接返回 GeometryReader 导致的类型揣度错误。
extension View {
func frameGetter(_ frame: Binding<CGRect>) -> some View {
modifier(FrameGetter(frame: frame))
}
}
struct FrameGetter: ViewModifier {
@Binding var frame: CGRect
func body(content: Content) -> some View {
content
.background(
GeometryReader { proxy -> AnyView in
let rect = proxy.frame(in: .global)
DispatchQueue.main.async {
self.frame = rect
}
return AnyView(EmptyView())
})
}
}
运用起来也很简略:
struct MyView: View {
@State private var frame: CGRect = CGRect()
var body: some View {
Rectangle()
.frameGetter($frame)
}
}