Сначала определите некоторую структуру, где хранить положение .center некоторого View
struct PositionData: Identifiable {
let id: Int
let center: Anchor<CGPoint>
}
Встроенный механизм для сохранения таких данных и представления их в родительский View - установка / чтение (или реагирование) на значения, соответствующие протоколу PreferenceKey.
struct Positions: PreferenceKey {
static var defaultValue: [PositionData] = []
static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) {
value.append(contentsOf: nextValue())
}
}
Чтобы иметь возможность считывать центральные позиции View, мы можем использовать хорошо известный и широко обсуждаемый GeometryReader. Я определяю свой PositionReader как View, и здесь мы можем просто сохранить его центральное положение в наших настройках для дальнейшего использования. Нет необходимости переводить центр в другую систему координат. Чтобы идентифицировать View, его значение тега также должно быть сохранено
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)]
}
//}
}
}
Чтобы продемонстрировать, как использовать все это вместе, см. Следующее простое приложение (copy - paste - run)
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:))
, чтобы сохранить центральную позицию просмотра (этого можно избежать, применяя .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))
}
}
используется для создания маленького черного прямоугольника, который будет позиционировать себя в центре других видов. Просто нажмите где-нибудь в зеленом или красном прямоугольнике, и черный сразу же переместится: -)
Вот представление этого запущенного примера приложения.
