在之前的SwiftUI之View Tree(PreferenceKey)這篇文章中,介紹了父view如何通過(guò)PreferenceKey獲取子view的信息绵咱,我們演示了這樣一個(gè)效果:
由于能夠獲取所有子view的位置信息砌梆,因此綠色圓圈可以移動(dòng)到準(zhǔn)確的位置上濒憋,核心代碼如下:
struct NumberPreferenceViewSetter: View {
let idx: Int
var body: some View {
GeometryReader { proxy in
Circle()
.stroke(Color.clear, lineWidth: 5)
.preference(key: NumberPreferenceKey.self, value: [NumberPreferenceValue(viewIdx: idx, rect: proxy.frame(in: .named("ZStackSpace")))])
}
}
}
var body: some View {
ZStack(alignment: .topLeading) {
...
VStack {
...
}
}
.onPreferenceChange(NumberPreferenceKey.self) { preferences in
for pre in preferences {
self.rects[pre.viewIdx] = pre.rect
}
}
.coordinateSpace(name: "ZStackSpace")
想了解更多的同學(xué),可以去看那篇文章但壮,在上邊的代碼中冀泻,我們必須把Circle()
放到GeometryReader
中才能獲取到view的frame信息常侣。
那么在本篇文章中,我們通過(guò)anchorPreference腔长,可以輕松獲取view的位置信息袭祟,我們通過(guò)演示下邊這樣一個(gè)效果,來(lái)說(shuō)明它的用法:
首先捞附,我們?nèi)匀恍枰x子view需要傳遞的數(shù)據(jù)信息是什么巾乳?
struct MySegementPreferenceData {
let viewIdx: Int
let bounds: Anchor<CGRect>
var topLeading: Anchor<CGPoint>? = nil
}
然后自定義一個(gè)唯一的PreferenceKey類(lèi)別,用于把同一個(gè)key下的數(shù)據(jù)匯總起來(lái):
struct MySegementPreferenceKey: PreferenceKey {
typealias Value = [MySegementPreferenceData]
static var defaultValue: Value = []
static func reduce(value: inout [MySegementPreferenceData], nextValue: () -> [MySegementPreferenceData]) {
value.append(contentsOf: nextValue())
}
}
最后在通過(guò).anchorPreference
modifier把數(shù)據(jù)傳遞進(jìn)來(lái):
struct SegementItem: View {
let title: String
let index: Int
let selectedIndex: Int
var body: some View {
Text(title)
...
.anchorPreference(key: MySegementPreferenceKey.self, value: .bounds, transform: {
[MySegementPreferenceData(viewIdx: index, bounds: $0)]
})
}
}
通過(guò)上邊代碼鸟召,可以看出胆绊,value: .bounds
中的bounds表明了我們要傳遞的位置信息,這里也可以是.topLeading
,.bottomTrailing
等等欧募。
像上邊的MySegementPreferenceData压状,包含了兩個(gè)信息,分別是bounds和topLeading跟继,如果連續(xù)寫(xiě)2個(gè).anchorPreference
是不行的种冬,最后邊的數(shù)據(jù)會(huì)覆蓋前邊的數(shù)據(jù),遇到這種情況舔糖,需要使用.transformAnchorPreference
:
.anchorPreference(key: MySegementPreferenceKey.self, value: .bounds, transform: {
[MySegementPreferenceData(viewIdx: index, bounds: $0)]
})
.transformAnchorPreference(key: MySegementPreferenceKey.self, value: .topLeading, transform: { (value: inout [MySegementPreferenceData], anchor: Anchor<CGPoint>) in
value[0].topLeading = anchor
})
.transformAnchorPreference
把獲取的.topLeading
賦值給了MySegementPreferenceData娱两。
本篇文章的演示代碼可以在這里下載:AnchorPreferenceDemo.swift
有意思的一點(diǎn)是,如果我打開(kāi)代碼中注釋的那一行代碼.animation(.easeInOut)
:
func createBottomLine(_ proxy: GeometryProxy, preferences: [MySegementPreferenceData]) -> some View {
let p = preferences.first(where: { $0.viewIdx == self.selectedIndex })
let bounds = proxy[p!.bounds]
return RoundedRectangle(cornerRadius: 2.5)
.foregroundColor(.green)
.frame(width: bounds.width, height: 5)
.offset(x: bounds.minX, y: bounds.height - 5)
/// 開(kāi)啟動(dòng)畫(huà)
.animation(.easeInOut)
}
運(yùn)行后的效果非常有意思:
總結(jié)
可能大家會(huì)有疑問(wèn)金吗,在哪種情況下需要用到AnchorPreferences呢十兢?其實(shí)很簡(jiǎn)單,當(dāng)你需要子view的位置信息的時(shí)候摇庙,考慮這個(gè)技術(shù)就可以了旱物。我們會(huì)在接下來(lái)的兩篇文章中講到幾個(gè)實(shí)際應(yīng)用。其中二叉樹(shù)的例子還算有趣卫袒。
SwiftUI集合:[FuckingSwiftUI