无法触摸正在移动的 UIImageView Swift

Can't touch UIImageView that is moving in Swift

我制作了瓷砖从屏幕顶部落到屏幕底部的掉落瓷砖游戏。 这个游戏系统是当你触摸一个方块时,方块就会隐藏。

图块是自定义的 class(GameTile class),但 GameViewController 中的 Touches Began 不起作用。 我该如何解决?

GameTile.swift

class GameTile: UIImageView {

    init(named: String, frame: CGRect) {
        super.init(frame: frame)
        super.image = (UIImage(named: named))
        super.isUserInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class GameTileNormal: GameTile {
    let namedDefault: String
    var frameDefault: CGRect
    let isHiddenDefault: Bool
    var isUserInteractionEnabledDefault: Bool
    let colorName: UIColor

    init(
        named: String,
        frame: CGRect,
        isHidden: Bool = false,
        isUserInteractionEnabled: Bool = true,
        color: UIColor = UIColor.blue) {
        namedDefault = named
        isHiddenDefault = isHidden
        frameDefault = frame
        isUserInteractionEnabledDefault = isUserInteractionEnabled
        colorName = color

        super.init(named: named, frame: frame)
        super.isHidden = isHiddenDefault
        super.isUserInteractionEnabled = isUserInteractionEnabledDefault
        super.backgroundColor = colorName

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

GameView.swift

class GameView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.isUserInteractionEnabled = true

        self.backgroundColor = (UIColor.white)

        self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)


            //make tiles
            let tileNormal = GameTileNormal.init(named: "clear",
                                                 frame: CGRect(x:0), y:-60, width:60, height:60),isUserInteractionEnabled: true)
            self.addSubview(tileNormal)

            //move tiles
            moveTile(tile: tileNormal, lane: 1)
    }
}

    func moveTile(tile: GameTile, lane: Int) {

        UIImageView.animate(withDuration: TimeInterval(2.0),
                            delay: 0.0,
                            options: .curveLinear,
                            animations: {
                                tile.frame.origin.y = UIScreen.main.bounds.size.height
        }, completion: {finished in
            tile.removeFromSuperview()

            //make new tile
            self.makeTiles(lane: lane)

        })
    }

GameViewController.swift

class GameViewController: UIViewController {

var gameView: GameView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    self.view.isUserInteractionEnabled = true

    gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
    self.view.addSubview(trapView)

}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touchEvent = touches.first!

    if let gameView = self.gameView {

        // touchEvent.view is "gameView", not the view whose kind of class is GameTileNormal...
        if let touchedGameTile = touchEvent.view as? GameTileNormal {
            print("Touched normal tile")
            touchEvent.view?.isHidden = true
            touchEvent.view?.isUserInteractionEnabled = false

        }else{
            // other view
        }
    }
}

更新

我更改了将图块从 UIImageView.animation 移动到计时器的方式。 然后如果我触摸了瓷砖,在 touchesBegan、GameViewController 中 if (tile.layer.presentation()?.hitTest(location)) != nil { 之后它没有通过.....

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touchEvent = touches.first!
        let location = touchEvent.location(in: touchEvent.view)

        if let standardView = self.standardView {

            for tile in standardView.tiles {

             //breakpoint stops here

                if (tile.layer.presentation()?.hitTest(location)) != nil {
                    //breakpoint doesn't through here
                    if tile is GameTileNormal {

                        //normal tile touched

                    }else{

                    }
                    break
                }
            }
        }
    }

移动方块

makeTileTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateTilesPositionY(timer:)), userInfo: sendArray, repeats: true)

更新方块位置(放下方块)

