了解 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) 开始,但您已经有了一个居中的正方形,因此 xy 中的偏移量均为 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")
}