问题
TL;DR
I'm looking for an array type (var array = [TheTypeImLookingFor]()
) like 'all objects that subclasses UIViewController
and implements the protocol MyProtocol
.
Explanation
I'm building a kind of wizard view with a container view and embedded child views (controller). No problem, this will work as long, as I have only one base
type of child view controllers.
Due to the content of screens, I have now a bunch of view controllers of type MyTableViewController
which is a subclass of UITableViewController
and other view controllers that have regular UIViewControllers
as base.
All of the view controllers have one thing in common. A default data property myData: MyObject
.
I created a protocol MyProtocol
that contains this property.
Now, I have to combine all this view controllers into one array to use it as wizard steps. As long as I only have to access the view controller methods (array items are type of UIViewController
) I'm able to use var viewControllers = [UIViewController]()
or if I wanna only access the myData
property, I change the array item type to MyObject
.
But the problem is, I have to access the methods from the UIViewController
and from the protocol.
That's why I'm looking for an array type like 'all objects that subclasses UIViewController
and implements the protocol MyProtocol
.
I tried:
var viewControllers = [UIViewController: MyProtocol]()
// is a dict- `var viewControllers = UIViewController where MyProtocol
- `var viewControllers = UIViewController.conforms(to: MyProtocol)
- ...
But nothing works as expected.
回答1:
As far as I know, there's currently no way to type something so that it describes anything which inherits from a given class and conforms to a given protocol.
One possible hacky workaround would be to just create a wrapper type in order to perform typecasting for you in the case that you need to treat the instance as a MyProtocol
.
struct MyProtocolViewController {
let base: UIViewController
init<T : UIViewController>(_ base: T) where T : MyProtocol {
self.base = base
}
func asMyProtocol() -> MyProtocol {
return base as! MyProtocol
}
}
Now you can create a [MyProtocolViewController]
, and can either treat an element as a UIViewController
, or a MyProtocol
.
// given that ViewController and AnotherViewController conform to MyProtocol.
let viewControllers = [MyProtocolViewController(ViewController()),
MyProtocolViewController(AnotherViewController())]
for viewController in viewControllers {
print(viewController.asMyProtocol().myData)
print(viewController.base.prefersStatusBarHidden)
}
回答2:
You could use protocol composition with a placeholder protocol for the class:
protocol UIViewControllerClass {}
extension UIViewController: UIViewControllerClass {}
protocol MyProtocol:class {}
class MySpecialVC:UIViewController,MyProtocol {}
var viewControllers = [UIViewControllerClass & MyProtocol]()
viewControllers.append( MySpecialVC() )
This covers the type safety part but doesn't let you access UIViewController methods without type casting. You can reduce the type casting ugliness by adding a typed property to your protocol (when it applies to the base class)
extension MyProtocol where Self: UIViewControllerClass
{
var vc:UIViewController { return self as! UIViewController }
}
// accessing the view controller's methods would then only require insertion of a property name.
viewControllers.first!.vc.view
Alternatively, you could define the UIViewController methods you need to call in the placeholder protocol but that could quickly become tiresome and redundant if you're going to use many of them.
回答3:
Why not simply create :
Why not creating :
class ObservingViewController : UIViewController, MyProtocol {
}
var viewControllers : [ObservingViewController] = []
回答4:
You can also create a protocol
that defines all the UIViewController
functions that you need. Make sure that you copy the method signature, otherwise you will have to implement the functions again.
protocol UIViewControllerInteractions {
//copy the signature from the methods you want to interact with here, e.g.
var title: String? { get set }
}
Then, you can extend your existing protocol.
protocol MyProtocol: UIViewControllerInteractions { }
Or create a new protocol that extends UIViewControllerInteractions
and MyProtocol
.
protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { }
Now, when you extend your SubclassUIViewController
, you still only have to add your myData
because the methods in the UIViewControllerInteractions
are already implemented by UIViewController
(that's why we copied the method signature)
class SubclassUIViewController: MyProtocol {
var myData ...
}
You can now have an array
of MyProtocol
or MyProtocolViewController
and also call the methods defined in UIViewControllerInteractions
which will call the UIViewController
methods.
var viewController: [MyProtocol] = [...]
viewController.forEach { (vc) in
print(vc.myData)
print(vc.title)
}
回答5:
I had a similar issue and solved it with a custom base class. Imagine an array like:
var viewControllers: [MapViewController]
which all should extend from UIViewController
and implement the following protocol:
protocol MapViewControllerDelegate {
func zoomToUser()
}
Then I've declared a base class like:
class MapViewController: UIViewController {
var delegate: MapViewControllerDelegate?
}
Caution: this class doesn't implement the above protocol but holds a property which provides the desired functionality. The next step is to define one of the UIViewController
that will be added to the array:
class GoogleMapsViewController: MapViewController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension GoogleMapsViewController: MapViewControllerDelegate {
func zoomToUser() {
// Place custom google maps code here
}
}
The important part is located in the viewDidLoad method. The view controller assigns itself as the delegate.
Usage:
let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController]
for mapVC in mapViewControllers {
mapVC.delegate?.zoomToUser()
}
The benefits:
- The
MapViewController
is like an abstract class and If I change the MapViewControllerDelegate the compiler forces me to implement the changes in theGoogleMapsViewController
and in theMapboxMapsViewController
. - If I need a second protocol I could just implement a second delegate property.
- No type casting needed like in the other answers. Each
UIViewController
is still aUIViewController
and provides all its methods.
来源:https://stackoverflow.com/questions/42090028/create-an-array-of-objects-that-implements-a-specific-protocol