@objc func updateTilesPositionY(timer: Timer){

//tile info
let timerInfo:[Any] = timer.userInfo as! [Any]
let tile:GameTile =  timerInfo[0] as! GameTile
let lane: Int = timerInfo[1] as! Int

//drop tile
tile.frame.origin.y = tile.frame.origin.y+1

//if tile reached on the bottom
if tile.frame.origin.y >= UIScreen.main.bounds.size.height {
    if tile is GameTileNormal {
        self.showGameOverView()
    }
}else{
    //drop tile
}

UIImageView.animate中添加选项.allowUserInteraction:

UIImageView.animate(withDuration: TimeInterval(2.0),
                    delay: 0.0,
                    options: [.curveLinear, .allowUserInteraction],
                    animations: {
                        tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
    ...

默认情况下,在动画期间不允许用户交互。

更新

但是,要测试用户是否击中了移动物体,您会遇到一些困难。例如,参见 。基本上,UIView对象并没有真正移动,你可以很容易地测试在触发动画后,动画对象的frame直接设置到结束位置。只是表示层绘制移动视图。

你必须始终检查游戏中所有移动的方块,并测试每个方块是否已被触摸(这里我假设你有对游戏中所有 tiles 的引用) :

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touchEvent = touches.first!
    let location = touchEvent.location(in: touchEvent.view)

    if let gameView = self.gameView {
        for tile in tiles {
            // go over all the moving objects in your scene and hit test all of them
            if let touchedLayer = tile.layer.presentation()?.hitTest(location) {
                // if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop

                tile.isHidden = true
                tile.isUserInteractionEnabled = false
                tile.removeFromSuperview()
                break
            }
        }
    }
}

使用 layer.presentationLayer 到 运行 一个 hitTest 如果那个 hitTest return 一个 CALayer 那么你正在触摸那个 titleView,事实上,这仅在您的标题为 userInteractionEnabled = false

时才有效

完整代码

import UIKit

class GameTile: UIImageView {

    init(named: String, frame: CGRect) {
        super.init(frame: frame)
        super.image = (UIImage(named: named))
        super.isUserInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class GameTileNormal: GameTile {
    let namedDefault: String
    var frameDefault: CGRect
    let isHiddenDefault: Bool
    var isUserInteractionEnabledDefault: Bool
    let colorName: UIColor

    init(
        named: String,
        frame: CGRect,
        isHidden: Bool = false,
        isUserInteractionEnabled: Bool = false,
        color: UIColor = UIColor.blue) {
        namedDefault = named
        isHiddenDefault = isHidden
        frameDefault = frame
        isUserInteractionEnabledDefault = isUserInteractionEnabled
        colorName = color

        super.init(named: named, frame: frame)
        super.isHidden = isHiddenDefault
        super.isUserInteractionEnabled = isUserInteractionEnabledDefault
        super.backgroundColor = colorName

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class GameView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.isUserInteractionEnabled = true

        self.backgroundColor = (UIColor.white)

        self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)


        //make tiles
        let tileNormal = GameTileNormal.init(named: "clear",
                                             frame: CGRect(x:0, y:-60, width:60, height:60),isUserInteractionEnabled: false)
        self.addSubview(tileNormal)
        //move tiles
        moveTile(tile: tileNormal, lane: 1)

        self.layer.borderWidth = 1
        self.layer.borderColor = UIColor.blue.cgColor
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func moveTile(tile: GameTile, lane: Int) {

        UIImageView.animate(withDuration: TimeInterval(10),
                            delay: 0.0,
                            options: .curveLinear,
                            animations: {
                                tile.frame.origin.y = UIScreen.main.bounds.size.height
        }, completion: {finished in
            tile.removeFromSuperview()

            //make new tile
            //self.makeTiles(lane: lane)

        })
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touchEvent = touches.first!
        let location = touchEvent.location(in: touchEvent.view)

        for tile in self.subviews {
            // go over all the moving objects in your scene and hit test all of them
            if tile.layer.presentation()?.hitTest(location) != nil {
                // if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
                tile.isHidden = true
                break
            }
        }
    }

}

class ViewController: UIViewController {

    var gameView: GameView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.view.isUserInteractionEnabled = true

        gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
        self.view.addSubview(gameView)

    }

}