Swift: How to handle tightly coupled views? - ios

I have a UIViewController that is part of a UINavigationController and I want a custom view in my navigation bar. The code is then something like this:
class MyViewController: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.customNavigationView = MyNavigationView()
self.navigationItem.titleView = self.customNavigationView
}
}
MyViewController and MyNavigationView are tightly coupled in a sense - they form a single piece of UI and only make sense when used together. I wonder what the preferred way to handle such cases is pattern-wise. In particular, what's the preferred way to send messages from MyNavigationView to MyViewController (such as buttons tapped).
I saw the following options:
Use a delegate pattern, create a MyNavigationViewDelegate protocol and add a delegate property to MyNavigationView. While the most object-oriented approach, it seems a bit "over-engineered" to me and has a lot of overhead for something that is basically a single unit of UI
Make MyNavigationView an inner class of MyNavigationController to indicate their strong relationship. Seems fine, but I like to have a file per class for easy navigation
Use a weak var myViewController: MyViewController? in MyNavigationView. I don't think the variable should be an optional, though, because semantically it is not
Use a let myViewController: MyViewController and a custom initializer in MyNavigationView that sets it. Seems the best option to me right now, but I'm not sure if it can create a memory leak since its a strong reference cycle
Thanks to #Mohsen Hossein pour: Sending notifications from MyNavigationView to MyViewController.
I wonder what other people's thoughts on this are. Is there are clear pattern that should be used here or is it a matter of taste?

While any answer to this question is ultimately opinionated in the MVP design pattern a view should not contain any business logic, just display logic. which means it should not know anything about its host.
The way I would approach it is to write the connection code and the interaction code in its host instead.
class MyNavigationView : UIView
{
var button : UIButton!
}
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
self.customNavigationView = MyNavigationView()
self.navigationItem.titleView = self.customNavigationView
// here you can assign gesture recognizers or add event listeners
}
}

I would use a notification if you only need to know if something happened in the other view, it is easier and faster to implement, if you need more explanation on how to use notifications please ask and I can edit my answer and explain some more.

Related

Refactoring OOP to Protocol Oriented Programming

