UICollisionBehavior - представления проходят через границы

В рамках реализации UICollisionBehaviour я устанавливаю границы для края экрана. Затем я добавил некоторые взгляды и, наконец, подключил к ним один из них UIPanGestureRecognizer.

Теперь я могу обойти меньшие виды с помощью перетаскиваемого вида.

Hammer-time!

Проблема: Если я углую меньший вид и продолжаю толкать его к краю экрана, он, в конце концов, проскочит мимо границы и попадет в ловушку с другой стороны. Мой "вид молотка", который я использую для того, чтобы ударить и подтолкнуть другие виды вокруг, также попадет в границы. То есть он застревает/не перетаскивается в сторону экрана.

Я сделал очень маленький пример, чтобы увидеть, могу ли я воспроизвести его, когда у меня было очень мало просмотров и никаких противоречивых действий, взгляды все еще проходят через границы. Либо UIDynamics не может справиться очень много, либо (более вероятно), я каким-то образом неправильно настроил ее.

Маленький пример ниже имеет странное поведение:

class ViewController: UIViewController {

var animator: UIDynamicAnimator?
var collisionBehaviour: UICollisionBehavior?
var panBehaviour: UIAttachmentBehavior?

override func viewDidLoad() {
    super.viewDidLoad()
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    setup()
}

func setup() {
    //setup collisionBehaviour and animator
    collisionBehaviour = UICollisionBehavior()
    collisionBehaviour!.collisionMode = UICollisionBehaviorMode.allZeros
    animator = UIDynamicAnimator(referenceView: view)

    //add boundaries
    collisionBehaviour!.addBoundaryWithIdentifier("verticalMin", fromPoint: CGPointMake(0, 0), toPoint: CGPointMake(0, CGRectGetHeight(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("verticalMax", fromPoint: CGPointMake(CGRectGetMaxX(view.frame), 0), toPoint: CGPointMake(CGRectGetMaxX(view.frame), CGRectGetHeight(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("horizontalMin", fromPoint: CGPointMake(0, CGRectGetMaxY(view.frame)), toPoint: CGPointMake(CGRectGetMaxX(view.frame), CGRectGetMaxY(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("horizontalMax", fromPoint: CGPointMake(0, 0), toPoint: CGPointMake(CGRectGetMaxX(view.frame), 0))

    //        collisionBehaviour!.translatesReferenceBoundsIntoBoundary = true // same effect as the above boundaries
    //setup up some round views to push around
    for i in 0..<5 {
        let ball = UIView(frame: CGRectMake(0, 30, 50, 50))
        ball.center = view.center
        ball.backgroundColor = UIColor.greenColor()
        ball.layer.cornerRadius = CGRectGetWidth(ball.frame) * 0.5
        view.addSubview(ball)
        collisionBehaviour!.addItem(ball)
    }

    //setup a hammer view which can be dragged and used to squeze the ball views of the screen
    let hammer = UIView(frame: CGRectMake(0, 0, 100, 100))
    hammer.backgroundColor = UIColor.redColor()
    view.addSubview(hammer)
    collisionBehaviour!.addItem(hammer)

    let noRotationBehaviour = UIDynamicItemBehavior(items: [hammer])
    noRotationBehaviour.allowsRotation = false
    animator!.addBehavior(noRotationBehaviour)

    let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("handlePan:"))
    hammer.addGestureRecognizer(panGestureRecognizer)

    //"start" the collision detection
    animator!.addBehavior(collisionBehaviour!)
}

//Move the hammer around
func handlePan(recognizer: UIPanGestureRecognizer) {
    if let view = recognizer.view {
        let location = recognizer.locationInView(self.view)
        switch recognizer.state {
        case .Began:
            panBehaviour = UIAttachmentBehavior(item: view, attachedToAnchor: location)
            animator!.addBehavior(panBehaviour!)
            println("begin")
        case .Changed:
            panBehaviour!.anchorPoint = location
            println("change \(location)")
        case .Ended:
            println("ended")
            animator!.removeBehavior(panBehaviour!)
        default:
            println("done")
        }
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

Спасибо за предоставленную помощь.

Ответы

Ответ 1

Хорошая новость: вы не делаете ничего плохого. Плохая новость: это ожидаемое поведение. Ваш молот подталкивает взгляд к контрольным границам, пока он, наконец, не прорвется, потому что физика вашей модели говорит, что она должна делать. Эталонная граница не является границей, которую нельзя пересечь, она определяет только область, в которой ваши физические правила применяются последовательно.

Это не документировано, но на странице UICollisionBehavior указано:

При установке начальной позиции для динамического элемента необходимо обеспечить что его границы не пересекают никаких границ столкновения. поведение анимации для такого неулокального элемента - undefined.

Это только частично верно, в вашем случае (как и у меня), если элемент выходит за границу после инициализации, это поведение также undefined.

Я попытался упорядочить набор шаров в правой части представления. Под правым шаром имеется опорная точка. Желтый вид - это эталонный вид, и все начинается отлично... this works

Но, поскольку я добавил больше мячей, они указывают, что они больше не подходят, они начнут выскакивать. В самом деле, один на верхней правой части изображения выскочил из нижнего центра и проката вокруг опорного зрения против часовой стрелки, чтобы быть вблизи точки привязки. this is broken

UPDATE: Для решения вы можете установить collisionDelegate вашего collisionBehaviors и захватить начало и конец столкновений, а затем переместить свой вид обратно в границу и подальше от вашего молотка, чтобы он выглядел так, как будто они сбежали.

Как вы поняли, translatesReferenceBoundsIntoBoundary эквивалентен набору вызовов addBoundaryWithIdentifier.

Использование:

collisionBehaviour!.translatesReferenceBoundsIntoBoundary = true 

то же самое, что:

//add boundaries
    collisionBehaviour!.addBoundaryWithIdentifier("verticalMin", fromPoint: CGPointMake(0, 0), toPoint: CGPointMake(0, CGRectGetHeight(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("verticalMax", fromPoint: CGPointMake(CGRectGetMaxX(view.frame), 0), toPoint: CGPointMake(CGRectGetMaxX(view.frame), CGRectGetHeight(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("horizontalMin", fromPoint: CGPointMake(0, CGRectGetMaxY(view.frame)), toPoint: CGPointMake(CGRectGetMaxX(view.frame), CGRectGetMaxY(view.frame)))
    collisionBehaviour!.addBoundaryWithIdentifier("horizontalMax", fromPoint: CGPointMake(0, 0), toPoint: CGPointMake(CGRectGetMaxX(view.frame), 0))