Adding multiple buttons + receiving touchUpInside event inside UICollectionViewCell SWIFT programmatically - ios

I am trying to build a CollectionView for a list all programmatically in Swift without using Storyboard. I was able to add a tap gesture event to the cell.
However, When I added a set of buttons to the UICollectionViewCell's contentView, the buttons do not receive any touch events.
Inside Controller
let sampleCollectionView = UICollectionView(frame: (..), collectionViewLayout: layout)
//Register the UICollectionViewCell inside UICollectionView
sampleCollectionView.registerClass(sampleCollectionViewCell.self, forCellWithReuseIdentifier: "sampleCell")
//Add Tap Gesture event to the cell area
let tap = UITapGestureRecognizer(target: self, action: "handleTapForCell:")
sampleCollectionView.addGestureRecognizer(tap)
func handleTapForCell(recognizer: UITapGestureRecognizer){
//I can break in here
}
Inside CollectionViewCell
class sampleCollectionViewCell: UICollectionViewCell
{
override init(frame: CGRect) {
var buttonBack = UIButton(type: .Custom)
buttonBack.addTarget(self, action: "actionGoBack:", forControlEvents: UIControlEvents.TouchUpInside)
..
self.contentView.addSubview(buttonBack)
}
func actionGoBack(sender: UIButton){
//I want to get my touch action break in here when I tap right inside the button but it won't
}
}
Is CollectionViewCell suited to accept more than one type of tap action (tapping on the whole cell versus multiple buttons inside cell)?
Here is what I tried so far:
Disable Tap Gesture on CollectionView, still not receiving events
Without adding tap events to button, I tried to find the "location" of tap inside the cell, then check if this is in bounds of the button, if so, fire an event. I find this work around to be buggy when I try to add animation or scroll.
Thanks for your suggestions and help.

You can set the gesture recognizer to not block the touches in subview
gestureRecognizer.cancelsTouchesInView = false
You can also implement UIGestureRecognizerDelegate and set yourself as delegate. Then you can implement shouldReceiveTouch
optional public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool
There you can check which view is targeted and return false if you don't want to react to the touch in the cells gesture recognizer.

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.

UISearchBar Sometimes Resigns When Tapping On Views

My view controller setup is as follows: UISearchBar on top of screen, keyboard on the bottom, and other views between them: custom UIView, UIView, UITableView. When I tap on the table view cells and UIButtons inside UIViews, I can trigger my actions successfully while keeping UISearchBar still visible on screen. But when I tap on UIViews that do not have buttons, this triggers searchBarShouldEndEditing as if search bar loses the focus. I want to disable this behavior and let only Cancel button and keyboard's Done button to trigger UISearchBar closure.
I thought this has to do with bubbling effect and tried to implement this fix:
extension UIViewController : UIGestureRecognizerDelegate{
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
#IBAction func didTap(_ gestureRecognizer : UITapGestureRecognizer ) {
//
}
but this did not address the issue and UISearchBar still calls searchBarShouldEndEditing. Of course, I could add logic to searchBarShouldEndEditing to return false but not sure this is effective enough.
How can I ensure UISearchBar remains in place while tapping on other elements like UIViews?

TableCell pangesture got ignore when inside a collectionview

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

Disabling user interaction for subview interferes with event handling for superview?

