Naming objects which implement delegates to cleanup UIViewController - ios

I noticed that some of my view controllers have become pretty big and I'd like to avoid the MVC (Massive View Controller).
I found that my view controllers often implement a lot of delegates from other view controllers which I may or may not present at runtime. Also they are often datasources for table- or collectionviews.
Its not to hard to refactor the view controllers and put the datasources and delegates into their own structs or classes and then just have a bunch of lazy optional properties which I can pass around.
My problem is: I'm really bad at naming things and I wonder if there are some best practices on how to name the delegates and datasource objects.
Here are a few examples of delegates and datasources I currently implement in my viewcontroller but which I want to put in their own objects:
UITableViewDataSource
UITableVIewDelegate
UICollectionViewDataSource
UICollectionViewDelegate
ImagePickerDelegate
DatePickerKeyboardDelegate
KeyboardAccessoryToolbarDelegate
AControllerDelegate
BControllerDelegate
CControllerDelegate
And my view controller are named after what they do, followed by Controller (I don't like to name them ViewController because it makes their name even longer):
ImagesController
FoodController
StoreController
AController
BController
CController
Okay so now to an example. AController could push BController onto the navigation stack. AController currently implements the BControllerDelegate but does not actually need to do that. Instead, AController could hold a object which implements the BControllerDelegate and just configures the delegate before it pushes BController and sets BControllers delegate.
But how would I name the object which implements the BControllerDelegate and how would I name the variable which holds this object in my AController?
class AController: UIViewController {
let whatsMyName = WhatsMyName()
}
struct WhatsMyName: BControllerDelegate {}
Update
Yesterday I started refactoring a few of my view controllers. As also suggested by danh (I hope I understood you right), I currently let the view controller still be the delegate of my datasources. Though I now have separate datasource objects and I currently configure everything like so:
struct MyModel {
var name: String
init(name: String) { self.name = name }
}
class MyModelDataSource: NSObject, UITableViewDataSource {
let myModelCollection: [MyModel] = [MyModel(name:"Hello"), MyModel(name:"World")]
init(tableView: UITableView) {
//setup tableview, register cells, set row height, etc.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myModelCollection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel.text = myModelCollection[indexPath.row].name
return cell
}
}
protocol MyViewControllerDelegate: class {
func myViewController(_ myViewController: MyViewController, picked: MyModel)
}
class MyViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
weak var myViewControllerDelegate: MyViewControllerDelegate?
lazy var myModelDataSource: MyModelDataSource = {
return MyModelDataSource(tableView: self.tableView)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = myModelDataSource
tableView.delegate = self
if myViewControllerDelegate == nil { delegate = self }
}
}
extension MyViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myViewControllerDelegate?.myViewController(self, picked: myModelDataSource.myModelCollection[indexPath.row])
}
}
extension MyViewController: MyViewControllerDelegate {
func myViewController(_ myViewController: MyViewController, picked: MyModel) {
//Perform drill down
}
}
extension SomeOtherViewController: MyViewControllerDelegate {
func myViewController(_ myViewController: MyViewController, picked: MyModel) {
//Perform picking of item and dismiss/pop myViewController
}
}
The deal with this is, that it probably still light years away from being perfect. I think actually the viewcontroller could or maybe even sholud still be the delegate and datasource for every tableview (keep in mind that you might have multipe tableview outlets and therefore also multiple datasource objects).
It could then just forward the datasource functions to the appropriate datasource object which is stored as lazy instance variable. That would allow me to have datasources which only have 1 section and "stitch together" multiple datasources into one tableview by using a datasource per section (I hope that you know what I mean).
Also I decided to avoid subclassing the controller for the sake of picking entries as suggested by danh. Instead I set the controller as it's own delegate if no other controller wants to do the job.
This is like a default implementation which basically says "If nobody wants to be my delegate, I'm a drill down controller. Otherwise my delegate will know what to do with me" and though this example only makes use of didSelectRowAt: in reality I delegate often also other things like accessoryTypeFor: and custom functions like when I use the controller as a picker to de-/select multiple entries at once, displaying selected entries with checkmarks an so on.
Hopefully I understood danh correctly and I call the datasources in my viewcontroller after the model they hold together with "DataSource" whereas the DataSource object itself holds a "xxxCollection". Please correct me if I'm wrong. :)
So to sum up, the "naming convention" or "best practice" (as there is probably not a real one besides what we just came up with) is:
Be consistent with the naming
Make the name descriptive and don't violate common language agreements even if it makes the name a little longer (e. g. it's probably better to name it MyVeryOwnSuperDuperCoolViewController rather than MyVeryOwnSuperDuperCoolController if the controller is in fact a UIViewController except if you have very good reasons to do so and if you do, do it consistently)
Name your DataSource after the model it has a collection of (e. g. MyModelDataSource)
The DataSource should have a collection of the model (e. g. var myModelCollection: [MyModel]
I hope I haven't forgotten something. Still interested if somebody has written down something about this topic somewhere which could be considered a best practice or best-practice-suggestion.

I agree with your thinking that reducing vc size by factoring is a good idea, but whether you factor something, and by what means should depend on semantics.
Let me suggest a pattern with this example: Say your app is about driving, and somewhere within it, user must select a car. The view controller in this case might be (ought to be) named CarPickerViewController. Why? "Car" is obvious, "Picker" is a norm borrowed from elsewhere in the SDK culture, and "ViewController" is practically a rule (which you've opted to violate, but at least you've done so deliberately, reasonably, consistently, etc.).
Certainly, the most important noun in this story is a Car, a thing with wheels and a motor, and so on. But there's also an important collective noun, representing a group of zero or more cars. If they were geese, we'd call it a "gaggle", but my rule for nouns that don't have an existing specific collective noun is to add a collective suffix, as in CarCollection.
To recap so far, we have CarPickerViewController, which holds a CarCollection which wraps an array of Car. And certainly, that CarCollection can (should) conform to UITableViewDatasource.
What about the delegate? Here again, imo, we should be driven by semantics, not file arrangement. Probably, only job the delegate has in this example is to learn about selection in the table view. We could, even though it would be poor design, just let the presenting view controller conform to the table view delegate protocol this way:
// in CarPickerViewController.h
#property(weak,nonatomic) id<UITableViewDelegate> delegate;
// in CarPickerViewController.m
- (void)setDelegate:(id<UITableViewDelegate>) delegate {
self.tableView.delegate = delegate;
}
The presenting view controller would then set itself as the delegate, and implement tableView:didSelectRowAtIndexPath:, not crazy, but passing it a tableView? Which tableView? The better answer here is a new protocol, defined by the picker:
// in CarPickerViewController.h
#protocol CarPickerDelegate <NSObject>
- (void)carPicker:(CarPickerViewController *)vc didPickCar:(Car *)car;
#end
// in CarPickerViewController.m (which remains the table view delegate)
- (void)tableView:(UITableView *)didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Car *car = self.carArray[indexPath.row];
[self.delegate carPicker:self didPickCar:car];
}
Should we factor out an another class to be the UITableViewDatasource which does this translation? I say, no way! So to recap again:
CarPickerViewController - conforms to UITableViewDelegate
CarCollection - conforms to UITableViewDatasource
Car - a humble model
Just a little more: Let's say there was a drill-down interface where we could browse car details. I think we'll end up renaming our "picker" class to be more generically, CarViewController. It will have a table view of cars and not much else. A subclass, our original CarPickerViewController, will continue to implement the one method didSelectRowAtIndexPath as it did before, but it's superclass implementation will perform drill-down navigation on the same method.
I could go on, but I fear I've gone on too long. Hope this gives enough sense of an approach that you can apply in your app.

For your above way, you are going to write a lot of boilerplate codes and make it even more complicated.
There is a good practice to organize your Delegate is to use extension in stead of the above way.
For example:
class AController: UIViewController {
}
extension AController: BControllerDataSource {
}
extension AController: CControllerDelegate {
}
You even do not need to put in one file, you can separate it if your ViewController is too long. For example.
AController+BControllerDatasource.swift
AController+CControllerDelegate.swift
It makes ViewController look clean and keep related logic functions in separate groups.

Related

Is a blank function conventional in subclass that conforms to custom protocol?

I have two main screens in my app, currently both just subclasses of UIViewController. These two view controllers are very similar - they both implement my custom subclass of UIView called HeaderView that is responsible for displaying information and taking user input. As it stands, this code is repetitive because the HeaderView setup is the same for both view controllers - the only difference is what happens when the user confirms the text entry in HeaderView.
To cut down on repetitive code, I am creating a class called InputViewController (a subclass of UIViewController) that houses the aspects of the two view controllers that are identical. Eventually, I want the two view controllers to subclass InputViewController instead of UIViewController.
class InputViewController: UIViewController, InputProtocol {
private let headerView = HeaderView()
override func viewDidLoad() {
super.viewDidLoad()
// layout, etc.
setupCallbacks()
}
internal func setupCallbacks() {
headerView.onUpdate = { (text: String) in
// called when user confirms text entry in headerView
self.onHeaderUpdate()
}
}
internal func onHeaderUpdate() {} // Blank function
}
setupCallbacks() and onHeaderUpdate() are methods defined in the protocol that the InputViewController conforms to. The HeaderView implements a callback closure that is handled in setupCallbacks() by headerView.onUpdate...
The protocol that InputViewController conforms to:
protocol InputProtocol {
func setupCallbacks()
func onHeaderUpdate()
}
To illustrate this, I drew up a diagram;
Since I want the subclasses of InputViewController to override the onHeaderUpdate() method, is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank or is there another solution to this?
is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank
Yes, that is called an abstract method. It is common to give it code that crashes deliberately, as a way of saying, “I exist only to be overridden in a subclass.”
(I should go further and say that what you are creating, a base view controller that carries out initial configurations that all subclasses must implement, is also normal.)

Can ViewModel class conforms to UITableviewDelegate and UITableViewDataSource in iOS as per MVVM

I have a viewcontroller which displays a table view with complex UI and different type of data depending on certain conditions/usertype. This involves logic to segregate and process data on user selection and hide/unhide expand/close section. As I am using MVVM pattern, can my viewmodel class conform to UITableviewDelegate and UITableViewDataSource, so that I have a thinner viewcontroller?
Something like -
class HomeViewController: UIViewController {
.
.
let viewModel = HomeViewModel()
#IBOutlet weak var tableView: UITableView!
.
.
tableView.delegate = viewModel
tableView.dataSource = viewModel
}
class HomeViewModel: UITableViewDataSource, UITableViewDelegate {
//Implement delegates
}
Yes you can, set any object that conforms to those protocols as the delegate, data source or separate them to different objects, anything and anyone can implement a protocol.
When writing a complex table view data source or delegate, It’s better to define a type whose purpose is to be a table view’s data source. This helps to better separate responsibilities between objects.
You can find an example by Apple implementing different objects as the table/collection View's data source here:
Advanced User Interfaces with Collection Views by apple
These object/s doesn't have to be your viewModel, but please see a good example when it is:
MVVM with viewModel as the table datasource

Data exchange between UIViewControllers, Service and Views

Top brick problem I face every time is a passing data between component.
I can separate this problem in few sub-problems:
Do we need to pass data to views? Do we need to change model directly in view?
Can we use some service call directly from views, or we need to pass back all operation to UIViewConrtoller and only then controller will request appropriate service.
Specifying indexes instead of real models.
So the first question demonstrate case when we create UITableViewCell and pass to it data directly. So we now have ability to modify some properties of this data object. Let's say we have PlayListViewController that implement UITableView datasource. If you see bellow I set data model directly into view.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = viewController.theTableView.dequeueReusableCellWithIdentifier(String(SongTableViewCell.self)) as! SongTableViewCell
cell.song = songs[indexPath.row]
return cell
}
Second one for example, when you want to trigger something in your service directly from your SongTableViewCell without using any delegate calls. Let's say in SongTableViewCell I want to play my song and on this cell I have play button. So then it is a simple solution - I can bind cell with UIButton action touch up inside for example and invoke needed operation in my service:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
MusicService.playSong(song)
}
But usually I do another things. I store delegate instance in my cell that pass back any action to a controller and controller decides what to do:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton()
}
In this case SongTableViewCell looks like this:
class SongTableViewCell: UITabeViewCell
{
weak var delegate:SongTableViewCellDelegate?
...
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton(index)
}
}
and my view controller implements this didTappedPlayButton method where it calls MusicService.playSong(song). Here is 3rd problem if we have not pushed model object into UITableviewCell then we need to say somehow to view controller that it needs to play some appropriate song from array. So I use index that I set into the UITableviewCell which is sometimes can tangle other developers. I don't know if it's better to use index or data model. I understood advantage of changeability but index say nothing for developers and data model object says a lot.
I know it's more architecture questions, but maybe we can outline some props and cons of these 3 approaches/problems.
I found a post and it says
Classes=more complexity. Values=less complexity. Not sure it can fit all my subquestion, but maybe values it much better then transit entire class.

Swift, how to tell a controller that another controller is its delegate

