How to make a UIImageView tappable and cause it to do something? (Swift)
In swift 3.0:
imvEditName.isUserInteractionEnabled = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imvEditName.addGestureRecognizer(tapRecognizer)
And the target method:
func imageTapped(sender: UIImageView) {
print("image tapped")
}
Once you've create the view, you need to set it's userInteractionEnabled property to true. Then you need to attach a gesture to it.
imageView.userInteractionEnabled = true
//now you need a tap gesture recognizer
//note that target and action point to what happens when the action is recognized.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("imageTapped:"))
//Add the recognizer to your view.
imageView.addGestureRecognizer(tapRecognizer)
Now you still need the function, in this case imageTapped:, which is where the action happens when the gesture is recognized. The gesture that was recognized will be sent as an argument, and you can find out which imageView was tapped from they gestures view property.
func imageTapped(gestureRecognizer: UITapGestureRecognizer) {
//tappedImageView will be the image view that was tapped.
//dismiss it, animate it off screen, whatever.
let tappedImageView = gestureRecognizer.view!
}
My previous answer is still accurate for adding a tap recognizer to a UIImageView, however it is not sufficient for the purposes of a view that is animating. If you are actually creating a game, you'll likely want to look into SpriteKit, as it is literally tailor made for this. However, this can be accomplished with normal UIKit. I've tested two approaches, which both work.
Both approaches worked far better on an actual device than in the simulator
Approach 1, Simpler yet slightly less precise up to a tenth of second slower in my tests.
Instead of adding a tap gesture to the UIImageView, add the gesture to the superview. Keep a reference to your blocks in an array property, and check if any blocks are intercepted by the tap.
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
override func viewDidLoad() {
super.viewDidLoad()
// Adding recognizer to the whole view.
let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("screenTapped:"))
view.addGestureRecognizer(tapRecognizer)
blocks.append(createBlock())
}
//changed to return the created block so it can be stored in an array.
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.blocks.append(self.createBlock())
})
return imageView
}
func screenTapped(gestureRecognizer: UITapGestureRecognizer) {
let tapLocation = gestureRecognizer.locationInView(view)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(tapLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
Approach 2, Best Performance outside of SpriteKit
In approach two, you subclass UIView, and set your main view to it instead of the UIView. You only need to override a single method. I'm storing the block array in the view for convenience.
First the TouchView.
import UIKit
class TouchView: UIView {
var blocks = [UIImageView]()
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// ignoring multiple touches for now
if touches.count == 1 {
if let touch = touches.first as? UITouch {
let touchLocation = touch.locationInView(self)
//This is to keep track of the blocks to remove from the array.
//If you remove them as you iterate the array, and you have more than
//one block to remove, you may end up removing the wrong block
//or with an index out of bounds.
var removeBlocks = [Int]()
for (index, block) in enumerate(blocks) {
//this is the frame of the view as we see it on screen.
//unfortunately block.frame is not where we see it making a recognizer
//on the block itself not work.
if block.layer.presentationLayer().frame.contains(touchLocation) {
//Then this block was clicked.
block.removeFromSuperview()
removeBlocks.append(index)
}
}
//get the indexes ro remove backwards so we are removing from
//back to front.
for index in removeBlocks.reverse() {
blocks.removeAtIndex(index)
}
}
}
}
}
Now the ViewController would look like:
import UIKit
class ViewController: UIViewController {
let size = CGFloat(40)
let xPosition = CGFloat(14)
let options = UIViewAnimationOptions.Autoreverse
var blocks = [UIImageView]()
// computed get only property for conveniece of accessing block array
var touchView:TouchView {
get {
return self.view as! TouchView
}
}
override func viewDidLoad() {
super.viewDidLoad()
touchView.blocks.append(createBlock())
// Do any additional setup after loading the view, typically from a nib.
}
func createBlock() -> UIImageView {
let imageName = "block.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: xPosition, y: -40, width: size, height: size)
self.view.addSubview(imageView)
UIView.animateWithDuration(2, delay: 0.0, options: options, animations: {
imageView.backgroundColor = UIColor.redColor()
imageView.frame = CGRect(x: self.xPosition, y: 590, width: self.size, height: self.size)
}, completion: { animationFinished in
imageView.removeFromSuperview()
self.touchView.blocks.append(self.createBlock())
})
return imageView
}
}
In my tests, this seems to work pretty good, even with quickly animated "blocks". Again, if you are planning on making a full fledged game, you should really look at SpriteKit. There is a really simple tutorial here that should get you started. If you can remove the accepted answer from my previous answer, that would help others not be lead down the wrong path, unless they only are looking to add a gesture to a non-moving UIImageView.