Search code examples
iosswiftinheritancecontrollercomposition

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?


Solution

  • 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.