Why does swift hide default implementation for restricted protocols? - ios

I have a protocol which declaration looks like this:
protocol RefreshableView where Self: UIView {
func reload()
}
And it has default implementation which looks as follows:
extension RefreshableView {
func reload() {
print("Default implementation")
}
}
Then if I declare another (empty) extension of UIView conforming to this protocol I get compile-time error stating that UIView does not conform to the protocol.
extension UIView: RefreshableView {}
It should not be a case from my point of view, as default implementation is provided. However if I remove where statement (restriction to the classes which can conform to the protocol) from declaration of the protocol, everything works as expected. Another option to silence this error is to give the same where statement next to default extension declaration, but it feels redundant as I already let compiler know the protocol is supposed for narrow audience. Is there an explanation to this behavior?

What you're saying is that this doesn't compile:
protocol RefreshableView where Self: UIView {
func reload()
}
extension RefreshableView {
func reload() {
print("Default implementation")
}
}
extension UIView: RefreshableView {
}
As Rob Napier points out in a comment, that's a very odd thing to say, because if UIView itself is going to adopt RefreshableView, then what's the protocol for? The original declaration of the protocol means that only a UIView subclass can adopt RefreshableView, so what the compiler expects is that that's what will happen:
protocol RefreshableView where Self: UIView {
func reload()
}
extension RefreshableView {
func reload() {
print("Default implementation")
}
}
class MyView: UIView {}
extension MyView: RefreshableView {
}
That is a useful real-world case, and it compiles just fine.
So you could file a bug against your original code, but you have to admit it
is a very peculiar edge case to start with; you are saying something that no one would in fact ever say.

Related

View Controller only Protocol has no access to View Controller properties

I have a UIViewController only protocol
protocol VCProtocol where Self: UIViewController {}
I have a function with VCProtocol parameter. Inside the function I can not access any property of UIViewController
func testFunction(vcProtocol: VCProtocol) {
// vcProtocol.view ‼️ error: Value of type 'VCProtocol' has no member 'view'
}
Though I can cast the protocol parameter to UIViewController and then access the property like this:
func testFunction(vcProtocol: VCProtocol) {
(vcProtocol as! UIViewController).view
}
Is this is the way? Do we have any better way?
You can use the & operator to combine protocols
protocol VCProtocol where Self: UIViewController {}
func testFunction(vcProtocol: VCProtocol & UIViewController) {
let view = vcProtocol.view
}
It seems like this is now supported properly from Swift 5. You can try it Xcode 10.2 beta 4. For older versions, you would have to resort to #Ricky Mo's solution.
protocol VCProtocol: UIViewController {
func testFunction(vcProtocol: VCProtocol)
}
class A: UIViewController, VCProtocol {
func testFunction(vcProtocol: VCProtocol) {
debugPrint(vcProtocol.view)
}
}
From the notes,
Protocols can now constrain their conforming types to those that
subclass a given class. Two equivalent forms are supported:
protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ }
Swift 4.2 accepted the second form, but it wasn’t fully implemented
and could sometimes crash at compile time or runtime. (SR-5581)
(38077232)

Default protocol implementation causes 'does not conform to protocol' error

I am trying to add a default implementation to one of my delegate methods. However, after adding the default implementation and removing the method from the class that implements the protocol, I get does not conform to protocol error. It works in a playground.
protocol NavigationDelegate: NSObjectProtocol {
func didSetToolbarVisible(_ isVisible: Bool)
}
extension NavigationDelegate {
func didSetToolbarVisible(_ isVisible: Bool) {
print("Default implementation")
}
}
class MyViewController: NavigationDelegate {
// 'does not conform to protocol' error
}
What am I missing?
A class does not conform to NSObjectProtocol by default, that causes the error.
Change
protocol NavigationDelegate: NSObjectProtocol
to
protocol NavigationDelegate: class
Your NavigationDelegate uses a base protocol of NSObjectProtocol. This means that anything that conforms to NavigationDelegate must also conform to NSObjectProtocol. Change your class declaration to the following:
class MyViewController: NSObject, NavigationDelegate.
Solved it! My NavigationDelegate and its extension were in a different target than the one that MyViewController belongs to. Simply moving the extension to the same target worked.
Hope this helps someone in the future 🤞

