1

I'm trying to write a view extension with a ViewModifier that allows me to position any view in the CoordinateSpace of another. My code so far does work, except that it has alignment issues if one of the CoordinateSpaces has a safe area. I'm able to fix it by adding .edgesIgnoringSafeArea(.all) to the target CoordinateSpace but I would like to use this extension without having to worry about the safe area of the other CoordinateSystems.

To clarify my code:

In my code the first circle is inside a HStack and GeometryReader and sets a CoordinateSpace called "otherSpace". The circle in the other HStack sets its position based on the position in "otherSpace". The first HStack and GeometryReader is on top, and has a top edgeInset of 20. The second one has none. Looking at the values the GeometryProxy (in the ViewModifier) gives me, I can't find a way to figure out if there is a way to calculate the edgeInsets.

Here is my code so far:

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        
        VStack {
            HStack {
                GeometryReader() { geometryProxy in
                    Circle()
                        .stroke(lineWidth: 3)
                        .fill(Color.blue)
                        .frame(width: 100, height: 100, alignment: .center)
                        .coordinateSpace(name: "otherSpace")
                        .position(CGPoint(x: 100, y: 100))
                        .edgesIgnoringSafeArea(.all) //I'd like a way not to use this.
                        .onAppear() {
                    }
                }.background(Color.green)
            }
            HStack {
                Circle()
                    .foregroundColor(.red)
                    .frame(width: 100, height: 100, alignment: .center)
                    .position(CGPoint(x: 100, y: 100), in: .named("otherSpace"))
            }
        }
    }
}

extension View {
    func position(_ position: CGPoint, in coordinateSpace: CoordinateSpace) -> some View {
        ModifiedContent(content: self, modifier: AbsolutePosition(position: position, coordinateSpace: coordinateSpace))
    }
}

struct AbsolutePosition: ViewModifier {
    
    @State var position: CGPoint
    
    let coordinateSpace: CoordinateSpace
    
    @State var localPosition: CGPoint = .zero
    @State var targetPosition: CGPoint = .zero
    
    func body(content: Content) -> some View {
        
        GeometryReader { geometry in
            content.onAppear() {
                localPosition = geometry.frame(in: .local).origin
                targetPosition = geometry.frame(in: coordinateSpace).origin
                position.x = localPosition.x - targetPosition.x + position.x
                position.y = localPosition.y - targetPosition.y + position.y
            }.position(position)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

UPDATE with images.

The first image shows the result when using .edgesIgnoringSafeArea(.all) on the target circle. I want to get to this result no matter if the target circle (CoordinateSpace) has a safe area or not. But when I remove .edgesIgnoringSafeArea(.all) I get the 2nd image as a result because my extension doesn't know if there is a safe area or not and always calculates as if there is none.

enter image description here enter image description here

Marco Boerner
  • 1,243
  • 1
  • 11
  • 34
  • Can you upload an image of what you are trying to achieve? – user1046037 Jan 07 '21 at 12:59
  • @user1046037 I've added two images with the desired result (first) and the current result without the manually added `.edgesIgnoringSafeArea(.all)` modifier. – Marco Boerner Jan 07 '21 at 21:38
  • I am not sure I understand, just to let you know `geometryProxy.safeAreaInsets.top` might help achieve what you are trying to do. Keep it simple, let the parent view decide where it wants to place the child view. Let the child view just be concerned about how to render itself within the given space. It's always best not to fight the SwiftUI system – user1046037 Jan 08 '21 at 01:05
  • The thing is I need this to do a drag and drop in between two different views. For this I do need to position it exactly. I'm just trying to make my life easier with that extension. However it seems it's not completely possible without the extra step of ignoring the safe areas of the first view. – Marco Boerner Jan 08 '21 at 12:42
  • You could use `edgesIgnoringSafeArea(.all)` but counter it with `geometryProxy.safeAreaInsets` (top / bottom / leading / trailing), that way it would serve the purpose even with / without safe area insets. – user1046037 Jan 08 '21 at 16:38
  • 1
    Not sure if SpriteKit would be more suitable for you are trying to acheive – user1046037 Jan 08 '21 at 16:58
  • SpriteKit is obviously the better solution as it has already methods for exactly that. But unfortunately my project is bound to SwiftUI only. :/ I guess I will just stick with using edgesIgnoringSafeArea for now. Thanks anyways! : ) – Marco Boerner Jan 08 '21 at 17:14

0 Answers0