I'm learning Swift and I'm studying the delegation pattern.
I think I understand exactly what is delegation and how it works, but I have a question.
I have a situation where Controller A is the delegate for Controller B.
In controller B I define a delegate protocol.
In controller B I set a variable delegate (optional)
In controller B I send message when something happens to the delegate
Controller A must adopt method of my protocol to become a delegate
I cannot understand if every delegate controller (in this case A) listens for messages sent by controller B or If I have to tell to controller B that A is now his delegate.
I notice that someone use this code (in controller A)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Example" {
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddItemViewController
controller.delegate = self
}
}
Is this the only way to tell a delegator who is his delegate?
I believe, you need to tell a deligator who is its delegate upon creation of that it. Now, the delegator can be created programatically or through storyboard. So, based on that you have two options, you can tell it who is its delegator programatically like you showed in the code or from IB.
The key here is upon creation. Let's me explain myself. Take the case of a UIView. Say, you want a Custom UIView object(CustomView). So, you drag and drop a UIView in your View Controller and in the identity inspector, you assign its class as of your CustomView's class. So, basically, as soon as the controller is created, your custom view will also be created. Now, you can either say it that the View Controller in which it is created is its delegate or You can go to the IB and connect the view's delegate to the View Controller.
Now, let's assume that you wanted the custom view to be created in your ViewController programatically. In that case, you would probably call the -initWithFrame: method to create the view and upon creation you tell that delegator that who is its delegate like-
myCustomView.delegate = self;
same goes with a View Controller.
controller.delegate = self;
So, basically to tell a delegator who is its delegate, you first need that delegator to be created. At least, that's what I think.
I think one of the best example of delegation is UITableView.
Whenever you want the control of various properties of a tableView e.g. rowHeight etc, you set your controller to be the delegate of your tableview. To set the delegate of your tableView you need to have tableView created obviously as pointed out by #natasha.
So in your case, you can set delegate of your delegator when you create it or when you find a need for the controller to be delegate of your delegator but you definitely need your delegator to be present to set its property.
You can set your controller as delegate at any time when you require control.
I'm sure you want your UIViewController to act like described, but here is a simpler example how to use the delegation pattern with custom classes:
protocol ControllerBDelegate: class {
func somethingHappendInControllerB(value: String)
/* not optional here and passes a value from B to A*/
/* forces you to implement the function */
}
class ControllerB {
var delegate: ControllerBDelegate?
private func someFunctionThatDoSomethingWhenThisControllerIsAlive() {
/* did some magic here and now I want to tell it to my delegate */
self.delegate?.somethingHappendInControllerB(value: "hey there, I'm a magician")
}
func doSomething() {
/* do something here */
self.someFunctionThatDoSomethingWhenThisControllerIsAlive()
/* call the function so the magic can really happen in this example */
}
}
class ControllerA: ControllerBDelegate {
let controllerB = ControllerB()
init() {
self.controllerB.delegate = self /* lets say we add here our delegate*/
self.controllerB.doSomething() /* tell your controller B to do something */
}
func somethingHappendInControllerB(value: String) {
print(value) /* should print "hey there, I'm a magician" */
}
}
I wrote the code from my mind and not testet it yet, but you should get the idea how to use such a pattern.

Pass data when dismiss modal viewController in swift

I'm trying to pass data from the modal ViewController to his source ViewController. I think I have to use delegation but it doesn't work.
protocol communicationControllerCamera{
func backFromCamera()
}
class Camera: UIViewController{
var delegate: communicationControllerCamera
init(){
self.delegate.backFromCamera()
}
}
class SceneBuilder: UIViewController, communicationControllerCamera{
func backFromCamera(){ // Never called
println("YEAHH")
}
}
The backFromCamera method it's not called. What did I do wrong?
You didn't set a delegate so it was empty when you tried to call backFromCamera().
Here's a simple working example you can test out. Notice the use of the optional type (?) for the delegate.
// Camera class
protocol communicationControllerCamera {
func backFromCamera()
}
class Camera: UIViewController {
var delegate: communicationControllerCamera? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.backFromCamera()
}
}
// SceneBuilder class
class SceneBuilder: UIViewController, communicationControllerCamera {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var myCamera = Camera()
myCamera.delegate = self
self.presentModalViewController(myCamera, animated: true)
}
func backFromCamera() {
println("Back from camera")
}
}
You can find all the information you need in Apple's Swift documentation.
Obviously the chosen answer is correct, but it didn't help me. I did successfully implement protocols though, so I wanted to provide my own explanation in case anyone is struggling with grasping the concept, like I was.
Protocol Code Is Written in Three Places:
Two ViewController Classes
The Protocol itself (code written outside of VC classes)
When I write my protocols, I put them in my "ToolBox" document and I still write comments to remind myself which VCs are doing what. Two examples:
So there is always:
The protocol code (shown above)
Code in a VC which initiates the action
Code in a VC which is delegated to carry out the action
1. The protocol code
See the image above for a reference. Essentially, the protocol code is just where you give the protocol a name and declare what functions you want to remotely call/delegate to. Name the protocol. Declare the names of the functions that can be called upon and declare their parameter types such as string, etc.
2. Code in a VC which initiates the action
This is the code that initiates the protocol. In this example, this is code from a table cell, which needs to delegate some work back to the main table VC. The first screenshot shows the creation of the delegate variable and the second screenshot is the actual use of that variable.
So the below code are table-cell buttons. They all need to trigger code outside of the cell VC, so they all trigger functions using the protocol I declared above.
3. Code in a VC which is delegated to carry out the action
Now the protocol is being called, but which VC answers the call? To answer that question, choose the VC and add the protocol name to the class declaration:
Lastly, you need the actual meat of the whole thing. Not the trigger, not the protocol itself, not the class declaration... but the actual function you want to call:
Hope This Helps
I don't know why protocols just wouldn't sink through my thick skull but they wouldn't. I hope this helps others like me!

Resources