Deselecting custom UITableViewCell that was selected by UILongPressGestureRecognizer - ios

I have a custom UITableViewCell class My custom UITableViewCell
If user wants to select first cell, UILongPressGestureRecognizer called after that cell selected and when user had already chosen minimum one cell, then he could choose another cells without long pressing on it, just calling function didSelectRow. I tried do it by myself, but I couldn't. It was actually working but first user that was selected by long press, can't be deselected. I searched and found that I should cancel touches in view, but it didn't work for me. So cells that are called with didSelectRow are working fine, I can select and deselect, except one cell that was selected by UILongPressGesture.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ContactCell {
if inSelectionMode {
selectUser(cell: cell, indexPath: indexPath)
} else {
if let cameraViewControl = presentingViewController as? CameraViewController {
cameraViewControl.smallView()
}
dismiss(animated: true, completion: nil)
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if tableView == contactTblView {
let tap = UILongPressGestureRecognizer(target: self, action: #selector(longTapSelectUser(_:)))
tap.cancelsTouchesInView = false
cell.addGestureRecognizer(tap)
}
}
func longTapSelectUser(_ gesture: UILongPressGestureRecognizer) {
if let cell = gesture.view as? ContactCell, let indexPath = self.contactTblView.indexPath(for: cell) {
inSelectionMode = true
selectUser(cell: cell, indexPath: indexPath)
}
}

Related

Open URL from API when Button is Pressed in Custom TableViewCell

I want to click a button in a custom xib file and it goes to a link passed in through an api using decodable.
I am able to make the entire row redirect to a link from the api by using:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let item = page!.tradeshows[indexPath.row]
if let eventURLS = item.url {
UIApplication.sharedApplication().openURL(eventURLS, options: [:], completionHandler: nil)
} else {print("link not working")
}
}
But I don't want to select the entire row, I only want to select the button from the custom .xib file.
I also tried:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell",
for: indexPath) as!EventTableViewCell
let item = page!.tradeshows[indexPath.row]
cell.registerButton.addTarget(self, action: #selector(UpcomingEventsViewController.onClickedRegistrationButton(_: )),
for: .touchUpInside)
cell.registerButton.tag = indexPath.row
return cell
}
#objc func onClickedRegistrationButton(_ button: UIButton) {
let buttonLink = button.tag
}
but I'm not sure how to set the link from the json data so that the indexPath is correct using this second approach.
As the action is not directly related to the view controller it's more efficient to pass the URL to the cell and open the URL there. The target / action code is not needed.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as! EventTableViewCell
let item = page!.tradeshows[indexPath.row]
cell.url = item.url
return cell
}
In the cell add a property url and an IBAction for the button
class EventTableViewCell : UITableViewCell {
var url : URL!
// other properties
#IBAction func pushButton(_ sender : UIButton) {
UIApplication.shared.open(url)
}
// other code
}
Side note:
Your first snippet is Swift 2 code. The method will never be called in Swift 3+
You need
#objc func onClickedRegistrationButton(_ button: UIButton) {
UIApplication.sharedApplication().openURL(NSURL(string:tableData[button.tag])!)
}

UITableview Update Single Cell

