Create an array of objects that implements a specific protocol - ios

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.

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)
}

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.

Why not simply create :
Why not creating :
class ObservingViewController : UIViewController, MyProtocol {
}
var viewControllers : [ObservingViewController] = []

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)
}

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 the GoogleMapsViewController and in the MapboxMapsViewController.
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 a UIViewController and provides all its methods.

Related

How to subclass with base class data retain in Swift?

How i can pass data from First screen to Second screen provided that must use inheritance and data entered in base class should be available in second screen after push.
class FirstViewController {
var dataArray = [CustomModel]()
//methods will manupulate data
}
Then push subclass SecondViewController from class FirstViewController
class SecondViewController : FirstViewController {
print(dataArray)
}
Is this possible ? any solution to this ? I just wanted reuse most of code in many screens. Any help.
When you inherit class B with parent class A, then you can access its data members.
Here check this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//call show function of class B which is inherit from class A
let classB = B()
classB.showData()
}
}
class A {
var dataArray: [String] = ["ABC", "123", "XYZ"]
}
class B: A {
func showData()
{
print(dataArray)
}
}
Output:
["ABC", "123", "XYZ"]
This is basic rule of inheritance, is this what you are looking for! if not kindly explain your problem in detail.
P.S
you can also use static data members they are accessible without making object.

Problem with type constrained protocols defined in a separate module

I'm running into an issue where type constrained protocols defined in a separate module aren't acknowledging the constrained type.
For example if I define the following protocol:
public protocol SomeVCProtocol where Self: UIViewController {
func something()
}
And conform to it:
extension SomeViewController: SomeVCProtocol {
func something() { }
}
let someVC: SomeVCProtocol = SomeViewController()
someVC.something()
someVC.present(UIViewController(), animated: true)
There's no issue here. I can call the protocol-specific methods and UIViewController methods. However the moment I define SomeVCProtocol in a separate module, I no longer have access to the UIViewController methods. Is this just a shortcoming of Swift, or am I just missing something?

Is it pssible to use generic protocols in viewcontrollers in Swift?

I want to create a generic TableViewController for selecting any data that conforms to Equatable protocol, so when I need to have a ViewController to select some equatable data, I just create a ViewController that inherits the generic TableViewController.
What I'm thinking is:
Create a generic tableviewcontroller
// Example of any Equatable data types
struct Student: Equatable {
...
}
class GenericDataSelectionViewController<T: Equatable>: UITableViewController {
var items: [T] = []
...
}
and write all other basic codes for displaying and selceting data, so when I need a viewcontroller to select Student data, I just create a new viewcontroller:
class SelectStudentViewController: GenericDataSelectionViewController<Student>
When I need to call this vc to select a student, I just do
class fooViewController: UIViewController {
...
func selectStudent() {
let destVC = SelectStudentViewController()
destVC.items = students
// show destVC ...
}
...
}
But in order to get the selected data, I also need a generic protocol, so I create a protocol:
protocol DataSelectionProtocol: class {
associatedtype T: Equatable
func didSelect(_ option: T)
}
So in the generic vc, I create a variable
weak var delegate: DataSelectionProtocol?
and inside the selectStudent() function above, I add destVC.delegate = self and implement fooViewController to conform to DataSelectionProtocol and use didSelect function to handle the selected student data.
But I get a Protocol 'DataSelectionProtocol' can only be used as a generic constraint because it has Self or associated type requirements error when a add the delegate variable in generic tableviewcontroller.
So is this the correct way to do it? Or I need to create another protocol that conforms to DataSelectionProtocol for selecting student (but I feel this is not the correct way as I would have to create new protocols for new data types, and this is not so 'Generic')?

Create a member for the UIViewController class

I tried to do some research but couldn't figure it out, so is it possible to create a member for the class UIViewController, or any class for that matter?
In every single one of my UIViewController subclasses I declare the data member
userdata = [NSManagedObject]()
So I was wondering if I could declare the variable "userdata" inside the actual UIViewController class, either directly or through an external file.
You can simply create a sub-class of UIViewController which has the userdata property and then derive all of your view controllers from that class instead of UIViewController
Something like:
class BaseViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:BaseViewController {
// Your sub view controller implementation goes here
}
You should use extensions.
extension UIViewController {
var userData : [NSManagedObject] {
get {
return [NSManagedObject]()
}
}
}
If you don't want every UIViewController to have that property, you will have to use subclassing.
class DataViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:DataViewController {
// Do something stuff to the View here
}
You can use extensions if userData is a computed property:
extension UIViewController {
var userData: [NSManagedObject] {
get { return an array from somewhere else }
set { set the value to somewhere else }
}
}
If your property is not computed but stored, you must use a subclass:
class BaseViewController: UIViewController {
var userData: [NSManagedObject] = []
}
And make every VC of yours inherit this class. The disadvantage of using this approach is that your view controllers can't inherit any other class, like UITableViewController.
So here is the best method I came up with.
Create a protocol:
protocol MyVC {
var userData: [NSManagedObject] { get set }
}
Now make every VC of yours conform to this protocol. In every VC, just start typing userData and use enter to select the right completion that Xcode provides and the property will be automatically added for you. If you forgot to do this, the compilation will fail.