CustomView contains a subview, SubView, which implements a tap handler. However, for this implementation, SubView should ignore taps and let CustomView handle them.
The code below is supposed to achieve this, but setting userInteractionEnabled to false prevents taps on SubView from cascading to CustomView. Shouldn't CustomView still receive tap events if SubView has userInteractionEnabled set to false?
The Apple documentation says setting userInteractionEnabled to false causes events to get ignored, not that the view will swallow them so superviews don't receive them.
// CustomView class, which is a subclass of UIView
// Handle taps
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doTap))
addGestureRecognizer(singleTap)
// Add SubView
view.insertSubview(SubView, atIndex: 0)
SubView.userInteractionEnabled = false
Updated code (doInit is called since buttonTapped is invoked on taps) but still not working:
class CustomButton : UIButton, UIGestureRecognizerDelegate {
private func doInit() {
...
// Handle taps
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
tapRecognizer.delegate = self
addGestureRecognizer(tapRecognizer)
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
SubView will still receive the taps, but it will ignore them and it will stop there. If a view has userInteractionEnabled set to false, it will block touches that are meant for parent views.
My guess is you want to add this gesture recognizer delegate:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool
This will allow your parent view to receive touches at the same time as your SubView.
Building on Laynemoseley's answer, this is the relevant bit from the Apple docs:
In iOS 6.0 and later,
default control actions prevent overlapping gesture recognizer
behavior. For example, the default action for a button is a single
tap. If you have a single tap gesture recognizer attached to a
button’s parent view, and the user taps the button, then the button’s
action method receives the touch event instead of the gesture
recognizer. This applies only to gesture recognition that overlaps the
default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UIStepper,
UISegmentedControl, and UIPageControl. A single finger swipe on the
knob of a UISlider, in a direction parallel to the slider. A single
finger pan gesture on the knob of a UISwitch, in a direction parallel
to the switch. If you have a custom subclass of one of these controls
and you want to change the default action, attach a gesture recognizer
directly to the control instead of to the parent view. Then, the
gesture recognizer receives the touch event first. As always, be sure
to read the iOS Human Interface Guidelines to ensure that your app
offers an intuitive user experience, especially when overriding the
default behavior of a standard control.
It seems the only solution is to override the gesture recognition and allow simultaneous gestures to get recognized.

UIButton not responding used in a custom UITableViewCell

I know this issue is already been asked few times in SO. Despite trying those out, I am still unable to solve my problem.
I am using a UITableView inside a UIViewController. I have a custom UITableViewCell which has couple of buttons in it. However, I am not able to make the Button respond to Click event.
The development environment is iOS 9 and Swift 2
Snippets used :
BranchNearMeTableViewCell.swift contains
#IBOutlet weak var btnDetails: UIButton!
view controller class
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.btnDetails.tag = indexPath.row
cell.btnDetails.addTarget(self, action: "showDetails:", forControlEvents: .TouchUpInside)
}
func showDetails(sender: UIButton){
print("Button Pressed:")
}
Additional Info:
TableView and TableCellView has User interaction disabled in Interface builder since don't want the entire cell to be clickable.
UIButton inside TableViewCell has User Interaction enabled.
Being an iOS noob, I may be making a silly mistake which I might have overlooked.
Similar questions that I checked include:
SO1
SO2
SO3
I Deeply appreciate any help regarding this question.
I faced a similar issue. I was programmatically adding an UIButton to the UITableViewCell via addSubview. The button would not respond to touch events. Using Debug View Hierarchy, I finally discovered that any subviews added to the UITableViewCell was behind contentView, which was blocking user input from reaching the UIButton. The issue was resolved by adding the UIButton to contentView instead of the UITableViewCell.
I would have userInteractionEnabled set to true on the table view cell as well. I would prevent taps using the UITableView allowsSelection to false
Also remember to remove the target and action in tableView:cellForRowAtIndexPath: since the cells are recycled, the button might already have the target and action, it might add a second.
I found a simple solution:
Inherits UITableViewCell, and override init()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
//init subviews, eg. self.switch = UISwitch()
super.init(style: style, reuseIdentifier: reuseIdentifier)
// add this line magic code
contentView.isUserInteractionEnabled = true
//add subviews, e.g. self.addSubView(self.switch)
}
You only have to do (in ViewDidLoad):
mTableView.delaysContentTouches = false
For programmatically created views, the only thing to remember is to declare buttons using lazy var in UITableViewCell. And also add subviews to contentView instead of the cell itself For example:
class CounterCell: UITableViewCell {
lazy var incrementButton: UIButton = {
let button = UIButton()
button.setTitle("+", for: .normal)
button.addTarget(self, action: #selector(incrementAction), for: .touchUpInside)
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(incrementButton)
// Your constrains here
}
#objc func incrementAction() {
}
}
When using programmatically views, there's no need to add .userInteractionEnabled flags.
Then to take the action out of the cell, just add a delegate and assign it from the UITableViewDataSource.
I came across this issue today, with a button inside a static UITableview cell, that was not responding to user events.
I realised the 'Content View' of the cell also has a 'User Interaction Enabled' tick box. Make sure you select the 'Content View' inside the UITableview cell in your Document Outline menu, then tick the box for 'User Interaction Enabled' in the Attributes Inspector - see attached photo for reference. 'User Interaction Enabled' also needs to be checked for the cell for this to work.
Hope this helps. XCcode screen shot
Also, make sure you are adding target actions to your buttons outside their setup. So instead of
let button: UIButton = {
//addTarget...
}()
you can have a function to set up your buttons after something happens:
func setButtonsUp() {
// myButton.addTarget
}
For anyone else struggling, here's my solution:
sendSubviewToBack(cell.contentView)
The thing that there's now an extra UITableViewCellContentView layer which blocks interaction with views behind it.
Related issue: An extra UITableViewCellContentView overlay appears in a TableView on iOS 14 preventing taps, but works fine on iOS 13
Ad a first sight nothing seems to be wrong with your code.
So I suggest you to add a background color to the superview of the button, why? because if the button is outside the frame of its superview it will never receive touches.
If you see that the button is not inside the background color probably you have an issue positioning the item, check constraints or whatever you are using.
Check also the frame of the button.
You can also do both by inspecting the view at runtime, here a tutorial.
I dont know what wrong in the code but i can suggest which i personally use and it works for me
In BranchNearMeTableViewCell.swift
#IBOutlet var btnDetails: UIButton!
#IBAction func btnDetailsClick(sender: AnyObject) {
tapButton?(self)
}
var tapButton: (UITableViewCell -> Void)?
In Table view controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.tapButton = {(user) in
//User will be tablecell here do whatever you want to do here
}
}
So if you click on button in table cell this cell.tapButton will be called you can do whatever you want to do here
The only things we need to do is in cellForRowAt just put:
cell.selectionStyle = .none
in this way, UITableview will bypass the touch of selecting cells and allow buttons inside our cells to be clickable.
set cell and cell content view isUserInteractionEnabled = true
Add Tapgesture to the button
Add a closure to handle gesture action
Add target for that button.
button.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
Set tag of that button since you are using it.
button.tag = indexPath.row
Achieve this by subclassing UITableViewCell. button on that cell, connect it via outlet.
Make sure button.isUserInteractionEnabled = true
To get the tag in the connected function:
#objc func connected(sender: UIButton){
let buttonTag = sender.tag
}
Make sure that ALL of tableView's superviews do have isUserInteractionEnabled set to true
User interaction was already enabled for my UIButton. The thing that worked for me is
switching the stackView distribution to "Fill".

Resources