UIKitdynamic:识别圆形形状和边界

我正在写一个应用程序,我使用UIKitdynamic来模拟不同圈子之间的相互作用。

我用下面的代码创build我的圈子:

self = [super initWithFrame:CGRectMake(location.x - radius/2.0, location.y - radius/2, radius, radius)]; if (self) { [self.layer setCornerRadius: radius /2.0f]; self.clipsToBounds = YES; self.layer.masksToBounds = YES; self.backgroundColor = color; self.userInteractionEnabled = NO; } return self; 

其中位置表示圆的期望位置,半径是其半径。

然后我将这些圈子添加到不同的UIBehaviours,做到:

 [_collision addItem:circle]; [_gravity addItem:circle]; [_itemBehaviour addItem:circle]; 

itemBaviour定义如下:

 _itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[square]]; _itemBehaviour.elasticity = 1; _itemBehaviour.friction = 0; _itemBehaviour.resistance = 0; _itemBehaviour.angularResistance = 0; _itemBehaviour.allowsRotation = NO; 

我遇到的问题是我的圈子performance得像方块一样。 当以某种方式击中时,他们会获得angular动量并失去速度。 如果他们再次碰撞,有时angular动量再次恢复到速度。 这看起来正常的方块,但是当视图是圆的,就像在我的情况下,这种行为看起来很奇怪,不自然。

打开一些debugging选项,我做了这个截图: 小圆实际上是一个正方形

正如你所看到的,这个圆圈显然是一个正方形。

所以我的问题是,我如何创build一个真正的圆形的UIVIew,并将在UIKitdynamic中像这样performance?

好的,首先。

您启用的debugging选项显示透明单元格的区域。 圆的视图实际上是一个带有圆边的正方形。

所有视图都是矩形的。 他们出现圆形的方式是通过使angular落透明(因此angular半径)。

其次,你要用UIKit Dynamics做什么? 屏幕上的内容看起来像是在尝试创build某种游戏。

dynamic是为了用于UI的更自然和真实的animation。 这并不意味着是一个全function的物理引擎。

如果你想要这样的东西,那么你最好使用Sprite Kit。

我知道这个问题早于iOS 9,但为了将来读者的利益,现在可以使用UIDynamicItemCollisionBoundsTypePath和一个循环的collisionBoundingPath来定义一个视图。

所以,当你不能“创build一个真正的圆形的UIView ”的时候,你可以定义一个path来定义在视图中渲染的形状以及animation的碰撞边界,产生一个圆形视图的效果即使视图本身显然仍然是矩形的,就像所有视图一样):

 @interface CircleView: UIView @property (nonatomic) CGFloat lineWidth; @property (nonatomic, strong) CAShapeLayer *shapeLayer; @end @implementation CircleView - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self configure]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self configure]; } return self; } - (instancetype)init { return [self initWithFrame:CGRectZero]; } - (void)configure { self.translatesAutoresizingMaskIntoConstraints = false; // create shape layer for circle self.shapeLayer = [CAShapeLayer layer]; self.shapeLayer.strokeColor = [[UIColor blueColor] CGColor]; self.shapeLayer.fillColor = [[[UIColor blueColor] colorWithAlphaComponent:0.5] CGColor]; self.lineWidth = 3; [self.layer addSublayer:self.shapeLayer]; } - (void)layoutSubviews { [super layoutSubviews]; // path of shape layer is with respect to center of the `bounds` CGPoint center = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2, self.bounds.origin.y + self.bounds.size.height / 2); self.shapeLayer.path = [[self circularPathWithLineWidth:self.lineWidth center:center] CGPath]; } - (UIDynamicItemCollisionBoundsType)collisionBoundsType { return UIDynamicItemCollisionBoundsTypePath; } - (UIBezierPath *)collisionBoundingPath { // path of collision bounding path is with respect to center of the dynamic item, so center of this path will be CGPointZero return [self circularPathWithLineWidth:0 center:CGPointZero]; } - (UIBezierPath *)circularPathWithLineWidth:(CGFloat)lineWidth center:(CGPoint)center { CGFloat radius = (MIN(self.bounds.size.width, self.bounds.size.height) - self.lineWidth) / 2; return [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:true]; } @end 

然后,当你进行碰撞时,它将会遵守collisionBoundingPath值:

 self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; // create circle views CircleView *circle1 = [[CircleView alloc] initWithFrame:CGRectMake(60, 100, 80, 80)]; [self.view addSubview:circle1]; CircleView *circle2 = [[CircleView alloc] initWithFrame:CGRectMake(250, 150, 120, 120)]; [self.view addSubview:circle2]; // have them collide with each other UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[circle1, circle2]]; [self.animator addBehavior:collision]; // with perfect elasticity UIDynamicItemBehavior *behavior = [[UIDynamicItemBehavior alloc] initWithItems:@[circle1, circle2]]; behavior.elasticity = 1; [self.animator addBehavior:behavior]; // and push one of the circles UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:@[circle1] mode:UIPushBehaviorModeInstantaneous]; [push setAngle:0 magnitude:1]; [self.animator addBehavior:push]; 

这产生:

在这里输入图像说明

顺便说一下,应该指出的是,文档概述了path的一些限制:

您创build的path对象必须表示具有逆时针或顺时针缠绕的凸多边形,且path不得相交。 path的(0,0)点必须位于相应dynamic项目的中心点 。 如果中心点与path的原点不匹配,则碰撞行为可能无法正常工作。

但是一个简单的圆形path很容易满足这些标准


或者,对于Swift用户:

 class CircleView: UIView { var lineWidth: CGFloat = 3 var shapeLayer: CAShapeLayer = { let _shapeLayer = CAShapeLayer() _shapeLayer.strokeColor = UIColor.blue.cgColor _shapeLayer.fillColor = UIColor.blue.withAlphaComponent(0.5).cgColor return _shapeLayer }() override func layoutSubviews() { super.layoutSubviews() layer.addSublayer(shapeLayer) shapeLayer.lineWidth = lineWidth let center = CGPoint(x: bounds.midX, y: bounds.midY) shapeLayer.path = circularPath(lineWidth: lineWidth, center: center).cgPath } private func circularPath(lineWidth: CGFloat = 0, center: CGPoint = .zero) -> UIBezierPath { let radius = (min(bounds.width, bounds.height) - lineWidth) / 2 return UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true) } override var collisionBoundsType: UIDynamicItemCollisionBoundsType { return .path } override var collisionBoundingPath: UIBezierPath { return circularPath() } } class ViewController: UIViewController { let animator = UIDynamicAnimator() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let circle1 = CircleView(frame: CGRect(x: 60, y: 100, width: 80, height: 80)) view.addSubview(circle1) let circle2 = CircleView(frame: CGRect(x: 250, y: 150, width: 120, height: 120)) view.addSubview(circle2) animator.addBehavior(UICollisionBehavior(items: [circle1, circle2])) let behavior = UIDynamicItemBehavior(items: [circle1, circle2]) behavior.elasticity = 1 animator.addBehavior(behavior) let push = UIPushBehavior(items: [circle1], mode: .instantaneous) push.setAngle(0, magnitude: 1) animator.addBehavior(push) } }