TableCell pangesture got ignore when inside a collectionview - ios

Question and situation explaination
Hi I have a custom collection view that I make each collectionview cell the size of the screen and make it scroll horizontally so I get like a paging effect.
In each collectionview cell I have a tableview which also take up the whole size of that collectionview cell. I'm trying to implement trailing and leading swipe action of my tableview cell but it is being ignore because of the swipe gesture to scroll the collectionview cells.
Is there a way to maybe like set the collectionview cell to only watch for pangestures that are near the edge of the screen then scroll the collectionview, other wise the pan gesture will just be send to the tableview instead?
Alternative solution (temporary work around)
Though I have found a work around but I still very much interested in finding out if what I'm trying to do above is possible so if you know please leave your answer
So after searching and searching stackoverflow there seem to be other people with the same problem as mine but doesnt seem to be able to found an answer. Thus I have come up with another idea and I like to share so if anyone come across this problem they can have options to choose from.
My idea is adding a long press gesture regconizer for the tableview you can achieve that as follow in swift 4
func setupLongPressGesture() {
let longPressGesture: UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress))
longPressGesture.minimumPressDuration = 1.0 // 1 second press
longPressGesture.delegate = self
dataDisplayTable.addGestureRecognizer(longPressGesture)
}
#objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer){
if gestureRecognizer.state == .began {
let touchPoint = gestureRecognizer.location(in: dataDisplayTable)
if let indexPath = dataDisplayTable.indexPathForRow(at: touchPoint) {
// let cell = dataDisplayTable.cellForRow(at: indexPath) - use this to retreive the cell at the above indexPath to do something with it
// print(cell?.reuseIdentifier) - use this to retreive the cell identifier and do something with it
}
}
}
Then add this to your viewDidLoad()
setupLongPressGesture()

Related

Gesture recogniser is impacting other functionality

I have a gesture recogniser that is stopping the collectionView function from working properly. Example code is added below.
The view controller has a collection view, where cells can be deleted in a similar way to how apps are deleted on an iPhone home screen. The user long presses the screen for the collection cells to start shaking and stops shaking once the screen is tapped (not on the delete button for the cells).
Now that this delete functionality is working, I can no longer click on the cells to open a new view controller with the associated information. Is there a way to add a conditional so that the tapRecognizer only happens while the cells are shaking, so that the collectionView function works when a cell in the collection view is tapped?
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed))
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapPressed))
self.collectionView.addGestureRecognizer(longPressRecognizer)
self.collectionView.addGestureRecognizer(tapRecognizer)
}
//Tap screen
#objc func tapPressed(sender: UITapGestureRecognizer) {
stopCellShaking()
}
//Open the information for the selected cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Function to open new screen
showInformation(chosenCell: exampleList[indexPath.row])
}
Is there a way to add a conditional so that the tapRecognizer only happens while the cells are shaking
Absolutely. In general, just turn off the tap recognizer by setting its isEnabled to false. When the shaking starts (long press), set the tap gesture recognizer's isEnabled to true.

Did select on table view cell does not response

