Search code examples
swiftuidraggesture

Checking for view overlapping in SwiftUI


See the comment in the code below.

struct ContentView: View {
    @State private var offset = CGSize.zero

    var body: some View {
        VStack {
            Circle()
                .fill(.blue)
                .frame(width: 200, height: 200)
            
            Circle()
                .fill(.red)
                .frame(width: 100, height: 100)
                .offset(offset)
                .gesture(
                    DragGesture()
                        .onChanged { gesture in
                            offset = gesture.translation
                        }
                        .onEnded { _ in
                            //how to check if the small red circle is more than halfway into the large blue circle???
                        }
                )
        }
        
        
    }
}

Or more generally, how can you check for view intersection/overlapping in SwiftUI?


Solution

  • You can use a GeometryReader in the background of each shape to find their frames in the global coordinate space.

    To determine if the red circle is more than half inside the blue circle, you can test whether the mid-point of the red frame is enclosed within the path of the blue circle.

    To have the offset reset automatically, I changed the state variable to a GestureState.

    struct ContentView: View {
        @GestureState private var offset = CGSize.zero
        @State private var bluePath: Path?
        @State private var isMoreThanHalfInside = false
    
        var body: some View {
            VStack {
                Circle()
                    .fill(.blue)
                    .frame(width: 200, height: 200)
                    .background(
                        GeometryReader { proxy in
                            let frame = proxy.frame(in: .global)
                            Color.clear
                                .onAppear {
                                    bluePath = Circle().path(in: frame)
                                }
                        }
                    )
    
                Circle()
                    .fill(isMoreThanHalfInside ? .yellow : .red)
                    .opacity(offset == .zero ? 1 : 0.9)
                    .overlay(Image(systemName: "plus").fontWeight(.thin))
                    .frame(width: 100, height: 100)
                    .background(
                        GeometryReader { proxy in
                            let frame = proxy.frame(in: .global)
                            let midPoint = CGPoint(x: frame.midX, y: frame.midY)
                            Color.clear
                                .onChange(of: midPoint) { oldVal, newVal in
                                    isMoreThanHalfInside = bluePath?.contains(newVal) ?? false
                                }
                        }
                    )
                    .offset(offset)
                    .gesture(
                        DragGesture(minimumDistance: 0)
                            .updating($offset) { val, state, trans in
                                state = val.translation
                            }
                    )
            }
        }
    }
    

    Animation