GeometryReader的使用
4个月前 • 244次点击 • 来自 SwiftUI
GeometryReader是一个容器视图,根据其自身大小和坐标空间定义其内容。
/// A container view that defines its content as a function of its own
/// size and coordinate space. Returns a flexible preferred size to its
/// parent layout.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@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
}
如果你想要自定义绘制一个矩形,大小是父级视图的一半。位置位于父级视图右边距里5像素的视图。 此时就可以使用GeometryReader。
struct ContentView: View {
var body: some View {
VStack {
Text("Hello There!")
MyRectangle()
}.frame(width: 150, height: 100).border(Color.black)
}
}
struct MyRectangle: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.path(in: CGRect(x: geometry.size.width + 5,
y: 0,
width: geometry.size.width / 2.0,
height: geometry.size.height / 2.0))
.fill(Color.red)
}
}
}
GeometryProxy
GeometryReader尾随闭包类型是:
(GeometryProxy) -> Content
GeometryProxy类中有两个计算型属性,一个方法,和一个下标取值:
/// Acts as a proxy for access to the size and coordinate space (for
/// anchor resolution) of the container view.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
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.
///容器视图safeAreaInsets
public var safeAreaInsets: EdgeInsets { get }
/// The container view's bounds rectangle converted to a defined
/// coordinate space.
///父级视图建议区域的大小位置
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
}
父级视图会自动安排GeometryReader的视图大小,如下例:
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geo in
Text("Hello, World!")
.frame(width: geo.size.width * 0.9, height: 40)
.background(Color.red)
}.background(Color.green)
Text("More text")
.background(Color.blue)
}
}
}
可以看到,“More text”被推到了底部,而GeometryReader占据了剩余的所有空间,但是这是一个首选大小,而不是绝对大小,这意味着它仍然依赖于它的父级。
接下来重点来了:
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
frame方法暴露给我们了父级视图建议区域的大小位置,可以通过 .local / .global / .named() 来获取不同的坐标空间。
如下例:
struct OuterView: View {
var body: some View {
VStack {
Text("Top")
InnerView()
.background(Color.green)
Text("Bottom")
}
}
}
struct InnerView: View {
var body: some View {
HStack {
Text("Left")
GeometryReader { geo in
Text("Center")
.background(Color.blue)
.onTapGesture {
print("Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
print("Custom center: \(geo.frame(in: .named("Custom")).midX) x \(geo.frame(in: .named("Custom")).midY)")
print("Local center: \(geo.frame(in: .local).midX) x \(geo.frame(in: .local).midY)")
}
}
.background(Color.orange)
Text("Right")
}
}
}
struct ContentView: View {
var body: some View {
OuterView()
.background(Color.red)
.coordinateSpace(name: "Custom")
}
}
输出为:
Global center: 182.5 x 345.75
Custom center: 182.5 x 325.75
Local center: 144.5 x 292.75
3个点输出的坐标都不同,是因为它们的坐标系参照点是不同的:
- .global的(0,0)是整个屏幕的左上角,包括了safe area,
- OuterView通过修饰符“.coordinateSpace(name: "Custom")”自定义了坐标空间,.named()的(0,0)是OuterView左上角,不包括safe area,
- .local的(0,0)是InnerView左上角
你想使用哪个坐标空间取决于你想回答的问题:
- 想知道这个视图在屏幕上的什么位置?使用全局空间。
- 想知道这个视图相对于它的父视图的位置吗?使用本地空间。
- 如何知道这个视图相对于其他视图的位置?使用自定义空间。
下面我们使用GeometryReader画一个每个角指定不同的半径的矩形。
struct ContentView: View {
var body: some View {
HStack {
Text("SwiftUI")
.foregroundColor(.black).font(.title).padding(15)
.background(RoundedCorners(color: .green, tr: 30, bl: 30))
// .overlay(RoundedCorners(color: .green, tr: 30, bl: 30).opacity(0.5))
Text("Devler")
.foregroundColor(.black).font(.title).padding(15)
.background(RoundedCorners(color: .blue, tl: 30, br: 30))
// .overlay(RoundedCorners(color: .blue, tl: 30, br: 30).opacity(0.5))
}.padding(20).border(Color.gray).shadow(radius: 3)
}
}
struct RoundedCorners: View {
var color: Color = .black
var tl: CGFloat = 0.0
var tr: CGFloat = 0.0
var bl: CGFloat = 0.0
var br: CGFloat = 0.0
var body: some View {
GeometryReader { geometry in
Path { path in
let w = geometry.size.width
let h = geometry.size.height
// We make sure the redius does not exceed the bounds dimensions
let tr = min(min(self.tr, h / 2), w / 2)
let tl = min(min(self.tl, h / 2), w / 2)
let bl = min(min(self.bl, h / 2), w / 2)
let br = min(min(self.br, h / 2), w / 2)
path.move(to: CGPoint(x: w / 2.0, y: 0))
path.addLine(to: CGPoint(x: w - tr, y: 0))
path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
path.addLine(to: CGPoint(x: w, y: h - br))
path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
path.addLine(to: CGPoint(x: bl, y: h))
path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: tl))
path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
}
.fill(self.color)
}
}
}
标签