UIKit Dynamics:识别圆形形状和边界

dan*_* f. 3 core-animation uikit ios uikit-dynamics

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

我用以下代码创建了我的圈子:

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;
Run Code Online (Sandbox Code Playgroud)

其中location表示圆的所需位置,半径表示半径.

然后,我通过执行以下操作将这些圈子添加到不同的UIBehaviours:

[_collision addItem:circle];
[_gravity addItem:circle];
[_itemBehaviour addItem:circle];
Run Code Online (Sandbox Code Playgroud)

itemBaviour定义如下:

_itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
_itemBehaviour.elasticity = 1;
_itemBehaviour.friction = 0;
_itemBehaviour.resistance = 0;
_itemBehaviour.angularResistance = 0;
_itemBehaviour.allowsRotation = NO;
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是,我的圈子表现为正方形.当以某种方式击中时,它们会获得角动量并失去速度.如果它们再次碰撞,有时角动量再次恢复到速度.这对于正方形来说看起来很正常,但是当视图是圆的时候,就像在我的情况下一样,这种行为看起来很奇怪而且不自然.

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

如你所见,圆圈显然是一个正方形.

所以我的问题是,如何创建一个真正的圆形UIVIew并在UIKit Dynamics中表现如此?

Rob*_*Rob 7

我知道这个问题早的iOS 9,但对于未来的读者的利益,你现在可以定义视图collisionBoundsTypeUIDynamicItemCollisionBoundsTypePath和一个圆形collisionBoundingPath.

因此,虽然您不能"创建一个UIView真正的圆形",但您可以定义一个路径,既定义在视图内部渲染的形状,又定义动画制作者的碰撞边界,从而产生圆形视图的效果(甚至虽然视图本身显然仍然是矩形的,因为所有视图都是):

@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
Run Code Online (Sandbox Code Playgroud)

然后,当你进行碰撞时,它会尊重这些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];
Run Code Online (Sandbox Code Playgroud)

产量:

在此输入图像描述

顺便说一句,应该注意的是,文档概述了路径的一些限制:

您创建的路径对象必须表示逆时针或顺时针缠绕的凸多边形,并且路径不得与自身相交.路径的(0,0)点必须位于相应动态项的中心点.如果中心点与路径的原点不匹配,则碰撞行为可能无法按预期工作.

但是简单的圆形路径很容易满足这些标准.


或者,对于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)
    }

}
Run Code Online (Sandbox Code Playgroud)