I got a play button on my tableview custom cell, whenever I tapped the play button. I want the selected button image to change to pause image.
The issue is that all the other buttons images are getting updated.
So all the images are changing to the pause image, instead of the selected button.
I tried to get the indexpath of the button tapped and reload only that row, but that doesn't seem to make a difference.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let playerCell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as! PlayerCell
let item = subcategory?.items?[indexPath.row]
// Tap gestures extension for cell button action
playerCell.playPause.addTapGestureRecognizer {
AudioController.shared.setupPlayer(item: item)
if let selectedCell = tableView.cellForRow(at: indexPath) as? PlayerCell {
selectedCell.playPause.setImage(#imageLiteral(resourceName: "pause"), for: .normal)
tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 1)], with: .none)
}
print("index \(indexPath)")
}
What you can do is add a tag to the button. So within the method that you create the cells override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell, you add a tag to the button within that cells that represents the indexPath. Then from within the selector that you assign the button, you can get the cell that you want to alter.
For example:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath)
cell.button.tag = indePath.row
cell.button.addTarget(self, action: #selector(yourSelector(_sender:)), for: .touchUpInside)
}
func yourSelector(_sender: UIButton){
let cell = tableView.cellForRow(at: IndexPath(row: sender.tag, section: 0)) as! YourCellType
// Change the image, play/pause audio for that cell
}
When you tap on the button in cell, tableView(_:didSelectRowAt:) won't be trigger, so I suggest using delegate to detect button action.
And you need to keep tracking the change of the cell's button status.
For example:
PlayCell.swift
protocol PlayCellDelegate: class {
func playCellPlayButtonDidPress(at indexPath: IndexPath)
}
class PlayerCell: UITableViewCell {
let playButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(playButtonPressed(_:)), for: .touchUpInside)
return button
}()
weak var delegate: PlayCellDelegate?
var item: MyItem? {
didSet {
if item?.status == .paused {
// set pause image for playButton here
} else if item?.status == .playing {
// set play image for playButton here
}
}
}
var indexPath: IndexPath?
#objc func playButtonPressed(_ sender: UIButton) {
guard let indexPath = self.indexPath else { return }
delegate?.playCellPlayButtonDidPress(at: indexPath)
}
}
Model.swift
struct Subcategory {
// ...
var items: [MyItem]?
}
struct MyItem {
// ...
var status: Status.stop
enum Status {
case playing, paused, stopped // etc..
}
}
TableViewController.swift
class TableViewController: UITableViewController, PlayCellDelegate {
private var subcategory: Subcategory?
private let cellId = "Cell"
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? PlayerCell {
cell.delegate = self
cell.item = subcategory?.items?[indexPath.row]
cell.indexPath = indexPath
return cell
}
return UITableViewCell()
}
func playCellPlayButtonDidPress(at indexPath: IndexPath) {
// you only need to change model here, and reloadRows will update the cell.
if subcategory?.items?[indexPath.row].status == .play {
subcategory?.items?[indexPath.row].status = .pause
} // other logic..
tableView.reloadRows(at: [indexPath], with: .none)
}
}
Hope it helps!

Issue with UILongPressGestureRecognizer while using it in table view cell

I am implementing a long press in the uitableview through storyboard in swift3. I have only one prototype cell set in the storyboard. But the problem is the long press is being detected only in the first cell. Rest of the cells are not listening to the long press gesture.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let row = indexPath.row
cell.textLabel?.text = "Label"
return cell
}
#IBAction func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}
The warning shown in the console is:
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.
Attach the gesture to the tableview, and when the gesture is triggered figure out which indexPath was selected.
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longPress(_:)))
tableView?.addGestureRecognizer(longPressRecognizer)
}
func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
let point = guesture.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: point);
print("Long Press \(String(describing: indexPath))")
}
}
Because a tableview is a kind of scrollview it is best to attach the gestures to the tableview itself and not any of its subview. This way it is less likely to interfere with the other gestures that must be tracked.
You need to add gesture for all cell in cellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let row = indexPath.row
cell.textLabel?.text = "Label"
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(HomeViewController.longPress(_:)))
cell?.addGestureRecognizer(longPressRecognizer)
return cell
}
func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
print("Long Press")
}
}

IndexPath in tablew view cell returned is wrong on click of a label in a UITableView