I have FirstViewController which has some IBOutlets, functions and presenter. Then I have SecondViewController which has all same functions as FirstViewController but with one additional function, IBOutlets and presenter exactly same as presenter of FirstViewController, but with 1 additonal functions.
I have read about OOP and POP and people recommend to use POP.
But my question is if in this situation when I need to have SecondViewController with all same functions and IBOutlets as in FirstViewController, inheritance is not better than protocols?
If I want to use protocol, how should it to be done if I want to avoid rewrite same code? With protocols default implementation?
With inheritance
class FirstViewController: UIViewController {
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
...
...
func firstFunction() {
...
}
func secondFunction() {
...
}
}
class SecondViewController: FirstViewController {
#IBOutlet weak var additionalButton: UIButton!
...
...
func additionalFunction() {
...
}
}
With default implementation
protocol FirstViewControllerProtocols {
func firstFunction()
func secondFunction()
}
extension FirstViewControllerProtocols {
func firstFunction() {
print("do something")
}
func secondFunction() {
print("do something")
}
}
and then
class FirstViewController: UIViewController, FirstViewControllerProtocols {
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
}
protocol SecondViewControllerProtocol {
func thirdFunction()
}
extension SecondViewControllerProtocol {
func thirdFunction() {
print("do something new")
}
}
class SecondViewController: UIViewController, FirstViewControllerProtocols, SecondViewControllerProtocol {
#IBOutlet weak var additionalButton: UIButton!
}
But with protocol approach I can't inherit iboutlets.
Your example of POP is not correct. The point is just to extract the shared code into protocols, not duplicate things unnecessarily. So FirstViewControllerProtocols makes sense, but there's absolutely no reason for SecondViewControllerProtocol. What second type conforms to SecondViewControllerProtocol? If you get rid of that, you'll see that most of your problems go away.
But not all of them. You're also talking about IBOutlets, which are not just a matter of shared code. If SecondViewController really is a kind of FirstViewController (rather than just "happens to share a few buttons"), then inheritance might make sense. If it's just that there are a few related IBOutlets, duplicate the IBOutlets since they're related to the type. That's not inheritance.
Given your description, you're thinking in terms of "I have a lot of code that happens to be kind of similar, and so I should make a thing to abstract it." That's not the approach; if they're just "kind of similar" then ask yourself if you're creating abstractions for no purpose. If FirstViewController were changed, would SecondViewController necessarily also change? If not, they're not actually very related. "DRY" (Do not Repeat Yourself) is about concepts, not keystrokes.
The approach is "I have two things that are fundamentally similar, will change in similar ways over time, and have algorithms that should apply to both, so I should extract a protocol."
Subclassing one view controller class from another custom view controller (as well as using default implementations in protocols) seems so elegant at first glance, but nine times out of ten, it is a mistake. If it is an abstract “base view controller”, for some common behavior, that can sometimes bear some utility (though depending upon what this shared behavior is, there are often better patterns).
But if these two view controller classes are used for different scenes in your storyboard (and your reference to sharing IBOutlets suggests that this is the case), it becomes, in my experience, a maintenance nightmare. I used this pattern in a few early projects, was quite pleased with the elegance of my code at the time, but when I was later modifying and enhancing the respective view controllers, I regretted having two view controller classes for two different scenes so tightly entangled.
Nowadays, we strive for view controller classes that are as thin and dumb as possible. (See Dave DeLong’s A Better MVC posts or the video. It is also one of the many motivating factors behind MVVM and related patterns.) View controllers are solely intended for configuring views and initiating behaviors upon user interaction or other system events. Anything beyond that should generally be abstracted out of the view controller. And if there are common UI elements in various view controllers, we will often consider view controller containment to abstract the shared UI to a third view controller which becomes a child of the first two.
So rather than POP vs OOP, ask yourself whether all this shared behavior belongs in either one of these two view controllers at all. If the shared behaviors are various UIKit delegate methods (e.g. table data source, collection view delegate methods, etc.), that calls for one solution. If they are business logic, it calls for a different solution. If they are utility methods, yet another. If it is a common set of subviews, again, yet another pattern. It just depends.

Creating Controllers for the UIViews found in a UIViewController

In the iOS world, we're all used to the following pattern:
class UIViewController {
open var view: UIView!
}
A ViewController is obviously a controller controlling the view, which contains a lot of subviews.
Now, I have a lot of subviews that I want to reuse, and I want to enrich them with more functionalities. Think of a UISlider, a UILabel, or a UITableView that react to some events or some changes in the model. These subviews also need to be #IBDesignable with IBInspectable properties for customisation purposes. I also want to share those components through a library as well.
So in a way, I want small controllers controlling those subviews that will end up in the view of the ViewController.
I am thinking of doing this for the UIKit classes:
#IBDesignable
public class CustomSlider: UISlider {
}
That is a nice way to be able to provide the component with customisation options. The downside is that we're using inheritance here (would rather use composition), and I'm not sure if CustomSlider is really considered here a controller or not.
Can anyone tell me what are good practices around creating controllers for subviews that are customisable? Thanks in advance!
EDIT: Specific case for Views that have delegates and datasource:
#objc public class CustomTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
#IBInspectable public var someCustomField: UInt = 0
public override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
dataSource = self
delegate = self
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
dataSource = self
delegate = self
}
// Implement UITableViewDataSource and UITableViewDelegate
}
Is this bad pattern to have datasource = self and delegate = self, or is it ok?
It's a judgement call. I'd say if all you are doing is adding features to a single view class, it is probably better to just use a custom subclass like your CustomSlider example.
If, on the other hand, you're using small suites of objects, like a slider, a text field, a segmented control and a couple of labels, you might want to think about using container views, embed segues, and custom view controllers. Think of that as setting up tiles that manage sets of UI elements. You can put such a view controller anywhere you want, and size it as needed.
You could also create a custom controller object that manages one or more custom views, but there isn't really system "plumbing" for that, so the burden is on you to build mechanisms to support that approach. You have to teach your view controllers to talk to a controller object that isn't a view, but that HAS views inside it.
Apple does what you're talking about in a couple of instances: UITableViewController and UICollectionViewController, and I think they didn't do it right. A UITableViewController or UICollectionViewController can manage only a single view. You can't put a button at the bottom, or a label somewhere, or a segmented control. The content view of one of those specialized view controllers must be the corresponding view object, which limits their usefulness. You can, of course, get around the problem by using container views and embed segues, and that leads me to my suggestion.
EDIT:
As far as making a view object it's own data source, I would call that "a violation of the separation of powers". A table view is a view object, and the data source is a model object. By combining them, you're collapsing the view and the model into one. That's going to create a larger, very specialized object that is less likely to be reusable.
The same goes for making an object it's own delegate. The idea of the delegate pattern is to be able to leave certain decisions about an object's behavior up to somebody else, so that its more flexible. By making it its own delegate you are limiting the table view's range of behavior and making it less reusable.
Neither thing is going to "warp your mind, curve your spine, and make the enemy win the war" but they seem ill-advised.

