无法触摸在 Swift 中移动的 UIImageView

Posted

技术标签:

【中文标题】无法触摸在 Swift 中移动的 UIImageView【英文标题】:Can't touch UIImageView that is moving in Swift 【发布时间】:2018-03-28 07:33:27 【问题描述】:

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

图块是自定义类(GameTile 类),但 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 移动到 Timer。 然后如果我碰了瓷砖,在touchesBegan,GameViewControllerif (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

【问题讨论】:

看到这个***.com/questions/17810576/… UIView animation prevents touch events?的可能重复 查看这个答案***.com/questions/37818440/… 【参考方案1】:

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
    ...

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

更新

但是,要测试用户是否击中了移动的物体,您会遇到一些困难。例如,请参见 SO question。基本上,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
            
        
    

【讨论】:

@bao 你是什么意思它没有很好地工作?发生了什么? 当我触摸 GameTileNormal 时,什么也没有发生。这个应用程序通过了touchBegan,但没有通过“如果让touchedGameTile = touchEvent.view as?GameTileNormal”。 “touchesEvent.view”是日志中的“gameView”... @bao 所以解决方案有效,只是您的其余代码无法按您希望的那样工作.. 放一个断点并调试它 原来这段代码是通过touchesBegan,但是if let touchGameTile = touchEvent.view as之后没有通过? GameTileNormal 。我会找到这个解决方案,但如果你找到新的解决方案,请告诉我。谢谢。 @bao 你有可以运行的代码。设置断点并检查你在touchEvent.view 中得到的内容,看看你应该如何应对它【参考方案2】:

使用 layer.presentationLayer 运行 hitTest 如果 hitTest 返回 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)

    


【讨论】:

我认为这行不通,因为touchEvent.view 几乎不会是被触摸的游戏图块(除非触摸发生在图块的最终位置) - 如果是这种情况,原始代码会起作用的 我将检查 OP 设置,但我的回答是基本上将 hitTest 与presentationLayer 一起使用@MilanNosáľ 我现在看到你基本上已经将我的答案添加到你的@MilanNosáľ @bao 看我的答案 @Reinier Melian 谢谢!现在我想知道当我放下许多瓷砖并且其中一个落到底部但其他瓷砖没有掉落时如何停止这个动画......我不知道如何停止 UIImageVIew.animation......跨度>

以上是关于无法触摸在 Swift 中移动的 UIImageView的主要内容,如果未能解决你的问题,请参考以下文章

根据 Swift 中的触摸正确移动 Sprite

当在 Swift Spritekit 中触摸并按住屏幕时,我将如何向上移动我的节点?

Swift:禁用对 UITableViewCell 的触摸

swift--触摸(UITouch)事件(点击,移动,抬起)

为啥触摸 SCNView 顶部的滑块会在 iOS 8/Swift 上将相机移动到下方?

如何在 Swift 3 中多点触摸 UICollectionViewCell