I have a fairly big application which has a lot of collection views. Most of the collection view have same implementations for Data Source and the Flow Layout Delegate (same sizes, margins etc). I am trying to create a single protocol which provides the default implementations of UICollectionViewDataSource and UICollectionViewDelegateFlowLayout. Here is my code.
protocol TiledCollectionView{}
extension UICollectionViewDataSource where Self: TiledCollectionView{
//default implementation of the 3 methods to load the data ...
}
extension UICollectionViewDelegateFlowLayout where Self: TiledCollectionView {
//default implementation for layout methods to set default margins etc...
}
class MyViewController: UIViewController, TiledCollectionView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
// the rest of the required logic for view controller
// here I Don't implement any CollectionView methods since I have provided the default implementation already
}
The problem is that, the compiler complains that MyViewController does not conform to UICollectionViewDataSource. This should not be the case because I am clearly saying that add the default implementations if the type is TiledCollectionView.
Can some one help?
I know it's not exactly what you asked, I tried - it didn't work. Now looking for possible answer, because had similiar situation. But I can offer you such on option how to hide in your custom protocol all the logic for delegate/dataSource implementation.
class CollectionViewProtocolHandler: NSObject, UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return UICollectionViewCell() // only for test
}
}
protocol CollectionViewProtocol {
var handler: CollectionViewProtocolHandler! {get set}
mutating func useProtocolForCollectionView(collectionView: UICollectionView)
}
extension CollectionViewProtocol {
mutating func useProtocolForCollectionView(collectionView: UICollectionView) {
handler = CollectionViewProtocolHandler()
collectionView.delegate = handler
collectionView.dataSource = handler
}
}
class ViewController: UIViewController, CollectionViewProtocol {
var handler: CollectionViewProtocolHandler! // CollectionViewProtocol convenience
override func viewDidLoad() {
super.viewDidLoad()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.backgroundColor = .redColor()
view.addSubview(collectionView)
var reference = self
reference.useProtocolForCollectionView(collectionView) // for initialize protocol
}
}
I expect the problem is that this is an Objective-C protocol. Objective-C has never heard of a protocol extension. Therefore it has no knowledge that this protocol extension is injecting two functions into MyClass. It can't see them, and so as far as it is concerned, the protocol requirements are not satisfied.
To add to, but modify, what katleta3000 answered with, you can restrict a protocol to only apply to a 'class'
CollectionViewProtocol : class
so that you don't need 'useProtocolForCollectionView:' to be mutating
Which then makes it so you don't need that var reference = self and you can just say self.userProtocolForCollectionView(collectionView)
Especially if you only plan on implementing this protocol only with NSObject's or class types (UIViewController, UICollectionView, etc.)
Related
I made an extension for the NSCollectionViewDelegate, in wich I declared two new functions for handling clicking on NSCollectionViewItems. I call the delegate methods from a custom NSCollectionViewItems subclass and set the collectionView's delegate to self in my ViewController. However the new functions are called in the NSCollectionViewDelegate, but not in my ViewController.
My NSCollectionViewDelegate extension:
extension NSCollectionViewDelegate {
func collectionView(_ collectionView: NSCollectionView, didDoubleClickOnItem item: Int) {
print("didDoubleClickOnItem delegate")
}
func collectionView(_ collectionView: NSCollectionView, didRightClickOnItem item: Int) {
print("didRightClickOnItem delegate")
}
}
These functions are called in my NSCollectionViewItem subclass with:
self.collectionView.delegate?.collectionView(self.collectionView, didDoubleClickOnItem: itemIndex)
But the implemented function in my ViewController
func collectionView(_ collectionView: NSCollectionView, didDoubleClickOnItem item: Int) {
print("didDoubleClickOnItem")
}
doesn't get called.
What am I doing wrong?
Thanks in advance,
Fabian
Like #AMAN77 said, you have extended the basic delegate functionalities of NSCollectionViewDelegate with some new (and already implemented) functionality, so the compiler sees no harm in that.
There's no rule that forces you to use only one delegate. If you would like your ViewController to be called, as delegate on behalf of NSCollectionViewItems, do the following.
Create a protocol that serves your needs; what would someone tell your delegate?
Probably the following:
protocol NSCollectionViewClickHandler {
func collectionView(_ collectionView: NSCollectionView, didDoubleClickOnItem item: Int)
func collectionView(_ collectionView: NSCollectionView, didRightClickOnItem item: Int)
}
So, since you want ViewController to act as a delegate, it should implement these functions. (You already did that for at least didDoubleClickOnItem)
imho It looks neat if you would do that in an extension of your ViewController
class ViewController {
// All the regular stuff goes in here
// …
override func viewDidLoad() {
super.viewDidLoad()
// set self as (NSCollectionViewClickHandler) delegate of some object
}
}
extension ViewController: NSCollectionViewDelegate {
// implement those function of NSCollectionViewDelegate that you'd like to use,
// and the ones that are required.
}
extension ViewController: NSCollectionViewClickHandler {
func collectionView(_ collectionView: NSCollectionView, didDoubleClickOnItem item: Int) {
print("didDoubleClickOnItem")
}
func collectionView(_ collectionView: NSCollectionView, didRightClickOnItem item: Int) {
print("didDoubleClickOnItem")
}
// If you need other methods to properly implement your delegate methods,
// you can group them in this extension as well: they logically belong together.
// …
}
class YourSubclassOfNSCollectionViewItems: NSCollectionViewItems {
var clickDelegate: NSCollectionViewClickHandler?
func someFunction() {
// does something, and gets the itemIndex
clickDelegate?.collectionView(self.collectionView, didDoubleClickOnItem: itemIndex)
}
}
I hope you now have a better understanding of how to use proto-…uh I mean delegates ;-) Good luck! (Bonus tip: here is a quick-and-easy intro to learn more about delegates and other design patterns)
You are not implementing the function in the viewcontroller. That is being treated as a separate function that is never called. Right now the extension is the only part being called. You could try override the function but it might be better to use an observer from the extension or directly the view (pass in the item number while you are at it) in the long run.
NotificationCenter.default.addObserver(self, selector: #selector(self.handleDoubleClick), name: ObserverNotifNames.loginError, object: nil)
NotificationCenter.default.post(name: "didDoubleClickOnItem", object:nil, userInfo:["item":item])
func handleDoubleClick(_ notification:Notification){
let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
let item = userInfo["item"]
//Do something with the item
}
I have multiple view controllers which shows same kind of cells. I want to set delegate in a protocol extension like this:
class ProductsViewController: UIViewController, ProductShowcase {
//other properties
#IBOutlet weak var productCollectionView: UICollectionView!
var dataSource: DataSource!
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
setupCollectionView()
}
func didSelectProduct(product: Product) {
print(product)
}
//other functions
}
protocol ProductShowcase: UICollectionViewDelegate {
var dataSource: DataSource! { get set }
var productCollectionView: UICollectionView! { get }
func didSelectProduct(product: Product)
}
extension ProductShowcase {
func setupCollectionView() {
productCollectionView.registerClass(ProductCollectionViewCell.self, forCellWithReuseIdentifier: "productCell")
productCollectionView.dataSource = dataSource
print(self) //prints ProductsViewController
productCollectionView.delegate = self //
print(productCollectionView.delegate) //prints optional ProductsViewController
}
}
extension ProductShowcase {
//this delegate method is not called
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
didSelectProduct(dataSource.dataObjects[indexPath.row])
}
}
When didSelectItemAtIndexPath is implemented in ProductsViewController it gets called. Is there something I missed or is this a wrong approach?
It is a Objective-C interoperability limitation. You are not allowed to implement protocols with optionals function in protocol extension like you wanted (protocols which are from Objective-C type UIKit control's delegates and datasources, etc.). You can have default implementation of only protocol that are written like:
// No, #objc in the front of protocol. (i.e. objc-type protocol)
protocol X {
}
I started working on this question app.
I began by tableView of the categories:
For data exchange, I decided to use a protocol:
protocol Category {
func data(object:AnyObject)
}
In the first ViewController has the following code:
class ViewController: UIViewController {
var items:[String] = ["Desktop","Tablet","Phone"]
let CategoriesData:Category? = nil
override func viewDidLoad() {
super.viewDidLoad()
CategoriesData?.data(items)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In the second ViewController (tableView in Container) have the following code:
class CategoriesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, Category {
#IBOutlet var table: UITableView!
var items:[String] = []
func data(object: AnyObject) {
self.items = (object as? [String])!
print(object)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.table.dequeueReusableCellWithIdentifier("SegueStage") as! TableViewCell
cell.nameLabel.text = items[indexPath.row]
return cell
}
}
For me, apparently it's all right. But nothing appeared on the simulator.
My question is: If the Container use to present another viewController as passing data by protocols should be done?
EDITED
I answered why the TO:s solution didn't work as intended, but I just realised that I haven't given a viable answer to how to use protocols as delegates for the ViewController -> ViewController communication. I'll leave the half-answer below until someone can possibly answer the full question better.
In the way protocol is used in your code, you define your protocol Category to be a delegate for instances of the type ViewController. When an instance of type ViewController is initialised in---and hence owned locally in the scope of---some other class, the instance can delegate callbacks to the owning class.
The problem is that your CategoriesViewController does not contain any instances of type ViewController. We note that both these classes are, in themselves, subclasses of UIViewController, but none of them contain instances of one another. Hence, your CategoriesViewController does indeed conform to protocol Category, by implemented the protocol method data(...), but there's no ViewController instance in CategoriesViewController that can do callbacks to this function. Hence, your code compile file, but as it is, method data(...) in CategoriesViewController will never be called.
I might be mistaken, but as far as I know, protocol delegates are used to do callbacks between models (for model in MVC design) and controllers (see example below), whereas in your case, you want a delegate directly between two controllers.
As an example of model-delegate-controller design, consider some custom user control, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
}
}
var delegate: CustomUserControlDelegate?
// ... some methods/actions associated with your user control.
}
Now lets assume an instance of your CustomUserControl is used in a a view controller, say ViewController. Your delegate functions for the custom control can be used in the view controller to observe key changes in the model for CustomUserControl, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField instances (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
// Instance of CustomUserControl in this UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl.delegate = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
}
I have a class extension:
extension UICollectionViewCell {
class func registerFromNibInCollectionView(collectionView: UICollectionView, forReuseIdentifier identifier: String) {
...
}
}
Some of collection view cells conforms to protocol UICollectionViewCellsProtocol. I'd like to create another version of registerFromNibInCollectionView (with different method signature) for cells, that conforms to protocol.
I've tried extension UICollectionViewCell where Self: UICollectionViewCellsProtocol {, but have no luck. Any ideas how to implement it?
You can solve that by extending the protocol itself
protocol UICollectionViewProtocol {}
extension UICollectionViewProtocol {
static func registerFromNibInCollectionView (collectionView: UICollectionView, forReuseIdentifier identifier: String) {
//code
}
}
class MyClass:UICollectionViewCell, UICollectionViewProtocol {
}
MyClass.register... // will work fine
I'd like to expand the functionality of a delegate that I've been using to accept a two different types as the second parameter. When I attempt to add an overloaded method, I get two errors:
So my question is, Is there a way to overload a method in a protocol in swift to allow different parameters?
Error 1
Type 'ViewController' does not conform to protocol 'myCellDelegate'
Error 2
Cannot assign a value of type 'ViewController' to a value of type 'myCellDelegate?'
myCellDelegate.swift
protocol myCellDelegate {
func didChangeState(# sender: SettingCell, isOn: Bool)
func didChangeState(# sender: SettingCell, time: Int) // error
}
(in ViewController.Swift)
class ViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate, myCellDelegate {
cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomSettingCell") as! SettingCell
let section = sectionNames[0]
let logItem = logItems[indexPath.row]
cell.settingsLabel?.text = logItem.settingLabel
cell.settingsSwitch.on = logItem.switchState
cell.cellDelegate = self
return cell
}
Usage
func didChangeState(#sender: SettingCell, isOn: Bool) {
...
}
The immediate answer is YES there is a way to overload a method in a protocol, and I was pretty much there had I only listened to the error messages.
I simply forgot to implement the method in my ViewController. Although its obvious to me now, it wasn't apparent to me at the time because the method name was the same.
So the final code should look like this:
myCellDelegate.swift
protocol myCellDelegate {
func didChangeState(# sender: SettingCell, isOn: Bool)
func didChangeState(# sender: SettingCell, time: Int)
}
ViewController.swift
class ViewController: UITableViewController, UITableViewDelegate, myCellDelegate {
func didChangeState(#sender: SettingCell, isOn: Bool) {
...
}
func didChangeState(#sender: SettingCell, time: Int) {
...
}
}
As #woodstock suggested in the OP, this is probably a good time to use a generic type rather than an overloaded method.