Why/when do we have to call super.ViewDidLoad?

Everyone tells me "Use super.viewDidLoad() because it's just like that" or "I've been doing it always like that, so keep it", "It's wrong if you don't call super", etc.
override func viewDidLoad() {
super.viewDidLoad()
// other stuff goes here
}
I've only found a few topics about Objective-C cases and they were not so enlightening, but I'm developing in Swift 3, so can any expert give me a good detailed explanation on this?
Is it a case of just good practice or are there any hidden effects?
Usually it's a good idea to call super for all functions you override that don't have a return value.
You don't know the implementation of viewDidLoad. UIViewController could be doing some important setup stuff there and not calling it would not give it the chance to run it's own viewDidLoad code.
Same thing goes when inheriting from a UIViewController subclass.
Even if calling super.viewDidLoad doesn't do anything, always calling it is a good habit to get into. If you get into the habit of not calling it, you might forget to call it when it's needed. For example when subclassing a ViewController that depends on it from a 3rd party framework or from your own code base.
Take this contrived example:
class PrintingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("view has loaded")
}
}
class UserViewController: PrintingViewController {
override func viewDidLoad() {
super.viewDidLoad()
// do view setup here
}
}
Not calling viewDidLoad here would never give PrintingViewController a chance to run its own viewDidLoad code
If you don't want to do anything in viewDidLoad just don't implement it. The super method will be called anyway.
I have a secret, when I worked at Apple I read the source code for UIKit, partly to answer questions I had like this, viewDidLoad is empty in all the UI*ViewController classes.
Naturally I am not there anymore, they may have changed this.
I think calling super.viewDidLoad() is, first of all, a good practice.
The usual thing in iOS is to do all of your subclass setups after the superclass has completed the setup that it needs to do (initializing properties, laying things out, etc.). If you don't give the superclass a chance to handle all of its setups before you start changing things around, it's possible you'll encounter some strange bugs and behavior.
We can draw a parallel with Class Initialization: "A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property." We're doing this to be sure that all superclass properties have a value and based on that fact we could safely use them through inheritance in our subclass.
Rule of thumb:
When initializing/setting up, run the superclass' implementations first.
When tearing down/cleaning up, run the superclass' implementation last.
Assuming that viewDidLoad() is some sort of initialization we should call super.viewDidLoad() first to correctly set things up in the superclass.
If we check the implementation of viewDidLoad() in UIViewController base class, we can see that it's empty. So maybe the only one reason to calling super.viewDidLoad() from you child class is a good coding style :-) Lets follow it!
That depends on the implementation of viewDidLoad in the class UIViewController (from which all view controllers inherit). If it's empty than calling super.viewDidLoad() won't do much. However if it has some functionality regarding the view controller then you certainly would want to use it.
Since it's not in your hands regarding the implementation of a UIViewController you should always call this method
When inheriting directly from UIViewController, call super when its documentation tells you to.
For example, viewWillAppear(Bool) says, "If you override this method, you must call super at some point in your implementation," whereas viewDidLoad() does not.
If you are not inheriting directly from UIViewController and the class you are inheriting from does not have reliable documentation, or may silently introduce a breaking change requiring that super be called, then always call super.
if your class is been inherited from UIViewController directly then there is no need to invoke super.viewDidLoad. This definately make your code look bit consice but usually iOS community suggest to call it anyway.
if your class is been inherited from custom UIViewController which indeed has some functionality which your class can leverage then invoke super.viewDidLoad.

