UIKit Dynamics:识别圆形和边界

Posted

技术标签:

【中文标题】UIKit Dynamics:识别圆形和边界【英文标题】:UIKit Dynamics: recognize rounded Shapes and Boundaries 【发布时间】:2014-04-28 17:28:09 【问题描述】:

我正在编写一个应用程序,我使用 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;

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

然后我将这些圆圈添加到不同的 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;

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

打开一些调试选项,我做了这个截图:

如您所见,圆看起来是一个正方形。

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

【问题讨论】:

square 是屏幕截图中较大的正方形,只是另一个 UIView。如果这是问题,我会非常惊讶 是的,我意识到了这一点。您的 q 中的代码顺序是向后的,所以我没有意识到您正在向其中添加圆圈。但是,请参阅我的回答。 为了未来读者的利益,在 ios 9 中,现在可以使用 UIDynamicItem 属性 collisionBoundsTypecollisionBoundingPath 谢谢@Rob,很高兴知道。未来的读者应该看看,在我写这篇文章的时候,iOS7 是当前版本的 iirc。 @IulianOnofrei 完成。 【参考方案1】:

我知道这个问题早于 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

然后,当您进行碰撞时,它将遵循 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];

产生:

顺便提一下,the documentation 概述了路径的一些限制:

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

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


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


【讨论】:

现在应该是公认的答案。谢谢@Rob!【参考方案2】:

好的,首先。

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

所有视图都是矩形的。它们呈现圆形的方式是使角透明(因此是角半径)。

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

Dynamics 旨在用于更自然和真实的 UI 动画。它并不是一个完整的物理引擎。

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

【讨论】:

感谢您的回答,它消除了我的一些疑问。我并不是真的想做任何复杂的事情,这只是为了做作业,我想展示一些好的东西。我认为我不会将 uikit 动力学用于比这更复杂的事情。您认为该套件为更好的动画提供便利的观点很有意义,并解释了它的一些缺点。稍后我将链接到精灵套件!感谢您的帮助 不用担心。您可以在某些约束条件下通过动态获得一些非常好的效果。我建议在 Ray Wenderlich 网站上查看 sprite kit。在那里获得高级物理真的很容易。 这个问题与Sprite Kit无关。 @IulianOnofrei 这个问题已经 3 岁了。我的回答也是。你想说啥?你复活了一个死问题。是的,Rob 发表他的评论作为答案可能会很好。是不是让我的回答错了?不。OP 试图用 UIKitDynamics 做一些它不是为它设计的事情。所以我说他们最好还是使用 SpriteKit。如果我试图以一种不理想的方式做某事,我希望任何人都能为我做同样的事情。也许操作者不知道 SpriteKit?你有想过吗?还是你只是想变得粗鲁? 我认为您应该将您的评论添加为编辑@Fogmeister。人们可能不会比您的答案更进一步,因为这是公认的答案,而实际上他们应该看到 Rob 的答案。

以上是关于UIKit Dynamics:识别圆形和边界的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 UIKit Dynamics 让 UIView 反弹

UIKit Dynamics 和 UIKit Animation 有啥区别?

带有平移、旋转和捏合手势的 UIKit Dynamics

iOS 力学动画生成器UIKit Dynamics 之碰撞效果解说

UIKit Dynamics 的性能问题

CollectionView + UIKit Dynamics 在 performBatchUpdates 上崩溃: