SwiftUI 将位置设置为不同视图的中心

Kev*_*inP 5 swift 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()
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Asp*_*eri 7

这是可能的方法(稍微简化了您的初始快照并添加了一些方便的View扩展)。

使用 Xcode 11.2 / iOS 13.2 进行测试

在此输入图像描述

extension View {
    func rectReader(_ binding: Binding<CGRect>, in space: CoordinateSpace) -> some View {
        self.background(GeometryReader { (geometry) -> AnyView in
            let rect = geometry.frame(in: space)
            DispatchQueue.main.async {
                binding.wrappedValue = rect
            }
            return AnyView(Rectangle().fill(Color.clear))
        })
    }
}

struct ContentView: View {

    @State private var tap = false
    @State private var bottomRect: CGRect = .zero

    var body: some View {
        ZStack(alignment: .bottom) {
            RoundedRectangle(cornerRadius: 10)
                .frame(maxWidth: .infinity, maxHeight: 50, alignment: .center)
                .padding()
                .rectReader($bottomRect, in: .named("board"))

            Rectangle()
                .foregroundColor(.red)
                .overlay(Text("Click me")
                    .fontWeight(.light)
                    .foregroundColor(.white)
                )
                .frame(width: 50, height: 50)
                .position(x: self.tap ? bottomRect.midX : 50,
                          y: self.tap ? bottomRect.midY : 50)
                .onTapGesture {
                    withAnimation {
                        self.tap.toggle()
                    }
                }
        }.coordinateSpace(name: "board")
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Mamaessen 我仍然建议在一些现实世界的应用程序中使用更通用的解决方案,正如我的答案所示。 (2认同)

use*_*734 7

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

struct PositionData: Identifiable {
    let id: Int
    let center: Anchor<CGPoint>
}
Run Code Online (Sandbox Code Playgroud)

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

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

为了能够读取 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)]
            }
        //}
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

代码几乎不言自明,我们.background(PositionReader(tag:))用来保存视图的中心位置(这可以通过直接在视图上应用 .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))
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

在此处输入图片说明