探究视图树-part2 AnchorPreferences


译自 Inspecting the View Tree (Anchor Preferences) – Part 2 – The SwiftUI Lab
更多内容,欢迎重视公众号「Swift花园」

在 第一部分 的文章中,咱们介绍了 preference 的运用。关于向上传递信息的. 5 a场景,这个东西非常有用。经过界说 PreferenceKey 的关联类型,咱们能够把任何东西放进那里。

在第二部分,是时分介绍 Anchor Preferences 了。写这篇文章的时分,我找不到任何相关文档,博客或许文章介绍怎么运用这些难以了解的工具。所以,请你们加入我,一同探索这未知的疆域。

直觉上一开端 Anchor Preference 不好了解,但一旦咱们把握了它们,就很难忘记了。为了让工作简略一点,咱们仍是以第一部分里的比如来解说。假如你关于应战本身现已很熟悉,那当然很好,这样你就能专注于一切这些新特性。不像之前的处理计划,咱们不再用到坐标系,并且会把 .onPreferenceChange() 换成别的东西。

那个比如又来了:咱们要让R h 1边框从一个月份移动到另一个月份,带有动画效果:

探究视图树-part2 AnchorPreferences

锚点偏好(Anchor Preference)

现在让咱们热烈欢迎: Anchor。这是一个存放类型 T 的不透明类型,T 能够是 CGRect 或许 CGPoint。咱们经过用 Anchor 来拜访视图的鸿沟,用 Anchor 拜访诸j r Q ( Y如 top,topLeading,topTrailing,center,trailing,bottom,bottomLeading,bottomTrailing 和 leading 等视图特点。

因为它是一个不透明类型,所以咱们不能独自运用它。还记得{ A # i 0 0 GeometryReader to the Rescu, i ! & z ) De一文中介绍过 GeometryProxy 的下标 getter 吗?当咱们运用 Anchor 的值作为 geometry proxy 的索引时,你能够得到表明 CGRect 或许 CGPoint 值。一起,它们现已被转换成 Geometr* d RyReader 视图的坐标空间。

咱们先经过修正 PreferenceKey 处理的数据开端吧。在这个比如中,咱们要用 Anchor 替换掉 CGRect:

struct MyTextPreferenceData {H . W P %
let viewIdx: Int
let bounds: Anchor<CGRect>
}

咱们的 Pr5 v ~ * k { [eferenceKey 坚持不变:

struct MyTextPreferenceKey: PreferenceKey {
typealias Value = [MyTextPreferenceData]
static var defaultValue: [MyP _  p #TextPreferenceData] = [0 p 2 F g ~ o]
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [c g z - 0 ] YMyTE W 6 L textPreferenceData]) {
value.append(contentsOf: nextValue())
}
}

MonthView 现在变得更简洁了。咱们不再运用 .preference(),而是调用 modifier .anchorPrefere% 5 mnce()。不像其他办法,这儿咱们指定一个值(在m w p S 5比如里是 .bounds),它表明咱们的J n 7 u K K transfo! X 1 E u Xrm 闭包拿到一个表明被修正视图的鸿沟] + – G I . (的 AnchorO P f % d } 2 i w 。跟惯例特点相似的处理方式,咱们用 ($0) 来创立咱们的 MyTextPreferenceData 值。这样一来,咱们# 9 u % T就不再需求在 .background() modifier 里运用 GeometryReader 来获取文本视图的鸿沟了。

为了便于你了解,咱们仍是看看代码:

struct MonthView: View {
@Binding var activeMonth: Int
let label: St! o O Q o ` uring
let idx: Int
var body: some View {
Text(label)
.padding(10)
.anchorPreference(R t I , M * | Rkey: MyTextPreferenceKey.self, valu , = - D g / ( {e: .bounds, transform: { [M- I ( f DyTextPreferenceData(viewIdx: self.idx, bounds: $0)] })
.onTapGesture { selfm _ 4 -.activeMonth = self.idx }
}
}

最终,咱们来更新 ContentView。这儿有一c – W =些变化。首要,咱们不再运用 .onPreferenceChange(),而是调用 .backgroundPreferenceValue(S c ^ _ i)。这是一个相似 .background() 的 modifier,但它有一个很大的改进:能够拜访整个视图树的偏好数组。经过这种方式,咱们得到了一切月份视图的鸿沟,能够运用它们来核算需求在t e H何处制作鸿沟。

还有一个当地需求运用 GeometryReader,这是为了了解 Anchor 的值。注意,咱们不再需求关怀坐标空间了,GeometryReader 会处理它。

struct ContentView : Vie5 7 0 1 lw {
@State private var activeIdx: Int = 0
var body: someV J R - 8 View {
VStack {
Spacer()
HStack {
MonthView(activeMonth: $activeIdx, label: "January", idx: 0)
MonthVi{ T F s 3ew(activeMonth: $actk v # o ,iveIdx, label: "February", idx: 1)
MonthView(activeMonth: $activeIdx, label: "March", idx: 2)
MonthView(activeMo~ 9 Lnth: $activeIdx, label: "April", idx: 3)
}
Spacer()
HStack {
MonthView(activeMonth: $activeIdx, label: "May", idk - Q 0x: 4)
MonthView(active@ u @ 8 w j L Z ^Month: $activeIdx, label: "June", idx: 5)
MonthView(act. E g 7 q NiveMonth: $activef . @ s  F [ | FIdx, label: "July", idx: 6)
MonthView(activeMonth: $activeIdx, label: "August", idx: 7)
}
Spao # @ O = q wcer( R j()
HStackn 6 z 7 ] C ^ H {
MonthView(activeMonth: $activeIdx, label: "September", idx: 8)
MonthView(activeMonth: $activeIdx, label: "October", idx: 9)
MonthView(activeMonx F G ! h c e X Bth: $activeIdx+  J j 0 S G, label: "Na ` # m , q 6 b iovember", idx: 10)
MonthView(activeMonth: $activeIF u D c & 1 { Odx, label: "December", idx: 11)
}
Spacer(9 , X #)
}.backgroundPreferenceValue(MyTextPreferenceKey.self) { preferet q B I X nces in
GeometryReader { geometry in
self.createBorder(geometry, preferences)
.frame(maxWidth: .infinity, maxHeight: .inD & 2 R d Ffinity, alignment: .topLeading)
}
}
}
func createBorder(_ geometry: GeometryProxy, _ preferences: [MyTeD E S F l 6 , 6 9xtPreferenceData]) -> some View {
let p = preferences.first(where: { $0.viewIdx == self.activeIdx })
let bounds = p != nk y a e @ + q |il ? geometry[p!.bounds] : .zero
return RoundedRectangle(cornerRadius:N 7 D % n 15)
.stroke(lineWidth: 3.0)
.foregroundColor(Q t y A D TColor.green)
.frame(width: bounds.size.width, height: bounds.size.height)
.fixedS$ , # : v ) p m xizI j ^ U H S Z J pe()
.offset(x: bounds.minX, y: bounds.minY)
.animation(.easeInOut(du] ! Z B # ^ration: 1.0))
}1 3 W N N G . g
}

.backgroundPrefereM ^ l k n - w q wnceValue() 对应 .overlayPS o * ; | )referenceValue()。它的功用相同,只不过不是在后面制作,而是在视图前面制作。

带有一个 Pre[ e # |ferenceKey 的多个+ G _ E b锚点偏好

咱们现已知道不止有一F P p t种 Anchor 值。有 bounds,也有 topLeadV D X A B r +ingcenterbottom,等等。可能有时分咱们需求获取多个这样的值。可是s n ( Z q X ~ & k,正如咱们将学习到的,这并不像对一切这些值调用 .anchorPrefe` ] C v Y Z 0 Hrencr K L c ee()S Y f M w 那么简略。为了阐明这一点,咱们再来处理一遍那个问E x E c .题。

但这一回咱们不用 Anchor 来获取月份视图的鸿沟,而是运用两个独自的 Anchor 值。其中一个用于 topLeading,而另一个用于 bottoo @ ? 6mTrailing。提醒一下,关于这个特定问题,选用 Anchor 是更好的计划。不过咱们这儿选用第三m T o * z { B ` 5种办法,仅仅为了S d I F | D学习怎么在同一个视图上获取多个锚点偏好。

咱们首要修正 MyTextPreferenceData 来包容矩z k d W y k形的两个端点。这次咱们需求把它们设置为可选型,因为两者不能被一起设置。

struct MyTextPreferenceData {
let viewIdx: Int
var topLeading: Anchor<CGPoint>k h ` B p `;? = nil
v! s ) ! v N yar bottomTrailing: Anchor<v n Z V 5 2 m s;CGPoint>? = nil
}

PreferenceKey 坚持不变:

struct MyTextPreferency Q 7 l 9 eKey: PreferenceKey {
typealias Value = [/ E b D r 7MyTextPrefern d menceData]
static var defaultValue: [MyTextPreferenceData] = []
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferencej j GData]) {
value.append(contentsOf: nextValue())
}
}

