Container view's collectionVC blocked with Parent's tapGesture recognizer - ios

I have an extension of UIViewController as follows to dismiss the keyboard if the user taps on the screen. Within this view I have a scroll containing textfields and two container views. The container views contain a collectionView. I want my overall viewController to dismiss when tapped around but I still want my collectionView's didSelectitemAtIndexPath to trigger. How can I achieve this?
public extension UIViewController {
public func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
public func dismissKeyboard() {
view.endEditing(true)
}
}
and
class RegisterVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
logViewLoad()
self.hideKeyboardWhenTappedAround()
let controller = self.storyboard?.instantiateViewController(withIdentifier: "CollectionViewSearchVC") as! CollectionViewSearchVC
controller.type = "SC"
addChildViewController(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.delegate = self
containerView.addSubview(controller.view)
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
controller.didMove(toParentViewController: self)
subContractorVC = controller
let employeeController = self.storyboard?.instantiateViewController(withIdentifier: "CollectionViewSearchVC") as! CollectionViewSearchVC
employeeController.type = "V"
employeeController.delegate = self
addChildViewController(employeeController)
employeeController.view.translatesAutoresizingMaskIntoConstraints = false
employeeContainerView.addSubview(employeeController.view)
NSLayoutConstraint.activate([
employeeController.view.leadingAnchor.constraint(equalTo: employeeContainerView.leadingAnchor),
employeeController.view.trailingAnchor.constraint(equalTo: employeeContainerView.trailingAnchor),
employeeController.view.topAnchor.constraint(equalTo: employeeContainerView.topAnchor),
employeeController.view.bottomAnchor.constraint(equalTo: employeeContainerView.bottomAnchor)
])
employeeController.didMove(toParentViewController: self)
}

Implement the delegate methods for the GestureRecogniser in your ViewController and check for the class which is being touched, if that is of kind UICollectionView, then ignore that touch and let it do the default behaviour, i.e didSelect otherwise allow tap.
class ParentViewController: UIViewController {
var tap: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
tap = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(tap!)
}
public func hideKeyboardWhenTappedAround() {
dismissKeyboard()
}
#objc public func dismissKeyboard() {
view.endEditing(true)
}
}
class RegisterVC: ParentViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tap?.delegate = self
// YOUR EXISTING CODE HERE
}
// NEW CODE ADDED HERE
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
print("shouldReceive")
print(gestureRecognizer.view as Any) // CHECK FOR WHICH CLASS YOU ARE GETTING HERE WHEN YOU CLICK ON COLLECTIONVIEW
if (gestureRecognizer.view?.isKind(of: UICollectionView.self))! {
return false
}
return true
}
}
Try it and share the results.

You need to create a view behind the child VC's views and add the gesture to it

Related

iOS Swift: Send action (control event) from subclass of UIImageView

How can I send action (similar kind of tap event) from sub class of UIImageView to View Controller.
Here is reference answer, that I want to apply for UIImageView. (UIImageView does not have UIControl in its super class hierarchy)
Following is my code but not working as I don't know, how to implement, what I need.
class TappableImageView: UIImageView {
// Initializer methods.....
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
// how can I send tap/click event to all view controllers, where I've used this image from this point.
}
}
Is there any alternate/other solution that may work for me?
I will recommend you to use a closure to handle taps:
class TappableImageView: UIImageView {
var handleTap: (() -> Void)? = nil
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
handleTap?()
}
}
And then I your view controller you can use this i.e. in viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
yourTappableImageView.handleTap = {
print("an image view was tapped")
}
}
It assumes that your TappableImageView is stored in variable named yourTappableImageView.

UIGesturerecognizer is not working on UIView

