文章源地址:https://swiftui-lab.com/geometryreader-to-the-rescue/
作者: Javier
翻譯: Liaoworking
大多數(shù)情況下纠吴,SwiftUI都會發(fā)揮其神奇的布局的特性。但是有時候,我們需要對自定義視圖的布局進(jìn)行更多操作。目前我們有幾種工具。第一個需要我們?nèi)ヌ剿鞯木褪?strong>GeometryReader。
父級視圖想要什么?
當(dāng)我們創(chuàng)建自定義視圖時靶病,一般不用擔(dān)心旁邊視圖的布局或size。如果你想要創(chuàng)建一個正方形口予。只要用一個Rectangle娄周,就會以父級想要的size和position去畫出一個正方形。
在下面的示例中沪停,我們有一個frame為150×100的VStack煤辨。上面部分是Text,剩余空間都給了MyRectangle()木张。如圖所示都被藍(lán)色顏色填充:
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 {
Rectangle().fill(Color.blue)
}
}
正如你所看到的众辨,MyRectangle(),不用去設(shè)置size舷礼,它只有一個任務(wù)鹃彻,就是畫矩形。讓SwiftUI自己去管理好父級期望的子視圖的大小和位置妻献。 這個例子里Vstack就是父級視圖蛛株。
如果你想要知道更多關(guān)于父級視圖如何確定子視圖的位置和大小。我強烈推薦你看看2019WWDC session 237
Building Custom Views With SwiftUI
父級視圖會自動為子視圖找到合適的尺寸和位置育拨。但是如果你想要自定義繪制一個矩形谨履,大小是父級視圖的一半。位置位于父級視圖右邊距里5像素的視圖熬丧。
其實也并不復(fù)雜笋粟,這個時候可以用GeometryReader作為解決方案。
子視圖做了什么析蝴?
先看看Apple官方文檔如何介紹的GeometryReader:
A container view that defines its content as a function of its own size and coordinate space.
一個容器視圖害捕,根據(jù)其自身大小和坐標(biāo)空間定義其內(nèi)容。
這個解釋已經(jīng)算很詳細(xì)了闷畸。
那這句話是什么意思呢尝盼? 簡單來講GeometryReader就是另外一種view。驚不驚喜腾啥? 在SwiftUI中幾乎所有東西
都是View东涡。 在下面的例子中,GeometryReader讓你定義了它的content倘待。 但是與其他View 不同疮跑。你可以拿到一些你在其他View中拿不到的信息。
上面說到想要繪制一個大小是父級視圖的一半凸舵。位置位于父級視圖右邊距里5像素的視圖∽婺铮現(xiàn)在我們有了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.blue)
}
}
}
GeometryProxy
上面的例子中啊奄,閉包中的geometry是一個GeometryProxy類的變量渐苏。我們可以通過Inspecting the View Tree(檢查視圖樹結(jié)構(gòu))這篇文章去了解更多相關(guān)內(nèi)容。
在GeometryProxy類中有兩個計算型屬性菇夸,一個方法琼富,和一個下標(biāo)取值。
public var size: CGSize { get }
public var safeAreaInsets: EdgeInsets { get }
public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
public subscript<T>(anchor: Anchor<T>) -> T where T : Equatable { get }
size屬性是父級視圖建議的大小
GeometryProxy 把 safeAreaInsets也暴露給了我們庄新。
frame方法暴露給我們了父級視圖建議區(qū)域的大小位置鞠眉,可以通過.local,.global,.named()來獲取不同的坐標(biāo)空間。 .named() 用來獲取一個被命名的坐標(biāo)空間择诈。我們可以通過這個命名來獲取其他view坐標(biāo)空間械蹋。 Inspecting the View Tree 這篇文章中有具體的使用方法
最后,我們可以通過下標(biāo)取值來獲取一個錨點<T>羞芍。這個是GeometryReader的一個炫酷的功能哗戈。但也比較繁瑣,我將在second part of Inspecting the View Tree有講解荷科∥ㄒВ看完后就會有一個了解〔阶觯可以獲取視圖樹中任何子級視圖的size和x,y. 是不是很強大副渴,那你得先學(xué)啊。
吸收另外一個View的Geometry
GeometryReader 功能已經(jīng)相當(dāng)強大全度,但它如果與 .background()或 .overlay()的modifier相結(jié)合使用煮剧,功能就會更強大。
在我見過的教程中 background 都是以下面這種形式使用的:Text("hello").background(Color.red)
第一眼看将鸵,都會以為Color.red
是一個顏色參數(shù),它設(shè)置了背景色是紅色勉盅,其實并不是,Color.red
是一個View顶掉!它的功能就是把父級視圖所建議的區(qū)域填充為紅色草娜。它的父級就是背景。而且背景修改了Text痒筒。所以建議Color.red
所填充的區(qū)域就是Text("Hello")
所在的區(qū)域宰闰。是不是很優(yōu)美茬贵?
.overlay 修改器也是同樣的道理,只是它并不是繪制背景移袍,而是繪制前景而已解藻。
我們已經(jīng)知道了,我們可以給任意一個view使用.Color()方法還有 .background()方法葡盗。下面我們將結(jié)合GeometryReader螟左,畫一個每個角指定不同的半徑的矩形的例子來演示如何利用它們。
具體實現(xiàn)如下:
struct ContentView : View {
var body: some View {
HStack {
Text("SwiftUI")
.foregroundColor(.black).font(.title).padding(15)
.background(RoundedCorners(color: .green, tr: 30, bl: 30))
Text("Lab")
.foregroundColor(.black).font(.title).padding(15)
.background(RoundedCorners(color: .blue, tl: 30, br: 30))
}.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)
}
}
}
另外觅够,我們對自定義的Overlay設(shè)置透明度為0.5胶背,設(shè)置在Text上。
代碼如下:
Text("SwiftUI")
.foregroundColor(.black).font(.title).padding(15)
.overlay(RoundedCorners(color: .green, tr: 30, bl: 30).opacity(0.5))
Text("Lab")
.foregroundColor(.black).font(.title).padding(15)
.overlay(RoundedCorners(color: .blue, tl: 30, br: 30).opacity(0.5))
關(guān)于雞和雞蛋的問題
當(dāng)你開始使用GeometryReader喘先, 你就會發(fā)現(xiàn)所謂的雞和雞蛋的問題钳吟。
因為GeometryReader需要給子級試圖提供可用空間,它首先需要盡可能多的占用空間窘拯。 但是子類可能會設(shè)置一個小的空間砸抛,這時候GeometryReader還是盡可能的保持大。
一旦子級試圖確定了其所需空間树枫, 你可能會被迫縮小GeometryReader直焙。這時候子級試圖就會GeometryReader計算出的新的大小做出反應(yīng)。 一個循環(huán)就產(chǎn)生了砂轻。
所以 當(dāng)遇到是子級試圖依賴父級試圖的大小奔誓,還是父級試圖依賴于子級試圖的大小。 可能GeometryReader并不適合解決你的布局問題搔涝。由此厨喂,你可以看看我的下一篇文章Preferences and Anchor Preferences.
總結(jié)
今天所學(xué)的GeometryReader讓我們的自定義view知道了它們所需的大小和位置。 我們還學(xué)習(xí)了獲取其他view的geometry庄呈。
這只是很第一篇官方并沒有提及的講SwiftUI中的布局工具的文章蜕煌,接下來我們將會深度研究view的數(shù)層次,和子級試圖如何把數(shù)據(jù)向上傳遞诬留。點我查看