Search code examples
swiftuipathcontains

Why does path.contains(point) return seemingly inconsistent results? What am I missing?


enter image description here My goal is to check if a point is inside a region. The code below illustrates a sample with the region defined with an Envelope struct.

Sample points along the border, inside and outside of the region are also defined.

all is then depicted using "Chart"

To check if the point is inside or not, I construct a Path from the "Envelope" points and than check if this path.contains(the point).

Some points that are on the border are found to be outside, while others are inside. What am I missing here? Any ideas?

import SwiftUI
import Charts

struct Envelope:Identifiable {
    let id=UUID()
    var x:Double
    var y:Double
}

struct Point:Identifiable {
    let id=UUID()
    let point:CGPoint
}

struct ContentView: View {
    
    let envelope:[Envelope]=[Envelope(x: 1.0, y: 1.0),
                             Envelope(x: 1.0, y: 2.0),
                             Envelope(x: 2.0, y: 2.0),
                             Envelope(x: 2.0, y: 1.0),
                             Envelope(x: 1.0, y: 1.0)
    ]
    
    
    let pointToCheck1=CGPoint(x: 1.5, y: 1.5)
    let pointToCheck2=CGPoint(x: 1.0, y: 2.0)
    let pointToCheck3=CGPoint(x: 2.0, y: 2.0)
    let pointToCheck4=CGPoint(x: 2.0, y: 1.0)
    let pointToCheck5=CGPoint(x: 1.0, y: 1.0)
    let pointToCheck6=CGPoint(x: 1.5, y: 1.0)
    let pointToCheck7=CGPoint(x: 1.0, y: 1.5)
    let pointToCheck8=CGPoint(x: 2.0, y: 1.5)
    let pointToCheck9=CGPoint(x: 1.5, y: 2.0)
    let pointToCheck10=CGPoint(x: 1.5, y: 2.1)
    
    
    
    var points:[Point] {
        [Point(point: pointToCheck1),
         Point(point: pointToCheck2),
         Point(point: pointToCheck3),
         Point(point: pointToCheck4),
         Point(point: pointToCheck5),
         Point(point: pointToCheck6),
         Point(point: pointToCheck7),
         Point(point: pointToCheck8),
         Point(point: pointToCheck9),
         Point(point: pointToCheck10)]
    }
    
    
    var body: some View {
        ZStack {
            Chart {
                ForEach(envelope) { item in
                    LineMark (x: .value("", item.x),
                              y: .value("", item.y)
                    ).lineStyle(by: .value("", "CG Limit"))
                }
                
                ForEach(points) { point in
                    PointMark (x: .value("", Double(point.point.x)),
                               y: .value("", Double(point.point.y)))
                    .foregroundStyle(Color.orange)
                    .annotation {
                        Text("\(checkIntersection(point.point)) \(point.point)")
                    }
                }
                
            }
            .chartXScale(domain: [0.7, 2.3])
            .chartYScale(domain: [0.7, 2.3])
            
            
            
        }
    }
    
    private func checkIntersection(_ point:CGPoint)->String {
        var toReturn="Outside"
        
        var pathObject=Path()
        var isFirst=true
        for sort in envelope {
            let cgPoint=CGPoint(x: sort.x, y: sort.y)
            if isFirst {
                pathObject.move(to: cgPoint)
                isFirst=false
            }else{
                pathObject.addLine(to: cgPoint)
            }
        }
        
        if pathObject.contains(point) {
            toReturn="Inside"
        }
        
        return toReturn
    }
    
    
}

Solution

  • I may have found a solution. Instead of using Path(), I tried UIBezierPath() which returns my expected result.

    private func checkIntersection(_ point:CGPoint)->String {
            var toReturn="Outside"
            
            var pathObject=UIBezierPath()
            var isFirst=true
            for sort in envelope {
                let cgPoint=CGPoint(x: sort.x, y: sort.y)
                if isFirst {
                    pathObject.move(to: cgPoint)
                    isFirst=false
                }else{
                    pathObject.addLine(to: cgPoint)
                }
            }
            if pathObject.contains(point) {
                toReturn="Inside"
            }
            
            return toReturn
        }