When I am tapping on UIView but Gesture is not working.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
I just want to perform this action on UIView tap.
You may check in the debug view hierarchy if anything with alpha = 0 is overlapping your viewForSHadow.
You have to implement as follows:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_sender:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
...
}
Your code is more than enough to have working UIGestureRecognizer, you should check some other stuff like, is there something else that can consume the user interaction. And also to check if you use
isUserInteractionEnabled = false
to some parent view of viewForSHadow.
You have to use UIGestureRecognizerDelegate
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tapRecognizer.delegate = self
blackView.addGestureRecognizer(tapRecognizer)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
#objc func handleDismiss() {
print("handleDismiss")
}
Reference
Don't forget to set the UIGestureRecognizerDelegate in your class
You need to mention numberOfTapsRequired property of the UITapGestureRecognizer.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_sender:)))
tap.numberOfTapsRequired = 1
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTapGesture(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
// Why are you hiding this view **viewForSHadow**
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
Also, make sure you are not doing viewForSHadow.isUserInteractionEnabled = false anywhere in the code.
First of all you code doesn't compile. The handleTap(_:) signature must be like:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
Secondly, you need to first try with the minimal code in a separate project. And by minimal code I mean what you've added in the question.
class ViewController: UIViewController {
#IBOutlet weak var viewForSHadow: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
}
Try with just the above code and see if you can get it working. The above code is working well at my end without any delegate or numberOfTaps.

iOS - How to dismiss keyboard from the navigationItem.searchController when tap anywhere on the UIView?

I have implemented the new SearchController with its searchBar and the searchResultsController.
Here is how I implemented it :
The resultViewController:
lazy var resultViewController: SearchResultViewController = {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let searchResultViewController = storyboard.instantiateViewController(withIdentifier: "SearchResultViewController") as! SearchResultViewController
searchResultViewController.delegate = self
return searchResultViewController
}()
And this is the SearchController:
lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: resultViewController)
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchResultsUpdater = self
searchController.searchBar.placeholder = "Search.city.label".localizable()
searchController.searchBar.tintColor = UIColor.white
searchController.searchBar.barTintColor = UIColor.white
UITextField.appearance(whenContainedInInstancesOf: [type(of: searchController.searchBar)]).tintColor = UIColor(red:0.00, green:0.47, blue:0.78, alpha:1.0)
if let textfield = searchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
// Background color
backgroundview.backgroundColor = UIColor.white
// Rounded corner
backgroundview.layer.cornerRadius = 10;
backgroundview.clipsToBounds = true;
}
}
definesPresentationContext = true
return searchController
}()
In my viewWillAppear I set the navigationItem.searchController :
self.searchController.isActive = true
if #available(iOS 11.0, *) {
self.navigationItem.searchController = searchController
self.navigationItem.hidesSearchBarWhenScrolling = true
} else {
// Fallback on earlier versions
}
I have been able to handle the cancelButtonClicked :
extension HomeViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.endEditing(true)
self.searchController.isActive = false
}
}
This is doing the "cancel" animation, hiding keyboard + inactive state on searchBar/searchController. Both at the same time, with 1 tap on cancel Button.
But I am unable to achieve this when the user tap anywhere on the view.
I tried with tap gesture but it requires me 2 tap to achieve the same behavior.
NB:
I got an UICollectionView in my UIViewController, which takes all the place in the UIView.
Here is what I have tried :
override func viewDidLoad() {
super.viewDidLoad()
handleTapAnywhereToRemoveKeyboard()
}
func handleTapAnywhereToRemoveKeyboard() {
let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(sender:)))
//singleTapGestureRecognizer.numberOfTapsRequired = 1
singleTapGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(singleTapGestureRecognizer)
}
#objc func singleTap(sender: UITapGestureRecognizer) {
self.searchController.isActive = false
self.searchController.searchBar.resignFirstResponder()
self.searchController.searchBar.endEditing(true)
}
EDIT:
I was thinking, maybe it's because my searchBar and searchController aren't in the UIViewController's view hierarchy, but more in the NavigationController one.
So I also tried with :
navigationController?.view.endEditing(true)
I then was thinking, maybe it's because the UIScrollView within my UICollectionView is catching the tap.
So I tried to link the tap gesture on the UICollectionView instead of the UIView, but without success.
There is no need to add UITapGestureRecognizer as proposed above. UIViewContoller already conforms to UIResponder interface (legacy from Objective C), so you can override this method like this:
extension UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.window?.endEditing(true)
super.touchesEnded(touches, with: event)
}
}
Please create UIViewController for dismiss keyboard througout application.
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
Use above code in your UIViewController file as below.
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
}
Update:
You can also use below code to dismiss keyboard from any class.
UIApplication.shared.sendAction(#selector(UIResponder.resign‌​FirstResponder), to: nil, from: nil, for: nil)
Try the solution from Dimple Desai but add the gesture recognizer to the TableView or CollectionView or whatever is laying on top of your UIView. Then it should work.
override func viewDidLoad(){
let tapView = UITapGestureRecognizer(target: self, action: #selector(self.hideKeyboard))
self.view.addGestureRecognizer(tapView)
}
#objc func hideKeyboard(tap: UITapGestureRecognizer){
self.view.endEditing(true)
}

Does UILabel have any value to make it selectable?

Does UILabel have any value that can be set in order to make it selectable?
I have a label that I want to be selectable, (long press and a copy btn shows up) kinda like in Safari.
Self-contained Solution (Swift 5)
You can adapt the solution from #BJHSolutions and NSHipster to make the following self-contained SelectableLabel:
import UIKit
/// Label that allows selection with long-press gesture, e.g. for copy-paste.
class SelectableLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
addGestureRecognizer(
UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress(_:))
)
)
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
// MARK: - UIResponderStandardEditActions
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
}
// MARK: - Long-press Handler
#objc func handleLongPress(_ recognizer: UIGestureRecognizer) {
if recognizer.state == .began,
let recognizerView = recognizer.view,
let recognizerSuperview = recognizerView.superview {
recognizerView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperview)
UIMenuController.shared.setMenuVisible(true, animated:true)
}
}
}
Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.
Create a subclass of UILabel and implement the following methods:
override func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
return (action == "copy:")
}
// MARK: - UIResponderStandardEditActions
override func copy(sender: AnyObject?) {
UIPasteboard.generalPasteboard().string = text
}
Then in your view controller, you can add a long press gesture to your label:
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)
and handle the long press with this method:
func handleLongPressGesture(recognizer: UIGestureRecognizer) {
if let recognizerView = recognizer.view,
recognizerSuperView = recognizerView.superview
{
let menuController = UIMenuController.sharedMenuController()
menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
menuController.setMenuVisible(true, animated:true)
recognizerView.becomeFirstResponder()
}}
NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.
UILabel inherits from UIView so you can just add a long press gesture recognizer to the label. Note that you have to change isUserInteractionEnabled to true, because it defaults to false for labels.
import UIKit
class ViewController: UIViewController {
let label = UILabel()
override func viewDidLoad() {
view.addSubview(label)
label.text = "hello"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressLabel(longPressGestureRecognizer:)))
label.addGestureRecognizer(longPressGestureRecognizer)
label.isUserInteractionEnabled = true
}
#objc private func longPressLabel (longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .began {
print("long press began")
} else if longPressGestureRecognizer.state == .ended {
print("long press ended")
}
}
}
I've implemented a UILabel subclass that provides all of the functionality needed. Note that if you're using this with interface builder, you'll need to adjust the init methods.
/// A label that can be copied.
class CopyableLabel: UILabel
{
// MARK: - Initialisation
/// Creates a new label.
init()
{
super.init(frame: .zero)
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
self.addGestureRecognizer(gestureRecognizer)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// MARK: - Responder chain
override var canBecomeFirstResponder: Bool
{
return true
}
// MARK: - Actions
/// Method called when a long press is triggered.
func handleLongPressGesture(_ gestuerRecognizer: UILongPressGestureRecognizer)
{
guard let superview = self.superview else { return }
let menuController = UIMenuController.shared
menuController.setTargetRect(self.frame, in: superview)
menuController.setMenuVisible(true, animated:true)
self.becomeFirstResponder()
}
override func copy(_ sender: Any?)
{
UIPasteboard.general.string = self.text
}
}

TabBar hides in viewDidLoad but doesnt in gesture function

I tried calling tabBarController!.tabBar.hidden = true in viewDidLoad() and it hides the TabBar. However, I tried to set tap gesture and hide the bar on Tap. The parent viewController that has ScrollView inside it with subview (that is connected with IBOutlet as myView)
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
myView.addGestureRecognizer(tap)
}
func handleTap(sender: UITapGestureRecognizer? = nil) {
print("A") // logs successfully
if TabBarHidden == false {
print("B") // logs successfully
//I tried:
tabBarController?.tabBar.hidden = true
// I also tried
tabBarController?.tabBar.alpha = 0
tabBarController?.tabBar.frame.origin.x += 50
hidesBottomBarWhenPushed = true
} else {
...
TabBarHidden = false
}
}
hidden does work when I call it in viewDidLoad as I said, but not if I call in tap gesture function. What may be the problem? What am I missing?
this code totally works for me:
class ViewController: UIViewController {
var tabBarHidden: Bool = false {
didSet {
tabBarController?.tabBar.hidden = tabBarHidden
}
}
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapGestureRecognized(sender: UITapGestureRecognizer) {
tabBarHidden = !tabBarHidden
}
}

Resources