Alternative to override extension's method

I want to extend UIView by adding some functions, and override them in any subclass of UIView that I want. I found in apple documentations that I can't override extensions (and the compiler will complain) which make some sense. So
I need someone to suggest an alternative way to the below:
extension UIView {
func hide() { //do almost nothing }
}
class myLabel: UILabel {
override func hide() {
//do work on uilabel that can't be done on imgView
}
}
class myImageView: UIImageView {
override func hide() {
//do work on imgView that can't be done on uilabel
}
}
And the reason I want this is that later in my code I will face the below code and I have to many subclasses and I don't want to write too many if-lets trying to cast the view to myLabel, myTextView, myImageView... etc
let view = cell.viewWithTag(someTag)
// and I want to write this below without casting
view.hide()
I tried with protocols and protocol extensions but I couldn't make it though.
Any thoughts?
Note: func hide() is just an example. My func will have more to do.
**Question updated to be clear.
EDIT: Updating answer to make use of protocols also
Protocols does in various ways enable to you replace subclassing in some cases however you still need your class to conform to the protocol to be able to see and override those methods
You can have a protocol for example:
protocol SomeProtocol {
func hide()
}
To do what you are intending to do it is best to have a parent subclass UIView with all functions that can be overridden for example (in this updated answer you can have your methods to override inside the protocol and have your subclasses conform to it):
class ParentView : UIView, SomeProtocol {
func hide() {
print("PARENT")
}
func anyOtherMethod() {
}
}
and then have all the other UIView's that need to override those methods subclass ParentView:
class ViewOne : ParentView {
override func hide() {
print("VIEW ONE")
}
}
class ViewTwo : ParentView {
override func hide() {
print("VIEW TWO")
}
}
So even if you later place this code:
let view = cell.viewWithTag(someTag)
// and I want to write this below without casting
view.hide()
you won't need to explicitly cast your UIView's, the view will call it's intended overridden method, unless and until you call super in your overridden method also
EDIT: More on making use of protocols
In the case you need other controls to also have a hide() method to override then you can still have to subclass, for example in the case of UILabel you need to override it:
class ParentLabel : UILabel, SomeProtocol {
func hide() {
print("PARENT LABEL")
}
}
then you can write the intended code with casting to your protocol
if let view = cell.viewWithTag(someTag) as? SomeProtocol {
view.hide() // prints PARENT LABEL
}
and either use that subclassed UILabel control or if you need in some cases some label to override that behavior then you can still create a child subclass of ParentLabel:
class LabelOne : ParentLabel {
override func hide() {
print("LABEL ONE")
}
}

How to create constraint on protocol

I tried to create protocol which can be only implemented by classes which inherit from UIView, what was my surprise when this code compiles without errors (in Swift 3.0):
protocol TestsProtocol {
func test()
}
extension TestsProtocol where Self: UIView { }
class FooClass: TestsProtocol {
func test() {
}
}
We can see that FooClass don't inherit from UIView, using protocol extension I wan't to force that only classes which inherit from UIView can implement it.
As far as I remember this would not compile in Swift 2.1
You cannot do this in Swift. The extension syntax does something else:
extension TestsProtocol where Self: UIView {
func useful() {
// do something useful
}
}
now any class which implements TestsProtocol and is a UIView (or subclass) also has the useful() function.
You can do that easily by limit protocol from be extendable from any type other than UIView :
protocol TestsProtocol:UIView {
func test()
}
class FooClass: TestsProtocol {
func test() {
}
}
So this will cause compile error
'TestsProtocol' requires that 'FooClass' inherit from 'UIView'

Overriding delegate property of UIScrollView in Swift (like UICollectionView does)

UIScrollView has a delegate property which conforms to UIScrollViewDelegate
protocol UIScrollViewDelegate : NSObjectProtocol {
//...
}
class UIScrollView : UIView, NSCoding {
unowned(unsafe) var delegate: UIScrollViewDelegate?
//...
}
UICollectionView overrides this property with a different type UICollectionViewDelegate
protocol UICollectionViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
//...
}
class UICollectionView : UIScrollView {
unowned(unsafe) var delegate: UICollectionViewDelegate?
//...
}
When I try to override UIScrollViews delegate with my protocol like so:
protocol MyScrollViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
//...
}
class MyScrollView: UIScrollView {
unowned(unsafe) var delegate: MyScrollViewDelegate?
}
the compiler gives me two warnings:
Property 'delegate' with type 'MyScrollViewDelegate?' cannot override a property with type 'UIScrollViewDelegate?'
'unowned' cannot be applied to non-class type 'MyScrollViewDelegate?'
How can I subclass UIScrollView and override type of delegate property (i.e. use a custom delegate protocol) ?
I think overriding an inherited property is something that's possible in Objective-C but not (at least currently) in Swift. The way I've handled this is to declare a separate delegate as a computed property of the correct type that gets and sets the actual delegate:
#objc protocol MyScrollViewDelegate : UIScrollViewDelegate, NSObjectProtocol {
func myHeight() -> CGFloat
// ...
}
class MyScrollView: UIScrollView {
var myDelegate: MyScrollViewDelegate? {
get { return self.delegate as? MyScrollViewDelegate }
set { self.delegate = newValue }
}
}
This way anything that calls the scroll view delegate normally still works, and you can call your particular delegate methods on self.myDelegate, like this:
if let height = self.myDelegate?.myHeight() {
// ...
}
You can do like this:
protocol ExtendedUIScrollViewDelegate: UIScrollViewDelegate {
func someNewFunction()
}
class CustomScrollView: UIScrollView {
weak var myDelegate: ExtendedScrollViewDelegate?
override weak var delegate: UIScrollViewDelegate? {
didSet {
myDelegate = delegate as? ExtendedScrollViewDelegate
}
}
}
Hope this helps
My favoured method personally is not to subclass scrollviews directly but to make a UIView subclass containing and acting as delegate for a separate scrollview, then forward that scrollview's delegate messages on to the UIView subclass's own delegate where necessary. This also allows for the adding of custom controls outside of the area defined by the scroll view. It may seem a little inelegant compared to a direct subclass, but it does at least avoid unpleasant hacks.
Here is a solution for changing the type of the overriding properties in Swift. It is especially useful when you need to extend protocols of delegates.
#objc protocol ExtendedUIScrollViewDelegate: UIScrollViewDelegate {
func someNewFunction()
}
class CustomScrollView: UIScrollView {
weak var delegateInterceptor: ExtendedScrollViewDelegate?
override var delegate: UIScrollViewDelegate! {
didSet {
if let newValue = delegate {
let castedDelegate = unsafeBitCast(delegate, ExtendedScrollViewDelegate.self)
delegateInterceptor = castedDelegate
}
else {
delegateInterceptor = nil
}
}
}
}
This works as tested with Swift version 1.2. I hope it helps.
You can override get and set method by declare function like:
func setDelegate(delegate:UITableViewDelegate?){
self.delegateInterceptor = delegate;
}
swift compiler the property to method as Objective-c does.
Consider the following situation:
class BaseProp {}
class Base {
var prop: BaseProp
}
Then if you do this:
class DerivedProp: BaseProp {}
class Derived: Base {
override var prop: DerivedProp
}
Then if would break the subclassing principles (namely, the Liskov Substitution Principle). Basically what you are doing is limiting the scope of "var prop" from wider "BaseProp" type to a more narrow "DerivedProp" type. Then this kind of code would be possible, which does not make sense:
class UnrelatedProp: BaseProp {}
let derived = Derived()
let base = derived as Base
base.prop = UnrelatedProp()
Note that we are assigning an instance of UnrelatedProp to the property, which does not make sense for the Derived instance which we actually operate with. ObjectiveC allows such kind of ambiguity, but Swift doesn't.

Resources