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
Related
I am writing some transitioning protocols, and I would like them to only be available to UIView classes:
public protocol TransitioningView where Self: UIView
{
func transitionDelay(pushFrom: UIView?) -> TimeInterval?
func transitionDuration(popTo: UIView?) -> TimeInterval
func transitionDuration(pushFrom: UIView?) -> TimeInterval
func animateTransition(popTo: UIView? , finished: ((Bool) -> Void)?)
func animateTransition(pushFrom: UIView?, finished: ((Bool) -> Void)?)
}
But this doesn't seem quite right. The compiler does not consider a UIView that has been cast to a TransitioningView as being a UIView.
example:
let x = myController.view as? TransitioningView
x.animateTransition(popTo: nil, finished: nil) // OK
x.backgroundColor = .clear // NOPE
This is a bit annoying because sometimes I want to pass around a view that I have confirmed to be a TransitioningView. Is there a better pattern?
In your example, public protocol TransitioningView where Self: UIView, you are defining that the TransitioningView requires an inheritance from UIView, but i can't see any implementation of TransitioningView into UIView.
I'll give you two examples on how you can do this.
First you can create a default implementation of the protocol:
extension TransitioningView {
public func transitionDelay(pushFrom: UIView?) -> TimeInterval? {
return 1
}
public func transitionDuration(popTo: UIView?) -> TimeInterval {
return 1
}
public func transitionDuration(pushFrom: UIView?) -> TimeInterval {
return 1
}
public func animateTransition(popTo: UIView?, finished: ((Bool) -> Void)?) {
}
public func animateTransition(pushFrom: UIView?, finished: ((Bool) -> Void)?) {
}
}
This example will make every object that inherits from UIView conform to TransitioningView, this might be a over-kill depending on how much you utilize the TransitioningView functionality:
extension UIView: TransitioningView {}
The last example will implement TransitioningView directly in the custom class:
class MyCustomView: UIView, TransitioningView {}
class MyCustomLabel: UILabel, TransitioningView {}
Independent of how you implement it they both use the same default implementation in the extension TransitioningView. This behavior can be "overridden" in the either an extension or in the class itself.
extension TransitioningView where Self: UITextField {
public func animateTransition(pushFrom: UIView?, finished: ((Bool) -> Void)?) {
// Some custom transition
}
}
or
class MyCustomView: UIView, TransitioningView {
func transitionDuration(popTo: UIView?) -> TimeInterval {
return 2
}
}
For further reference:
Extensions - The Swift Programming Language (Swift 4)
Protocols - The Swift Programming Language (Swift 4)
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 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.)
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.