Selecting UICollectionViewCell in the presence of UITapGestureRecognizer - swift

I am trying to respond to UICollectionViewCell selection:
private func setupCellAction() {
collectionView?.rx.itemSelected
.asObservable()
.subscribe(onNext: { [weak self] indexPath in
print("itemSelected!")
let cell = self?.collectionView?.cellForItem(at: indexPath) as? CellTypeCollectionViewCell
self?.performSegue(withIdentifier: "showBarchartSegue", sender: cell)
}).disposed(by: disposeBag)
}
But somehow onNext method is never called. I tried putting setupCellAction() in viewDidLoad, viewWillAppear and viewDidAppear but it is not working. Any suggestions would be greatly appreciated.
Update
I tried the suggestion from the following thread: How to select CollectionView cell in RxSwift
and added .debug("RX: Model selected") before the subscribe method. I see the output in the console that it is subscribed once.
Update
I tried rewriting the setupCellAction() in the following way:
private func setupCellAction() {
collectionView?.rx.modelSelected(CellTypeCollectionViewCell.self)
.asObservable()
.debug("RX: Model selected")
.subscribe(onNext: { [weak self] cell in
print("itemSelected!")
self?.performSegue(withIdentifier: "showBarchartSegue", sender: cell)
}).disposed(by: disposeBag)
}
It is not working either. I see also that it is subscribed once in the console.
Update
UICollectionViewController was embedded in another container UIViewController, and in it I defined UITapGestureRecognizer. After commenting out the code for the UITapGestureRecognizer, the itemSelected() method started to work. Right now I need a way to let the tap event through if it happened on the UICollectionViewCell. Is there a way to do that?
The code for tapping in the container controller (viewDidLoad):
let tap = UITapGestureRecognizer(target: self, action:
#selector(self.handleTap(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
The handleTap():
#objc func handleTap(_ sender: UITapGestureRecognizer) {
tableView.isHidden = true
searchBar.resignFirstResponder()
}

You can let taps through with the UIGestureRecognizerDelegate protocol and implementing the method
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool.
Basically, you need to return false whenever you touch a UICollectionViewCell, if I understood the problem correctly.
You can do this by using the method func indexPathForItem(at point: CGPoint) -> IndexPath? from the UICollectionView. If the given CGPoint matches a cell's location, you will get its IndexPath.
Don't forget to translate the touch location to the collection view's frame - you can use UITouch's func location(in view: UIView?) -> CGPoint for this.
It would probably look somewhat like this:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: collectionView)
return collectionView.indexPathForItem(at: point) == nil
}

Related

Detect userEvent/pan/scroll on orthogonal sections in UICollectionViewCompositionalLayout

I am using autoScroll on an orthogonal section of the collectionView using compositional layout. I need to invalidate the autoscroll timer as soon as the user manually scrolls the section.
I could use scrollViewDidBeginDragging / scrollViewWillBeginDecelerating, but the scrollView delegates never get called on orthogonal sections.
If anyone has any workaround to detect user scroll event in this case, it will be helpful. Thank you.
After trying out several solutions, I found the best and the simplest solution. I added a UIPanGestureRecogniser to the UICollectionViewCell to listen to user pan events. In the selector, I just invalidate the timer. That's it!
Also we need to return true by overriding gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) so that the vertical scroll and horizontal scrolls works properly.
This is what I added to the UICollectionViewCell class:
class CustomCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: .zero)
pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
pan.delegate = self
self.addGestureRecognizer(pan)
}
#objc private func handlePan(_ pan: UIPanGestureRecognizer) {
delegate?.invalidateTimer()
}
}
extension CustomCell: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
With this, every time the user tries to scroll, I invalidate the autoScroll timer

Screen edge gesture is not recognized in PDFView (UIViewer) [Swift, iOS 15, PDFKit]

