我有两个不同的视图,一个红色矩形和一个总是位于屏幕底部的黑色矩形。当我单击红色矩形时,它应该将自身定位在另一个矩形内。
目前,红色矩形是静态定位的:.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)
这是可能的方法(稍微简化了您的初始快照并添加了一些方便的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)
首先定义一些结构来存储一些视图的 .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)
用于创建小的黑色矩形,它将自己定位在其他视图的中心。只需点击绿色或红色矩形中的任意位置,黑色矩形就会立即移动:-)
这是此示例应用程序运行的视图。
| 归档时间: |
|
| 查看次数: |
2806 次 |
| 最近记录: |