首页 > 解决方案 > SwiftUI 将位置设置为不同视图的中心

问题描述

我有两个不同的视图,一个红色矩形和一个黑色矩形,始终位于屏幕底部。当我单击红色矩形时,它应该将自己定位在另一个矩形内。

点我

目前红色矩形是静态定位的:.position(x: self.tap ? 210 : 50, y: self.tap ? 777 : 50). 有没有办法将 210 和 777 动态替换到黑色矩形中心位置的位置?

我知道我可以使用 GeometryReader 来获取视图大小,但是如何使用该大小来定位不同的视图?这甚至是正确的方法吗?

struct ContentView: View {

    @State private var tap = false

    var body: some View {
        ZStack {
            VStack {
                Spacer()
                RoundedRectangle(cornerRadius: 10)
                    .frame(maxWidth: .infinity, maxHeight: 50, alignment: .center)
            }
            .padding()

            VStack {
                ZStack {
                    Rectangle()
                        .foregroundColor(.red)
                    Text("Click me")
                        .fontWeight(.light)
                        .foregroundColor(.white)
                }
                .frame(width: 50, height: 50)
                .position(x: self.tap ? 210 : 50, y: self.tap ? 777 : 50)
                .onTapGesture {
                    withAnimation {
                        self.tap.toggle()
                    }
                }
            }
        }
    }
}

标签: swiftswiftui

解决方案


首先定义一些结构来存储一些视图的 .center 位置

struct PositionData: Identifiable {
    let id: Int
    let center: Anchor<CGPoint>
}

保存这些数据并将它们公开给父视图的内置机制是设置/读取(或反应)符合 PreferenceKey 协议的值。

struct Positions: PreferenceKey {
    static var defaultValue: [PositionData] = []
    static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) {
        value.append(contentsOf: nextValue())
    }
}

为了能够读取 View 的中心位置,我们可以使用众所周知且广泛讨论的 GeometryReader。我将我的 PositionReader 定义为一个视图,在这里我们可以简单地将其中心位置保存在我们的首选项中以供进一步使用。无需将中心转换为不同的坐标系。要识别视图,还必须保存其标签值

struct PositionReader: View {
    let tag: Int
    var body: some View {
        // we don't need geometry reader at all
        //GeometryReader { proxy in
            Color.clear.anchorPreference(key: Positions.self, value: .center) { (anchor)  in
                [PositionData(id: self.tag, center: anchor)]
            }
        //}
    }
}

要演示如何一起使用所有这些,请参阅下一个简单的应用程序(复制 - 粘贴 - 运行)

import SwiftUI

struct PositionData: Identifiable {
    let id: Int
    let center: Anchor<CGPoint>
}
struct Positions: PreferenceKey {
    static var defaultValue: [PositionData] = []
    static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) {
        value.append(contentsOf: nextValue())
    }
}

struct PositionReader: View {
    let tag: Int
    var body: some View {
        Color.clear.anchorPreference(key: Positions.self, value: .center) { (anchor)  in
                [PositionData(id: self.tag, center: anchor)]
        }
    }
}

struct ContentView: View {
    @State var tag = 0
    var body: some View {
        ZStack {
            VStack {
                Color.green.background(PositionReader(tag: 0))
                    .onTapGesture {
                    self.tag = 0
                }
                HStack {
                    Rectangle()
                        .foregroundColor(Color.red)
                        .aspectRatio(1, contentMode: .fit)
                        .background(PositionReader(tag: 1))
                        .onTapGesture {
                            self.tag = 1
                        }
                    Rectangle()
                        .foregroundColor(Color.red)
                        .aspectRatio(1, contentMode: .fit)
                        .background(PositionReader(tag: 2))
                        .onTapGesture {
                            self.tag = 2
                        }
                    Rectangle()
                        .foregroundColor(Color.red)
                        .aspectRatio(1, contentMode: .fit)
                        .background(PositionReader(tag: 3))
                        .onTapGesture {
                            self.tag = 3
                        }
                }
            }
        }.overlayPreferenceValue(Positions.self) { preferences in
            GeometryReader { proxy in
                Rectangle().frame(width: 50, height: 50).position( self.getPosition(proxy: proxy, tag: self.tag, preferences: preferences))
            }
        }
    }
    func getPosition(proxy: GeometryProxy, tag: Int, preferences: [PositionData])->CGPoint {
        let p = preferences.filter({ (p) -> Bool in
            p.id == tag
            })[0]
        return proxy[p.center]
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

代码几乎是不言自明的,我们.background(PositionReader(tag:))用来保存 View 的中心位置(这可以通过直接在 View 上应用 .anchorPreference 来避免)和

.overlayPreferenceValue(Positions.self) { preferences in
    GeometryReader { proxy in
        Rectangle().frame(width: 50, height: 50).position( self.getPosition(proxy: proxy, tag: self.tag, preferences: preferences))
    }
}

用于创建将自身定位在其他视图中心的黑色小矩形。只需点击绿色或红色矩形中的任意位置,黑色的会立即移动 :-)

这是此示例应用程序运行的视图。

在此处输入图像描述


推荐阅读