咱们的月份视图约束需求设置两个锚点偏好。可是,假如咱们对同一个视图调用超过一次 .anchorPreference(),那么只要最终一次会生效。因而,咱们只能调用一次 .anchorPreference(),然后用 .2 i 7 Q K rtransformAnchorPreference() 填充缺失的数据:

struct Mont= X l 4 ~ s ! u hView: View {
@Binding va7 . ! 1 4 Ur activei _ OMonth: Int
let label: String
let idx: Int
var body: some View {
Text(label)
.padding(10)
.anchorPrefer8 F S lence(key: MyTextPreferenceKey# j U , ~ (.V & ?self, value: .topLeading,l b q 9 @ J K transform: { [MyTextPreferenceData(vied 5 L vwIdx: self.idD x ` O H a f u Zx, topLeading: $0)] })
.transformAnchorPreH i R vference(key:g ^ b p 2 I MyTextPreferenceKey.self, value: .bottomTrailing, transform: { ( value: inout [MyTextPreferencu n + 1 k o 3 E reData], anchor: Anchor<CGPoint>) in
v| 4 D % B , ualue[0].botto( & A i a . . }mTrailing = anchor
})
.onTapGesture { self.activeMonth = self.idx }
}
}

最终e l , $ y i ^ },咱们相应地更新 .createBorder(),令它根据两个点代替矩形来核算:

struct ContentView : View {
@State private var ax k @ sctiveIdx: Int = 0
var body: some View {
VStack {
Spacer()
HStack {
MonthView(activeMonth: $active4 @ j t K P 6Idx, label: "January", idx: 0)
MonthView(activeMonth: $% j { WactiveIdx, lj J j % 0abel: "February", idx: 1)
MonthView(activeMonth: $activeIdx, label: "March", idx: 2)
MonthView(activeM, `  8 h j = v Month: $activeIdx, lam + 0bel: "April: $ z", idx: 3)
}
Sp% e 9 P s (acer()
HStack {
MonthView(activeMonth: $activeIdx, label: "May", idx: 4)
MonthView(activeMonth: $activeIdx, label: "June", idx: 5U I P U * C)
MonthView(actA 4 $i{ X  @veMonth: $activeIdx, label: "July", idx: 6)
MonthView(activeMonth: $activz s H 8 / o ( veIdx, label: "Augusp Q , 6 rt", idx: 7)
}
SpaceQ ] C w d c Jr()
HStack {
MonthViewz { D ] S(activeMonth: $W [ L ( * & K mactiveIdx, label: "September", idx: 8)
MonthView(activeMonth: $activeIdx, label: "October", idx: 9)
MonthView(active9 V , t 3 Q 1Month: $activeIdx, label: "November", i[ 9 p 4 s 1 =dx: 10)
MonthView(activeMonth: $activ` z R g b ~eIdx, label: "December", idx: 11)
}
Spacer()
}.backgroundPreferenceValue(MyTexo T o 0 LtPre) | d | * t -ferenceKey.self) { prefeW ( Z I R C xrences in
GeometryReai j N S ) lder { geometry in
self.createBorder(geometry, preferences)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeadio ! * z Bng)
}
}
}
func createBorder(_ geometry: GeometryPro] 0 /xy, _ preferences: [MyT@ $ q u e N _ 9 fextPreferenceData]) -> some View {
let p = preJ 7 - # 3fereW b Nnces.first(where: { $0.view& Q  o ]Idx == self.activeIdx })
let aTopLeading = p?.topLeading
let aBottomTrailing = p?.bottom- d A h j ? o GTrailing
let topLeading = aTopLead- Y  Ping != nil ? geometry[aTopLeading!] : .zero
let bottomTrailing = aBottomT5 x 7 z v |railing !Z l C e 9 x= nil ? geometry[aBottomTrailing!] : .zero
return RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 3.0)
.foregro3 1 ; I ! | QundColor(Color.green)
.frame(width:% % ? = . o % & bottomTrailing.x - topLeading.x, height: bottq S a [ 8 { D romTrailing.y - topLeading.y)
.fixedSize()
.offset(x: topLeading.x, y: topLeadin| @ g.y)
.animation(.ea1 u 3 d v CseInOut(duration: 1.0))
}
}

嵌套的视图

目前为止,咱们一直在处理兄弟视图(或许表亲视图)5 ` n 5 P。可是,当咱们要给嵌套的视图设置偏好时,工作就变得更有应战性了。e $ Q N $ f K X这个时分 .transfor( h Q 9mAnchorPreferencel 2 [ = ] * =() 变得更_ ~ y S p加重要。例如,8 M } # 5 o Q你有两个视图,分别是父级和子级,在两者上都设置 .anchorPreference() 将不起作用。子视图的闭包将不会履行。要处理这个问题,需求在子视图运用 anchorPreference() 在父视图上运用 transformAnchorPreference()。至于为什么要这么做,咱们详细阐明。

下一步

这个系列的最终一步,咱们要运用一个不一样的比如来解说。咱们要创立一个迷你地图。这个迷你地图经过读取一个表单的视图树来构建。咱们还将看x A + S d H h到修正表单的视图树怎么对迷你地图产生直接影响,它只对表单视图树的偏好变化做出响应。

先睹为快:

探究视图树-part2 AnchorPreferences



我的公众号 这儿有Swift及核算机编程的相关文章,以及优秀& s t D x国外文章翻译,欢迎重视~

探究视图树-part2 AnchorPreferences

发表评论

提供最优质的资源集合

立即查看 了解详情