最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Accessing Coordinate Space Across Different Hierarchies - Stack Overflow

programmeradmin4浏览0评论

I have a circle view that is not in the same hierarchy where I can access the needed coordinateSpace in another hierarchy. My app's UI does not allow me to place my circle view within the hierarchy of the coordinateSpace. Even if I did, there are two different coordinateSpace instances in two separate hierarchies. The issue is how to access and use the coordinateSpace from another hierarchy to set my circle's position at that origin. I am using macOS 12.

import SwiftUI

struct ContentView: View {
    @State private var rectWhite: CGRect = CGRect()
    @State private var rectBlue: CGRect = CGRect()
    @State private var circlePoint: CGPoint = CGPoint()
    var body: some View {
        VStack {
            HStack {
                Color.white
                    .frame(width: 200.0, height: 200.0)
                    .background {
                        GeometryReader { geometryValue in
                            let frame = geometryValue.frame(in: .named("white"))
                            Color.clear
                                .onAppear {
                                    print("onAppear white:", frame)
                                    rectWhite = frame
                                }
                                .onChange(of: frame) { newValue in
                                    print("onChange white:", newValue)
                                    rectWhite = newValue
                                }
                            
                        }
                    }
                    .coordinateSpace(name: "white")
                    .padding(25)
                    .border(Color.black)
                
                Spacer()
                
                Color.blue
                    .frame(width: 200.0, height: 200.0)
                    .background {
                        GeometryReader { geometryValue in
                            let frame = geometryValue.frame(in: .named("blue"))
                            Color.clear
                                .onAppear {
                                    print("onAppear blue:", frame)
                                    rectBlue = frame
                                }
                                .onChange(of: frame) { newValue in
                                    print("onChange blue:", newValue)
                                    rectBlue = newValue
                                }
                            
                        }
                    }
                    .coordinateSpace(name: "blue")
                    .padding(25)
                    .border(Color.black)


            }

            Circle()
                .fill(Color.red)
                .opacity(0.75)
                .frame(width: 50, height: 50)
                .position(circlePoint)
            
            HStack {
                Button("set on white origin") {
                    circlePoint = rectWhite.origin
                }
                
                Button("set on blue origin") {
                    circlePoint = rectBlue.origin
                }
            }
        }
        .padding()

    }
}

I have a circle view that is not in the same hierarchy where I can access the needed coordinateSpace in another hierarchy. My app's UI does not allow me to place my circle view within the hierarchy of the coordinateSpace. Even if I did, there are two different coordinateSpace instances in two separate hierarchies. The issue is how to access and use the coordinateSpace from another hierarchy to set my circle's position at that origin. I am using macOS 12.

import SwiftUI

struct ContentView: View {
    @State private var rectWhite: CGRect = CGRect()
    @State private var rectBlue: CGRect = CGRect()
    @State private var circlePoint: CGPoint = CGPoint()
    var body: some View {
        VStack {
            HStack {
                Color.white
                    .frame(width: 200.0, height: 200.0)
                    .background {
                        GeometryReader { geometryValue in
                            let frame = geometryValue.frame(in: .named("white"))
                            Color.clear
                                .onAppear {
                                    print("onAppear white:", frame)
                                    rectWhite = frame
                                }
                                .onChange(of: frame) { newValue in
                                    print("onChange white:", newValue)
                                    rectWhite = newValue
                                }
                            
                        }
                    }
                    .coordinateSpace(name: "white")
                    .padding(25)
                    .border(Color.black)
                
                Spacer()
                
                Color.blue
                    .frame(width: 200.0, height: 200.0)
                    .background {
                        GeometryReader { geometryValue in
                            let frame = geometryValue.frame(in: .named("blue"))
                            Color.clear
                                .onAppear {
                                    print("onAppear blue:", frame)
                                    rectBlue = frame
                                }
                                .onChange(of: frame) { newValue in
                                    print("onChange blue:", newValue)
                                    rectBlue = newValue
                                }
                            
                        }
                    }
                    .coordinateSpace(name: "blue")
                    .padding(25)
                    .border(Color.black)


            }

            Circle()
                .fill(Color.red)
                .opacity(0.75)
                .frame(width: 50, height: 50)
                .position(circlePoint)
            
            HStack {
                Button("set on white origin") {
                    circlePoint = rectWhite.origin
                }
                
                Button("set on blue origin") {
                    circlePoint = rectBlue.origin
                }
            }
        }
        .padding()

    }
}
Share Improve this question asked Feb 16 at 18:08 MangoMango 635 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

Views can only read the coordinate spaces of their parents. You should put .coordinateSpace(name:) at the parent of all three views you are interested in, and work in that coordinate space only.

What complicates things is that the coordinate space that .position is working in, is a bit weird. As I explained in this answer, .position first takes up as much space as possible, and the CGPoint you give it is treated as a point in the rectangle it ends up taking up.

