Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
637 views
in Technique[技术] by (71.8m points)

swift - SwiftUI set position to center of different view

I have two different views, one red rect and one black rect that is always positioned on the bottom of the screen. When I click the red rect it should position itself inside the other rect.

click me

Currently the red rect is positioned statically: .position(x: self.tap ? 210 : 50, y: self.tap ? 777 : 50). Is there a way to replace the 210 and 777 dynamically to the position of the black rects center position?

I know that I can use the GeometryReader to get the views size, but how do I use that size to position a different view? Would this even be the right way?

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()
                    }
                }
            }
        }
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

First define some structure where to store .center position of some View

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

The build-in mechanism to save such data and expose them to parent View is to set / read (or react) on values which conforms to PreferenceKey protocol.

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

To be able to read the center positions of View we can use well known and widely discussed GeometryReader. I define my PositionReader as a View and here we can simply save its center position in our preferences for further usage. There is no need to translate the center to different coordinate system. To identify the View its tag value must be saved as well

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)]
            }
        //}
    }
}

To demonstrate how to use all this together see next simple application (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()
    }
}

The code is almost self explanatory, we use .background(PositionReader(tag:)) to save the center position of View (this could be avoided by applying .anchorPreference directly on the View) and

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

is used to create small black rectangle which will position itself at center of other Views. Just tap anywhere in green or red rectangles, and the black one will move immediately :-)

Here is view of this sample application running.

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...