I have a tableView where I want each cell to respond to a long press gesture.
I attached the gesture recogniser to the prototype cell and connected it to an IBAction on the tableViewController's swift code.
When I run the app, the first cell loads fine, it responds to the long press gesture and no errors.
When I add another item to the table, I get this error:
2016-09-09 12:56:52.963 Day Care Register[1044:222155] WARNING: A Gesture
recognizer (<UILongPressGestureRecognizer: 0x7906a220; state =
Possible; view = <Day_Care_Register.DogTableViewCell 0x799c2c00>;
target= <(action=dogMarkedForBoarding:, target=
<Day_Care_Register.DogTableViewController 0x7906aa10>)>>) was setup in
a storyboard/xib to be added to more than one view (->
<Day_Care_Register.DogTableViewCell: 0x7a1bac00; baseClass =
UITableViewCell; frame = (0 0; 600 90); clipsToBounds = YES;
gestureRecognizers = <NSArray: 0x79180200>; layer = <CALayer:
0x7916cbe0>>) at a time, this was never allowed, and is now enforced.
Beginning with iOS 9.0 it will be put in the first view it is loaded
into.
OK, so if I understand this right, I can't use the same gesture recogniser over multiple cells?
How do I fix that?
I'm not sure what code you guys would want to see, so if you want to see anything, feel free to ask in a comment and I'll happily provide as soon as I can.
The way I've done this in the past is to create a gesture recognizer for the UITableViewController, assigning it a target selector of the form:
func handleLongPress(recognizer: UILongPressGestureRecognizer)
When you implement handleLongPress(_:) you can iterate over the visible cells in the table view, testing whether the visible cell's frame contains the point recognizer.locationInView(Self.tableView):
func handleLongPress(recognizer: UILongPressGestureRecognizer) -> Void {
guard let touchPoint : CGPoint = recognizer.locationInView(self.tableView) else { return }
let cellInFocus : UITableViewCell? = {
for cell in self.tableView.visibleCells {
if cell.frame.containsPoint(touchPoint) {
return cell
}
}
return nil
}()
guard let _ = cellInFocus else { return }
// Take desired action with cell...
}
Hope that helps!
Related
CollectionView Cell
ㄴ A
ㄴㄴ B
this is example of my collectionView cell View stack
the problem is
I put gestures and actions in B view.
If you turn on the accessibility, i think call collectionView's delegate = didSelectItemAt.. but my collectionView call tap Gesture action
I have never set up a gesture related to accessibility..
Is there a tap gesture relationship with voice over?
This phenomenon is only reproduced in certain views.
why this problem cause..?
private lazy var AContainer = UIView().then {
$0.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(actionGesture)))
}
AContainer.flex.define { flex in
flex.addItem(textLabel)
flex.addItem(testImageView)
.size(10)
}
contentView.flex.addItem().define { flex in
flex.addItem(AContainer)
}
#objc func actionGesture() {
print("testestest")
}
i catch the issue .
accessibilityActivationPosition's default position is mid,,
gesture action is in B view's center,,
so i solved this probelm by this code
B.accessibilityActivationPosition = CGPoint(x: 0, y: 0)
I'm trying to work out the best method for finding the exact subview (if any) at a given CGPoint in a UIView.
Part of my scene/ViewController is a custom UIView subclass (designed in a XIB), which presents a UIStackView of subviews (also custom, XIB-designed).
The VC has a UITapGestureRecognizer on that custom view, and in my #IBAction, I want to identify which specific one of its subviews was tapped, then handle it accordingly:
#IBAction func handleTap(recognizer:UITapGestureRecognizer) {
let tapLocation = recognizer.location(in: recognizer.view)
if let subviewTapped = customView.subviewAtLocation(tapLocation) {
handleTapForSubview(subviewTapped)
}
}
However, I don't know how to implement that subviewAtLocation(CGPoint) method. I wasn't able to find anything in the standard UIView methods.
Any advice on how it should be done would be welcome.
Alternatively, I considered adding a tap recognizer to each of the subviews instead, then delegating to the parent, then to the VC, but that feels inefficient, and like it's putting too much control logic in the views, rather than the VC.
Thank you.
A solution would be using the contains(point:) method of CGRect. The idea is to iterate over the stack view's subviews and check which subviews contains the touched point. Here:
#IBAction func handleTap(recognizer:UITapGestureRecognizer) {
let tapLocation = recognizer.location(in: recognizer.view)
let filteredSubviews = stackView.subviews.filter { subView -> Bool in
return subView.frame.contains(tapLocation)
}
guard let subviewTapped = filteredSubviews.first else {
// No subview touched
return
}
// process subviewTapped however you want
}
//use hitTest() method. this gives the view which contain the point
let subView = parentView.hitTest(point, with: nil)
I'm not sure why but my UITapGestureRecognizer is not firing correctly. Well in fact it is not firing at all. I am trying to get a function to run on the tap of an image.
Info about the setup:
Not using storyboards, loading everything programmatically
The view that I am loading this on also has a UICollectionView on it
ok so for the code:
The UIImageView is declared like so:
let backButtonIcon: UIImageView = {
let bbi = UIImageView()
bbi.image = UIImage(named: "backIcon")
bbi.translatesAutoresizingMaskIntoConstraints = false
bbi.layer.zPosition = 2
return bbi
}()
I then added that to the view:
view.addSubview(backButtonIcon)
to add the tap functionality to the UIImageView I am using:
let tapBackButton = UITapGestureRecognizer(target: self, action: #selector(backButtonPressed))
backButtonIcon.isUserInteractionEnabled = true
backButtonIcon.addGestureRecognizer(tapBackButton)
and lastly, here is the function that I am trying to run, just a simple print at the moment:
func backButtonPressed(sender: UITapGestureRecognizer) {
print("Go Back Pressed")
}
Update:
I have already tried adding:
bbi.isUserInteractionEnabled = true
and also:
backButtonIcon.isUserInteractionEnabled = true
Update 2:
I created another blank view and used the code exactly as it is here and it worked.
Could it be something to do with the UICollectionView?
I made the UICollectionView zPosition -1 and the UIImageView zPosition at 1 but that alas that also did not work.
Image views ignore user events by default. Normally, you use image views only to present visual content in your interface. If you want an image view to handle user interactions as well, change the value of its isUserInteractionEnabled property to true. After doing that, you can attach gesture recognizers or use any other event handling techniques to respond to touch events or other user-initiated events.
source: https://developer.apple.com/reference/uikit/uiimageview
I think the issue is with the UICollectionView, which is composed of UICollectionViewCells that already have a tap enabled by definition.
I'm looking through a very old app (Swift 1.x) I wrote for my personal usage (as in it never saw the light of day) and here's what I seemed to do - you probably need to do some variation of this:
Subclassed UICollectionViewCell and added a UILabel and UIImageView as subviews to it, populating them (along with a NSURL for the tap action).
Declared (and adopted) a specific UIViewController to also be my UICollectionView delegate (also for my purposes it was a SFSafariViewController delegate).
In that VC's viewDidLoad() I did set up a long press (for removing) and double tap (for reloading) gestures, but did not set up a tap gesture - it isn't needed.
In that VC's viewDidLoad() I loaded (and populated) the cells.
Here's the thing I think you are getting stuck on, but without more code I'm not sure:
Coded against:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? BookmarkCell {
selectedBookmarkSection = indexPath.section
selectedBookmarkRow = indexPath.row
sfc = SFSafariViewController(URL: cell.targetUrl!, entersReaderIfAvailable:true)
sfc.delegate = self
presentViewController(sfc, animated: true, completion: nil)
} else {
// Error indexPath is not on screen: this should never happen.
}
}
Please be kind, as this was something I wrote back in July 2014! (I update the collectionView(didSelectItemAt:indexPath:) for Swift 3.x but that's it.)
TR;DR: I think - particularly if your tap is seen outside of a UICollectionView - that this may get you working.
I've got a tableView inside of a pageViewController and when swiping on a cell to bring up the option to delete the cell the gesture is only recognized under certain circumstances, say you swiped very quickly and aggressively.
I imagine this is happening because it's not sure whether the swiping gesture is meant for the pageView or the tableView. Is there a way to specifically determine where the swipe gesture is happening to enable a nice smooth display of the delete button?
Theory:
Both UIPageViewController and UITableView are implemented using UIScrollView, where UIPageViewController embeds UIScrollView and UITableView is a subclass of UIScrollView
UITableView also uses a couple of UIPanGestureRecognizers to bring in all the magic. One of these is UISwipeActionPanGestureRecognizer which handles the swipe to delete actions.
This issue is caused because UIPageViewControllers UIPanGestureRecognizer wins in the conflict with the UITableViews UISwipeActionPanGestureRecognizers.
So we have to some how tell UIPageViewController to ignore gestures if UITableViews UISwipeActionPanGestureRecognizer are in action.
Luckily there is something already provided by UIGestureRecognizer.
UIGestureRecognizer's require(toFail otherGestureRecognizer: UIGestureRecognizer) creates a relation between the two gesture recognizers that will prevent the gesture's actions being called until the other gesture recognizer fails.
So all we had to do is fail UIPageViewControllers embedded UIScrollviews panGestureRecognizer when UITableViews UISwipeActionPanGestureRecognizer are triggered.
There are two ways you could achieve this.
Solution 1: Add a new gesture recognizer to table view and Mimic UISwipeActionPanGestureRecognizer. And make UIPageViewController panGesture require to fail this new gestureRecognizer
Solution 2 (A bit dirty): Make a string comparision to the UITableView's UISwipeActionPanGestureRecognizer and make UIPageViewController panGesture require to fail this new gestureRecognizer
Code:
Solution 1
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
if let pageController = parent?.parent as? UIPageViewController {
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: nil)
gestureRecognizer.delaysTouchesBegan = true
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
tableView.addGestureRecognizer(gestureRecognizer)
pageController.scrollView?.canCancelContentTouches = false
pageController.scrollView?.panGestureRecognizer.require(toFail: gestureRecognizer)
}
}
And finally delegate methods
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let translation = panGesture.translation(in: tableView)
// In my case I have only trailing actions, so I used below condition.
return translation.x < 0
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view == tableView
}
Solution 2 (A bit dirty)
A helpful utility to get UIPageViewControllers embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController holding the UITableView and call it from viewDidLoad()
func handleSwipeDelete() {
guard let pageController = parent as? UIPageViewController else {
return
}
pageController.scrollView?.canCancelContentTouches = false
tableView.gestureRecognizers?.forEach { recognizer in
let name = String(describing: type(of: recognizer))
guard name == "_UISwipeActionPanGestureRecognizer" else {
return
}
pageController.scrollView?
.panGestureRecognizer
.require(toFail: recognizer)
}
}
I had the same problem. I found a solution that works well.
Put this in your UIPageViewController's viewDidLoad func.
if let myView = view?.subviews.first as? UIScrollView {
myView.canCancelContentTouches = false
}
PageViewControllers have an auto-generated subview that handles the gestures. We can prevent these subviews from cancelling content touches. The tableview will be able to capture swipes for the delete button, while still interpreting swipes that fail the tableview's gesture requirements as page swipes. The delete button will show in cases where you hold and swipe or swipe "aggressively."
You can set delaysContentTouches to false on the tableView itself as well. This solution worked for my collection view's UISlider elements.
See Swift 4.0 code below:
yourTableView.delaysContentTouches = false
I have found a working solution by reassigning UIScrollView's panGestureRecognizer's delegate to my class and ignoring the original delegate when a right-to-left pan is detected. I used method swizzling for that.
Please check my sample project: https://github.com/kambala-decapitator/SwipeToDeleteInsidePageVC
In case you have a tableView in the first or the last viewController inside of your UIPageViewController maybe think of disabling the bouncing at the end of your UIPageViewController first and then implement this solution.
In case you don't know how to disable the bounce of the UIPageViewController, check out my answer here.
I have a prototype cell with an UIImageView, when user tap this ImageView, app should display a Collection View where user can select an alternative icon for the cell. So, in UITableViewCell I added a Gesture Recognizer:
internal let iconTappedGR = UITapGestureRecognizer()
then I implemented it in table's cellForRowAtIndexPath:
cell.iconTappedGR.addTarget(self, action: #selector(changeIcon))
cell.iconView.gestureRecognizers = []
cell.iconView.gestureRecognizers!.append(cell.iconTappedGR)
and I added a changeIcon function
func changeIcon () {
print("imageView tapped!")
}
trouble is that it doesn't works; I tried even using storyboard but is the same...where am I wrong?
I solved using
cell.iconView.userInteractionEnabled = true
following this answer https://stackoverflow.com/a/36495864/2085352 to a question posted 30 minutes later than mine!