In swift, I have a uitableviewCell that has double tap and single tap implemented. The double tap works. However, I am having a bit of trouble with single tap. Due to the present of double tap, I implemented single tap with a timer. The following code successfully prints out "Single Tapped"
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var now = NSDate().timeIntervalSince1970
if (now - lastClick < 0.3) && indexPath.isEqual(lastIndexPath) {
// Double tapped on cell
if let cell = discoverTableView.cellForRowAtIndexPath(indexPath) as? CommentCell {
cell.doubleTapCellActive({ (addBumpStatus, success) in
})
}
singleTapTimer?.invalidate()
} else {
singleTapTimer = NSTimer.scheduledTimerWithTimeInterval(0.31, target: self, selector: #selector(DiscoverVC.singleTapped), userInfo: nil, repeats: false)
}
lastClick = now
lastIndexPath = indexPath
}
func singleTapped() {
print("Single tapped")
}
However, the problem is, I want the single tap to know which index path was selected. I tried doing something like
#selector(DiscoverVC.singleTapped(_:indexPath))
func singleTapped(indexPath: NSIndexPath) {}
But this gives me an error as selector does not like this syntax. Is there a way to make the selector work or a better way of doing it?
Thanks,
The usual way is to implement the action with the NSTimer parameter
func singleTapped(timer : NSTimer) {
print("Single tapped", timer.userInfo!["indexPath"] as! NSIndexPath)
}
and pass custom data via the userInfo property of the timer for example
singleTapTimer = NSTimer.scheduledTimerWithTimeInterval(0.31,
target: self,
selector: #selector(singleTapped(_:)),
userInfo: ["indexPath":indexPath],
repeats: false)
Related
I have a tableView with a timer in each cell that runs whentableView:didSelectRowAtIndexPath is called. I'm trying to use a label (cellName) in a custom cell (CustomCell) to show the timer incrementing. I'm using #selector in .scheduledTimer to call a seperate function named getTimer() to increment the value. Code as it is below.
I've abbreviated this code to show only the relevant info
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var time = 0
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell: CustomCell = tableView.cellForRow(at: indexPath)! as!
CustomCell
var timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.getTimer), userInfo:nil, repeats: true)
cell.cellName.text = String(self.getTimer())
}
func getTimer() -> String {
time += 1
return String(time)
}
}
Two things:
My time variable is defined on the class level so that getTimer() can increment this on every .scheduledTimer refresh. I'd like to define it in :didSelectRowAtIndexPath so that I can run multiple timers at once. Is there a way I define this in :didSelectRowAtIndexPath, and maybe get the result through returning the increment from getTimer()?
Currently I am updating my cellName with a string returned from getTimer. This is static and currently thinking of ways that I can refresh it when .scheduledTimer refreshes. Should I be maybe passing indexPath.row to getTimer and updating it in the getTimer() function itself?
Your logic to the problem is not right. I'll give you a quick solution to your problem. Create two variables timeArray and timerArray in the class scope
var timeArray = [Int]()
var timerArray = [Timer]()
Put this code in your viewDidLoad. When creating the array, replace the count 1 to the number of cells in your tableview.
timeArray = Array(repeating: 0, count: 1)
let timer = Timer(timeInterval: 1, target: self, selector: #selector(self.timerFired(_:)), userInfo: nil, repeats: true)
timerArray = Array(repeating: timer, count: 1)
In your didSelectRow tableview delegate method handle the timer validation and invalidation
if timerArray[indexPath.row].isValid{
timerArray[indexPath.row].invalidate()
}
else{
timerArray[indexPath.row] = Timer(timeInterval: 1, target: self, selector: #selector(self.timerFired(_:)), userInfo: indexPath.row, repeats: true)
timerArray[indexPath.row].fire()
}
Add this function that will get triggered when the timer is fired.
func timerFired(_ timer:Timer){
if let index = timer.userInfo as? Int{
timeArray[index] += 1
}
}
Finally, in you cellForRow datasource method of the tableview set the label text
cell.cellName.text = time[indexPath.row]
Don't forget to reload the particular cell when the timerFired function.
I want to keep calling following function for 'n' number of times, I'll write that code later. Now am facing a problem.
func keepHighlighting(myLbl : UILabel)
{
myLbl.text = "hi"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for : indexPath) as! TableViewCell
let mySelector = #selector(self.keepHighlighting(duaLbl : cell.tempLbl))
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: mySelector, userInfo: nil, repeats: true);
timer.fire()
return cell
}
Error:
Argument of '#selector' does not refer to an '#objc' method, property, or initializer
You've misunderstood selectors. self.keepHighlighting(duaLbl : cell.tempLbl) is a function call. A selector is the name of a method (it's technically a message, but those generally resolve into a method call).
You cannot pass parameters via a selector. The method you're calling from a timer should have the signature void someMethod(_ timer: Timer). For that, the selector would be #selector(someMethod). (That compiles down to someMethod:, but you generally won't have to deal with that fact.)
Depending on what you're trying to do here, there are several approaches, but it's not quite clear what you're trying to do. My recommendation, though, is to put this logic into the cell itself rather than trying to manage it from the controller. It is generally much easier to have each cell manage its own behaviors than try to have the controller keep track of every cell and tweak it.
You have to annotate your method with #objc and use the Timers userInfoto parse arguments:
#objc func keepHighlighting(timer:Timer)
{
let myLbl = timer.userInfo as! UILabel
myLbl.text = "hi"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for : indexPath) as! UITableViewCell
let mySelector = #selector(self.keepHighlighting)
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: mySelector, userInfo: cell.tempLbl, repeats: true);
timer.fire()
return cell
}
I currently have a sidebar menu in an iOS 9.2 app running on Xcode 7.2 written in Swift 2 that allows the user what data to load to populate the view. I'm using SWRevealViewController to create that sidebar. I have a container view controller which has the front page and the sidebar page listing all the options the user has. Each time the user selects a cell from the sidebar table, it performs a segue that allows the front page to be refreshed. What I want to do is to allow the user to delete a cell from the table with a long press. I'd like to show an AlertViewController to confirm the user's decision, and if "Yes" is selected, I want to delete the cell, and select the very first item in the table. I've tried following the instructions from Long press on UITableView
but I keep getting the error "unrecognized selector sent to instance"
Here's the code that I'm using for setting up the gestureRecognizer in the cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell;
cell.textLabel!.text = tableArray[indexPath.row];
let holdToDelete = UILongPressGestureRecognizer(target: cell, action: "longPressDelete");
holdToDelete.minimumPressDuration = 1.00;
cell.addGestureRecognizer(holdToDelete);
return cell;
}
And here's the longPressDelete function:
func longPressDelete(sender: UILongPressGestureRecognizer) {
let alert: UIAlertController = UIAlertController(title: "Please Confirm", message: "Are you sure you want to delete this car from your database?", preferredStyle: .Alert);
alert.addAction(UIAlertAction(title: "Yes", style: .Destructive, handler: { (UIAlertAction) -> Void in
if let tv = self.tableView {
let point: CGPoint = sender.locationInView(self.tableView);
let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(point)!;
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade);
NSUserDefaults.standardUserDefaults().removeObjectForKey("fillUp" + tableArray[indexPath.row]);
NSUserDefaults.standardUserDefaults().removeObjectForKey("services" + tableArray[indexPath.row]);
tableArray.removeAtIndex(indexPath.row);
NSUserDefaults.standardUserDefaults().setObject(tableArray, forKey: "cars");
self.deleted = true;
self.performSegueWithIdentifier("tableToDashboard", sender: self);
}
}));
alert.addAction(UIAlertAction(title: "No", style: .Default, handler: nil));
self.presentViewController(alert, animated: true, completion: nil);
}
Here's the prepareForSegue function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (deleted) {
let indexPath: NSIndexPath = NSIndexPath(forRow: 0, inSection: 0);
fillUpKey = "fillUp" + tableArray[indexPath.row];
servicesKey = "services" + tableArray[indexPath.row];
localFillUpArray = [fillUp]();
} else {
let indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow!;
fillUpKey = "fillUp" + tableArray[indexPath.row];
servicesKey = "services" + tableArray[indexPath.row];
localFillUpArray = [fillUp]();
}
}
What I'd like to happen is that the user deletes the item in the cell, and the app then performs a segue to the front screen after loading the information from another source. Thanks for taking the time to read this and possibly provide an answer. I hope I haven't made a rookie mistake somewhere.
Incorrect Selector
let holdToDelete = UILongPressGestureRecognizer(target: self,
action: "longPressDelete:");
: after longPressDelete indicates that the method func longPressDelete(sender: UILongPressGestureRecognizer) actually accepts parameters.
self for target, assuming that the selector belongs to the same class that registered it.
The current selector "longPressDelete" would match a method signature without parameters:
func longPressDelete() { }
Very simple example if you want to select cell in uitableview
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longTap))
cell.addGestureRecognizer(longGesture)
// longTap
func longTap(gestureReconizer: UILongPressGestureRecognizer) {
print("Long tap")
let longPress = gestureReconizer as UILongPressGestureRecognizer
_ = longPress.state
let locationInView = longPress.location(in: tableview)
let indexPath = tableview.indexPathForRow(at: locationInView)
// whatever you want with indexPath use it //
}
I have a tableview
I set timer in ViewDidLoad() as follows
self.timer = NSTimer(timeInterval: 10.0, target: self, selector: Selector("fireCellsUpdate"), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
func fireCellsUpdate() {
print("In fireCellsUpdate")
let notification = NSNotification(name: "CustomCellUpdate", object:UILabel())
NSNotificationCenter.defaultCenter().postNotification(notification)
}
and in tableviewcell as follows
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "mycell")
var lbl_uploadTime = UILabel()
lbl_uploadTime.tag = indexPath.row
lbl_uploadTime.text = "3 hours 2 mins"
cell.contentView.addsubView(lbl_uploadTime)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateCountdownLabel:", name: "CustomCellUpdate", object: nil)
}
How can I update text for lbl_uploadTime without reloading whole tableview?
Here I reload whole tableview
func updateCountdownLabel(notification: NSNotification) {
println("broadcast received in cell %#",notification)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableview_DiscoverVideos.reloadData()
})
}
But I want to change only label text without reloading whole tableview. Please help me.
I recommend you to subclass UITableViewCell and add observer in that cell and then update to that UILabel to whatever you need. You should not add observer in cellForRowAtIndexPath: method.
Update: improvement:
Add observers in only custom cells where you need to change Label.
I have a tableView and I want update it every 10 seconds. For it I do:
var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: "reloadTable", userInfo: nil, repeats: true)
func reloadTable() {
print("reload")
self.tableView.reloadData()
}
but it doesn't reload correctly. What it means:
Yes, it reloads, but my data doesn't update, but when I drag my tableView to top and leave it, my tableView cells' data update. How can I achieve this effect programmatically?
UPDATE
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> onlineUserCell {
let cell:onlineUserCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! onlineUserCell
let user = OneRoster.userFromRosterAtIndexPath(indexPath: indexPath)
cell.username.text = user.displayName
if user.unreadMessages.intValue > 0 {
cell.backgroundColor = .orangeColor()
} else {
cell.backgroundColor = .whiteColor()
}
configurePhotoForCell(cell, user: user)
cell.avatarImage.layer.cornerRadius = cell.avatarImage.frame.size.height / 2
cell.avatarImage.clipsToBounds = true
return cell;
}
I'm not 100% sure, but if the table isn't visually updating, you probably aren't calling the UI code on the main thread. All UI code needs to be executed on the main thread in iOS.
Look up dispatch_async and/or performSelectorOnMainThread.
You need to update your datasource programmatically too.The reloadData() method just refresh the cell.If you don't refresh the datasource(the actually array) there is noting different.