UIView touch event in controller
You will have to add it through code. First, create the view and add it to the hierarchy:
var myView = UIView(frame: CGRectMake(100, 100, 100, 100))
self.view.addSubview(myView)
After that initialize gesture recognizer. Until Swift 2:
let gesture = UITapGestureRecognizer(target: self, action: "someAction:")
After Swift 2:
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.someAction (_:)))
Then bind it to the view:
self.myView.addGestureRecognizer(gesture)
Swift 3:
func someAction(_ sender:UITapGestureRecognizer){
// do other task
}
Swift 4 just add @objc
before func
:
@objc func someAction(_ sender:UITapGestureRecognizer){
// do other task
}
Swift UI:
Text("Tap me!").tapAction {
print("Tapped!")
}
How to add a touch event to a UIView?
In iOS 3.2 and higher, you can use gesture recognizers. For example, this is how you would handle a tap event:
//The setup code (in viewDidLoad in your view controller)
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleFingerTap];
//The event handling method
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView:[recognizer.view superview]];
//Do stuff here...
}
There are a bunch of built in gestures as well. Check out the docs for iOS event handling and UIGestureRecognizer
. I also have a bunch of sample code up on github that might help.
Touch Event on UIView
A very general way to get touches is to override these methods in a custom UIView subclass:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
The official docs for these methods is under Responding to Touch Events in the UIResponder class docs (UIView inherits those methods since it's a subclass of UIResponder). Longer and more introductory docs can be found in the Event Handling Guide for iOS.
If you just want to detect a tap (touch-down, then touch-up within your view), it's easiest to add a UIButton
as a subview of your view, and add your own custom method as a target/action pair for that button. Custom buttons are invisible by default, so it wouldn't affect the look of your view.
If you're looking for more advanced interactions, it's also good to know about the UIGestureRecognizer
class.
How to detect touch event across multiple views on my view controller?
Do it like this
class YourCustomView : UIView {
func addTapGesture(action : @escaping ()->Void ){
self.gestureRecognizers?.forEach({ (gr) in
self.removeGestureRecognizer(gr)
})
let tap = MyTapGestureRecognizer(target: self , action: #selector(self.handleTap(_:)))
tap.action = action
tap.numberOfTapsRequired = 1
self.addGestureRecognizer(tap)
self.isUserInteractionEnabled = true
}
}
or you can use this even in a extension of UIView if you're going to use it in multiple types of UIViews.
And then use it like:
let yourView = YourCustomView()
yourView.addTapGesture{[weak self] in
//implement tap event here
}
Notice that [weak self] is going to avoid getting a strong reference for closure to avoid a retain cycle there.
Preventing unhandled touch events on a child view controller from passing through to container view
I think I understand better what's going on here thanks to this very helpful WWDC video.
Given an incoming touch, first the system associates that touch with the deepest hit-tested view; in my case that's the UIScrollView. Then it apparently walks back up the hierarchy of superviews looking for any other attached recognizers. This behavior is implied by this key bit of documentation:
A gesture recognizer operates on touches hit-tested to a specific view and all of that view’s subviews.
The scroll view has its own internal pan recognizer(s), which either cancel unrecognized touches or possibly fall back on responder methods that don't happen to forward touches up the responder chain. That explains why my responder methods never get called, even when my own recognizers are disabled.
Armed with this information, I can think of a few possible ways to solve my problem, such as:
- Use gesture delegate methods to ignore touches if/when the associated view is under a child controller.
- Write a "null" gesture recognizer subclass that captures all touches and ignores them, and attach that to the root view of the child controller.
But what I ended up doing was simply to rearrange my view hierarchy with a new empty view at the top, so that my child controller views can be siblings of the main content view rather than its subviews.
So the view hierarchy changes from this:
to this:
This solves my problem: my gesture recognizers no longer interact with touches that are hit-tested to the child controller's views. And I think it better captures the conceptual relationships between my app's controllers, without requiring any additional logic.
Xcode & Swift - Detecting user touch of UIView inside of UIScrollView
By setting userInteractionEnabled
to NO
for your scroll view, the view controller will start receiving touch events since UIViewController
is a subclass of UIResponder
. You can override one or more of these methods in your view controller to respond to these touches:
- touchesBegan: withEvent:
- touchesMoved: withEvent:
- touchesEnded: withEvent:
- touchesCancelled: withEvent:
I created some example code to demonstrate how you could do this:
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
// This array keeps track of all obstacle views
var obstacleViews : [UIView] = []
override func viewDidLoad() {
super.viewDidLoad()
// Create an obstacle view and add it to the scroll view for testing purposes
let obstacleView = UIView(frame: CGRectMake(100,100,100,100))
obstacleView.backgroundColor = UIColor.redColor()
scrollView.addSubview(obstacleView)
// Add the obstacle view to the array
obstacleViews += obstacleView
}
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
testTouches(touches)
}
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
testTouches(touches)
}
func testTouches(touches: NSSet!) {
// Get the first touch and its location in this view controller's view coordinate system
let touch = touches.allObjects[0] as UITouch
let touchLocation = touch.locationInView(self.view)
for obstacleView in obstacleViews {
// Convert the location of the obstacle view to this view controller's view coordinate system
let obstacleViewFrame = self.view.convertRect(obstacleView.frame, fromView: obstacleView.superview)
// Check if the touch is inside the obstacle view
if CGRectContainsPoint(obstacleViewFrame, touchLocation) {
println("Game over!")
}
}
}
}
Related Topics
Xcode 10, Command Codesign Failed with a Nonzero Exit Code
Xcode 5.1: Libcordova.A Architecture Problems
How to Call a View Controller Programmatically
How to Listen for All Notifications Sent to the iOS Nsnotificationcenter's Defaultcenter
Uitextview: Disable Selection, Allow Links
Using Uipageviewcontroller with Swift and Multiple View Controllers
iOS 8 Keyboard Hides My Textview
How to Play Video Stream with Mpmovieplayercontroller in iOS
Xcode 6 Standard Architectures Exclude Armv7S
Is It Okay to Leave Out Unnecessary Launch Images
Keeping the Contentoffset in a Uicollectionview While Rotating Interface Orientation
Convert Coordinates to City Name
Alamofire Download in Background Session
Detect Backspace Event in Uitextfield
How Does the Whatsapp Web Client Still Work with the Latest iOS Update (Sdk Version 13.0+)
How to Get Managedobjectcontext for Viewcontroller Other Than Getting It from Appdelegate