I'd recommend restructuring the view so that the circle is in a ZStack, so that .position would use the same coordinate space as the ZStack, as the circle will be the same size as the ZStack.

struct ContentView: View {
    // all these are in the parent coordinate space
    @State private var rectWhite: CGRect = CGRect()
    @State private var rectBlue: CGRect = CGRect()
    @State private var circlePoint: CGPoint = CGPoint()

    var body: some View {
        ZStack {
            VStack {
                HStack {
                    Color.white
                        .frame(width: 200.0, height: 200.0)
                        .modifier(FrameReader(frame: $rectWhite, coordinateSpace: .named("parent")))
                        .padding(25)
                        .border(Color.black)
                    Spacer()
                    Color.blue
                        .frame(width: 200.0, height: 200.0)
                        .modifier(FrameReader(frame: $rectBlue, coordinateSpace: .named("parent")))
                        .padding(25)
                        .border(Color.black)
                    
                    
                }
                HStack {
                    Button("set on white origin") {
                        circlePoint = rectWhite.origin
                    }
                    
                    Button("set on blue origin") {
                        circlePoint = rectBlue.origin
                    }
                }
            }
            Circle()
                .fill(Color.red)
                .opacity(0.75)
                .frame(width: 50, height: 50)
                .position(circlePoint)
        }
        .coordinateSpace(name: "parent")
        .padding()
    }
}

If you want to keep the circle in a VStack, you would also need to read the circle's frame relative to the parent's coordinate space, and do some conversions before passing it to .position.

struct ContentView: View {
    // all these are in the parent coordinate space
    @State private var rectWhite: CGRect = CGRect()
    @State private var rectBlue: CGRect = CGRect()
    @State private var circleFrame: CGRect = CGRect() // New @State here
    @State private var circlePoint: CGPoint = CGPoint()
    var body: some View {
        VStack {
            HStack {
                Color.white
                    .frame(width: 200.0, height: 200.0)
                    .modifier(FrameReader(frame: $rectWhite, coordinateSpace: .named("parent")))
                    .padding(25)
                    .border(Color.black)
                
                Spacer()
                
                Color.blue
                    .frame(width: 200.0, height: 200.0)
                    .modifier(FrameReader(frame: $rectBlue, coordinateSpace: .named("parent")))
                    .padding(25)
                    .border(Color.black)


            }

            Circle()
                .fill(Color.red)
                .opacity(0.75)
                .frame(width: 50, height: 50)

                // note this conversion we are doing
                .position(x: circlePoint.x - circleFrame.origin.x, y: circlePoint.y - circleFrame.origin.y)
                .modifier(FrameReader(frame: $circleFrame, coordinateSpace: .named("parent")))

            HStack {
                Button("set on white origin") {
                    circlePoint = rectWhite.origin
                }
                
                Button("set on blue origin") {
                    circlePoint = rectBlue.origin
                }
            }
        }
        .coordinateSpace(name: "parent")
        .padding()

    }
}

In both of the above code snippets, I have extracted the GeometryReader into its own view modifier, presented below for completeness.

struct FrameReader: ViewModifier {
    @Binding var frame: CGRect
    let coordinateSpace: CoordinateSpace
    
    func body(content: Content) -> some View {
        content
            .background {
                GeometryReader { geometryValue in
                    let frame = geometryValue.frame(in: coordinateSpace)
                    Color.clear
                        .onAppear {
                            self.frame = frame
                        }
                        .onChange(of: frame) { newValue in
                            self.frame = newValue
                        }
                    
                }
            }
    }
}

Another way to match the position of views across hierarchies is to use .matchedGeometryEffect. Here is how your example can be updated to work this way:

struct ContentView: View {
    @State private var circleTarget = ""
    @Namespace private var ns

    var body: some View {
        VStack {
            HStack {
                Color.white
                    .frame(width: 200.0, height: 200.0)
                    .matchedGeometryEffect(id: "white", in: ns, anchor: .topLeading)
                    .padding(25)
                    .border(.black)

                Spacer()

                Color.blue
                    .frame(width: 200.0, height: 200.0)
                    .matchedGeometryEffect(id: "blue", in: ns, anchor: .topLeading)
                    .padding(25)
                    .border(.black)
            }

            Circle()
                .fill(.red)
                .opacity(0.75)
                .frame(width: 50, height: 50)
                .matchedGeometryEffect(
                    id: circleTarget,
                    in: ns,
                    properties: .position,
                    anchor: .center,
                    isSource: false
                )

            HStack {
                Button("set on white origin") {
                    circleTarget = "white"
                }

                Button("set on blue origin") {
                    circleTarget = "blue"
                }
            }
        }
        .padding()
        .animation(.default, value: circleTarget)
    }
}
发布评论

评论列表(0)

  1. 暂无评论