I have a subclass of UITableViewCell that is shown in a TableView. Each cell has a text field. When the textFieldDidEndEditing func is called, I want to save the entered text as an attribute of an NSManagedObject in my Managed Object Context.
This function is implemented in my tableViewCell class:
func textFieldDidEndEditing(textField: UITextField) {
let viewController = ViewController()
let indexPath: NSIndexPath!
viewController.updateCommitsInMOC(self, atIndexPath: indexPath!)
}
And this is the function it calls. This function is implemented in my ViewController class, the one that controls the TableView which is made up of the tableViewCells:
func updateCommitsInMOC(cell: CommitTableViewCell, atIndexPath indexPath: NSIndexPath) {
// Fetch Commit
let commit = fetchedResultsController.objectAtIndexPath(indexPath) as! Commit
// Update Cell
commit.contents = cell.commitContents.text!
if cell.repeatStatus.selectedSegmentIndex == 1 { commit.repeatStatus = true }
saveManagedObjectContext()
}
I'm of course open to any suggestions as to other ways to implement the saving behavior every time the user is done editing the text field.
Is your question "How do I get the IndexPath"? Instead of the UITableviewCell trying to figure out what it's indexPath is in textFieldDidEndEditing, why don't you just figure it out within updateCommitsInMOC function?
Assuming you have a reference to your tableView you can just do this
func updateCommitsInMOC(cell: CommitTableViewCell) {
guard let indexPath = tableView.indexPathForCell(cell) else {
return
}
// Fetch Commit
let commit = fetchedResultsController.objectAtIndexPath(indexPath) as! Commit
// Update Cell
commit.contents = cell.commitContents.text!
if cell.repeatStatus.selectedSegmentIndex == 1 { commit.repeatStatus = true }
saveManagedObjectContext()
}
You can add a tag as row in cell textField.
like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCell")
cell.textField.tag = indexPath.row
return cell
}
and the textField delegate:
func textFieldDidEndEditing(textField: UITextField) {
let viewController = ViewController()
let indexPath = NSIndexPath(forRow: textField.tag, section: 0)
viewController.updateCommitsInMOC(self, atIndexPath: indexPath!)
}
or you can use the superview:
func textFieldDidEndEditing(textField: UITextField) {
let view = textField.superview!
let cell = view.superview as! UITableViewCell
let viewController = ViewController()
let indexPath = itemTable.indexPathForCell(cell)
viewController.updateCommitsInMOC(self, atIndexPath: indexPath!)
}
I suggest you to use in your tableview the
setEditing(editing, animated: animated) method.
Then inside of it you can manage the single object retrieving it from the fetchResultController.indexPathForObject(inputObject) or as you used fetchedResultsController.objectAtIndexPath(indexPath).
Finally you can use self.managedObjectContext.saveToPersistentStore() or self.managedObjectContext.save().
Related
I have a tableView who need to contain two different view, the name of the first one is CustomTableViewCell the second one is CustomDeliveryTableViewCell
I want my variable to take the two cell, I don't understand this error.
Here my function
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell: UITableViewCell
cell = Bundle.main.loadNibNamed("CustomTableViewCell", owner: self, options: nil)?.first as! UITableViewCell
if (!self.appReady)
{
return cell
}
let arrayOfCard = self.selectedCard(section: indexPath.section, row: indexPath.row)
let json:JSON = JSON(arrayOfCard)
if (json[0]["cards"][indexPath.row]["category"] == "delivery")
{
cell = Bundle.main.loadNibNamed("CustomDeliveryTableViewCell", owner: self, options: nil)?.first as! CustomTableViewCell
}
cell = fillCell(cell: cell, json: json, index: indexPath.row)
return cell
}
My fonction fillCell is prototype like that
func fillCell(cell: CustomTableViewCell, json:JSON, index:Int) ->
CustomTableViewCell
Edit
Here the code of actual fillCell function
func fillCell(cell: UITableViewCell, json:JSON, index:Int) -> UITableViewCell {
if (json[0]["cards"][index]["category"] == "train")
{
if let type = json[0]["cards"][index]["category"] as JSON?
{
cell.labelType.text = type.string
}
if let departureStation = json[0]["cards"][index]["train"]["departure"]["station"] as JSON?
{
cell.labelDepartureStation.text = departureStation.string
}
// Do some code
}
else if (json[0]["cards"][index]["category"] == "delivery")
{
//Do some code
return cell
}
else{
//Do some code
return cell
}
}
Your initial assignment creates a cell of type CustomDeliveryTableViewCell.
Within the if block you're trying to assign a CustomTableViewCell to the same variable. This will only work if CustomTableViewCell is a subclass of CustomDeliveryTableViewCell
When you call fillCell( it's expecting CustomTableViewCell, but cell is a CustomDeliveryTableViewCell
If you declare var cell: UITableViewCell then you can assign either type to it.
I have custom cell in tableView
inside it I have a button , I want when user click on the button push viewController, how I can do that and how I can know which cell user use its button, because here not didSelectRowAtIndexPath
Create a custom button class
class CustomButton: UIButton {
var indexPath: NSIndexPath!
}
In your custom cell create the button of the CustomButton type add the following lines in cellForRowAtIndexPath
cell.yourCustomButton.indexPath = indexPath
Define IBAction like this
#IBAction func customButtonClicked(sender: CustomButton) {
let indexPath: NSIndexPath = sender.indexPath
// Do whatever you want with the indexPath
}
Add Tag of button in Cell and add Target for your transition Function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as! CustomCell
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector( self.transitonMethod ), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
Fetch indexPath from tag of sender Button and Fetch Cell for this indexPath.Push your controller on your navigation controller
func transitonMethod(sender: AnyObject){
let indexPath = NSIndexPath.init(index: sender.tag)
let cell = tableView.cellForRowAtIndexPath(indexPath)
self.navigationController?.pushViewController(yourController, animated: true)
}
In cellforRow method of tableview:
1) set tag of your button e.g. yourButton.tag = indexpath.row
2) set target method of yourbutton e.g.buttonPressed(sender:UIButton)
3) Now in that target method you will get indexpath.row by sender.tag
Declare an IBAction as follow as suggested in IOS 7 - How to get the indexPath from button placed in UITableViewCell :
#IBAction func didTapOnButton(sender: UIButton) {
let cell: UITableViewCell = sender.superview as! UITableViewCell
let indexPath: NSIndexPath = self.tableView.indexPathForCell(cell)
// Do anything with the indexPath
}
Or the other method :
#IBAction func didTapOnButton(sender: UIButton) {
let center: CGPoint = sender.center
let rootViewPoint: CGPoint = sender.superview?.convertPoint(center, toView: tableView)
let indexPath: NSIndexPath = tableView.indexPathForRowAtPoint(rootViewPoint!)
print(indexPath)
}
Then work with that indexPath to do whatever you need.
I have used a tableview with 16 cells on a view controller. Each cell has a textfield and a picker view as a inputview for textfield. The odd thing is that When I choose the value for the first cell, it's fine. When I scrolled down to the last cell, the value is same as the first one. But I have never touched the last cell. Why would this happened?
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
// selected value in Uipickerview in Swift
answerText.text = pickerDataSource[row]
answerText.tag = row
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let selectValue = sender.tag
if let txtf = sender as? UITextField {
if let superview = txtf.superview {
if let cell = superview.superview as? AddFollowTableViewCell {
rowIndex = myTable.indexPathForCell(cell)?.row
dictAnswer[rowIndex] = selectValue - 1
}
}
}
}
After two days, it solved by thousands of trials:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell") as! AddFollowTableViewCell
if(cell.identifier == true){
cell.answerText.text = selectedAnswerForRow[indexPath.row]
}
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
dictAnswer[indexPath.row] = cell.pickerValue
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
cell.identifier = true
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let cell = sender.superview?.superview as! AddFollowTableViewCell
rowIndex = myTable.indexPathForCell(cell)?.row
selectedAnswerForRow[rowIndex] = cell.answerValue
print(selectedAnswerForRow[rowIndex])
cell.answerText.text = sender.text
cell.identifier = true
}
It might have some performance issue need to be optimised , but it shows exactly what i want. LOL
You're basically recycling your views and not clearing them. That's the whole point of -dequeueReusableCellWithIdentifier:indexPath:.
Allocating and deallocating memory is very power consuming, so the system recycles every cell that goes out of viewport bounds.
You don't set the text inside answerText (I assume it's the text field that causes trouble) so its content will be kept when recycled.
Assuming you'll store user selection inside a dictionary var selectedAnswerForRow: [IndexPath:String]:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: "textFieldDidChange:"), forControlEvents: UIControlEvents.EditingDidEnd)
cell.answerText.text = self.selectedAnswerForRow[indexPath] ?? "" // add this
return cell
}
self.selectedAnswerForRow[indexPath] ?? "" returns the result or an empty string if it's not present in the dictionary.
Also, you're adding several times the action for edition control event. You have to check first if it isn't already bound.
Because the cell is reused. So you have to implement prepareForReuse() in your custom cell class and reset all the changing variables
UPDATE
See :
class MyCell : UITableViewCell {
#IBOutlet weak var myTextField : UITextField!
//Add the following
override func prepareForReuse() {
myTextField.text = nil
myTextField.inputView = myPickerView
super.prepareForReuse()
}
}
I have an UITableView populated by a cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("customTableViewCell") as! UITableViewCell
let task = frc.objectAtIndexPath(indexPath) as! Task
cell.textLabel?.text = task.summary
var detail = task.detail
var context = task.context
var due = task.date
var status = task.status
var responsible = task.responsable
var folder = task.folder
cell.detailTextLabel?.text = "Contexte: \(context), Detail: \(detail), Status: \(status), Ending date: \(due)"
return cell
}
On the storyboard, I have made a segue when clicking one cell of the tableView to open a detailViewController
this is my didSelectRowAtIndexPath:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
self.name = cell!.textLabel!.text!
println(self.name)
self.performSegueWithIdentifier("Show Detail", sender: indexPath);
}
and the prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if let identifier = segue.identifier{
switch identifier {
case "Show Detail":
let indexPath = self.tableView.indexPathForSelectedRow()
let editTaskVC = segue.destinationViewController as! EditTaskViewController
editTaskVC.Name = "cell.textLabel?.text is what I would like to.."
default: break
}
}
}
If I do editTaskVC.Name = indexPath?.description I can see the description of the cell clicked like, <NSIndexPath: 0x78f96ab0>... for example.
Is it possible, instead of printing the description of the indexPath, printing the cell.textLabel?.text of the clicked row?
I have seen many, many tutorials or posts on forum but I haven't succeed to solve my problem...
Thank you for your help.
Regards.
Your intention is to pass along the cell.textLabel?.text to the destination view controller right?
You're taking a needless detour. The sender parameter in performSegueWithIdentifier: can take in an AnyObject, so you can go right ahead and pass it the name.
self.performSegueWithIdentifier("Show Detail", sender: name)
That way, prepareForSegue will have the item you need to pass along to the next view controller. Simply assign editTaskVC = sender as! String and you're good to go.
The piece of knowledge you were missing is that, the sender parameter in performSegueWithIdentifier: sender will automatically pass the sender's contents into prepareForSegue, as the sender parameter.
Since you already have the index path, you can simply invoke the table's cellForRowAtIndexPath to obtain the cell:
if let indexPath = self.tableView.indexPathForSelectedRow() {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? UITableViewCell {
let editTaskVC = segue.destinationViewController as! EditTaskViewController
editTaskVC.Name = cell.textLabel?.text
}
}
The indexPathForSelectedRow returns nil in 2 cases only:
if the index is out of range
if the cell is not visible
I have an app that has a custom button in a custom cell. If you select the cell it segues to the a detail view, which is perfect. If I select a button in a cell, the code below prints the cell index into the console.
I need to access the contents of the selected cell (Using the button) and add them to an array or dictionary. I am new to this so struggling to find out how to access the contents of the cell. I tried using didselectrowatindexpath, but I don't know how to force the index to be that of the tag...
So basically, if there are 3 cells with 'Dog', 'Cat', 'Bird' as the cell.repeatLabel.text in each cell and I select the buttons in the rows 1 and 3 (Index 0 and 2), it should add 'Dog' and 'Bird' to the array/dictionary.
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsCollection.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
// Configure the cell...
var currentRepeat = postsCollection[indexPath.row]
cell.repeatLabel?.text = currentRepeat.product
cell.repeatCount?.text = "Repeat: " + String(currentRepeat.currentrepeat) + " of " + String(currentRepeat.totalrepeat)
cell.accessoryType = UITableViewCellAccessoryType.DetailDisclosureButton
cell.checkButton.tag = indexPath.row;
cell.checkButton.addTarget(self, action: Selector("selectItem:"), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func selectItem(sender:UIButton){
println("Selected item in row \(sender.tag)")
}
OPTION 1. Handling it with delegation
The right way of handling events fired from your cell's subviews is to use delegation.
So you can follow the steps:
1. Above your class definition write a protocol with a single instance method inside your custom cell:
protocol CustomCellDelegate {
func cellButtonTapped(cell: CustomCell)
}
2. Inside your class definition declare a delegate variable and call the protocol method on the delegate:
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
3. Conform to the CustomCellDelegate in the class where your table view is:
class ViewController: CustomCellDelegate
4. Set your cell's delegate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell
cell.delegate = self
return cell
}
5. Implement the required method in your view controller class.
EDIT: First define an empty array and then modify it like this:
private var selectedItems = [String]()
func cellButtonTapped(cell: CustomCell) {
let indexPath = self.tableView.indexPathForRowAtPoint(cell.center)!
let selectedItem = items[indexPath.row]
if let selectedItemIndex = find(selectedItems, selectedItem) {
selectedItems.removeAtIndex(selectedItemIndex)
} else {
selectedItems.append(selectedItem)
}
}
where items is an array defined in my view controller:
private let items = ["Dog", "Cat", "Elephant", "Fox", "Ant", "Dolphin", "Donkey", "Horse", "Frog", "Cow", "Goose", "Turtle", "Sheep"]
OPTION 2. Handling it using closures
I've decided to come back and show you another way of handling these type of situations. Using a closure in this case will result in less code and you'll achieve your goal.
1. Declare a closure variable inside your cell class:
var tapped: ((CustomCell) -> Void)?
2. Invoke the closure inside your button handler.
#IBAction func buttonTapped(sender: AnyObject) {
tapped?(self)
}
3. In tableView(_:cellForRowAtIndexPath:) in the containing view controller class :
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
cell.tapped = { [unowned self] (selectedCell) -> Void in
let path = tableView.indexPathForRowAtPoint(selectedCell.center)!
let selectedItem = self.items[path.row]
println("the selected item is \(selectedItem)")
}
Since you have 1 section in the table view you can get the cell object as below.
let indexPath = NSIndexPath(forRow: tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomCell!
where tag you will get from button tag.
Swift 3
I just get solution for access cell in #IBAction function using superview of button tag.
let cell = sender.superview?.superview as! ProductCell
var intQty = Int(cell.txtQty.text!);
intQty = intQty! + 1
let strQty = String(describing: intQty!);
cell.txtQty.text = strQty
#IBAction func buttonTap(sender: UIButton) {
let button = sender as UIButton
let indexPath = self.tableView.indexPathForRowAtPoint(sender.center)!
}
I updated option 1 of the answer from Vasil Garov for Swift 3
1. Create a protocol for your CustomCell:
protocol CustomCellDelegate {
func cellButtonTapped(cell: CustomCell)
}
2. For your TableViewCell declare a delegate variable and call the protocol method on it:
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
3. Conform to the CustomCellDelegate in the class where your tableView is:
class ViewController: CustomCellDelegate
4. Set your cell's delegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as CustomCell
cell.delegate = self
return cell
}
5. Implement the required method in your ViewController.
Based on Cao's answer, here is a solution to handle buttons in a collection view cell.
#IBAction func actionButton(sender: UIButton) {
let point = collectionView.convertPoint(sender.center, fromView: sender.superview)
let indexPath = collectionView.indexPathForItemAtPoint(point)
}
Be aware that the convertPoint() function call will translate the button point coordinates in the collection view space. Without, indexPath will always refer to the same cell number 0
XCODE 8: Important Note
Do not forget to set the tags to a different value than 0.
If you attempt to cast an object with tag = 0 it might work but in some weird cases it doesn't.
The fix is to set the tags to different values.
Hope it helps someone.