无法触摸在 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 Spritekit 中触摸并按住屏幕时,我将如何向上移动我的节点?
swift--触摸(UITouch)事件(点击,移动,抬起)