Alex Basson
Alex Basson

Reputation: 11287

Swift: Subclassing a UIViewController's root view

tl;dr: How can I tell Swift that I'm overriding MyViewController's view property with a subclass of UIView?


What I'd like to do

I'm a big fan of providing a subclass of UIView for a UIViewController's view. For example:

// MyView --------------------------------------------------------

@interface MyView: UIView
@property (nonatomic, strong) UITableView *tableView;    
@end

@implementation MyView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        _tableView = [[UITableView alloc] initWithFrame:frame];
    }
    return self;
}

@end

// MyViewController ----------------------------------------------

@interface MyViewController: UIViewController <UITableViewDataSource>
@property (nonatomic, retain) MyView *view;
@end

@implementation MyViewController

- (void)loadView {
    self.view = [[MyView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.tableView.dataSource = self;
    // etc.
}

@end

This is great because it separates the view creation and layout logic from the view controller. Ok, fine.

Near as I can figure, this translates into Swift like so:

// MyView --------------------------------------------------------

class MyView: UIView {
    let tableView: UITableView!

    init(frame: CGRect) {
        super.init(frame: frame)
        tableView = UITableView(frame: frame)
    }
}

// MyViewController ----------------------------------------------

class MyViewController: UIViewController, UITableViewDataSource {
    override func loadView() {
        view = MyView(frame: UIScreen.mainScreen().bounds)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // this causes the compiler to complain with:
        // 'UIView' does not have a member named 'tableView'
        self.view.tableView.dataSource = self
    }
}

The trouble is I can't seem to figure out how to tell the view controller that its view is an instance of MyView rather than UIView itself.

Failed attempts

Here's what I've tried so far:

I've tried this at the top of MyViewController, with the following error:

override var view: MyView!
// error: Cannot override mutable property 'view' of
//        type 'UIView' with covariant type 'MyView!'

I've tried this in loadView, but no luck:

view = MyView(frame: UIScreen.mainScreen().bounds) as MyView
// this produces the same error as in the original code:
// 'UIView' does not have a member named 'tableView'

So here's the question

How can I tell Swift that I'm overriding MyViewController's view property with one of subclass MyView? Is this even possible? If not, why not?

Upvotes: 23

Views: 17319

Answers (2)

RK1979
RK1979

Reputation: 253

can we use something like this?

class MyViewController: UIViewController, UIScrollViewDelegate {

weak var viewAlias: UIScrollView!

override func loadView() {
    let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
    viewAlias = scrollView

    view = scrollView
}

override func viewDidLoad() {
    //super.viewDidLoad()
    viewAlias.delegate = self
    viewAlias.contentSize = view.frame.size

    // Do any additional setup after loading the view.

}

Upvotes: 0

Nate Cook
Nate Cook

Reputation: 93276

I think the exact same implementation may not be possible in Swift. From the Swift book section about overriding properties:

“You must always state both the name and the type of the property you are overriding, to enable the compiler to check that your override matches a superclass property with the same name and type.”

However, you could use a computed property that returns a typecast version of your view controller's view property, and it would be almost as clean:

class MyViewController: UIViewController, UITableViewDataSource {
    var myView: MyView! { return self.view as MyView }

    override func loadView() {
        view = MyView(frame: UIScreen.mainScreen().bounds)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.myView.tableView.dataSource = self
    }
}

Upvotes: 26

Related Questions