Swift 3: UITapGestureRecognizer Not Firing - ios

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.

Related

How to you activate drag and order mode immediately after you tap on a button

Based on https://github.com/pgpt10/DragAndDrop-CollectionView
By using
self.collectionView.dragInteractionEnabled = true
self.collectionView.dragDelegate = self
self.collectionView.dropDelegate = self
Once you long press anywhere within a collection view cell, the following function will be triggered
extension DragDropViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
let item = collectionView == collectionView1 ? self.items1[indexPath.row] : self.items2[indexPath.row]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
However, I have a different requirement.
I have a collection which looks as the following
I wish when user tap (not long press) on the right 3 horizontal lines icon, he can immediately perform drag and reorder. Tapping other region, or long press on the cell, will not have drag and reorder effect.
May I know how can I achieve so?
Some notable app in App Store which able to achieve such feature
I notice Google Keep in App Store, able to achieve such feature. By just tapping on the left most icon in their Todo list, we can immediately reorder the Todo list item.
Wondering how they did that?
Approach 1: Install long press gesture on Cell's reorder icon
I had tried
Install long UILongPressGestureRecognizer on cell's reorder icon.
Use gesture.minimumPressDuration = 0 to mimic tap behavior.
class TabInfoSettingsItemCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
...
let gesture = UILongPressGestureRecognizer(target:self, action: #selector(longPressGesture))
gesture.minimumPressDuration = 0
reorderImageView.addGestureRecognizer(gesture)
}
But the outcome isn't encouraging. The "move" action isn't working at all!
Complete code can be found here : https://github.com/yccheok/ios-tutorial/tree/gesture-on-cell/TabDemo
Approach 2: Install long press gesture on Collection View
I had tried
Install long UILongPressGestureRecognizer on Collection View
Use gesture.minimumPressDuration = 0 to mimic tap behavior.
Here's the code snippet
let gesture = UILongPressGestureRecognizer(target:self, action: #selector(longPressGesture))
// Mimic short tap. But this blocks the events for delete button and text field :-(
gesture.minimumPressDuration = 0
collectionView.addGestureRecognizer(gesture)
But the outcome isn't perfect.
How can we recognise the tap event only within the reorder icon (icon with 3 horizontal lines) boundary.
Delete button no longer work as UILongPressGestureRecognizer blocks it from receiving event.
Text field no longer work as UILongPressGestureRecognizer blocks it from receiving event.
Complete code can be found here : https://github.com/yccheok/ios-tutorial/tree/gesture-on-collection-view/TabDemo
remove UILongPressGestureRecognizer from UICollectionView, Remove gesture comment from TabInfoSettingsItemCell class.
Replace this method in TabInfoSettingsController:
func changed(_ gesture: UILongPressGestureRecognizer) {
print("==changed==")
collectionView?.updateInteractiveMovementTargetPosition(gesture.location(in: collectionView))
}
and try that works.

UIGestureRecognizer on background of UITableView [duplicate]

This question already has answers here:
How to detect tap on clear part of UITableView?
(4 answers)
Closed 3 years ago.
I am currently creating a UITableView with custom cells to allow users to search other users. When the table is filled with data (usernames), it works great. I check if the keyboard is visible when a tap is detected, and if so, I dismiss the keyboard. If the keyboard is not shown, it pushes to the UIViewController containing the user's profile. This is the code to do so:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(keyboardShowing) {
dismissKeyboard()
return
}
let userVC = UserController()
let navViewController = tabBarController?.selectedViewController as? UINavigationController
userVC.profileUserID = resultUsers[indexPath.row].userID
navViewController?.pushViewController(userVC, animated: true)
}
However, when there is only one row (so the search returns one user, for example), I want to be able to click outside the rows to allow the user to dismiss the keyboard. I tried the following:
let tableBGTap = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
self.userList.backgroundView?.addGestureRecognizer(tableBGTap)
This doesn't work though. Any idea on how I can make it possible to achieve what I want? Long story short: if there are fewer rows in my UITableView to fill the entire screen, I want to be able to tap on the "background" below the rows.
The reason of why
self.userList.backgroundView?.addGestureRecognizer(tableBGTap)
is not working because backgroundView is nil. Therefore, what you should do is:
userList.backgroundView = UIView()
self.userList.backgroundView?.addGestureRecognizer(tableBGTap)
Note That if you are setting a background color for the table view:
userList.backgroundColor = .red
won't be displayed anymore. As a workaround, you could add a color to the background view itself:
userList.backgroundView = UIView()
userList.backgroundView?.backgroundColor = .red
userList.backgroundView?.addGestureRecognizer(tapper)

How to achieve same functionality as iOS Mail app with swipeable tableview cell?