The problem is that I have a tableview with webview
Inside each cell and the content of webview can scroll
Horizontally, but not vertically. It’s height is the same as
Cells height. So the problem is when I am trying to detect
DidSelect of tableview cell it doesn’t response. But when I tried to put userinteraction of webview to false it works but webview can not scroll anymore. Is there any alternative ways of doing this. Btw webview cells must contain webview no other ways.
Here is the screenshot of why I need webview inside cell.
Each cell contains webview content
You can add UITapGestureRecognizer to the WebView and do the action inside the UITapGestureRecognizer. (Here I'm adding how to add a gesture recognizer, you have to adapt it to your own case)
let tapRecognizer = UITapGestureRecognizer(target: webView, action: #selector(self.handleSingleTap(_:)))
tapRecognizer.numberOfTapsRequired = 1
self.view.addGestureRecognizer(tapRecognizer)
func handleSingleTap(recognizer: UITapGestureRecognizer) {
//Do something here with the gesture
}
you can set delay touch false in order to have select event for cell.
tableview.delaysContentTouches = false
use this link for detail info - https://developer.apple.com/documentation/uikit/uiscrollview/1619398-delayscontenttouches

Multiple gestures on same view (drag view out of scrollable view) not working the right way?

In order to let my panGesture (attached to an UIImageView) to be called, I need to make sure that the user is not scrolling in the horizontal ScrollView the image lies within. I am achieving this like so:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
panGesture.require(toFail: self.scrollView.panGestureRecognizer)
This works well but as the scrollView is horizontal only, it only allows the panGesture to work horizontally. I want to also pull the UIImage vertically, but nothing happens because obviously, it will only be called if the scroll view fails (can only fail horizontally).
Is there a way to make sure it so that the above works but also allows the panGesture to be called vertically no matter what?
Ok I solved this with a combination of a few things. It looks like a lot to add, but it will come in handy in other projects.
Step 1: Enable the horizontal scrolling UIScrollView to "fail" both horizontally and vertically.
Although I only want my UIScrollView to scroll horizontally, I still need it to scroll vertically so that it can fail (explanation coming). Failing enables me to "pull" the subviews out of the UIScrollView.
The height of scrollView itself is 40 so the contentSize of the it must have a larger height for it to be able to barely scroll vertically.
self.effectTotalHeight.constant = 41
self.scrollView.contentSize = CGSize(width: contentWidth, height: self.effectTotalHeight.constant)
Great! It scrolls vertically. But now the content rubber-bands (which we do not want). Against what others on SO say, do not go cheap and just disable bounce. (Especially if you want the bounce when scrolling horizontally!)
Note: I also realized only disabling Bounce Horizontally in StoryBoard does... well, nothing (bug?).
Step 2: Add UIScrollViewDelegate to your View Controller class and detect scrolls
Now I want to detect the scroll and make sure that when scrolling vertically, it does not actually scroll. To do this, the contentOffset.y position should not change even though you are scrolling. UIScrollViewDelegate provides a delegate function scrollViewDidScroll that gets called when scrolling. Just set it as:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.contentOffset.y = 0.0
}
In order for this to be called, you need to set the delegate of the scrollView to self as well:
self.scrollView.delegate = self
Keep in mind this controls all UIScrollViews so provide an if statement or switch statement if you want it to only affect specific ones. In my case, this view controller only has one UIScrollView so I did not put anything.
Yay! So now it only scrolls horizontally again but this method only keeps the contentOffset.y at 0. It does not make it fail. We do need it to because scrollView failing vertically is the key to enabling pan gesture recognizers (what lets you pull and drag etc.). So let's make it fail!
Step 3: Override UIScrollView default gesture recognition
In order to override any of the default gesture recognizers, we need to add UIGestureRecognizerDelegate as another delegate method to your View Controller class.
The scrollView now needs its own pan gesture recognizer handler so that we can detect gestures on it. You also need to set the delegate of the new scrollGesture to self so we can detect it:
let scrollGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleScroll(_:)))
scrollGesture.delegate = self
self.scrollView.addGestureRecognizer(scrollGesture)
Set up the handleScroll function:
func handleScroll(_ recognizer: UIPanGestureRecognizer) {
// code here
}
This is all good, but why did we set this all up? Remember we disabled the contentOffset.y but the vertical scroll was not failing. Now we need to detect the direction of the scroll and let it fail if vertical so that panHandle can be activated.
Step 4: Detect gesture direction and let it fail (failure is good!)
I extended the UIPanGestureRecognizer so that it can detect and emit directions by making the following public extension:
public enum Direction: Int {
case Up
case Down
case Left
case Right
public var isX: Bool { return self == .Left || self == .Right }
public var isY: Bool { return !isX }
}
public extension UIPanGestureRecognizer {
public var direction: Direction? {
let velo = velocity(in: view)
let vertical = fabs(velo.y) > fabs(velo.x)
switch (vertical, velo.x, velo.y) {
case (true, _, let y) where y < 0: return .Up
case (true, _, let y) where y > 0: return .Down
case (false, let x, _) where x > 0: return .Right
case (false, let x, _) where x < 0: return .Left
default: return nil
}
}
}
Now in order to use it correctly, you can get the recognizer's .direction inside of the handleScroll function and detect the emitted directions. In this case I am looking for either .Up or .Down and I want it to emit a fail. We do this by disabling the recognizer it, but then re-enabling it immediately after:
func handleScroll(_ recognizer: UIPanGestureRecognizer) {
if (recognizer.direction == .Up || recognizer.direction == .Down) {
recognizer.isEnabled = false
recognizer.isEnabled = true
}
}
The reason .isEnabled is immediately set to true right after false is because false will emit the fail, enabling the other gesture ("pulling out" (panning) its inner views), but to not be disabled forever (or else it will cease being called). By setting it back to true, it lets this listener be re-enabled right after emitting the fail.
Step 5: Let multiple gestures work by overriding each other
Last but not least, this is a very very important step as it allows both pan gestures and scroll gestures to work independently and not have one single one always override the other.
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
And that's that! This was a lot of research on SO (mostly finding what did not work) and experimentation but it all came together.
This was written with the expectation that you have already written the pan gesture recognizers of the objects inside of the scroll view that you are "pulling out" and how to handle its states (.ended, .changed etc.). I suggest if you need help with that, search SO. There are tons of answers.
If you have any other questions about this answer, please let me know.