How does iOS delegation actually work?

I understand how to use delegation with iOS objects that already exist. For example, if I create an instance of a UITableView, and my view controller conforms to the UITableView delegate, I can implement the various methods of the UITableView delegate. My newly create table can receive notifications, for example, when didSelectRowAtIndexPath is called.
My question is why did my table get this particular delegate callback? My understanding is that the delegate is just a list of methods with no implementation. It seems to me there must be a lot more going on. What is really going on "behind the scenes"?
Image if I were to rename all the delegate methods to the following:
- mysteryMethod1
- mysteryMethod2
- mysteryMethod3... Etc
One of these methods is responsible for setting the height of a row at a particular index. Another one these methods will be responsible for editing a particular row.
Everything I read about delegation says the delegator makes a contract with the delegate. The delegate promises to implement the methods. When it does, somehow everything is wired up correctly and everything magically works. What is the magic that I'm not seeing?
I think that in order to know how delegates actually work you should create your own custom delegate first, that way you will see that there is no magic under the hood, you probably can't see the implementation of the actual apple build in delegate methods but I assure you that there is a lot of logic implemented in those but it's just not available for privacy reasons I assume.
When you create your custom delegate let's say for example...
You have Class A and in this class, you start by creating a protocol
protocol ClassADelegate: class {
func changeBackgroundColor(_ color: UIColor?)
}
In this class you have a delegate property like this.
weak var delegate: ClassADelegate?
Let's say that this class is a Viewcontroller and you have an IBACtion on it like a UIbutton, and your goal is that when you tap that button another ViewController in your app change its background color to blue. Inside this action in Class A you do this...
func someAction() {
delegate?.changeBackgroundColor(.blue)
}
,
Let's say that the "magic" happens here in class A, by the way if you are thinking in delegates using UITableview think that UItableView is class A.
ok so now you have Class B that is where you want to change the color right?
Well now class B needs to conform to the protocol like this, just like you also conform to the protocol UITableViewDelegate etc.
class ClassB: UIViewController, ClassADelegate {
}
Now think of the word delegate for a second and think what that means, you are just delegating responsibility to somebody else, right? and yes, in this case, ClassB is going to be the delegated, for that we need to have an instance of Class A in class B just to have access to it's delegate property.
let classa = ClassA()
classa.delegate = self
the final step is just to call the method of the protocol like this..
func changeBackgroundColor(_ color: UIColor?) {
view.backgroundColor = color
}
To conclude if yo see this method in your class but you don't have access to the implementation of the protocol, you will ask yourself "where does this magic color coming from??" but as you saw it just comes from another class where a protocol belongs to, hope this helps.

What would be better strategy for IBActions in protocols in Swift?

