You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让可拖拽带重力效果的UIView触碰屏幕底部时改变颜色

实现方块触碰屏幕底部时变色的功能

我正在开发一款简单游戏,初始状态下蓝色方块会在几秒后下落,用户可拖拽并释放它,方块会再次下落。我希望实现一个功能:当蓝色方块触碰屏幕底部边缘时改变其颜色。以下是我的代码:

import UIKit
import CoreMotion

class CanvasViewController: UIViewController {
    
    @IBOutlet weak var blueSquare: UIView!
    
    var animator: UIDynamicAnimator!
    var gravity: UIGravityBehavior!
    var motion: CMMotionManager!

    var queue: OperationQueue! // used for updating UI objects with motion
    
    var panGesture = UIPanGestureRecognizer()
    
    let colors = [UIColor.blue, UIColor.red, UIColor.yellow, UIColor.green]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        queue = OperationQueue.current
        animator = UIDynamicAnimator(referenceView: self.view)
        gravity = UIGravityBehavior(items: [blueSquare])
        
        motion = CMMotionManager()
        panGesture = UIPanGestureRecognizer(target: self, action: #selector(CanvasViewController.draggedView(_:)))
        blueSquare.isUserInteractionEnabled = true
        blueSquare.addGestureRecognizer(panGesture)
        
        animator.addBehavior(gravity)

        
        // the objects that responds to collision
        let collision = UICollisionBehavior(items: [blueSquare])

        // the boundary AKA the borders. In this case if the full ViewController view
        collision.addBoundary(withIdentifier: "borders" as NSCopying, for: UIBezierPath(rect: self.view.frame))
        
        
        animator.addBehavior(collision)
        
        //Elasticity
        let bounce = UIDynamicItemBehavior(items: [blueSquare])
        bounce.elasticity = 8
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.tabBarController?.tabBar.isHidden = true
    }
    
    @objc func draggedView(_ sender:UIPanGestureRecognizer){
        self.view.bringSubviewToFront(blueSquare)
        let translation = sender.translation(in: self.view)
        blueSquare.center = CGPoint(x: blueSquare.center.x + translation.x, y: blueSquare.center.y + translation.y)
        sender.setTranslation(CGPoint.zero, in: self.view)
        
        animator.updateItem(usingCurrentState: blueSquare)
    }
}

解决方法

1. 监听碰撞事件

要实现触碰底部变色,需要借助UICollisionBehaviorDelegate来监听方块和边界的碰撞。首先让控制器遵循这个协议:

class CanvasViewController: UIViewController, UICollisionBehaviorDelegate {
    // 原有代码不变
}

2. 调整碰撞行为的定义

把原来的局部collision变量改成类属性,方便在代理方法中调用,同时设置代理:

// 新增类属性
var collision: UICollisionBehavior!

override func viewDidLoad() {
    super.viewDidLoad()
    // ... 原有代码
    
    // 初始化碰撞行为并设置代理
    collision = UICollisionBehavior(items: [blueSquare])
    collision.addBoundary(withIdentifier: "borders" as NSCopying, for: UIBezierPath(rect: self.view.frame))
    collision.collisionDelegate = self
    animator.addBehavior(collision)
    
    // ... 原有代码
}

3. 实现碰撞检测逻辑

添加代理方法,判断碰撞是否发生在屏幕底部,然后切换方块颜色:

func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
    guard identifier as? String == "borders", let square = item as? UIView else { return }
    
    // 通过方块底部位置判断是否触碰底部
    let squareBottomY = square.frame.maxY
    let screenBottomY = view.bounds.maxY
    
    if squareBottomY >= screenBottomY - 1 { // 留1pt误差避免精度问题
        // 从颜色数组中选一个和当前不同的颜色
        let currentColor = square.backgroundColor
        var newColor: UIColor
        repeat {
            newColor = colors.randomElement()!
        } while newColor == currentColor
        square.backgroundColor = newColor
    }
}

4. 修复弹性值问题

注意你代码中bounce.elasticity = 8是错误的,弹性值范围是0到1(0完全不弹,1完全弹性),同时要把弹性行为添加到动画器中:

let bounce = UIDynamicItemBehavior(items: [blueSquare])
bounce.elasticity = 0.7 // 设置合理的弹性值
animator.addBehavior(bounce) // 别忘了添加到动画器

完整修改后的代码

import UIKit
import CoreMotion

class CanvasViewController: UIViewController, UICollisionBehaviorDelegate {
    
    @IBOutlet weak var blueSquare: UIView!
    
    var animator: UIDynamicAnimator!
    var gravity: UIGravityBehavior!
    var motion: CMMotionManager!
    var collision: UICollisionBehavior!
    
    var queue: OperationQueue!
    var panGesture = UIPanGestureRecognizer()
    let colors = [UIColor.blue, UIColor.red, UIColor.yellow, UIColor.green]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        queue = OperationQueue.current
        animator = UIDynamicAnimator(referenceView: self.view)
        gravity = UIGravityBehavior(items: [blueSquare])
        
        motion = CMMotionManager()
        panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
        blueSquare.isUserInteractionEnabled = true
        blueSquare.addGestureRecognizer(panGesture)
        
        animator.addBehavior(gravity)
        
        // 碰撞行为初始化与代理设置
        collision = UICollisionBehavior(items: [blueSquare])
        collision.addBoundary(withIdentifier: "borders" as NSCopying, for: UIBezierPath(rect: self.view.frame))
        collision.collisionDelegate = self
        animator.addBehavior(collision)
        
        // 修复弹性值并添加到动画器
        let bounce = UIDynamicItemBehavior(items: [blueSquare])
        bounce.elasticity = 0.7
        animator.addBehavior(bounce)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.tabBarController?.tabBar.isHidden = true
    }
    
    @objc func draggedView(_ sender:UIPanGestureRecognizer){
        self.view.bringSubviewToFront(blueSquare)
        let translation = sender.translation(in: self.view)
        blueSquare.center = CGPoint(x: blueSquare.center.x + translation.x, y: blueSquare.center.y + translation.y)
        sender.setTranslation(CGPoint.zero, in: self.view)
        
        animator.updateItem(usingCurrentState: blueSquare)
    }
    
    // 碰撞检测与变色逻辑
    func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
        guard identifier as? String == "borders", let square = item as? UIView else { return }
        
        let squareBottomY = square.frame.maxY
        let screenBottomY = view.bounds.maxY
        
        if squareBottomY >= screenBottomY - 1 {
            let currentColor = square.backgroundColor
            var newColor: UIColor
            repeat {
                newColor = colors.randomElement()!
            } while newColor == currentColor
            square.backgroundColor = newColor
        }
    }
}

内容的提问来源于stack exchange,提问作者Mateo Valencia Minota

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 17:40:31