Having multiple tableViews in a scrollview with paging makes tableHeaderView's searchBar non touchable

I have 5 tableViewControllers using UISearchResultsUpdating protocol with the new UISearchController
private var resultSearchController:UISearchController!
resultSearchController = UISearchController(searchResultsController: nil)
resultSearchController.searchResultsUpdater = self
resultSearchController.dimsBackgroundDuringPresentation = false
resultSearchController.searchBar.sizeToFit()
tableView.tableHeaderView = resultSearchController.searchBar
tableView.tableHeaderView?.backgroundColor = UIColor.redColor()
tableView.tableHeaderView?.userInteractionEnabled = true
When I load this viewController by pushing it on top of other viewControllers, everything works fine. I can touch the cells, I can touch the searchBar and operate a search.
Now, when I put 5 (or less, it doesn't matter) of those tableViewControllers inside a controller containing a scrollView
let rect: CGRect = self.view.frame
scrollView = UIScrollView(frame: rect)
scrollView.pagingEnabled = true
scrollView.addSubview(oneTableViewController)
scrollView.addSubview(twoTableViewController)...
I can navigate through the 5 tableViewController by swiping left and right.
I can select a row in any tableview.
but I can't touch the searchBar anymore...
I tried to set:
scrollView.exclusiveTouch = false
ensure that tableView.tableHeaderView?.userInteractionEnabled = true
Any ideas?
By looking at your problem I think there can be a better way to achieve what you are doing.
Instead of using 5 tableview in a scrollview, I will say use one tableview and you can set its cell to show data.. Now you can add scrollview in different cell and add your data on scrollview that will allow you to scroll left and right.
Otherwise if you wanna continue the same approach you are doing then,
you can rotate tableview to 90 degree so that it gives you a feel of left and right scrolling.
You can use your tableview'd delegate:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//do the check
if tableView == YourTableView {
//do your stuff. As you have indexPath you can get row at that indexPath
}
}
If you try to do such thing, don't forget to add the tableViewControllers as childViewControllers of your main ViewController.
self.addChildViewController(tableVC1)
tableVC1.didMoveToParentViewController(self)
self.addChildViewController(tableVC2)
tableVC2.didMoveToParentViewController(self)
now everything works fine!

Drag & Drop between two UICollectionViews

I need to drag a Cell from CollectionView One and drop it to
CollectionView Two.
The Drag & Drop inside one CollectionView is no Problem, but how
can i get the Cell out of CollectionView One to CollectionView Two?
Any ideas? Any projects or frameworks that have already solved this problem?
Thanks for helping!
https://github.com/Ice3SteveFortune/i3-dragndrop Check this out - its a helper I'm working on to achieve just that. It also supports tableviews
UPDATE
I've recently released the second version of this codebase, called BetweenKit. Its now a fully-fledged drag-and-drop framework.
Hope it proves useful !
When you select the cell from the first collection view, remove it from this collection view, create a new view as copy of that cell place it as subview of the superview on top of all views. Make that view movable using pan gestures. As soon as you "drop" this intermediary cell, detect its position and add it to the current collection view.
Ok, here is the simplest flow ever for the following example:
Add UIGestureRecognizer for every of UICollectionView.
Connect every gesture recognizer with one method:
#IBAction func longPressGestureChanged(recognizer: UILongPressGestureRecognizer) { ... }
Within UIViewController add #IBOutlet for each of UICollectionView:
#IBOutlet var collectionViewGreen: UICollectionView!
#IBOutlet var collectionViewYellow: UICollectionView!
Implement gesture recognizer method to detect changes:
#IBAction func longPressGestureChanged(recognizer: UILongPressGestureRecognizer) {
let globalLocation = recognizer.locationInView(view)
if CGRectContainsPoint(collectionViewGreen.frame, globalLocation) {
//you cover green collection view
let point = view.convertPoint(globalLocation, toView: collectionViewGreen)
if let indexPath = collectionViewGreen.indexPathForItemAtPoint(point) {
//you cover cell in green collection view
} else {
//you do not cover any of cells in green collection view
}
} else if CGRectContainsPoint(collectionViewYellow.frame, globalLocation) {
//you cover yellow collection view
let point = view.convertPoint(globalLocation, toView: collectionViewYellow)
if let indexPath = collectionViewYellow.indexPathForItemAtPoint(point) {
//you cover cell in yellow collection view
} else {
//you do not cover any of cells in yellow collection view
}
} else {
//you do not cover any of collection views
}
}

Resources