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

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')?

Related

Using protocol with generic data type to pass data between screens

I am Android Developer that started learning iOS. I am trying to pass data between the master-detail style app.
I got controller1 that has a list of ToDo items, and controller2 that allows to create a new ToDo item and add it to the list on controller1.
I have created a protocol:
protocol ListDataHolder {
associatedtype T
func addItem(item: T)
func reloadData()
}
Assigned self in prepare of controller1:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller2 = segue.destination as? Controller2{
controller2.toDoDataHolder = self
}
}
Declared delegate in controller2
// how do I tell to use ToDo class for generic type here
var toDoDataHolder: ListDataHolder? = nil
And use it like this:
#IBAction func onAddClicked(_ sender: Any) {
let toDo = ToDo()
...
toDoDataHolder?.addItem(item: toDo)
toDoDataHolder?.reloadData()
navigationController?.popViewController(animated: true)
}
I got a few errors when going this way:
For delegate declaration:
Protocol 'ListDataHolder' can only be used as a generic constraint because it has Self or associated type requirements
When using addItem() :
Cannot convert value of type 'ToDo' to expected argument type 'ListDataHolder.T'
Insert ' as! ListDataHolder.T'
Member 'addItem' cannot be used on value of protocol type 'ListDataHolder'; use a generic constraint instead
When I remove generic from protocol and just have addItem(item: ToDo), everything works fine. But I want to be able to use ListDataHolder with any data type.
This is just experimentation for me, I am not looking for a correct way to pass data between controllers.
EDIT: you can find complete code in this GitHub repo: github.com/Sermilion/ios_learning
What you need to do is to make the second view controller generic using the protocol and limit the type of objects being used (or held) by the class conforming to ListDataHolder
This can be done in the declaration of the view controller
class SecondViewController<Holder: ListDataHolder>: UIViewController where Holder.T == ToDo
then your method onAddClicked will work as is.
Instead of using associated type in ListDataHolder protocol use ToDo if your are always passing ToDo instance to the addItem method.
If you want this protocol addItem method will work with any genericType use following code.
protocol ListDataHolder {
func addItem<T:Decodable>(item: T)
func reloadData()
}
struct ToDo: Decodable {
}
class A: ListDataHolder {
func addItem<T:Decodable>(item: T) {
print("Add Item called")
}
func reloadData(){
}
}
Here you need to implement Delegation design pattern.
Please check Delegation design pattern in detail.

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.

How can assing generic (associated type require) protocol as delegate to any controller?

I have a protocol that have associatedtype named MessageType.
protocol MessageProtocol: class {
associatedtype MessageType
func sendMessage(_ with: MessageType)
}
Then implemented it in the controller
extension MainController: MessageProtocol{
typealias MessageType = String
func sendMessage(_ with: MessageType) {
// sending message
}
}
My purpose is using the protocol as delegate in other controller like below.
final class AnotherController {
weak var messagerDelegate: MessageProtocol?
...
}
But I get error of that
Protocol 'MessageProtocol' can only be used as a generic constraint
because it has Self or associated type requirements
Is there any way to handle this error?
I've reading Big Nerd Ranch blog post about this situation.
https://www.bignerdranch.com/blog/why-associated-type-requirements-become-generic-constraints/
I've learned about the situation but no idea about how it can be achived?
Thanks,
I handle the situation like that.
final class AnotherController<T: MessageProtocol> where T.MessageType == String {
weak var messagerDelegate: T?
...
}
And if I want to create anotherController instance programmatically, I created like that.
let instance = AnotherController<MainController>(frame: CGRect.zero)
instance.delegate = self
...
Because of MainController is comfortable with MessageProtocol from
extension MainController: MessageProtocol{}
Probably it's not common case but worked for me.

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.

Create an array of objects that implements a specific protocol

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.

Resources