I am creating a Swift project and I want to define a specific protocol that enforces other components to implement a animate method:
protocol AnimatableBehavior {
#IBAction func animate()
}
The problem is I want this method to be an IBAction, but I get this error from XCode:
Only instance methods can be declared 'IBAction'
My question is, how would you implement such a thing?
I have considered:
Remove #IBAction, but then I need to remember adding it in every class that implements. Not very elegant and error prone.
Create a base class instead of protocol, but then I am enforcing all components to subclass my base class instead of their own choice ones, so it is not a valid option.
Any other ideas?
EDIT: Response to comments below.
The idea of the IBAction on the protocol is because in the project there will be many different devs implementing small UI components, all of which have the animate method. The components can be added programatically or by Interface Builder and it is very convenient that they are always IBAction because I plan to compose them from IB files to simplify the View Controllers to the maximum extent (and this is clearly a View only task).
Therefore, the solution proposed below of adding a method in the controller that just calls the animate of the component is not good because it is redundant code and makes your Controller more dependent on your View.
The idea of letting the dev to remember adding the IBAction keyword on the method is workable, but as I said it is error prone (and by that I mean that there will be some forgetting about it), and I want to make sure that this is always accessible from IB. It also adds extra cognitive load, because I will need to document this lack of IBAction on the protocol and request the implementor to add it manually.
I know is not the common way of working in iOS and UIKit, but that was why I posted the question, maybe someone has an alternative idea.
It doesn't make any sense to have an #IBAction in a protocol. #IBAction is nothing more than a keyword for Interface Builder to have a hook when you're control+dragging from Interface Builder to your actual source code.
This is just a simple misunderstanding of what #IBAction actually is and does.
A method does not have to be marked as #IBAction in order for it to be the target of a UI element's actions. You programmatically hook up any method to any action using the addTarget set of methods that UI elements have. The method does not have to be marked as an #IBAction to do this.
Regardless of whether or not a protocol defines a method as #IBAction, the class conforming to the protocol can add it (and still be conforming to the protocol.
protocol FooProtocol {
func doSomething()
}
class ViewControllerA: UIViewController, FooProtocol {
#IBAction func doSomething() {
// do something
}
}
class ViewControllerB: UIViewController, FooProtocol {
func doSomething() {
// do something
}
}
Both of these view controller subclasses conform to the protocol, and having #IBAction there is ONLY necessary if you intend to hook up an action from interface builder!
Ultimately, whatever you're trying to do, if you think an #IBAction is necessary in your protocol, I think you're taking the wrong approach to something. It's hard to say what the right approach would be without knowing more details about what you're actually doing, but it never makes sense for #IBAction to belong in a protocol.
To me, it seems like the methods your protocol enforces shouldn't at all be tied to #IBAction methods. Instead, whatever user interaction should trigger the animation, should in turn call the animate method. For example, if we weren't talking about the protocol, my recommendation would be this sort of set up:
class ViewController: UIViewController {
#IBAction func buttonThatStartsAnimation {
self.animate()
}
func animate {
// code that does all the animation
}
}
So, with the protocol, we should take the same seperation of duties between the method that's actually initiating the animation code (which in the case of protocols, this is obviously some other outside class), and the animate method should only ever handle doing the relevant animations.
Importantly, just as a general rule, you shouldn't be directly referring to your #IBAction methods or your #IBOutlet variables directly from outside the class which defines them.
I totally agree with OP, although until Swift 3.1 you can't really declare anything as #IBOutlet, #IBAction, #objc etc in a protocol. As a workaround, I chose to build something based on pod 'ActionKit' and wrote something like:
protocol RequiresAnimation {
var animateButton: UIButton! { get }
func enableAnimateButton()
func actionAnimate()
}
extension RequiresAnimation where Self: UIViewController {
func enableAnimateButton() {
animateButton.addControlEvent(.touchUpInside) {
self.actionAnimate()
}
}
func actionAnimate() {
// animate here
}
}
And make your view controller:
class MyViewController: UIViewController, RequiresAnimation {
#IBOutlet var animateButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
enableAnimateButton()
}
}
I wish there would be any easier approach, but so far you make need to do these 2 things manually: declaring your button as #IBOutlet and call a setup function. The reason why we need to import ActionKit is that we can't addTarget in protocol extension.

Resources