SwiftUI установить положение в центре другого вида - PullRequest
1 голос
/ 02 марта 2020

У меня есть два разных вида, один красный прямоугольник и один черный прямоугольник, который всегда расположен в нижней части экрана. Когда я щелкаю по красному прямоугольнику, он должен располагаться внутри другого прямоугольника.

click me

В настоящее время красный прямоугольник расположен статически: .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()
                    }
                }
            }
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 02 марта 2020

Здесь возможен подход (с немного упрощенным исходным снимком и добавлением удобного расширения View).

Протестировано с Xcode 11.2 / iOS 13.2

enter image description here

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")
    }
}
1 голос
/ 02 марта 2020

Сначала определите некоторую структуру, где хранить положение .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))
    }
}

используется для создания маленького черного прямоугольника, который будет позиционировать себя в центре других видов. Просто нажмите где-нибудь в зеленом или красном прямоугольнике, и черный сразу же переместится: -)

Вот представление этого запущенного примера приложения.

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...