了解 SwiftUI 中的首选项
Understanding Preferences in SwiftUI
iOS 15 Swift 5.5 试图理解偏好,但在 GeometryReader 上苦苦挣扎
我在阅读教程时编写了这段代码,但它无法正常工作。绿框应该出现在红点上方,第一个。但它超过了中心那个。当我点击这个点时,它会移动到第三个...感觉就像 SwiftUI 画了整个东西然后改变了坐标的想法。当然我可以更改偏移量,但那是一个 hack。如果事情发生变化,SwiftUI 当然应该更新首选项。
import SwiftUI
struct ContentView: View {
@State private var activeIdx: Int = 0
@State private var rects: [CGRect] = Array<CGRect>(repeating: CGRect(), count: 12)
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY)
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
.animation(.easeInOut(duration: 1.0), value: rects)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
}
}
struct SubtleView: View {
@Binding var activeIdx:Int
let idx: Int
var body: some View {
Circle()
.fill(Color.red)
.frame(width: 64, height: 64)
.background(MyPreferenceViewSetter(idx: idx))
.onTapGesture {
activeIdx = idx
}
}
}
struct MyTextPreferenceData: Equatable {
let viewIdx: Int
let rect: CGRect
}
struct MyTextPreferenceKey: PreferenceKey {
typealias Value = [MyTextPreferenceData]
static var defaultValue: [MyTextPreferenceData] = []
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) {
value.append(contentsOf: nextValue())
}
}
struct MyPreferenceViewSetter: View {
let idx: Int
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: MyTextPreferenceKey.self,
value: [MyTextPreferenceData(viewIdx: self.idx, rect: geometry.frame(in: .named("myZstack")))])
}
}
}
尝试将 onPreferences 上下移动一个级别..将其附加到每个视图...但仍然没有更新...我在这里错过了什么=
您偏好读取绝对坐标,因此需要使用 position
而不是 offset
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY) // << here !!
测试 Xcode 13.2 / iOS 15.2
这里是完整的固定正文:
var body: some View {
ZStack {
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
.animation(.easeInOut(duration: 0.5), value: activeIdx)
问题是您使用的是 offset(x:y:)
而不是 position(x:y:)
。
您的 ZStack
的坐标将从 top-left 开始(本地)为 (0, 0)
。但是,默认情况下,绿色方块将在所有 3 个红色圆圈之间居中。这会导致问题,因为坐标从 (0, 0)
开始,但正方形开始居中(即偏离一个正方形)。
最初您的第一个圆从 (0, 0)
开始,但您已经有了一个居中的正方形,因此 x
和 y
中的偏移量均为 0
,因此已经居中,看起来你在不同的索引上。
通过替换为 position(x:y:)
并测量中点来解决此问题:
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY)
我认为答案很简单,ZStack
自然居中对齐,所以你的初始位置,在偏移量超过第二个圆圈之前,但你的 activeIdx
是 0,这是第一个圆圈。如果您在静态预览中的 Canvas 上查看它。只需将 ZStack
对齐方式更改为 .leading
对齐即可。
var body: some View {
ZStack(alignment: .leading) { // Set your alignment here
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY)
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
// Also change the animation value to activeIdx
.animation(.easeInOut(duration: 1.0), value: activeIdx)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
}
iOS 15 Swift 5.5 试图理解偏好,但在 GeometryReader 上苦苦挣扎
我在阅读教程时编写了这段代码,但它无法正常工作。绿框应该出现在红点上方,第一个。但它超过了中心那个。当我点击这个点时,它会移动到第三个...感觉就像 SwiftUI 画了整个东西然后改变了坐标的想法。当然我可以更改偏移量,但那是一个 hack。如果事情发生变化,SwiftUI 当然应该更新首选项。
import SwiftUI
struct ContentView: View {
@State private var activeIdx: Int = 0
@State private var rects: [CGRect] = Array<CGRect>(repeating: CGRect(), count: 12)
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY)
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
.animation(.easeInOut(duration: 1.0), value: rects)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
}
}
struct SubtleView: View {
@Binding var activeIdx:Int
let idx: Int
var body: some View {
Circle()
.fill(Color.red)
.frame(width: 64, height: 64)
.background(MyPreferenceViewSetter(idx: idx))
.onTapGesture {
activeIdx = idx
}
}
}
struct MyTextPreferenceData: Equatable {
let viewIdx: Int
let rect: CGRect
}
struct MyTextPreferenceKey: PreferenceKey {
typealias Value = [MyTextPreferenceData]
static var defaultValue: [MyTextPreferenceData] = []
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) {
value.append(contentsOf: nextValue())
}
}
struct MyPreferenceViewSetter: View {
let idx: Int
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: MyTextPreferenceKey.self,
value: [MyTextPreferenceData(viewIdx: self.idx, rect: geometry.frame(in: .named("myZstack")))])
}
}
}
尝试将 onPreferences 上下移动一个级别..将其附加到每个视图...但仍然没有更新...我在这里错过了什么=
您偏好读取绝对坐标,因此需要使用 position
而不是 offset
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY) // << here !!
测试 Xcode 13.2 / iOS 15.2
这里是完整的固定正文:
var body: some View {
ZStack {
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
.animation(.easeInOut(duration: 0.5), value: activeIdx)
问题是您使用的是 offset(x:y:)
而不是 position(x:y:)
。
您的 ZStack
的坐标将从 top-left 开始(本地)为 (0, 0)
。但是,默认情况下,绿色方块将在所有 3 个红色圆圈之间居中。这会导致问题,因为坐标从 (0, 0)
开始,但正方形开始居中(即偏离一个正方形)。
最初您的第一个圆从 (0, 0)
开始,但您已经有了一个居中的正方形,因此 x
和 y
中的偏移量均为 0
,因此已经居中,看起来你在不同的索引上。
通过替换为 position(x:y:)
并测量中点来解决此问题:
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.position(x: rects[activeIdx].midX, y: rects[activeIdx].midY)
我认为答案很简单,ZStack
自然居中对齐,所以你的初始位置,在偏移量超过第二个圆圈之前,但你的 activeIdx
是 0,这是第一个圆圈。如果您在静态预览中的 Canvas 上查看它。只需将 ZStack
对齐方式更改为 .leading
对齐即可。
var body: some View {
ZStack(alignment: .leading) { // Set your alignment here
RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green)
.frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height)
.offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY)
HStack {
SubtleView(activeIdx: $activeIdx, idx: 0)
SubtleView(activeIdx: $activeIdx, idx: 1)
SubtleView(activeIdx: $activeIdx, idx: 2)
}
// Also change the animation value to activeIdx
.animation(.easeInOut(duration: 1.0), value: activeIdx)
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
print("p \(p)")
}
}.coordinateSpace(name: "myZstack")
}