Pass object to a selector - ios

I use this code to detect a long pressed element:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// some stuff
if let labelCell = model as? TTTLabelCell{
labelCell.delegate = self
labelCell.textLabel.addGestureRecognizer(longPressRec)
}
}
This is the gesture recognizer
longPressRec.addTarget(self, action:#selector(labelLongPressed))
And this is the selector
func labelLongPressed(){
print("Label Long pressed")
// labelCell.backgroundColor = UIColor.blue
}
What I want to achieve, as commented on the code, is to pass the selected object (labelCell) to the selector labelLongPressed() which will allow me to change some attributes. Do you have an idea how to do that?

change func as below :
func labelLongPressed(_ sender: UITapGestureRecognizer){
print("Label Long pressed")
let labelCell: TTTLabelCell = sender.view // track as per your view hierarchy
labelCell.backgroundColor = UIColor.blue
}

Firstly, I'd suggest adding the gesture recognizer to the cell itself rather than to the text label. This gives the user a bigger tap area, and makes it easier to reference the cell when the long press is recognized.
labelCell.addGestureRecognizer(longPressRec)
Add an argument to your gesture action, to pass the gesture recognizer. Then we can use the recogniser's view, to get a reference to the cell — since we are adding the gesture recogniser directly to the cell.
func labelLongPressed(_ recognizer: UIGestureRecognizer) {
guard recognizer.state == .began else { return }
print("Label Long press began!")
if let labelCell = recognizer.view as? TTTLabelCell {
labelCell.backgroundColor = .blue
}
}
Finally, when adding the target to the Gesture Recognizer, update the Selector to match our function signature.
longPressRec.addTarget(self, action:#selector(labelLongPressed(_:)))

Related

Click on ImageView and execute function WITHOUT selectors in Swift/Xcode

I have a tableView with cells. Each cell has an imageView. When I click on the imageView, I want to execute a function with parameters that I pass in.
The only way that I know how to work with clicking on imageViews is something like this:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber))
cell.imageView.addGestureRecognizer(gestureRecognizer)
#objc func printNumber(){
print("4")
}
Now, imagine this exact same thing, but I want to pass the number to print into the function
I've seen a million different posts about how you can't pass parameters into selectors, so I'm not sure what to do in this case.
I want to do something like this (I know you can't do this)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber(num: 4))
cell.imageView.addGestureRecognizer(gestureRecognizer)
#objc func printNumber(String: num){
print(num)
}
I need to do this because every button will have a different output when pressed, depending on some other variables in the cell.
You don't need to pass in the number. Assuming the recognizer is on the image view you:
Get the gestures.view (this is the image view)
Walk up the responder chain to find the parent tableview cell and table view
Ask the tableview for the index path of the tableview cell (this is basically he number you wanted to pass in)
Call your function with the number
As for walking up the responder chain generically:
extension UIResponder {
func firstParent<Responder: UIResponder>(ofType type: Responder.Type ) -> Responder? {
next as? Responder ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
so your code is:
guard let cell = recognizer.view?.firstParent(ofType: UITableViewCell.self),
let tableView = recognizer.view?.firstParent(ofType: UITableView.self),
let indexPath = tableview.indexPath(for: cell) else {
return
}
// Do stuff with indexPath here
Use the accessibilityIdentifier property, only if you still want to use the #selector, so that UIGestureRecognizer can read the value of the value object.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourTableViewCell
…
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printNumber))
cell.imageView.accessibilityValue = String(4)
cell. imageView.isUserInteractionEnabled = true
cell.imageView.addGestureRecognizer(gestureRecognizer)
…
return cell
}
#objc func printNumber(sender: UITapGestureRecognizer) {
if let id = sender.view?.accessibilityIdentifier {
print(tag)
}
}

Figure out which of multiple buttons in UICollectionView cell was tapped

In my Swift code, I have a UICollectionViewCell with 3 buttons (all three have IBActions). From my UICollectionViewController I now want to "catch" the individual button taps.
I've followed this StackOverflow question and I can catch the UICollectionViewCell's touch-up inside up in my CollectionViewController with adding this line to the viewDidLoad
gestureRecognizer.cancelsTouchesInView = false
and with this function
func handleTapForCell(recognizer: UITapGestureRecognizer){
//I can break in here
}
But the piece missing now is how can I figure out which of the three buttons have been tapped? I have set different tags on the buttons but I have not found any place on the gestureRecognizer dealing with these tags.
Any ideas?
I think, you don't need to add Gesture on cell to get a button action of a tableviewCell. This code may help you:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Your tableviewCell code here
//set tag of cell button
cell.button1.tag = 1
cell.button2.tag = 2
cell.button3.tag = 3
//add action of your cell button
cell.button1.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
cell.button2.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
cell.button3.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
// return cell
}
func cellButtonTapped(sender:UIButton, event:AnyObject){
let touches: NSSet = event.allTouches()!
let touch = touches.anyObject()
let currentTouchPosition: CGPoint = (touch?.locationInView(YOUR_TABLEVIEW_INSTANCE))!
if let indexPath: NSIndexPath = self.YOUR_TABLEVIEW_INSTANCE.indexPathForRowAtPoint(currentTouchPosition)!{
if sender.tag == 1{
//cell first button tap
}else sender.tag == 2{
//cell second button tap
}
else sender.tag == 3{
//cell 3rd button tap
}
}
}
You can follow the protocol/delegate paradigm.
What you need to do is define a protocol in Custom cell. Then make the viewcontroller subscribe to the cell delegate.
Implement the IBActions inside the custom cell class. Call the delegate methods in the IBActions of the buttons. viewcontroller who is delegating for the cell will receive the callbacks for button taps inside the cell.