I am displaying a PDF file and would like to add a screen edge gesture to move pages around.
The following code works fine when entire the content of a PDF is displayed on the screen. However, when the PDF was zoomed, the screen edge gesture cannot even activate.
override func viewDidLoad(){
super.viewDidLoad()
//set up gesture to swipe from the edge
let leftScreenEdgeRecognizer = UIScreenEdgePanGestureRecognizer (
target: self, action: #selector(TextDocumentViewController.leftEdgePanGestureHandler(_ : )))
leftScreenEdgeRecognizer.edges = UIRectEdge.left
let rightScreenEdgeRecognizer = UIScreenEdgePanGestureRecognizer (
target: self, action: #selector(TextDocumentViewController.rightEdgePanGestureHandler(_ : )))
rightScreenEdgeRecognizer.edges = UIRectEdge.right
//register the gesture
pdfView.addGestureRecognizer(leftScreenEdgeRecognizer)
pdfView.addGestureRecognizer(rightScreenEdgeRecognizer)
}
//gesture functions here
#objc func leftEdgePanGestureHandler(_ sender: UIScreenEdgePanGestureRecognizer){
if(sender.state == UIGestureRecognizer.State.ended){
print ("Left Edge")
pdfView.goToPreviousPage(sender)
}
}
#objc func rightEdgePanGestureHandler(_ sender: UIScreenEdgePanGestureRecognizer){
if(sender.state == UIGestureRecognizer.State.ended){
print ("right Edge")
pdfView.goToNextPage(sender)
}
}
I tired to add a code like,
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
However, this is not working at all.
I was able to solve this problem thanks to the answer provided for my old question. I have totally forgotten about the post. After learning more about multiple gesture detections through try and error, I realized that I can do as follows to solve this posted question:
Enables the multiple gesture activation:
class ViewController: UIViewController, UIGestureRecognizerDelegate, UIDocumentPickerDelegate, PDFViewDelegate {
// ... other things
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer:
UIGestureRecognizer) -> Bool {
return true
}
}
Make sure to appropriately set delegate.
override func viewDidLoad(){
super.viewDidLoad()
//set gesture
leftScreenEdgeRecognizer.delegate = self
rightScreenEdgeRecognizer.delegate = self
}

How to disable tap gesture recognizer UIButton for dismisskeyboard?

I have this code:
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap = UITapGestureRecognizer(target: self, action: #selector(self.dissmissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
// Don't handle button taps
return !(touch.view is UIButton)
}
This is a global function!
When I press the login button the keyboard disappears and then I must to press it again to login. Is there a way to avoid this?
I want that when I press login button keyboard not disappear but when I press outside button keybord disappear
What you are missing is the delegate.
tap.delegate = self
Since you haven't add the delegate, your
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
method will not execute.
Have a breakpoint there and see that it is execute or not. I don't think so. By adding the delegate that I mentioned earlier will do the trick.
The code will be look like below.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.dissmissKeyboard))
tap.delegate = self
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
In the class you should implement the delegate class like below.
class ViewController: UIViewController, UIGestureRecognizerDelegate {

How to disable swipe back specifically on only one view controller

I know there have been similar questions asked, but none of them have worked for me.
I have this code to enable swiping back in my project
class InteractivePopRecognizer: NSObject {
// MARK: - Properties
fileprivate weak var navigationController: UINavigationController?
// MARK: - Init
init(controller: UINavigationController) {
self.navigationController = controller
super.init()
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
}
extension InteractivePopRecognizer: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return (navigationController?.viewControllers.count ?? 0) > 1
}
// This is necessary because without it, subviews of your top controller can cancel out your gesture recognizer on the edge.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
I have this VC stack
HomescreenVC -> Login/SignupVC -> UserProfileVC
I do not want them to be able to swipe back from the UserProfileVC.
A better approach is to clear them from the stack when you show UserProfileVC
let profile = self.storyboard?.instantiateViewController(withIdentifier: "profileID") as! UserProfileVC
self.navigationController?.viewControllers = [profile]
Edit: Do this inside profileVC
self.navigationController?.viewControllers = [self]
//
self.view.alpha = 0
UIView.animate(withDuration: 0.5) {
self.view.alpha = 1
}
I think you can remove gesture Recognizer from there too why you are not trying that.
Try something like this:-
view.gestureRecognizers?.removeAll()

UITapGestureRecognizer for UIPIckerView

UITapGestureRecognizer with UIPickerView not working. Why?
class someVC: UIViewController, UITapGestureRecognizerDelegate
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
self.pickerView.addGestureRecognizer(tap)
tap.delegate = self
}
func dismissKeyboard() {
textField.resignFirstResponder()
}
I solved problem by adding override getureRecognizer
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Looks ok, could it be that there is another gesture recognizer swallowing the tap? You need to make sure your object is on the top of the view stack to ensure it gets fired. Also you set the delegate, does the delegate methods get called? or do they not fire either? If they don't then it means your tap gesture is not being touched(another gesture is taking the touch), if it does get fired, it means there is something wrong with the action(but i doubt that is the case).