确定自定义 iOS 视图是否重叠

Determining if custom iOS views overlap

我定义了一个 CircleView class:

class CircleView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
    }
        
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        // Get the Graphics Context
        if let context = UIGraphicsGetCurrentContext() {
            
            // Set the circle outerline-width
            context.setLineWidth(5.0);
            
            // Set the circle outerline-colour
            UIColor.blue.set()
            
            // Create Circle
            let center = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
            let radius = (frame.size.width - 10)/2
            context.addArc(center: center, radius: radius, startAngle: 0.0, endAngle: .pi * 2.0, clockwise: true)
                        
            context.setFillColor(UIColor.blue.cgColor)
                
            // Draw
            context.strokePath()
            context.fillPath()
        }
    }

}

并用随机设置的数字创建了一个数组:

    var numberOfCircles: Int!
    var circles: [CircleView] = []

    numberOfCircles = Int.random(in: 1..<10)
    let circleWidth = CGFloat(50)
    let circleHeight = circleWidth
    
    var i = 0
    while i < numberOfCircles {
        let circleView = CircleView(frame: CGRect(x: 0.0, y: 0.0, width: circleWidth, height: circleHeight))
        circles.append(circleView)
        i += 1
    }

创建圆圈后,我调用一个函数 drawCircles,它将在屏幕上绘制它们:

 func drawCircles(){
        
        for c in circles {
            c.frame.origin = c.frame.randomPoint
            while !UIScreen.main.bounds.contains(c.frame.origin) {
                                    c.frame.origin = CGPoint()
                                    c.frame.origin = c.frame.randomPoint
 
                let prev = circles.before(c)
                if prev?.frame.intersects(c.frame) == true {
                    c.frame.origin = c.frame.randomPoint
                }
            }
        }
                         
        for c in circles {
            self.view.addSubview(c)
        }
    }

drawCircles 方法中的 while 循环确保没有圆圈放置在屏幕边界之外,并按预期工作。

我正在努力的是确保圆圈不会相互重叠,如下所示:

我正在使用以下方法来确定下一个

我正在使用此方法来确定圆圈数组中的上一个/下一个元素:

extension BidirectionalCollection where Iterator.Element: Equatable {
    typealias Element = Self.Iterator.Element

    func after(_ item: Element, loop: Bool = false) -> Element? {
        if let itemIndex = self.firstIndex(of: item) {
            let lastItem: Bool = (index(after:itemIndex) == endIndex)
            if loop && lastItem {
                return self.first
            } else if lastItem {
                return nil
            } else {
                return self[index(after:itemIndex)]
            }
        }
        return nil
    }

    func before(_ item: Element, loop: Bool = false) -> Element? {
        if let itemIndex = self.firstIndex(of: item) {
            let firstItem: Bool = (itemIndex == startIndex)
            if loop && firstItem {
                return self.last
            } else if firstItem {
                return nil
            } else {
                return self[index(before:itemIndex)]
            }
        }
        return nil
    }
}

然而,这个 if 语句;似乎没有做我想做的事;这是为了确保如果一个圆与另一个圆相交,将其原点更改为新的东西:

if prev?.frame.intersects(c.frame) == true {
    c.frame.origin = c.frame.randomPoint
}

如果有人对其中的逻辑有任何想法,或者对如何确保圆圈彼此不重叠有其他想法,那将会很有帮助!

编辑:我确实尝试了 Eugene 在他的回答中给出的建议,但仍然得到相同的结果:

  func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
        let xDist = a.x - b.x
        let yDist = a.y - b.y
        return CGFloat(sqrt(xDist * xDist + yDist * yDist))
    }
    

       if prev != nil {
                
                if distance((prev?.frame.origin)!, c.frame.origin) <= 40 {
                    print("2")
                    c.frame.origin = CGPoint()
                    c.frame.origin = c.frame.randomPoint
                    
                }
            }

但还是一样的结果

编辑 2

根据 Eugene 编辑过的答案/说明修改了我的 for 循环;仍然有重叠圆圈的问题:

for c in circles { c.frame.origin = c.frame.randomPoint

    let prev = circles.before(c)
    let viewMidX = self.circlesView.bounds.midX
    let viewMidY = self.circlesView.bounds.midY

    let xPosition = self.circlesView.frame.midX - viewMidX + CGFloat(arc4random_uniform(UInt32(viewMidX*2)))
    let yPosition = self.circlesView.frame.midY - viewMidY + CGFloat(arc4random_uniform(UInt32(viewMidY*2)))

    
    if let prev = prev {
        if distance(prev.center, c.center) <= 50 {
            c.center = CGPoint(x: xPosition, y: yPosition)
        }
    }

}

那是纯粹的几何挑战。只要确保圆心之间的距离大于或等于它们的半径之和即可。

编辑 1

使用 UIView.center 而不是 UIView.frame.originUIView.frame.origin 给你 UIView 的左上角。

if let prev = prev {
    if distance(prev.center, c.center) <= 50 {
         print("2")
         c.center = ...
    }
}

编辑 2

func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
    let xDist = a.x - b.x
    let yDist = a.y - b.y
    return CGFloat(hypot(xDist, yDist))
}

let prev = circles.before(c)

if let prevCircleCenter = prev?.center {
    let distance = distance(prevCenter, c.center)
    if distance <= 50 {
        let viewMidX = c.bounds.midX
        let viewMidY = c.bounds.midY
      
        var newCenter = c.center
        var centersVector = CGVector(dx: newCenter.x - prevCircleCenter.x, dy: newCenter.y - prevCircleCenter.y)
        centersVector.dx *= 51 / distance
        centersVector.dy *= 51 / distance
        newCenter.x = prevCircleCenter.x + centersVector.dx
        newCenter.y = prevCircleCenter.y + centersVector.dy
        c.center = newCenter
    }
}