在 Swift 的 UIKit Dynamics 中,如何定义一个圆边界来包含一个 UIView?

In Swift’s UIKit Dynamics, how can I define a circle boundary to contain a UIView?

我研究了很多,但我在任何地方都能找到的唯一示例是为了定义 UIView 的边界,以便它们 collide/bounce 在对象的外部彼此分开。

示例:一个球撞到另一个球,它们相互弹开。

但我想做的是创建一个圆形视图来包含其他 UIView,这样包含边界是一个圆,而不是默认的正方形。有办法实现吗?

是的,这完全可能。实现圆内碰撞的关键是

  1. 将碰撞行为的边界设置为圆形路径(自定义UIBezierPath)和
  2. 将动画师的referenceView设置为圆形视图。

输出:

故事板设置:

以下是上述情节提要的视图控制器代码。魔法发生在 simulateGravityAndCollision 方法中:

Full Xcode project

class ViewController: UIViewController {

    @IBOutlet weak var redCircle: UIView!
    @IBOutlet weak var whiteSquare: UIView!

    var animator:UIDynamicAnimator!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.redCircle.setCornerRadius(self.redCircle.bounds.width / 2)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { [unowned self] in
            self.simulateGravityAndCollision()
        }
    }

    //IMPORTANT
    func simulateGravityAndCollision() {

        //The dynamic animation happens only within the reference view, i.e., our red circle view
        animator = UIDynamicAnimator.init(referenceView: self.redCircle)

        //Only the inside white square will be affected by gravity
        let gravityBehaviour = UIGravityBehavior.init(items: [self.whiteSquare])

        //We also apply collision only to the white square
        let collisionBehaviour = UICollisionBehavior.init(items:[self.whiteSquare])

        //This is where we create the circle boundary from the redCircle view's bounds
        collisionBehaviour.addBoundary(withIdentifier: "CircleBoundary" as NSCopying, for: UIBezierPath.init(ovalIn: self.redCircle.bounds))

        animator.addBehavior(gravityBehaviour)
        animator.addBehavior(collisionBehaviour)
    }

}

extension UIView {

    open override func awakeFromNib() {
        super.awakeFromNib()
        self.layer.allowsEdgeAntialiasing = true
    }

    func setCornerRadius(_ amount:CGFloat) {
        self.layer.cornerRadius = amount
        self.layer.masksToBounds = true
        self.clipsToBounds = true
    }
}