I'm writing a to do list app, and when I click a UIButton to add a new task, I want a new TableViewCell to pop up, that contains a UITextField. However, when I set this UITextfield to first responder, with the following code:
let cellIdentifier = "TaskTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: newIndexPath) as! TaskTableViewCell
cell.nameLabel.becomeFirstResponder()
However when I do this, a keyboard pops up but a cursor does not, and when I type, I can see autocorrect options, but no characters ever appear in the textfield.
Wondering what's going on? Thanks!
You should create IBOutlet for textField and IBAction for button. And it should look like this:
class TaskTableViewCell: UITableViewCell
{
#IBAction func buttonPressed()
{
self.myTextField.becomeFirstResponder()
}
}
Related
Screenshot of the app:
I have an 'x' button to delete a TableViewCell which is in a table within a table. On click of the button, I would like to remove the cell, so I need to know the 2 indexes of the button click, the row if the first table view, and then within that tableview the row of the cell which the button was clicked. All I have is the sender.
So to be a bit clearer, in the screenshot, if someone clicks the x under the ford fiesta, I need to get indexpath 0 for the "subtableview" and 1 for the tableview, and that way I know to delete this element from the table datasource.
I do it successfully by doing:
var cell = sender.superview
while (cell != nil) && !((cell?.isKind(of: CustomCell.self))!) {
cell = cell?.superview
}
let tbl = cell?.superview as! UITableView
let indexPath = tbl.indexPath(for: (cell as? UITableViewCell)!
)
The stupid thing is I have to do it twice, once to find the index of the cell within the "sub"tableview, and then again to find the index of the "subtableview" within the tableview.
Is there a better way to do this? Isnt there a way to get the buttonClick to get the didSelectRowAt to fire and add the sender object to it (so I know that a button was clicked as opposed to the cell being selected)?
EDIT I forgot to mention that the first tableview opens and closes on click, so the main tableview has 2 different cell types, one closed (so no nested tableview) and then onselect of a row from that tableview, the cell is replaced with a detailed cell which has another tableview inside it, thats why sectioned tableview isnt a solution (to the best of my knowledge, I'm new here)
One way to do it is to use closures. You set up your cell with a closure and then call it. Pretty much like this:
class CellWithClosure: UITableViewCell {
var button: UIButton = UIButton()
var closureForButton: (Void) -> Void
func setupCell(closureForButton: #escaping (Void) -> Void) {
self.closureForButton = closureForButton
button.addTarget(self, action: #selector(buttonAction), for: UIControlEvents.touchUpInside)
}
#objc func buttonAction() {
closureForButton()
}
}
I have a custom UITableViewCell that contains a UISwitch and a UILabel. When the switch has been tapped, I would like to know which cell has had their switch activated/deactivated.
I have the following code in my cellForRowAtIndexPathMethod to add the target to my cell's UISwitch:
[cell.notifSwitch addTarget:self action:#selector(switchChangedFromCell:) forControlEvents:UIControlEventValueChanged];
Here is the code for the selector:
- (void)switchChangedFromCell:(id)sender {
HydrationNotificationTableViewCell *cell = (HydrationNotificationTableViewCell *)sender;
if ([cell.notifSwitch isOn]) {
NSLog(#"Notification for %# has been activated", cell.notifLabel.text);
}
else {
NSLog(#"Deactivated notification %#", cell.notifLabel.text);
}
}
Right off the bat I believe that it is wrong of me to cast the sender as a cell, since the sender really is the UISwitch from the cell. I would like to know how I can pass the cell itself as well so I know which cell has been changed.
One solution is to set a different tag for each UISwitch in my cellForRowAtIndexPath method, but I was wondering if there was a cleaner solution.
The bad news is that you can't do that... exactly.
The good news is that in your IBAction the UISwitch is the sender, and you can use that to figure out which cell contains the UISwitch.
I have a project on Github called TableViewExtension that shows how to do it.
The only real trick is this extension to UITableView:
public extension UITableView {
/**
This method returns the indexPath of the cell that contains the specified view
- Parameter view: The view to find.
- Returns: The indexPath of the cell containing the view, or nil if it can't be found
*/
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
If you add that extension you can then make your IBAction use the sender to figure out which control triggered the tap, and call that method to figure out which cell contains that control:
#IBAction func controllTriggered(_ sender: UISwitch) {
guard let indexPath = tableView.indexPathForView(sender) else { return }
//Now you have the indexPath of the cell containing `sender`
}
Edit:
Note that in your code you are trying to get the cell, and then query the different subviews of the cell to get state data. Don't do that. You should be storing your state data in your model object. The cell is for displaying information and interacting with the user.
If the user taps a switch the IBAction will have the switch as it's sender parameter. You can get the value of the Switch from sender.
I believe you can only pass the sender in a selector method like that.
You could consider having the UISwitch method be in your custom UITableViewCell class, and create a delegate protocol for your custom class that passes the cell to your ViewController.
I came up with another solution but this is also a bit ugly. I casted the cell to the sender's superview's superview (UISwitch >> Content View >> Cell)
HydrationNotificationTableViewCell *cell = (HydrationNotificationTableViewCell *)([senderSwitch superview].superview);
Hopefully there's a cleaner solution out there.
I am programmatically creating cells and adding a delete button to each one of them. The problem is that I'd like to toggle their .hidden state. The idea is to have an edit button that toggles all of the button's state at the same time. Maybe I am going about this the wrong way?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("verticalCell", forIndexPath: indexPath) as! RACollectionViewCell
let slide = panelSlides[indexPath.row]
cell.slideData = slide
cell.slideImageView.setImageWithUrl(NSURL(string: IMAGE_URL + slide.imageName + ".jpg")!)
cell.setNeedsLayout()
let image = UIImage(named: "ic_close") as UIImage?
var deleteButton = UIButton(type: UIButtonType.Custom) as UIButton
deleteButton.frame = CGRectMake(-25, -25, 100, 100)
deleteButton.setImage(image, forState: .Normal)
deleteButton.addTarget(self,action:#selector(deleteCell), forControlEvents:.TouchUpInside)
deleteButton.hidden = editOn
cell.addSubview(deleteButton)
return cell
}
#IBAction func EditButtonTap(sender: AnyObject) {
editOn = !editOn
sidePanelCollectionView.reloadData()
}
I think what you want to do is iterate over all of your data by index and then call cellForItemAtIndexPath: on your UICollectionView for each index. Then you can take that existing cell, cast it to your specific type as? RACollectionViewCell an then set the button hidden values this way.
Example (apologies i'm not in xcode to verify this precisely right now but this is the gist):
for (index, data) in myDataArray.enumerated() {
let cell = collectionView.cellForRowAtIndexPath(NSIndexPath(row: index, section: 0)) as? RACollectionViewCell
cell?.deleteButton.hidden = false
}
You probably also need some sort of isEditing Boolean variable in your view controller that keeps track of the fact that you are in an editing state so that as you scroll, newly configured cells continue to display with/without the button. You are going to need your existing code above as well to make sure it continues to work as scrolling occurs. Instead of creating a new delete button every time, you should put the button in your storyboard and set up a reference too and then you can just use something like cell.deleteButton.hidden = !isEditing
Okay, I'm going to try to break this down as simply as I am able. I have a tableView in a ViewController. I have two prototype cells for the table. I am reusing the cells multiple times to populate the table.
In one of the cells, I've added gesture recognizer to the label through which I'm making a textField visible on place of the label and hiding the label. Now I want the labels text to change to what I've entered in the textField when I'm done using the textField and hit the return key. So i implemented the UITextFieldDelegate protocol in the viewController. I've also added tags to each of the textFields in the cell so that I know what textField is returning and what row the textField is in.
Basically, what I want to know is if there is any way to get the indexPath if I already know the indexPath.row?
For the gesture recognizers, i was able to work around this issue by getting the indexPath from the tapped location:
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
let indexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(indexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
I need the indexPath so that I can refer to the elements contained within a cell. Can anyone offer any insights? Has anybody tried a similar approach? Is there any way I can access the cell by just using the row?
If you are able to get the indexPath inside the GestureMethod then you can create one instance property of type NSIndexPath store its value inside that Gesture's method and later used the indexPath inside textFieldShouldReturn delegate method, something like this.
var selectedIndexPath: NSIndexPath?
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
self.selectedIndexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(self.selectedIndexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
Now use this self.selectedIndexPath inside UITextFieldDelegate method.
Edit: From your question's comment you have told that you have just one Section so you can also create indexPath from that textField's tag this way.
func textFieldShouldReturn(textField: UITextField) -> Bool {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
//Or You can use self.selectedIndexPath also
}
In case of single or multiple sections, the below code will work
In your cellForRowAtIndexPath, set the tag as below:-
let tag = indexPath.section*100 + indexPath.row
cell.savedUserInput.tag = tag
cell.userDetailTextfield.tag = tag
In your textfield delegate method, get the indexPath as follows:-
func genderTapped(sender: UITapGestureRecognizer) {
let textfieldObject = sender as! UITextField
let sectionTag = textfieldObject.tag % 100
let rowTag = textfieldObject.tag / 100
let indexPath = NSIndexPath(forRow: rowTag.tag, inSection: sectionTag)
}
Disclaimer: This is not an answer to the literal question asked here, but it might provide an simpler solution to OP's goal.
Unless you need to do something in addition to what you described in your question it seems to me that a much easier solution would be not to use labels at all but in stead just use an UITextField and set it's enabled property to false when you want it to act like an label.
You can subclass the UITextField if you need the styling to change when the mode changes.
If you know the row number which you are accessing and the section in which the row is, then use this code
let indexPath = NSIndexPath(forRow: row, inSection: section)
For accessing the cell corresponding to this indexPath, use
let cell = tableView.cellForRowAtIndexPath(indexPath) as! tableViewCell
I am making a forum app similar to stack overflow. There is a UITableView displaying the answers to a question, and each cell (which contains an answer) also contains a 'correct answer' tick button. Only one answer can be selected as the correct answer at any given time. The correct answer is marked with a green tick, whilst the others are marked with a grey tick (I have two identical images but with different colors). I am trying to set it so when the user clicks on a tick button that is grey, the currently green button turns grey, and the grey button that was tapped turns green.
#IBOutlet weak var answeredTick: UIButton!
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell.answeredTick.tag = indexPath.row
}
#IBAction func answerTickPressed(sender: AnyObject) {
guard let answeredTick = sender as? UIButton else { return }
//indexOfOfficialAnswerId is the currently registered official ID
if let buttonToMakeGrey = answeredTick.viewWithTag(self.indexOfOfficialAnswerId) as? UIButton {
print("success!")
buttonToMakeGrey.setImage(UIImage(named: "greyTick.png"), forState: UIControlState.Normal)
} else {
print("no success")
}
}
However it always prints "no success". The value of indexOfOfficialAnswerId is correct during the if statement. I am able to make answerTicks that are grey turn green, but i cant make the green one grey. Why is this?
UPDATE: It appears that i can make the green tick grey if i click on the green tick, but not if i click on any of the grey ticks
You can find cell with green tick as so:
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.indexOfOfficialAnswerId inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
And then use your:
cell.answeredTick.setImage(UIImage(named: "greyTick.png"), forState: UIControlState.Normal)
Sorry for Objective-C code
Try reloading that cell after setting that button image or changing its state as selected. I guess that might be the problem .