How to create constraint on protocol - ios

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'

Related

Why does swift hide default implementation for restricted protocols?

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.

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)

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 generic swift function that can work for 2 different classes?

In my two classes, I have the following code
// Two classes who's code I am trying to reduce using protocols
class class1 {
var view: viewSubclass1!
func printName() {
print("This is the view: \(view)")
}
}
class class2 {
var view: viewSubclass2!
func printName() {
print("This is the view: \(view)")
}
}
// The protocol and protocol extension I am trying to create
protocol myProtocol {
var view: UIView {get set}
func printName()
}
extension myProtocol {
func printName() {
print("This is the view: \(view)")
}
}
// The subviews of UIView
class viewSubclass1: UIView {}
class viewSubclass2: UIView {}
As you can see, class1 and class2 are different classes, but have the same variable name: view and the same function names. The difference is that the view variable are of different types.
Question: Using protocols and protocol-extensions, how can I reduce the code between the two classes? I would not want to do repeat code because they are so similar. I have been trying different variations of the code, but I keep getting stuck. I do not understand it well. How can i get both classes to use the same code using a protocol, and a protocol extension?
(Updated)
With declaring the protocol and protocol extension:
protocol MyProtocol {
associatedtype BaseView: UIView
var view: BaseView! {get set}
func printName()
}
extension MyProtocol {
func printName() {
print("This is the view: \(view)")
}
}
And given two subclasses of UIView:
class ViewSubclass1: UIView {}
class ViewSubclass2: UIView {}
(Capitalized some type names for readability.)
And declaring two classes like this:
class Class1: MyProtocol {
var view: ViewSubclass1!
}
class Class2: MyProtocol {
var view: ViewSubclass2!
}
You'll find you can use your printName() for both Class1 and Class2.
One thing important here:
If you declare your view in the protocol as var view: UIView {get set}, var view: ViewSubclass1! cannot fulfill the requirement for the protocol.
(One thing is the difference of optionality, another is that you cannot assign any instances of UIView or UIView's subclasses to view: ViewSubclass1.)
But many things depend on what you really want to do in your common code.

Can't conform to obj-c protocol in Swift

I'm creating an iOS app with Swift. I discovered an animation I'd like to implement in my table view, but the code is in Objective-C.
Repository: https://github.com/recruit-mp/RMPZoomTransitionAnimator
I have successfully bridged Obj-C code to Swift but can't seem to conform to a required protocol.
The protocol:
#protocol RMPZoomTransitionAnimating <NSObject>
#required
- (UIImageView *)transitionSourceImageView;
- (UIColor *)transitionSourceBackgroundColor;
- (CGRect)transitionDestinationImageViewFrame;
#end
My Swift implementation:
First class that implements the protocol:
class ChallengeViewController: UIViewController, RMPZoomTransitionAnimating
func transitionSourceImageView() -> UIImageView {
return imageView
}
func transitionSourceBackgroundColor() -> UIColor {
return UIColor.whiteColor()
}
func transitionDestinationImageViewFrame() -> CGRect {
return imageView.frame
}
Second class:
class ChallengeTableViewController: UITableViewController, RMPZoomTransitionAnimating
func transitionSourceImageView() -> UIImageView {
return imageForTransition!
}
func transitionSourceBackgroundColor() -> UIColor {
return UIColor.whiteColor()
}
func transitionDestinationImageViewFrame() -> CGRect {
return imageFrame!
}
This check that occurs before animating always fails:
Protocol *animating = #protocol(RMPZoomTransitionAnimating);
BOOL doesNotConfirmProtocol = ![self.sourceTransition conformsToProtocol:animating] || ![self.destinationTransition conformsToProtocol:animating];
I've read this topic How to create class methods that conform to a protocol shared between Swift and Objective-C? but didn't found any help
Any clues would be really appreciated
Swift classes by themselves are not (by default) Objective-C Compatible.
You get compatibility by either inheriting from NSObject or adding #objc in front of your class. I suspect this "may" be your issues - but I unfortunately can't test it at the moment.
You may also have to add a few initializers like in your case one from NSCoder or something - I don't unfortunately recall off the top of my head - and I don't have access to Xcode right now.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
Try:
#objc
class ChallengeViewController: UIViewController, RMPZoomTransitionAnimating
func transitionSourceImageView() -> UIImageView {
return imageView
}
func transitionSourceBackgroundColor() -> UIColor {
return UIColor.whiteColor()
}
func transitionDestinationImageViewFrame() -> CGRect {
return imageView.frame
}
This will tell the compiler your class is objective-c compatible

Resources