This is not a massive issue with me, I am just struggling to understand what is happening. Other rather how to make it work the way I want it to work.
Consider the following code of any standard UITableViewController:
var tapGestureRecognizer = UITapGestureRecognizer()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let customCell = tableView.dequeueReusableCell(withIdentifier: customCellID) as? CustomTableViewCell else { return UITableViewCell() }
if indexPath.row == 0 {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .red
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
} else {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .blue
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
}
}
#objc private func tagIndexPathRowMethod(sender: UITapGestureRecognizer) {
print("Cell number tag: \(String(describing: sender.view?.tag))")
}
I've already tried splitting the properties, methods and cells into separate codes such as
var firstTapGestureRecognizer = UITapGestureRecognizer()
var secondTapGestureRecognizer = UITapGestureRecognizer()
etc, but the tag still prints only a 0 from both cells.
Could someone explain to me how to make the print statement in tagIndexPathRowMethod returns 0 as tag no matter if I tap in cell 0 or cell 1, but the print statements inside cellForRowAt prints the correct indexPath.row integers, 0 and 1? I know I could use didSelectRowAt, but I've just become stubborn I guess.
(I'm well aware of all times I'm breaking with the DRY principle, but it just serves as a pedagogical example.)
Updated answer
This happening because the you setting tag before adding gestures to the cell. In this case, tapGestureRecognizer.view is null at that time. Just do one thing set tag after adding gestures to the cell.
customCell.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.view?.tag = indexPath.row
You need to set the value of view tag inside the UITapGestureRecognizer class. Just add the line below after initializing 'customCell'.
customCell.tag = indexPath.row
Code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let customCell = tableView.dequeueReusableCell(withIdentifier: customCellID) as? CustomTableViewCell else { return UITableViewCell() }
customCell.tag = indexPath.row
if indexPath.row == 0 {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .red
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
} else {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .blue
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
}
}
#objc private func tagIndexPathRowMethod(sender: UITapGestureRecognizer) {
print("Cell number tag: \(String(describing: sender.view?.tag))")
}
You should not use cellForRow to add tapGesture to the cell ... as cell get reuse so same gesture will apply to multiple cells. So instead of adding it in cell for row .. add them in Custom cell init() method in your case in CustomTableViewCell class so it adds only once ... you can set Tag to that gesture in cellForRow method that will not cause any issue ...
Related
In this case i need my uiLabel clickable, so i add uiTapRecognizer. Then, i need to send the indexpath section to the tag so i can get the choosen section.
But when i try to get the sender.tag in tapFunction() there is error : UITapGestureRecognizer has no member tag
If i change the sender to uiButton -> func tapFunction(_ sender:uiButton) {
it doesn't print anything inside the function
Is there any workaround for this case (still using uilabel not change it to uibutton)?
Can i also send data when #selector tapfunciton(_: , data:"text")
and read it in tapFunction(data:String)? If this possible, can someone show it ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "LoanCell", for: indexPath) as? LoanCell else {
return UITableViewCell()
}
let item = viewModel.itemsForRowAt(indexPath: indexPath)
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapFunction(_:)))
cell.syaratLabel.tag = indexPath.section
cell.isUserInteractionEnabled = true
cell.syaratLabel.addGestureRecognizer(tap)
return cell
}
#objc
func tapFunction(_ sender:UITapGestureRecognizer) {
print("successprint")
let item = viewModel.itemsForRowAt(indexPath: IndexPath(row: 0, section: sender.tag))
let viewController = DetilSandKViewController(messageSnK:item.prdAgreementDetail, titleSnK:item.nomorKontrak)
viewController.modalPresentationStyle = .overCurrentContext
viewController.modalTransitionStyle = .crossDissolve
present(viewController, animated: true, completion: nil)
}
I have a UITextView and a Button programmatically constrained in a UIView when the text view is tapped the keyboard shows up and the view moves up with the keyboard as I want it to as I have functions that deal with this
My issue is when I put in some text into the UITextView I have to tap the button twice before the function I have set is called and the table view shows the data
Does anyone know the reason why this is happening and why it doesn't work the first time I tap the button here is my code
var message = [String]()
let messageView: UIView = {
let view = UIView()
return view
}()
let sendButton: UIButton = {
let button = UIButton()
button.setTitle(" send", for: .normal)
button.setTitleColor(.green, for: .normal)
button.addTarget(self, action: #selector(handleSend), for: .touchUpInside)
return button
}()
#objc func handleSend() {
message.append(messageTextView.text)
tableview.reloadData()
}
let messageTextView: UITextView = {
let textView = UITextView()
textView.textColor = .black
textView.font = UIFont.preferredFont(forTextStyle: .headline)
return textView
}()
let line: UILabel = {
let label = UILabel()
label.backgroundColor = .gray
return label
}()
let tableview: UITableView = {
let tv = UITableView()
tv.backgroundColor = UIColor.white
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
constraints()
hideKeyboardWhenTappedAround()
tableview.delegate = self
tableview.dataSource = self
tableview.register(TableViewCell.self, forCellReuseIdentifier: "cell")
tableview.separatorStyle = UITableViewCell.SeparatorStyle.none
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIWindow.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIWindow.keyboardWillHideNotification,
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return message.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.textLabel?.text = message[indexPath.row]
cell.selectionStyle = .none
cell.textLabel?.numberOfLines = 0
return cell
}
I come across this type of issue in many different areas, not only relating to the keyboard.
I've found the best solution is to put the funcs in a queue, 1 millisecond is even enough!.
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1), execute:
{
// YOUR CODE WILL BE EXECUTED IN THIS BLOCK
})
This DispatchQueue.main.asyncAfter has helped me solve many problems when the code should work but didn't.
Though your code is unclear, but I think I've figure out why you might be facing this issue.
I see you have a method call hideKeyboardWhenTappedAround() , here if you are using UITapGestureRecognizer you may want to set cancelsTouchesInView to false.
When this property is false, the view receives all the touches in the multi-touch sequence which is not in the case of it's default state which is true where previously delivered touches are cancelled.
I'm using a UISwipeGestureRecognizer to detect a swipe for a cell in a UITableViewCell, similar to THIS LINK which will allow the user to 'Like' a photo.
The problem is that I dont quite understand how to change the Like value for that specific post - and it doesn't have an indexPath like other 'built-in' methods. I also don't understand how it knows to use the cell that is showing predominantly on the screen, since there might be more than one cell that has not yet been "dequeued"?:
#objc func mySwipeAction (swipe: UISwipeGestureRecognizer) {
switch swipe.direction.rawValue {
case 1:
print ("the PostID you selected to LIKE is ...")
case 2:
print ("the PostID you selected to Undo your LIKE is ...")
default:
break
}
}
and my tableView looks like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postTopContributions", for: indexPath) as! PostTopContributions
let postImage = postImageArray [indexPath.row]
let imageURL = postImage.postImageURL
cell.delegate = self
cell.postSingleImage.loadImageUsingCacheWithUrlString(imageURL)
cell.postSingleLikes.text = "\(postImageArray [indexPath.row].contributionPhotoLikes)"
cell.postSingleImage.isUserInteractionEnabled = true
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.mySwipeAction(swipe:)))
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.mySwipeAction(swipe:)))
leftSwipe.direction = UISwipeGestureRecognizerDirection.left
rightSwipe.direction = UISwipeGestureRecognizerDirection.right
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
let selectedCell = self.postImageArray [indexPath.row]
return cell
}
I don't want to use the native TableView row swipe left to delete methods - for various UX purposes in this specific case.
You can try
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
cell.postSingleImage.tag = indexPath.row
Don't recommend adding gestures inside cellForRowAt , you may add
them inside init for programmatic cells or awakeFromNib for xib /
prototype cells
#objc func mySwipeAction (swipe: UISwipeGestureRecognizer) {
let index = swipe.view.tag
let selectedCell = self.postImageArray[index]
switch swipe.direction.rawValue {
case 1:
print ("the PostID you selected to LIKE is ...")
// edit dataSource array
case 2:
print ("the PostID you selected to Undo your LIKE is ...")
// edit dataSource array
default:
break
// reload table IndexPath
}
}
You can pass your indexpath as a parameter in your selector. and then add the like at yourArray[indexpath.row]
You can set the tag of the cell image that you are adding the GestureRecognizer to the indexPath row of the cell itself:
cell.postSingleImage.tag = indexPath.row
cell.postSingleImage.addGestureRecognizer(leftSwipe)
cell.postSingleImage.addGestureRecognizer(rightSwipe)
Then, you can determine which cell triggered the GestureRecognizer by getting the view's tag that triggered the swipe gesture:
#objc func mySwipeAction (gesture: UISwipeGestureRecognizer) {
let indexPathRow = gesture.view.tag
let indexPath = IndexPath(row: indexPathRow, section: 0) // assuming this is a 1 column table not a collection view
if let cell = tableView.cellForRow(at: indexPath) as? PostTopContributions {
// ... and then do what you would like with the PostTopContributions cell object
print ("the PostID you selected to LIKE is ... " + cell.id)
}
}
Hope that this helped!
I have the button working correctly, I just can't figure out how to disable it on tap. I'm not sure if I can reference it from the addSomething(sender: UIButton) function like I reference the sender.tag.
Any idea? Thanks for any help.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ExploreCell
// Configure the cell...
myCell.configureCell(teams[indexPath.row])
myCell.addSomethingButton.tag = indexPath.row
myCell.addSomethingButton.addTarget(self, action: #selector(self.addSomething), forControlEvents: .TouchUpInside)
myCell.addSomethingButton.enabled = true
//disable cell clicking
myCell.selectionStyle = UITableViewCellSelectionStyle.None
return myCell
}
What do you need to do is to store all tapped buttons in an array to check whether the button of this tag (current indexPath.row) has been tapped:
class ViewController: UIViewController {
var tappedButtonsTags = [Int]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ExploreCell
// Configure the cell...
myCell.configureCell(teams[indexPath.row])
myCell.addSomethingButton.tag = indexPath.row
// here is the check:
if tappedButtonsTags.contains(indexPath.row) {
myCell.addSomethingButton.enabled = false
} else {
myCell.addSomethingButton.addTarget(self, action: #selector(self.addSomething), forControlEvents: .TouchUpInside)
myCell.addSomethingButton.enabled = true
}
//disable cell clicking
myCell.selectionStyle = UITableViewCellSelectionStyle.None
return myCell
}
// I just Implemented this for demonstration purposes, you can merge this one with yours :)
func addSomething(button: UIButton) {
tappedButtonsTags.append(button.tag)
tableView.reloadData()
// ...
}
}
I Hope this helped.
I added button into cell-s and added action so if user touches it then the state is "Dislike" and if user touches again the state is "Like". However, the state applies to other cell buttons also. And if I scroll fast it just randomly picks what cell button should have the state. What causes this?
I call button with function inside cellForRowAt indexPath: IndexPath function like this:
cell.likeButton.addTarget(self, action: #selector(like), for: .touchUpInside)
And this is the function that is assigned to the button:
func like(sender: UIButton){
let section = 0
let row = sender.tag
let indexPath = IndexPath(row: row, section: section)
let cell: FeedTableViewCell = tableView.dequeueReusableCell(withIdentifier: "feedCell", for: indexPath) as! FeedTableViewCell
FIRDatabase.database().reference().child("posts").child(postsArray[indexPath.row].key).runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = FIRAuth.auth()?.currentUser?.uid {
var stars : Dictionary<String, Bool>
stars = post["stars"] as? [String : Bool] ?? [:]
var starCount = post["starCount"] as? Int ?? 0
if let _ = stars[uid] {
// Unstar the post and remove self from stars
starCount -= 1
stars.removeValue(forKey: uid)
cell.likeButton.tag = indexPath.row
cell.likeButton.setTitle("Like", for: .normal)
cell.likeLabel.text = "\(starCount)"
} else {
// Star the post and add self to stars
starCount += 1
stars[uid] = true
cell.likeButton.tag = indexPath.row
cell.likeButton.setTitle("Dislike", for: .normal)
cell.likeLabel.text = "\(starCount)"
}
post["starCount"] = starCount as AnyObject?
post["stars"] = stars as AnyObject?
// Set value and report transaction success
currentData.value = post
return FIRTransactionResult.success(withValue: currentData)
}
return FIRTransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
}
And like this I created the tableview with cells:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: FeedTableViewCell = tableView.dequeueReusableCell(withIdentifier: "feedCell", for: indexPath) as! FeedTableViewCell
cell.likeButton.tag = indexPath.row
cell.likeButton.addTarget(self, action: #selector(self.tapped), for: .touchUpInside)
}
What causes the state to transfer to the other buttons also? I even added tags so it detects the selected button. Is there something to do with cell reuse?
It adds likes to Firebase to the right one..
This is caused by reusing previous cells when scrolling and is the base mechanism of a table view.
You need to reset the state of your button on every call to cellForRowAtIndexPath.
Between let cell = ... and cell.starButton.addTarget you need to perform something like cell.starButton.deselect(), or .select(), based on the index path you're working on.
var selectindex : Int?
var selectedindex : NSMutableArray = NSMutableArray()
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("LikeCell", forIndexPath: indexPath)
let like: UIButton = (cell.viewWithTag(2) as! UIButton)
let comment: UIButton = (cell.viewWithTag(3) as! UIButton)
if selectedindex.containsObject(indexPath.row) {
like.setBackgroundImage(UIImage.init(named: "like.png"), forState: .Normal)
}else{
like.setBackgroundImage(UIImage.init(named: "like (1).png"), forState: .Normal)
}
comment.setBackgroundImage(UIImage(named: "chat.png"), forState: UIControlState.Normal)
like.addTarget(self, action: #selector(self.CloseMethod(_:event:)), forControlEvents: .TouchDown)
comment.addTarget(self, action: #selector(self.CloseMethod1(_:event:)), forControlEvents: .TouchDown)
return cell
}
#IBAction func CloseMethod(sender: UIButton, event: AnyObject) {
let touches = event.allTouches()!
let touch = touches.first!
let currentTouchPosition = touch.locationInView(self.tableview)
let indexPath = self.tableview.indexPathForRowAtPoint(currentTouchPosition)!
selectindex = indexPath.row
if selectedindex.containsObject(selectindex!) {
selectedindex.removeObject(selectindex!)
// call your firebase method for update database
}else{
selectedindex.addObject(selectindex!)
// call your firebase method for update database
}
self.tableview.reloadData()
}
Output :
https://www.dropbox.com/s/ub7wln5y6hdw0sz/like%20button.mov?dl=0
I think this issue is because of dequeuing your cell twice. you should try;
func like(sender: UIButton){
//your code ...
let cell: FeedTableViewCell = self.tableViewAddress.cellForRowAtIndexPath(indexPath) as! FeedTableViewCell
//Your code ...