How to make data visible for all view controllers?

Let's consider the following case:
I have a tab bar application where tapping each tab bar item takes user to other view that is handled by different view controller(typical pattern).
In one of my controllers I have method that downloads the important data and I want them to be global for whole application. What design pattern should I use?
One way to do that is to store this data using persistence such as core data, but is it the only way to make data visible for all view controllers? Maybe app delegate is able to perform such actions?
How in general you solve such situation where you have some data or variable which should be visible for all view controllers in your project?
Note that I'm not asking about persisting data across launches of the app, I just wonder how to make some data global in terms of the whole project.
Dont (emphasize DON'T) use following:
Singletons
AppDelegate (just another Singleton)
NSUserDefaults
Rather Don't:
Core Data
Do:
pass in either during instantiation or via properties
Why?
The DON'Ts messes up your memory
the Rather Don't messes with several principals of SOLID.
How would you do it correctly:
Create a base view controller that has a property that takes your data, make all your view controller inherit from it.
subclass UITabBarController
if a new view controller is selected, set the data to the view controller
the implementation is a bit tricky, this is from a real world app
class ContentTabBarController : UITabBarController {
private var kvoSelectedViewControllerContext: UInt8 = 1
required init(coder aDecoder: NSCoder) {
self.addObserver(self, forKeyPath: "selectedViewController", options: .New | .Old | .Initial , context: &kvoSelectedViewControllerContext)
}
deinit{
self.removeObserver(self, forKeyPath: "selectedViewController")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if context == &kvoSelectedViewControllerContext {
var targetVC : UIViewController?
if let viewController = change["new"] as? UIViewController{
if let oldViewController = change["old"] as? UIViewController{
if viewController != oldViewController {
targetVC = viewController
}
}
} else {
targetVC = self.viewControllers![0] as? UIViewController
}
self.configureTargetViewController(targetVC)
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.translucent = false
}
func configureTargetViewController(viewController: UIViewController?){
//setup data
}
}
How does the tab bar controller get the data.
Well, that is up to you. It could fetch core data, it could actually pass a fetching manager as data. It could read from disc or network. But for a sane design it should not do it itself but use another class for it. and an instance of this fetcher class should be set from outside the tab bar controller, i.e. from the App Delegate.
One easy way would be to make a struct and make it hold variables. Then, you can edit it anytime you would want to. For example:
struct Variables {
static var number = 4
}
Then you can edit the data inside Variables in any view controller you want by doing this code.
Variables.number = 6 //or any other number you want
A cleaner and efficient, although not necessarily different, way to do this is to create a singleton class, e.g. AppData, which you can access in a variety of ways, and which would be available to all your other classes. It has the benefit of separating your app-specific stuff from the app delegate stuff. You might define the class this way:
#interface AppData : NSObject
// Perhaps you'll declare some class methods here & objects...
#end
you can define ivars for the AppData class, and then manage a singleton instance of AppData. Use a class method, e.g. +sharedInstance, to get a handle to the singleton on which you could then call mehods. For example,
[[AppData sharedInstance] someMethod:myArgument];
Your implementation of +sharedInstance can be where you manage the actual creation of the singleton, which the method ultimately returns.
Try this simple method,
1) Create a variable in appdelegate.swift that could be visible to all viewcontroller.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
...
var Check:String!="" // to pass string
...
...
}
2) Create appdelegate instance in any viewcontroller
viewcontroller1.swift
import UIKit
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
...
...
override func viewDidLoad() {
super.viewDidLoad()
...
var tmp = "\(appDelegate.Check)"
appDelegate.Check="Modified"
}
}
Viewcontroller2.swift
import UIKit
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
...
...
override func viewDidLoad() {
super.viewDidLoad()
...
var getdata = "\(appDelegate.Check)"
println("Check data : \(getdata)") // Output : Check data : Modified
}
}

Resources