I am trying to replicate the same type of functionality as the stock iOS Mail app when a user swipes a tableview cell as currently seen here:
There are 3 options: More, Flag, and Archive. When the user taps on any one of the 3, the background color changes to indicate it's in a highlighted state. However, the icon and text do not change color.
I am trying to achieve the same effect.
I am following this tutorial guide to make a custom swipeable tableview cell using gestures:
How To Make A Swipeable Table View Cell With Actions – Without Going Nuts With Scroll Views
Currently I have this set up:
In each cell contains 3 UIViews, where each UIView contains an UIImageView and UILabel representing Dirty, Edit, Delete.
I am adding tap gestures to the 3 UIViews like so:
let dirtyButtonView = cell.viewWithTag(7)
let editButtonView = cell.viewWithTag(8)
let deleteButtonView = cell.viewWithTag(9)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyVC.buttonPressed))
tapRecognizer.numberOfTapsRequired = 1
dirtyButtonView?.addGestureRecognizer(tapRecognizer)
editButtonView?.addGestureRecognizer(tapRecognizer)
deleteButtonView?.addGestureRecognizer(tapRecognizer)
However, I cannot achieve the highlight state functionality that the Mail app has when a user selects one of the 3 options.
I'm not sure if this is the correct implementation, i.e. having a UIView and adding a gesture to it?
How can I achieve something similar to the iOS Mail app swipe functionality?
Thanks
I found exactly what I was looking for, SwipeCellKit, by jerkoch. This library performs the same exact actions as the stock iOS Mail app does when swiping to the left. No need to deal with different UIViews and UIButtons.
To use, simply conform to the SwipeTableViewCellDelegate, and use it in editActionsForRowAt like so:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete")
return [deleteAction]
}
Make sure to change the cell's class to SwipeTableViewCell and set its delegate like so: cell.delegate = self.
I would take a look at the SWTableViewCell by CEWendel. It looks like it has exactly what you're looking for.
May be late to answer this, but in case anyone else is looking - consider this read to answer the question: https://www.raywenderlich.com/62435/make-swipeable-table-view-cell-actions-without-going-nuts-scroll-views

Gesture Recognizer in prototype cell doesn't work

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!

tvos UICollectionView lose focus to previous Focus Cell

I am building a tvos application. i have a strange bug where UICollectionView lose focus of the previously selected cell when i navigate back to that particular view. The scenario is some thing this like this.
I have two UIViewControllers A and B. A has a UITableView and it has three prototype cells in it. Each cell has a horizontal scrolling UICollectionView inside it. When i click on any of UICollectionViewCell it navigates to the B (detail page). I am presenting B modally.
Now when i press Menu button on Siri remote view A appears again (in other words view B is removed from View hierarchy) but the current selected cell is different then the previously selected. I have tried to use remembersLastFocusedIndexPath with both true and false values and also tried by implementing
func indexPathForPreferredFocusedViewInCollectionView(collectionView: UICollectionView) -> NSIndexPath?
but the control neves comes to this function when i navigate back to view A. I am also reloading every thing in viewWillAppear function.
Can any one help me in this. Thanks
The property remembersLastFocusedIndexPath should be set to true for the collectionView and false for the tableView.
Also, Are you reloading the UITableView in viewWillAppear i.e Is the table data being refreshed when the B is popped and A appears?
If Yes, then lastFocusedIndexPath will be nil on reload.
We faced the same issue. We solved it by not reloading the contents when B is popped.
Maintain a flag say didPush. Set this flag to true when B is pushed. When A appears check whether the flag is set and only then fetch data and reload table.
This worked for us.
I don't remember exactly, but I know there was a known issue for remembersLastFocusedIndexPath where it wasn't working as intended.
This is one workaround, although take it with a grand of salt as it does seem slightly hacky and it uses the common (but potentially unstable) approach of overriding the preferredFocusedView property.
private var viewToFocus: UIView?
override var preferredFocusView: UIView? {
get {
return self.viewToFocus
}
}
Save locally the indexPath of the last cell in View A when presenting View B
// [1] Saving and scrolling to the correct indexPath:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
...
collectionView.scrollToItemAtIndexPath(indexPath:, atScrollPosition:, animated:)
}
// [2] Create a dispatchTime using GCD before setting the cell at indexPath as the preferredFocusView:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
...
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(CGFloat.min * CGFloat(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.viewToFocus = collectionView.cellForItemAtIndexPath(indexPath:)
// [3] Request an update
self.setNeedsFocusUpdate()
// [4] Force a focus update
self.updateFocusIfNeeded()
}
}
The reason we split the two methods into both viewWillAppear and viewDidAppear is that it eliminates a bit of the animation jump. If anyone else could jump in with suggestions to improve or even alternate solutions, I'd also be interested!
In Swift
If you want to focus collection view Cell then
You can use collectionview Delegate method, method name is
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator)
{
}
You can use this method like this...
func collectionView(collectionView: UICollectionView, didUpdateFocusInContext context: UICollectionViewFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let previousIndexPath = context.previouslyFocusedIndexPath,
let cell = collectionView.cellForItemAtIndexPath(previousIndexPath) {
cell.contentView.layer.borderWidth = 0.0
cell.contentView.layer.shadowRadius = 0.0
cell.contentView.layer.shadowOpacity = 0
}
if let indexPath = context.nextFocusedIndexPath,
let cell = collectionView.cellForItemAtIndexPath(indexPath) {
cell.contentView.layer.borderWidth = 8.0
cell.contentView.layer.borderColor = UIColor.blackColor().CGColor
cell.contentView.layer.shadowColor = UIColor.blackColor().CGColor
cell.contentView.layer.shadowRadius = 10.0
cell.contentView.layer.shadowOpacity = 0.9
cell.contentView.layer.shadowOffset = CGSize(width: 0, height: 0)
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: [.CenteredHorizontally, .CenteredVertically], animated: true)
}
}

Resources