Ios UIKit动态:识别圆形形状和边界

Ios UIKit动态:识别圆形形状和边界,ios,uikit,core-animation,uikit-dynamics,Ios,Uikit,Core Animation,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 = Y

我正在编写一个应用程序,其中我使用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;
其中位置表示圆的所需位置,半径表示圆的半径

然后,我通过以下操作将这些圆圈添加到不同的UIB行为中:

[_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中表现为圆形?

好的,首先

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

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

第二,你想用UIKit Dynamics做什么?屏幕上显示的内容看起来像是在尝试创建某种游戏

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


如果您想要类似的东西,那么最好使用Sprite工具包。

我知道这个问题早在iOS 9之前就有了,但是为了方便将来的读者,现在您可以使用
UIDynamicEmCollisionBoundsTypePath
和循环来定义视图

因此,虽然您不能“创建一个真正的圆形视图”,但您可以定义一条路径,该路径定义视图内部渲染的形状以及动画的碰撞边界,从而产生圆形视图的效果(尽管视图本身显然仍然是矩形的,就像所有视图一样):

然后,当您执行碰撞时,它将遵循
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];
这将产生:

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

创建的路径对象必须表示具有逆时针或顺时针缠绕的凸多边形,并且路径不得与自身相交。路径的(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)
    }

}

正方形是屏幕截图中较大的正方形,只是另一个视图。如果这是我意识到的问题,我会非常惊讶。你q中的代码顺序是倒序的,所以我没意识到你在给它加圆圈。但是,请看我的答案。为了方便未来的读者,在iOS 9中,现在可以使用
UIDynamicItem
properties
collisionBoundsType
collisionBoundingPath
。谢谢@Rob,很高兴知道。未来的读者应该研究一下,在我写这篇文章的时候,iOS7是iirc的当前版本。@IulianOnofrei完成了。谢谢你的回答,它消除了我的一些疑问。我并不是真的想做任何复杂的事情,这只是一个家庭作业,我想展示一些好东西。我不认为我会将uikit动力学用于比这更复杂的事情。你认为这个工具包可以方便制作更好的动画,这很有道理,并解释了它的一些缺点。稍后我将链接到精灵套件!谢谢你的帮助,不用担心。在某些约束条件下,使用动力学可以获得一些非常好的效果。我建议你去Ray Wenderlich的网站看看sprite工具包。在那里很容易获得高级物理。这个问题不是关于
Sprite Kit
@IulianOnofrei这个问题已经3岁了。我的答案也是。你的观点是什么?你又提出了一个死问题。是的,罗布把他的评论作为一个回答可能会有好处。这让我的答案错了吗?不。OP试图用UIKitDynamics做一些它不是为之设计的事情。所以我说他们最好使用SpriteKit。如果我试图以一种不理想的方式做某事,我希望任何人也能为我做同样的事情。也许警察不知道SpriteKit?你有想过吗?或者你只是想表现得粗鲁一点?我想你应该把你的评论添加为edit@Fogmeister。人们可能不会比你的答案更远,因为这是公认的答案,他们实际上应该看到罗布的答案。现在应该是公认的答案。谢谢你@Rob!
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)
    }

}