3

I was wondering if there is any problem with extending UIButton in order to have a ref of my Data?

For instance I have a table view and whenever user clicks on a cell I need to provide the user with the data for that specific cell.

Data is kept inside an array and array index was saved in UIButton tag but as my array gets updated, wrong indexes will be provided. So what i was trying to do was to extend a uibutton and then have a variable which holds my model.

This idea works fine but as Im not really experienced in swift I wanted to know what are the drawbacks and problems of doing such a thing.

Siyavash
  • 970
  • 9
  • 23
  • 1
    This idea is really bad. You should not subclass an UIButton first and foremost, it is against the UIKit guidelines, second, you can keep reference to your information in many other different ways. Remember you can have multiple sources of data, and you do not need to provide one and the same array for both your datasource and your references to your cells. Also, your UIButtons should get reused in the cell, they should not be tagged to hold any information. You should keep actual data outside of UIKit. https://stackoverflow.com/questions/13202161/why-shouldnt-i-subclass-a-uibutton –  Sep 04 '17 at 19:19
  • 1
    what do u mean by "but as my array gets updated, wrong indexes will be provided?" – Sandeep Bhandari Sep 04 '17 at 19:19
  • @Sneak Thank you for the comment. I will try to look into an alternative – Siyavash Sep 04 '17 at 19:32
  • @SandeepBhandari Basically all the button tags are messed up. If i click on the 9th cell, it returns button.tag as 0 and if i click on the first cell it returns 0 – Siyavash Sep 04 '17 at 19:33
  • @Siyavash You can use the **index** of the array as the indexPath.row . Or use an NSDictionary. –  Sep 04 '17 at 19:33
  • I think NSDictionary is a good idea – Siyavash Sep 04 '17 at 19:40
  • @siyavash: With all due respect NSDictionary inherits from NSObject which means NSDictionary is a instance of class that means when you pass the instance of your NSDictionary it is passed by reference n not pass by value (shallow copy n not deep copy) Swift isn't a object oriented programming language its a protocol oriented programming language and fundamentally swift prefers Pass by value types than pass by references :) Hence consider creating model as struct or Swift dictionary NSDictionary contradicts the principle of POP I believe – Sandeep Bhandari Sep 04 '17 at 19:46

1 Answers1

1

You don't need to save the index as Button's tag. Subclassing the UIButton as Sneak pointed out in comment is clearly a very bad idea. On top of that saving your model in a UIComponent is disasters design.

You can do it multiple ways. One that I find neat :

Step 1:

Create a Class for your Custom Cell. Lets say MyCollectionViewCell. Because your Cell has a button inside it, you should create IBAction of button inside the Cell.

class MyCollectionViewCell: UICollectionViewCell {


    @IBAction func myButtonTapped(_ sender: Any) {

    }

}

Step 2:

Lets declare a protocol that we will use to communicate with tableView/CollectionView's dataSource.

protocol MyCollectionViewCellProtocol : NSObjectProtocol {
    func buttonTapped(for cell : MyCollectionViewCell)
}

Step 3:

Lets create a property in our MyCollectionViewCell

weak var delegate : MyCollectionViewCellProtocol? = nil

After step 3 your MyCollectionViewCell class should look like

protocol MyCollectionViewCellProtocol : NSObjectProtocol {
    func buttonTapped(for cell : MyCollectionViewCell)
}

class MyCollectionViewCell: UICollectionViewCell {

    weak var delegate : MyCollectionViewCellProtocol? = nil

    @IBAction func myButtonTapped(_ sender: Any) {
        self.delegate?.buttonTapped(for: self)
    }

}

Step 4:

In your tableView's CellForRowAtIndexPath or CollectionView's sizeForItemAtIndexPath confirm pass ViewController as delegate to cell.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell : MyCollectionViewCell = //deque your cell
    (cell as! MyCollectionViewCell).delegate = self
}

Step 5:

Now make your ViewController confirm to protocol

class YourViewController: UIViewController,MyCollectionViewCellProtocol {

Step 6:

Finally implement method in your VC

func buttonTapped(for cell: MyCollectionViewCell) {
    let indexPath = self.collectionView?.indexPath(for: cell)
    //access array now
}

P.S:

Sorry though I know you are using TableView, in a hurry I have written code for CollectionView but the delegates are pretty same :) I hope you will be able to understand the logic

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • Thank you so much. This is what I have been looking for. However I am using the button to perform a segue and then prepare a segue. Would that make any difference in the structure of the code? – Siyavash Sep 04 '17 at 19:40
  • @siyavash : No, Dont drag segue directly from Button rather drag segue from ViewController to ViewController and in buttonTapped function finally once you access the actual object in array call self.performSegue(with identifier) :) If there is any doubt lemme know :D – Sandeep Bhandari Sep 04 '17 at 19:42
  • Awesome. Right now Im using this line of code to perform the segue. I haven't done any type of protocol or delegates like you said. " cell.cancellationButton.addTarget(self, action: #selector(appointmentCancellation), for: .touchUpInside) " and then I access the button tag via sender parameter. Is there anything wrong with this method ? – Siyavash Sep 04 '17 at 19:52
  • @siyavash : Problem with this method is you are setting the method appointmentCancellation to be called on tapping cancellationButton on your VC (I believe self is a VC here) but then all end up in same problem of finding which object from array to use, which cell was tapped ?? what was its index ??? So use my solution in buttonTapped method once you get the actual object from array call performSegue with identifier :) remove your code cell.cancellationButton.addTarget :) – Sandeep Bhandari Sep 04 '17 at 19:55
  • call perform Sgue from your VC not from Cell :) No need to call CancellationButtons target to perform segue :) You can call self.perfromSegue(with identifier) in your VC as well :) effect will be same – Sandeep Bhandari Sep 04 '17 at 19:57
  • 1
    Thank you very much :D . I will try your method now – Siyavash Sep 04 '17 at 20:00
  • I also just realised that my method was working perfectly fine but I was using the wrong array to get the data :D – Siyavash Sep 04 '17 at 20:02
  • 1
    @siyavash : Hahahahahaha :) There are multiple ways of achieving it select the one thats cleaner in design and architectural perspective :) Saving tag in button isn't a great approach but works fine, saving data in button definitely not a good option :D Think and then select :) Happy coding buddy :) – Sandeep Bhandari Sep 04 '17 at 20:04