I have a label inside a table view cell.
On click of the label I want to segue to another view controller after retrieving the correct indexPath.
I have added a gestureRecognizer to the label. On click of the label it returns the wrong indexPath , it does not return the index Path of the cell in which the label is.
How can I solve this problem . Any help will be appreciated. Thank you
Following is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
cell = tableView.dequeueReusableCell(withIdentifier: "ViewControllerCell", for: indexPath) as! TableCell
cell.name.text = feeds[indexPath.row].name
nameClicked()
return cell
}
func nameClicked(){
cell.name.isUserInteractionEnabled = true
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(TrendViewController.handleTap(gestureRecognizer:)))
cell.name.addGestureRecognizer(gestureRecognizer)
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
var touchPoint = cell.name.convert(CGPoint.zero, to: self.tableView)
var clickedLabelIndexPath = tableView.indexPathForRow(at: touchPoint)!
nameFromView = feeds[clickedLabelIndexPath.row].name
print("IndexPath at touch",clickedLabelIndexPath)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "profile") as! ProfileViewController
vc.clickedLabelIndexPath = clickedLabelIndexPath
vc.nameFromView = feeds[clickedLabelIndexPath.row].name
self.navigationController?.pushViewController(vc, animated: true)
}
You have declare cell as instance property in your class,so you are adding gesture to the same cell's label. So create new cell using dequeueReusableCell and changed your method nameClicked by adding argument of type cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create new cell
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewControllerCell", for: indexPath) as! TableCell
cell.name.text = feeds[indexPath.row].name
nameClicked(cell)
return cell
}
func nameClicked(_ cell: TableCell){
cell.name.isUserInteractionEnabled = true
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(TrendViewController.handleTap(gestureRecognizer:)))
cell.name.addGestureRecognizer(gestureRecognizer)
}
Now change your handleTap method like this to get the correct indexPath.
func handleTap(_ gestureRecognizer: UIGestureRecognizer) {
let point = self.tableView.convert(CGPoint.zero, from: gestureRecognizer.view!)
if let indexPath = self. tableView.indexPathForRow(at: point) {
print("\(indexPath.section) \(indexPath.row)")
//Add your code here
}
}
Note: If your cell having only single cell then it is batter if you use didSelectRowAt method instead of adding gesture to label.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
}
Maybe you should make condition for check if cell is touched ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewControllerCell", for: indexPath) as! TableCell
if cell.isTouched {
cell.setTouched
}
else {
cell.setNoTouched
}
return cell
}

Passing Parameters to Action Functions in Swift

I created tableView cells with an action and I need the action to know the indexPath of the cell that is selected. I can't figure out how to pass the indexPath as a parameter to the action or find any other way around it. Here is the code for the tableView cellForRowAt and the action to be performed:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! CustomCell
cell.foodName.text = self.menuItems[indexPath.row]
//adding gesture recognizer with action Tap to cell
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(Tap(gesture:index:IndexPath.row)))
cell.addGestureRecognizer(tapGesture)
return cell
}
func Tap(gesture: UIGestureRecognizer , index: Int){
print("Tap")
//necessary so that the page does not open twice
//adding the rating view
let ratingVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "rating") as! RatingView
ratingVC.foodName = selectedItem
self.addChildViewController(ratingVC)
ratingVC.view.frame = self.view.frame
self.view.addSubview(ratingVC.view)
ratingVC.didMove(toParentViewController: self)
}
I can't figure out how to pass the IndexPath.row as a parameter to Tap.
You don't have to do the functionality of selecting the cell, UITableViewDelegate already has this kind of behaviour.
By conforming to UITableViewDelegate and implementing tableView(_:didSelectRowAt:) method, you will be able to get the indexPath.row for the selected cell:
Tells the delegate that the specified row is now selected
So, after implementing tableView(_:didSelectRowAt:) you should get rid of tapGesture functionality, it should be similar to:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! CustomCell
cell.foodName.text = self.menuItems[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected row: \(indexPath.row)")
}
Use didSelectRowAt Indexpath method.
For custom Tap use following steps.
Assign tag to your cell.
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapBlurButton(_:)))
cell.tag = indexPath.row
cell.addGestureRecognizer(tapGesture)
2.Manage tap event
func tapBlurButton(_ sender: UITapGestureRecognizer) {
//Get indexpath.row value or Indexpath Value
print(sender.view.tag)
}
Hope it helps. :)
First, remove the tap gesture. Second implement the didSelectRow delegate of the tableView like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//add your rating view code here
let ratingVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "rating") as! RatingView
ratingVC.foodName = self.menuItems[indexPath.row] //get the selected item
self.addChildViewController(ratingVC)
ratingVC.view.frame = self.view.frame
self.view.addSubview(ratingVC.view)
ratingVC.didMove(toParentViewController: self)
}
Of course, you will want to add checks and guards to make sure you catch any errors.

Resources