Reputation: 12087
Here's the scenario. I have a number of different views I want to show, depending on the model object I'm showing the user. So I've set up a protocol whereby any view that implements it can be presented.
class MyItem { /* some model properties */ }
protocol ItemView: class {
// some protocol methods (e.g. updateWithItem(), etc)
func setupItem(item: MyItem)
}
class SpecificItemView: UIView, ItemView { /* there will be multiple classes like this */
func setupItem(item: MyItem) {
// do item setup
}
}
class AnotherItemView: UIView, ItemView {
func setupItem(item: MyItem) {
// do DIFFERENT item setup
}
}
Then when I go to use them in a view controller, I've been given one of my ItemView classes:
class MyViewController: UIViewController {
var itemView: ItemView? // could be a SpecificItemView or AnotherItemView
override func viewDidLoad() {
itemView?.setupItem(MyItem())
itemView?.removeFromSuperview() /* this line won't compile */
}
}
Everything works except that last line, where I try to call up to a UIView
method (removeFromSuperview
). No surprise there, since ItemView
has no relation to UIView
.
In Objective C I would solve this issue by specifying my itemView ivar like so:
@property (nonatomic, strong) UIView<ItemView> *itemView;
But I can't seem to find a similar syntax for Swift. I suspect there is no way to use this pattern in Swift. How do I achieve my overall goal of interchangeable UIView classes in a Swift-friendly manner?
One hacky solution I've found so far, is to add any UIView
methods I call (e.g. removeFromSuperview
) to my ItemView
protocol.
Another suggestion I got, not on SO, from Maurice Kelly, was to make a UIView
subclass, implementing the ItemView
protocol, that both SpecificItemView
and AnotherItemView
can descend from. You can see it implemented in this gist. While that solves the issue of encapsulating class AND protocol in a single type (e.g. var itemView: ItemViewParentClass
) it basically makes the protocol pointless, since you're now implementing all the protocol's methods in your parent class and overriding them in your subclasses. The single biggest drawback of this solutions is that you have to cast instances of subclasses (SpecificItemView
) to the new hypthetical parent class (ItemViewParentClass
) when you refer to them in your View Controller.
Upvotes: 3
Views: 3946
Reputation: 51
As of Swift 5, this is possible by constraining your protocol to only be applied to UIViews
. To do this, just do the following:
protocol ItemView: UIView {
func setupItem(item: MyItem)
}
By adding this constraint to your protocol, any object that the swift compiler recognizes as an ItemView
will also have all UIView
methods available on it.
Similarly, if a non-UIView
subclass attempts to implement the ItemView
protocol, your code will not compile.
Upvotes: 5
Reputation: 1907
Perhaps the following (in Swift 4) would do the trick?
class MyViewController: UIViewController {
typealias T = UIView & ItemView
var itemView: T?
}
Upvotes: 0
Reputation: 41226
As you're discovering, there's no way to specify both a class and a protocol for an object.
The easiest way to fix the problem is to add removeFromSuperview
(and any other UIView
methods you need to insure are present) to your protocol.
Upvotes: 1
Reputation: 13551
ItemView
does not extend UIView
, but your SpecificItemView
does. Naturally, no removeFromSuperView
exists on your ItemView
.
In your ViewController you declare an ItemView
type object, rather than a SpecificItemView
Change your MyViewController
code to:
var itemView:SpecificItemView?
Upvotes: -1