Using protocol and delegation in SWIFT - swift

I am trying to pass information over from a collection view cell to another view controller, then dismiss the view controller. I am trying to do this with delegation as I think this is the only way, so far I have set up my protocol like so:
#objc protocol CollectionViewImageDelegate {
optional func selectedCell(row: NSIndexPath, data: UIImage)
}
I have added the property:
var delegate : CollectionViewImageDelegate?
and then called it in the didSelectItemAtIndexPath
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let imageData = self.sources[indexPath.row]
self.delegate?.selectedCell?(indexPath, data: imageData)
self.dismissViewControllerAnimated(true, completion: nil)
}
Then how do I call it other View Controller so far I have done:
class ProfileViewController: UIViewController, CollectionViewImageDelegate {
var colViewImage : CollectionViewVC
in the view did load:
colViewImage.delegate = self
and this but nothing happens:
func selectedCell(row: NSIndexPath, data: UIImage) {
println(data) //Nothing prints
}
Nothing is printing I don't know why?
I'm new to protocol and delegates etc and can't seem to work out why it isnt passing over the image??
Thanks

Your CollectionViewController is calling this selectedCell method but you haven't linked the delegate yet.
Make your other ViewController conform to collectionViewImageDelegate (which should conventionally be spelled with a capital C) and implement your method selectedCell in it.
Set the ViewController as delegate of the CollectionViewController.
Also, change self.delegate?.selectedCell!(indexPath, data: imageData) into self.delegate?.selectedCell?(indexPath, data: imageData) to call the optional method selectedCell only if it was implemented by the delegate.

Related

How to dismiss UIViewController from UITableViewCell

I need to dismiss ViewController from the corresponding TableViewCell, but I'm getting an error message, "Value of type 'TableViewCell' has no member 'dismiss'"
self.dismiss(animated: true, completion: nil)
How can I dismiss the ViewController from the corresponding TableViewCell
If you just want the solution and don't really care about the structure of the application, the following will work. As the other guy mentioned, this probably isn't the best way to structure your application.
Make a delegate to the TableViewCell.
protocol TableViewDismissDelegate {
func dismissViewController()
}
class YourTableViewClass {
var delegate: TableViewDismissDelegate?
...
}
In your table view delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = mainTableView.dequeueReusableCell(withIdentifier: YourTableViewClass.identifier, for: indexPath) as! YourTableViewClass
cell.delegate = self
return cell
}
Make sure your view controller conforms to your protocol:
extension YourViewController: TableViewDismissDelegate {
func dismissViewController() {
self.dismiss(animated: true, completion: nil)
}
}
You cannot say self.dismiss in a UITableViewCell, as dismiss is a UIViewController command, and a UITableViewCell is not a UIViewController.
What I like to do in this situation is get a reference to the UIViewController so that I can tell it to dismiss. To do so, I create a UIResponder extension, like this:
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
That extension just walks up the responder chain looking for an instance of any class we care to name. So now self.next(ofType: UIViewController.self) is the view controller, and we can tell it to dismiss.
(There are plenty of other solutions, but that's just a solution that I happen to like.)
It may be argued, however, that you should never have gotten yourself in this situation in the first place. It is no business of a UITableViewCell to be telling anyone to dismiss anything. This is a violation of model-view-controller principles. You should probably be looking at a completely different architecture here.

Passing only index rather than UICollectionViewCell in delegate

I have the custom CollectionViewCell and when a user clicks on a button which is located in the collectionviewcell then delegate method is getting called. It works, but I am passing UICollectionViewCell as a param. However, it is not efficient.
I would like to pass only selected customviewcell's index rather than entire Cell Object. How would you achieve that?
protocol CustomCollectionViewCellDelegate: AnyObject {
func greenButtonTapped(cell: UICollectionViewCell)
}
class CustomCollectionViewCell : UICollectionViewCell {
weak var delegate: CustomCollectionViewCellDelegate?
#IBAction func greenButtonTapped(_ sender: Any) {
delegate?.greenButtonTapped(cell: self)
}
}
or something like that as a closure ?
var onTappingGreenButton: (UIButton, IndexPath) -> ()

removing the duplicating code code from cell delegate

I have a tableview, to configure a cell (from VC),
cell.model = dataSource[indexpath.row]
In the didSet of cell.model, I am initialising the cell contents.
Cell has 3 buttons, tapping on which, I am informing the VC through CellDelegate
protocol CellDelegate {
func didTapButton1(model: Model)
func didTapButton2(model: Model)
func didTapButton3(model: Model)
}
My concern:-
I don't want to pass the model here (as it is already associated with the Cell - somehow need to fetch the model from cell)
I would like to call didTapButton() without the parameter. Then in the VC,
extension VC: CellDelegate {
//I need to fetch the model associated with the cell.
func didTapButton1() { }
func didTapButton2() { }
func didTapButton3() { }
}
I could achieve this using closure, but it is not preferred here.
Any help would be appreciated.*
I'm guessing the reason why you don't want to pass the model is because having a model in all three methods look like code duplication. Well, if you have look at the delegates in the framework, such as UITableViewDelegate, UITextFieldDelegate, most, if not all, of them accept the thing that they are a delegate of as the first parameter. All the methods in UITableViewDelegate has a tableView parameter. Therefore, it would be OK for you to follow the pattern as well:
protocol CellDelegate {
func didTapButton1(_ cell: Cell)
func didTapButton2(_ cell: Cell)
func didTapButton3(_ cell: Cell)
}
Personally, I would write only one method in this delegate:
protocol CellDelegate {
func didTapButton(_ cell: Cell, buttonNumber: Int)
}
In the VC extension, you simply check buttonNumber to see which button is pressed:
switch buttonNumber {
case 1: button1Tapped()
case 2: button2Tapped()
case 3: button3Tapped()
default: fatalError()
}
// ...
func button1Tapped() { ... }
func button2Tapped() { ... }
func button3Tapped() { ... }

