Detect userEvent/pan/scroll on orthogonal sections in UICollectionViewCompositionalLayout - swift

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

Related

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
}

Selecting UICollectionViewCell in the presence of UITapGestureRecognizer

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
}

Dismiss keyboard for textfield in uitableviewcell in tableviewcontroller - Swift

I have a textfield and textview in my custom tableviewcell.
I have 4 different prototype cell with 4 different class created. there is a textfield in 1 prototype cell and a textview in the other.
I am not sure how I can do it and I dont understand the obj-c answers out there.
I've tried
override func viewDidLoad() {
super.viewDidLoad()
let tap: UIGestureRecognizer = UIGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
view.addGestureRecognizer(tap) }
and
func dismissKeyboard() {
self.view.endEditing(true)
}
I wanted to try
UITextFieldDelegate and touchesbegan and textfieldshouldreturn method, but there are no textfields to call in my tableviewcontroller.
Go to attributes inspector in storyboard and click tableView and set keyboard to dismiss interactively.
Or
Set textField delegate and implement
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool // called when 'return' key pressed. return false to ignore.
{
textField.resignFirstResponder()
return true
}
I had same problem, you can solve this with IQKeyboardManager, you don't need to complicate things with the custom cell classes, just install it on your project, when you're all set with installation, in the appDelegate inside the didFinishLaunchingWithOptions method, add this line of code:
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
Get IQKeyboardManager from here.

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).

UITableView as UIGestureRecognizerDelegate

I would like to extend UITableView by adding a custom UIPanGestureRecognizer:
extension UITableView {
func addCustomPanGestureRecognizer() {
let panGestureRecognizer = UIPanGestureRecognizer()
panGestureRecognizer.delegate = self
addGestureRecognizer(panGestureRecognizer)
// some additional setup
// ...
}
}
Since custom gesture recognizer interferes with scrolling, I tried to implement UIGestureRecognizerDelegate with an optional requirement:
extension UITableView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer == self.panGestureRecognizer
}
}
...but it's not getting called at all, despite delegating to tableView (as shown above).
Also, Xcode displays a warning:
Instance method 'gestureRecognizer(:shouldRecognizeSimultaneouslyWith:)' nearly matches optional requirement 'gestureRecognizer(:shouldRecognizeSimultaneouslyWith:)' of protocol 'UIGestureRecognizerDelegate'
To get rid of the warning, I tried adding #objc annotation before method definition, but with no luck.
Turns out, Xcode needed some additional help with finding an appropriate Objective-C method definition. To achieve this I had to provide method signature after #objc annotation:
extension UITableView: UIGestureRecognizerDelegate {
#objc(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer == self.panGestureRecognizer
}
}
The warning is still displayed, but the method is being called properly.