I am using a tableView and inside I have UIImage , UILabel and FloatRatingView.
FloatRatingView is a UIView , a star rating where you can rate 1-5.
So the tableView has multiple cells , and I want to get the movie title when I press FloatRatingView.
FloatRatingView has two delegate methods.
func floatRatingView(ratingView: FloatRatingView, didUpdate rating: Float) {
}
func floatRatingView(ratingView: FloatRatingView, isUpdating rating: Float) {
}
So far in my custom TableCell I have :
var delegate: FloatRatingViewDelegate?
#IBOutlet weak var userRating: FloatRatingView!{
didSet {
if userRating.rating>0 {
self.delegate!.floatRatingView(userRating, didUpdate: userRating.rating)
}}
}
and In the TableViewController :
class MoviesViewController: PFQueryTableViewController ,FloatRatingViewDelegate {
func floatRatingView(ratingView: FloatRatingView, isUpdating rating: Float) {
print("is updating")
}
func floatRatingView(ratingView: FloatRatingView, didUpdate rating: Float) {
print("did update")
}
and
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier:String = "cell"
var cell:TableCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? TableCell
if(cell == nil) {
cell = TableCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier)
}
cell?.delegate = self
Presuming that the FloatRatingView is inside the cell, the easiest methods are to either:
Add a delegate protocol to your cell class, implement FloatRatingViewDelegate in the class, and forward the callbacks to the cell delegate. Implement the cell delegate in your view controller.
Expose your cell class's FloatRatingView property, and set its delegate directly to the view controller (which will need to implement FloatRatingViewDelegate). Iterate over the visible cells, and find which one the float rating view is in.
However, a more modern solution would be:
Replace the delegate protocol with function properties (var didUpdate: (Float -> Void)?), set those in the view controller when dequeuing your cell. There, you can directly close over the indexPath reference.
This will result in less code overall.
Related
I have a text field in a tableView. I need to get the position of textfield but the problem is there are multiple section in it. I am able to get only one thing section or row using textfield.tag but I need both.
You can find the parent UIResponder of any class by walking up the UIResponder chain; both UITextField and UITableViewCell inherit from UIView, which inherits from UIResponder, so to get the parent tableViewCell of your textfield you can call this function on your textfield:
extension UIResponder {
func findParentTableViewCell () -> UITableViewCell? {
var parent: UIResponder = self
while let next = parent.next {
if let tableViewCell = parent as? UITableViewCell {
return tableViewCell
}
parent = next
}
return nil
}
}
Then once you have the tableViewCell, you just ask the tableView for its index path with tableView.indexPAth(for:)
You never need to use the tag field:
guard let cell = textField.findParentTableViewCell (),
let indexPath = tableView.indexPath(for: cell) else {
print("This textfield is not in the tableview!")
}
print("The indexPath is \(indexPath)")
You can use a variation of a previous answer that I wrote.
Use a delegate protocol between the cell and the tableview. This allows you to keep the text field delegate in the cell subclass, which enables you to assign the touch text field delegate to the prototype cell in Interface Builder, while still keeping the business logic in the view controller.
It also avoids the potentially fragile approach of navigating the view hierarchy or the use of the tag property, which has issues when cells indexes change (as a result of insertion, deletion or reordering), and which doesn't work where you need to know a section number as well as a row number, as is the case here.
CellSubclass.swift
protocol CellSubclassDelegate: class {
func textFieldUpdatedInCell(_ cell: CellSubclass)
}
class CellSubclass: UITableViewCell {
#IBOutlet var someTextField: UITextField!
var delegate: CellSubclassDelegate?
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
self.delegate?.textFieldUpdatedInCell(self)
return yes
}
ViewController.swift
class MyViewController: UIViewController, CellSubclassDelegate {
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass
cell.delegate = self
// Other cell setup
}
// MARK: CellSubclassDelegate
func textFieldUpdatedInCell(_ cell: CellSubclass) {
guard let indexPath = self.tableView.indexPathForCell(cell) else {
// Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
return
}
// Do whatever you need to do with the indexPath
print("Text field updated on row \(indexPath.row) of section \(indexPath.section")
}
}
You can also see Jacob King's answer using a closure rather than a delegate pattern in the same question.
In my iOS Switch app I have a BEMCheckBox in each table cell. When dequeuing a cell I want to set a delegate that gets called.
My problem is that the checkbox works fine but the delegate is not never called. How to add a delegate to each checkbox?
I want to know which indexPath for checkbox. The plan is to pass model object to the delegate and update it accordingly.
Table cell
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
cell.doneCheckbox.delegate = DoneBEMCheckBoxDelegate()
return cell
Delegate is very simple
class DoneBEMCheckBoxDelegate: NSObject, BEMCheckBoxDelegate {
#objc func didTap(_ checkBox: BEMCheckBox) {
print("Checkbox tapped")
}
}
cell.doneCheckbox.delegate = DoneBEMCheckBoxDelegate() is creating a new DoneBEMCheckBoxDelegate object in a local variable and assigning that as the delegate. Since the delegate property is weak, it will be released as soon as the function exits because there is no strong reference remaining.
I would suggest that having a separate object class to be the delegate probably isn't what you want anyway.
I would set the cell to be the check box delegate and then declare another protocol so that the cell can have its own delegate, which would be your table view controller.
protocol MyCellDelegate {
func checkBox(for cell: MyCell, isOn: Bool)
}
class MyCell: UITableViewCell, DoneBEMCheckBoxDelegate {
var delegate: MyCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
self.doneCheckBox.delegate = self
}
#objc func didTap(_ checkBox: BEMCheckBox) {
print("Checkbox tapped")
self.delegate?.checkBox(for: self, isOn: checkBox.isOn)
}
}
class YourViewController: MyCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
return cell
}
func checkBox(for cell: MyCell, isOn: Bool) {
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
// Now do whatever you need to with indexPath
}
}
This way you avoid creating additional objects and datastructures and you won't have a problem if cells are re-ordered as there is no dependency on index path.
I noticed that delegate is a weak reference in checkbox class, as it is supposed to be :) So my delegate was freed after method scope ended.
I fixed this by storing delegates in view controller during their usage.
var checkboxDelegates: [IndexPath:DoneBEMCheckBoxDelegate] = [:]
...
let checkboxDelegate = DoneBEMCheckBoxDelegate(realm: realm, set: set)
checkboxDelegates[indexPath] = checkboxDelegate
cell.doneCheckbox.delegate = checkboxDelegate
I have two UITextFields on the UITableViewCell and their IBOutlets are connected in the custom UITableViewCell class called as "CustomCell.swift".
The Enter button is there on the UIView of ViewController and its IBAction is there in the UIViewController class called as "ViewController".
On click of the Enter button I want to see if the two textFields are empty. How do I do it? Please help
create a Bool variable in your class where you have the button action
var isTextFieldTextEmpty: Bool!
then in your table view dataSource method cellForRowAtIndexPath add
if myCell.myTextField.text?.isEmpty == true {
self.isTextFieldTextEmpty = true
} else {
self.isTextFieldTextEmpty = false
}
then in the IBAction of your (Enter) button add
self.myTableView.reloadData()
self.myTableView.layoutIfNeeded()
print(self.isTextFieldTextEmpty)
if all text fields in all cells of the table view have text, it will print false, else if only one text fields among all the text fields has no text, it will print true
Here is a simple solution. It will work for any number of cells.
What you need to do is iterate through the cells and figure out if the textField that particular cell is holding is empty or not. Now the question is how will you iterate through the cells, is there any delegate for that? The answer is No.
You have to manually construct the indexPaths to get the cells from the Table.
Here is a simple walk through. Your set up is quite right. You should have a tableview in your ViewController. So, the IBOutlet of the tableview should be there. I named my TableView "myTableView". And the textField's Outlet should be inside the TableViewCell which is also right. At the end the action method for the Enter button should be in the view controller.
Make sure, you properly connect all the outlets.
Here is the sample custom TableViewCell -
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var internalTextField : UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And now just go to the ViewController.swift-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var myTableView : UITableView!
var numberOfCells = 2 //you can update it to be any number
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.dataSource! = self //assign the delegate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomTableViewCell
return cell;
}
#IBAction func pressedEnter(){
var row = 0
while row < numberOfCells { //iterate through the tableview's cells
let indexPath : NSIndexPath = NSIndexPath(forRow: row, inSection: 0) //Create the indexpath to get the cell
let cell : CustomTableViewCell = self.myTableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
if cell.internalTextField.text!.isEmpty{
print("TextField is Cell \(row) is Empty")
}
else{
print("TextField is Cell \(row) is NOT Empty")
}
row += 1
}
}
}
There are comments which explains everything. Hope this helps.
I'm not very experienced in Swift. I have a tableView and custom cells in it, where are several labels, UISlider and UISwitch.
When i change slider values and hit submit(bar button item), I want to collect UISlider and UISwitch values from all cells.
What i tried:
1.Tags: I reached some cells, but stopped and could not reach currently invisible cells, and finally read some opinions, that tags are unlikely to use.
Question: Are there any clear pro et contra?
2.CellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
return cell
}
#IBAction func submitTapped(sender: AnyObject) {
let cell = tableView(self.tableView , cellForRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) as! CustomTableViewCell
print(cell.Label1.text) // gives me
print(cell.Label2.text) // values
print(cell.customSlider.value) // gives me the value stated as
print(cell.customSwitch.on) // default
}
Do i understand correctly, that i call cellForRowAtIndexPath here and no wonder i get new instance of Custom Cell (processed by function)?
3."Wag the dog"
Unfortunately i've lost a SO link to discussion of this solution :(
I tried to reach UIViewController using .superview.superview..., but Xcode refused to eat 4 superviews (and i was not sure that i found correct number of .superviews).
Main idea is to give access to UIViewController property in Custom Cell:
add a property in CustomTableViewCell:
class CustomTableViewCell: UITableViewCell {
var viewController : MyViewController?
var cellNo = 0
//and so on
}
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var sliderValues: [Float] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
self.sliderValues.append (0.0) // just to be sure that each slider can put it's value to Array
cell.cellNo = indexPath.row // so the Custom Cell knows it's No
//---------------------//
cell.viewController = self
//---------------------//
return cell
}
}
//----------------------
//and back to Custom Cell
#IBAction func sliderValueChanged(sender: AnyObject) {
self.viewController?.sliderValues[self.cellNo] = self.customSlider.value
}
// and the same way with UISwitch
Good News, This works!
Question: are there any methods not to "wag the dog" and reach Custom Cell from UIViewController?
Looking at your solution I think you will have an issue because the cell is holding onto a reference to your viewController, so it will lead to memory leaks. You should use "weak var viewController : MyViewController?" if you were going to go down this route
However, as matt has said, you should not do this. It is better to update your model with this data. You might be able to pass the data directly to the cell to modify the data, but I do not know the format of your data, so another idea is you could create a delegate to pass the values back from the cell. An example is:
protocol CustomTableViewCellDelegate: class {
func didChangeSlider(value: Float, cellNo: Int)
//func didSwitchOn(value: Bool, cellNo: Int)
}
You would then add this to your cell, like this:
class CustomTableViewCell: UITableViewCell {
weak var delegate: CustomTableViewCellDelegate?
var cellNo = 0
//and so on
}
Then use the delegate here:
#IBAction func sliderValueChanged(sender: AnyObject) {
self.delegate?.didChangeSlider(self.customSlider.value, cellNo)
}
Finally in your ViewController when you create the cell, you need to do this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
cell.delegate = self
cell.cellNo = indexPath.row
return cell
}
Add at the end of your ViewController, add the delegate handler:
extension MyViewController: CustomTableViewCellDelegate {
func didChangeSlider(value: Float, cellNo: Int) {
//Save your value here
}
}
I have a CustomTableViewCell where there is an image. This image is associated with UITapGestureRecognizer.
When i click in this image i need pass the object, based on the cell and indexPath, to the selector, like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellTotal") as TotalTableViewCell
let schedulle = schedulleList[indexPath.row] as Schedulle
let tap = UITapGestureRecognizer(target: self, action: "edit:")
tap.numberOfTapsRequired = 1
tap.numberOfTouchesRequired = 1
//simulation code - here i need pass the object clicked
tap.AssociatedObject = schedulle
cell.imgSchedulleEdit.addGestureRecognizer(tap)
return cell
}
func edit(sender: UITapGestureRecognizer) {
//simulation code getAssociated Object
let schedulle = sender.getAssociatedObject as Schedulle
}
I know, this is not good, generally i use didSelectRow, but this app needs a click on specific image of cell.
Schedulle have attributes:
import Foundation
import CoreData
class Schedulle: NSManagedObject {
#NSManaged var createdDate: NSDate
#NSManaged var message: String
}
Instead of adding the gesture recognizer in the dequeue cell method, add it in the TotalTableViewCell subclass with the cell as the target, then provide a delegate callback to your view controller, along the lines of totalTableViewCellDidTapImage(cell: TotalTableViewCell)
Now in your view controller you can get the index path for this row by using
func totalTableViewCellDidTapImage(cell: TotalTableViewCell) {
let indexPath = myTableView.indexPathForCell(cell)
let schedulle = schedulleList[indexPath.row] as Schedulle
// do stuff with schedulle
}
Instead of using a UIImageView, use UIButton, and use the button's method setImage(_:forState:) to set your image. Then you can you activate the event using addTarget, which will pass an instance of the button to the action.
According #someGuy and #backsquare, i'll post the complete answer:
I changed for button instead of button.
1 - I defined the protocol, in custom table view cell:
protocol SchedulleEdit : NSObjectProtocol{
func totalTableViewCellEdit(cell: TotalTableViewCell)
}
2 - i created the var, in custom table view cell to receive the delegate
protocol SchedulleEdit : NSObjectProtocol{
func totalTableViewCellEdit(cell: TotalTableViewCell)
}
class TotalTableViewCell: UITableViewCell {
var delegate : SchedulleEdit? = nil
...
}
3 - i defined the function to capture selected cell and send it to the view controller that implement our protocol.
protocol SchedulleEdit : NSObjectProtocol{
func totalTableViewCellEdit(cell: TotalTableViewCell)
}
class TotalTableViewCell: UITableViewCell {
var delegate : SchedulleEdit? = nil
...
}
#IBAction func edit(sender: UIButton) {
self.delegate?.totalTableViewCellEdit(self)
}
}
4 - In ViewController or in TableViewController, depends your case, i implemented the protocol SchedulleEdit:
class TotalViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, SchedulleEdit{
...
}
5 - In cellForRowAtIndexPath i set the delegate for:
class TotalViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, SchedulleEdit{
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellTotal") as TotalTableViewCell
cell.delegate = self
...
}
}
6 - Finally i implemented the required method of our protocol:
class TotalViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, SchedulleEdit{
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellTotal") as TotalTableViewCell
cell.delegate = self
...
}
func totalTableViewCellEdit(cell: TotalTableViewCell) {
let indexPath = self.tblTotal.indexPathForCell(cell)
let row = indexPath?.row
let schedulle = self.schedulleList[row!] as Schedulle
println(schedulle.message)
}
}