How to add gesture to UITableViewCell?

I want to add a tap gesture to every cell in a UITableView that edits the content in it. The two ways to add a gesture are in code or through storyboard. I tried both and they failed.
Can I add a gesture to every cell in table with storyboard drag and drop? It seems to only add gesture to the first cell. Adding gesture in code, I wrote something like,
addGestureRecognizer(UITapGestureRecognizer(target: self,action:#selector(MyTableViewCell.tapEdit(_:))))
or
addGestureRecognizer(UITapGestureRecognizer(target: self, action:"tapEdit:"))
both work. But I'd like to let the UITableViewController handle this gesture because it does something with the datasource. How do I write my target and action?
EDIT:
addGestureRecognizer(UITapGestureRecognizer(target: MasterTableViewController.self, action:#selector(MasterTableViewController.newTapEdit(_:)))
it induce an error said, unrecognized selector sent to class 0x106e674e0...
To add gesture to UITableViewCell, you can follow the steps below:
First, add gesture recognizer to UITableView
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tableViewController.tapEdit(_:)))
tableView.addGestureRecognizer(tapGesture!)
tapGesture!.delegate = self
Then, define the selector. Use recognizer.locationInView to locate the cell you tap in tableView. And you can access the data in your dataSource by tapIndexPath, which is the indexPath of the cell the user tapped.
func tapEdit(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.Ended {
let tapLocation = recognizer.locationInView(self.tableView)
if let tapIndexPath = self.tableView.indexPathForRowAtPoint(tapLocation) {
if let tappedCell = self.tableView.cellForRowAtIndexPath(tapIndexPath) as? MyTableViewCell {
//do what you want to cell here
}
}
}
}
It is possible to add gesture directly to TableView cell and access the datasource in viewController, You need to set up a delegate:
In your custom cell:
import UIKit
class MyTableViewCell: UITableViewCell {
var delegate: myTableDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(MyTableViewCell.tapEdit(_:)))
addGestureRecognizer(tapGesture)
//tapGesture.delegate = ViewController()
}
func tapEdit(sender: UITapGestureRecognizer) {
delegate?.myTableDelegate()
}
}
protocol myTableDelegate {
func myTableDelegate()
}
In your viewController:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate, myTableDelegate {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 35
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MyTableViewCell
cell?.delegate = self
return cell!
}
func myTableDelegate() {
print("tapped")
//modify your datasource here
}
}
However, this method could cause problems, see UIGestureRecognizer and UITableViewCell issue. In this case, when the swipe gesture successes, the selector get called twice for some reason. I can't say the second method is a bad one as I haven't found any direct evidence yet, but after searching through Google, it seems like the first method is the standard way.
You don't need to add gesture recognizer to achieve what you are doing.
Use the UITableViewDelegate method tableView:didSelectRowAtIndexPath: to detect which row is tapped (this is what exactly your tapGesture is going to do) and then do your desired processing.
If you don't like the gray indication when you select cell, type this in your tableView:didEndDisplayingCell:forRowAtIndexPath: just before returning the cell:
cell?.selectionStyle = .None
Adding gesture in awakeFromNib method seems much more easier and works fine.
class TestCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
let panGesture = UIPanGestureRecognizer(target: self,
action: #selector(gestureAction))
addGestureRecognizer(panGesture)
}
#objc func gestureAction() {
print("gesture action")
}
}
The easiest way to do this is to add the gesture in a custom UITableViewCell. An easier alternative to setting up a custom delegate pattern is to inform the view controller of the edits would be to use a handler in the form of a closure that the view controller can provide and which is called when user editing is finished. I'm assuming a textField is used to allow cell editing.
class CustomTableViewCell: UITableViewCell {
func activateTitleEditing() {
textField.isEnabled = true
textField.becomeFirstResponder()
}
// This will hold the handler closure which the view controller provides
var resignationHandler: (() -> Void)?
#objc private func tap(_ recognizer: UITapGestureRecognizer) {
guard recognizer.state == .ended else { return }
activateTitleEditing()
}
#IBOutlet weak var textField: UITextField! { didSet {
textField.delegate = self
let tap = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
addGestureRecognizer(tap)
textField.isEnabled = false
}}
}
extension CustomTableViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
resignationHandler?()
}
}
And within your custom UITableViewController, pass in the handler to be able to make changes to your model. Don't forget to account for possible memory cycles in the closure.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// initialize and return table view cell
let cell = tableView.dequeueReusableCell(withIdentifier: K.documentCellIdentifier, for: indexPath)
assert(cell is CustomTableViewCell, "Document cell dequeuing error")
let customCell = cell as! DocumentTableViewCell
customCell.textField.text = documentModel.documents[indexPath.row]
customCell.resignationHandler = { [weak self, unowned customCell] in
guard let self = self else { return }
if let newTitle = customCell.textField.text {
self.cellModel.cells[indexPath.row] = newTitle
}
}
return customCell
}