Swift add button to UICollectionViewCell that opens new controller

I have a UICollectionViewCell header on a UICollectionViewController, and I've added a button to it. I would like for the button, when clicked, to push a new view controller atop the current one. The problem is that the button doesn't have access to the navigation controller of the UICollectionViewController, so I there's no way to directly push a controller from, say, a connector to the buttn (that I know of). Is there any way to achieve this? Maybe something can be overriden, such as a collectionView function. Thanks!
If you just want to process the cell selection there is a handy method in the UICollectionViewDelegate that you can implement to get the index path of the pressed cell.
If your goal is to have a custom button inside the cell (or maybe even several) you can use delegation pattern to retrieve user actions to your controller to than process in any way, including pushing/presenting new controllers. Assign the controller's instance (the one managing the collection view) to the delegate member of your cell.
Define a protocol that I would call something like MyCustomCellDelegate (replace MyCustomCell with a more appropriate name for your case). Something like MyCustomCellDelegate: class { func didPressButtonX() }
Declare an optional delegate property in your cell subclass. weak var delegate: MyCustomCellDelegate?
Implement your delegate protocol by the class you want to respond to button presses (or any other interactions defined by your protocol).
Every time you create/dequeue a cell for your UICollectionView to use you set the delegate property to the view controller managing the collection view. cell.delegate = self (if done inside the view controller itself).
After receiving the UI event inside your custom cell use your delegate property to retrieve the action to the controller (or with ever object you used when assigning the property). Something like: delegate?.didPressButtonX()
In your class that implements MyCustomCellDelegate use the method to push the new controller.
Below I will provide sample code that should give more details on the implementation of the proposed solution:
// In your UICollectionViewCell subclass file
protocol MyCustomCellDelegate: class {
func didPressButtonX()
func didPressButtonY()
}
MyCustomCell: UICollectionViewCell {
weak var delegate: MyCustomCellDelegate?
#IBOutlet var buttonX: UIButton!
#IBOutlet var buttonY: UIButton!
#IBAction func didPressButtonX(sender: Any) {
delegate?.didPressButtonX()
}
#IBAction func didPressButtonY(sender: Any) {
delegate?.didPressButtonY()
}
}
// Now in your UICollectionViewController subclass file
MyCustomCollectionViewController: UICollectionViewController {
// ...
override func collectionView(UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier identifier: "YourCellIdentifierGoesHere", for indexPath: indexPath) as! MyCustomCell
// In here we assign the delegate member of the cell to make sure once
// an UI event occurs the cell will call methods implemented by our controller
cell.delegate = self
// further cell setup if needed ...
return cell
}
}
// In order for the instance of our controller to be used as cell's delegate
// we implement the protocol that we defined earlier in the cell file
extension MyCustomCollectionViewController: MyCustomCellDelegate {
func didPressButtonX() {
print("X button was pressed")
// now lets finally push some new controller
let yourNextCoolViewController = UIViewController()
self.push(yourNextCoolViewController, animated: true)
// OR if you are using segues
self.performSegue(withIdentifier: "YourSegueIdentifierGoesHere", sender: self)
}
func didPressButtonY() {
print("Y button was pressed")
}
}

How do you access a UIViewController function from within a UICollectionCell?

I have a function within a UICollectionViewCell that requires access to the
hosting UIViewController. Currently 'makeContribution()' can't be accessed:
What is the proper way of accessing the host UIViewController that has the desired function?
Thanks to the insightful responses, here's the solution via delegation:
...
...
...
{makeContribution}
This is a mildly controversial question - the answer depends a little on your philosophy about MVC. Three (of possibly many) options would be:
Move the #IBAction to the view controller. Problem solved, but it might not be possible in your case.
Create a delegate. This would allow the coupling to be loose - you could create a ContributionDelegate protocol with the makeContribution() method, make your view controller conform to it, and then assign the view controller as a weak var contributionDelegate: ContributionDelegate? in your cell class. Then you just call:
contributionDelegate?.makeContribution()
Run up the NSResponder chain. This answer has a Swift extension on UIView that finds the first parent view controller, so you could use that:
extension UIView {
func parentViewController() -> UIViewController? {
var parentResponder: UIResponder? = self
while true {
if parentResponder == nil {
return nil
}
parentResponder = parentResponder!.nextResponder()
if parentResponder is UIViewController {
return (parentResponder as UIViewController)
}
}
}
}
// in your code:
if let parentVC = parentViewController() as? MyViewController {
parentVC.makeContribution()
}
Well, CollectionView or TableView?
Anyway, Set your ViewController as a delegate of the cell. like this:
#objc protocol ContributeCellDelegate {
func contributeCellWantsMakeContribution(cell:ContributeCell)
}
class ContributeCell: UICollectionViewCell {
// ...
weak var delegate:ContributeCellDelegate?
#IBAction func contributeAction(sender:UISegmentedControl) {
let isContribute = (sender.selectedSegmentIndex == 1)
if isContribute {
self.delegate?.contributeCellWantsMakeContribution(self)
}
else {
}
}
}
class ViewController: UIViewController, UICollectionViewDataSource, ContributeCellDelegate {
// ...
func collectionView(_ collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
cell = ...
if cell = cell as? ContributeTableViewCell {
cell.delegate = self
}
return cell
}
// MARK: ContributeCellDelegate methods
func contributeCellWantsMakeContribution(cell:ContributeCell) {
// do your work.
}
}