Multiple Tap Gestures in a UITableViewCell

I have several tap gestures being created and recognized on different UIViews from in my TableViewController and the different gestures are being recognized correctly. As seen in this code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
let tapOnView1 = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapOnView2 = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
cell.View1.addGestureRecognizer(tapOnView1)
cell.View1.userInteractionEnabled = true
cell.View2.addGestureRecognizer(tapOnView1)
cell.View2.userInteractionEnabled = true
return cell
}
My Handle tap looks like this:
func handleTap(sender:UITapGestureRecognizer) {
let tappedView = sender.view
self.tableView.beginUpdates()
if tappedView == cell.View1 {
print("View 1 Tapped")
} else if tappedView == cell.View2 {
print("View 2 Tapped")
}
}
I wanted to move all this code to my CustomCell UITableViewCell class as there are actually several more UIViews that have different actions that need to be taken on a tap. Additionally moving them all to the Cell itself seems to me to be the right thing to do. I searched for answers but the only true answer I have seen is to use buttons and there are several reasons that this is really not an option for me without some serious refactoring and rewriting. I have tried several iterations to the following code in my CustomCell class:
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapOnView1 = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
addGestureRecognizer(tapOnView1)
let tapOnView2 = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
addGestureRecognizer(tapOnView2)
}
and this handleTap function:
func handleTap(sender: UITapGestureRecognizer) {
if delegate != nil && item != nil {
if sender.view == view1 {
print("view1 tapped")
} else {
print("view2 tapped")
}
}
}
The tap for view1 is never called. It only ever calls the view2 tap no matter where in the cell I tap. I have tried using different Selector functions (i.e. handleTapOne: for View1, and handleTapTwo: for View2) but I can't seem to figure out how to do this.
Again it works in my UITableViewController but it does not work when I try to move all of the tap recognizers to the UITableViewCell.
Thanks for the help.
iphonic answered my question above. I was an idiot I guess. staring at a problem for two long and missing the most simplest of things.

Trying to assign imageview tag in cell an index number. Always gets assign 7, the last visible cell

I have a table view that list videos from the user's library. When the user taps on the image view, I want the video to play on a new view controller.
I'm having a problem where all of my imageviews' 'tag' property in the cells are being assigned with the number 7, the last visible cell. When I scroll all of the gets assign a different number. Can you guys help me figure out the problem?
I'm getting the correct image for the asset based on ' let asset = assets[indexPath.row]' and 'requestImageForAsset', so I'm not sure why indexPath.row is not working in this case.
I'm using the imageview tag to find the video in an array from the table view.
The custom cell:
class VideoCell: UITableViewCell {
#IBOutlet weak var screenShot: UIImageView!
}
the View Controller:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return assets.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! VideoCell
let asset = assets[indexPath.row]
imageManager!.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFill, options: nil) { (result, _ ) in
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? VideoCell {
cell.screenShot.image = result
cell.screenShot.tag = indexPath.row
}
}
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "VideoPlayer" {
if let playerVC = segue.destinationViewController as? AVPlayerVC {
dispatch_async(dispatch_get_main_queue()) {playerVC.player = self.player}
}
}
}
#IBAction func videoImageTapped(sender: AnyObject) {
let view = sender.view
let index = view!!.tag
let asset = assets[index]
imageManager!.requestPlayerItemForVideo(asset, options: nil) { (playerItem, info) -> Void in
self.player = AVPlayer(playerItem: playerItem!)
self.performSegueWithIdentifier("VideoPlayer", sender: self)
}
}
You'll actually want to change this to use a didSelectRowAtIndexPath method if a user can select a whole cell, if they need to select a button then you should create a custom cell class with the button inside the cell.
Then you can pass your current index in the button's tag to a method outside of the cellForAtIndexPath method.
On second thought, here's how you can do it with a tap gesture recognizer.
Create a sub class of a UITapGestureRecognizer and add a tag property. In your custom cell create an instance variable of the custom tap gesture recognizer type, in the awakeFromNib of the custom cell add the custom recognizer to the image view (set the image view to user interaction = true as well).
Back in your view controller with the table view assign the indexPath.row to the cell's new gesture recognizer tag property. Then add a target to the cell's custom gesture recognizer property.
In the target method add a sender parameter as the custom tap gesture recognizer (or AnyObject) and grab the .tag property to get your index.

Resources