I have a UIViewController
called LoginViewController
. I want to build the view of that LoginViewController
fully programmatically in a custom UIView
class called LoginView
instead of building all the elements within my LoginViewController
. This way I'm preventing "View" code in a Controller class (MVC).
In the code below I'm setting the view of my LoginViewController
to my LoginView
which for simplicity only contains 2 UILabels
class LoginViewController: UIViewController {
override func loadView() {
super.loadView()
self.view = LoginView(frame: CGRect.zero)
}
The LoginView class initialises both labels and should set some constraints.
class LoginView: UIView {
var usernameLabel: UILabel!
var passwordLabel: UILabel!
override init (frame : CGRect) {
super.init(frame : frame)
setupLabels()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func setupLabels(){
//Init labels and set a simple text
self.usernameLabel = UILabel()
self.usernameLabel.text = "Username"
self.passwordLabel = UILabel()
self.passwordLabel.text = "Password"
//Set constraints which aren't possible since there is no contentView, perhaps using the frame?
}
}
This doesn't work since the view's bounds are 0. However I couldn't find any resource that gives insight in whether this is possible, so I tried my approach which didn't work.
How you set the view of a UIViewController to a custom UIView which is made programmatically? Or is the above snippet recommended?
This is the working solution based on Jadar's answer:
class LoginViewController: UIViewController { override func loadView() { view = LoginView() } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } class LoginView: UIView { var usernameLabel: UILabel! var passwordLabel: UILabel! override init(frame: CGRect) { super.init(frame: frame) self.usernameLabel = UILabel() self.usernameLabel.text = "Username" self.passwordLabel = UILabel() self.passwordLabel.text = "Password" addSubview(usernameLabel) addSubview(passwordLabel) if let superview = usernameLabel.superview{ //Setting AutoLayout using SnapKit framework usernameLabel.snp.makeConstraints { (make) in make.center.equalTo(superview) } } }
Result:
Override the
layoutSubviews
method to update the frames of the subviews inside your custom view.And never call
super.loadView()
. This is documented for theloadView
method.You should load the custom view when LoginViewController's layout constraints are already loaded, try this:
In your Viewcontroller's loadView method do this:
In your UIView's custom class do this:
Now your UIView has a frame , and you can setup all your views through code by providing them frames.
It looks there are really two questions here. One, what is the best way to programmatically set up a ViewController. The other, how to set up a View programmatically.
First, The best way to have a ViewController programmatically use a different UIView subclass is to initialize and assign it in the
loadView
method. Per Apple's docs:This would look something like this:
This way you shouldn't have to deal with sizing it, as the View Controller itself should take care of it (as it does with it's own UIView).
Remember, do not call
super.loadView()
or the controller will be confused. Also, the first time I tried this I got a black screen because I forgot to callwindow.makeKeyAndVisible()
in my App Delegate. In this case the view was never even added to the window hierarchy. You can always use the view introspecter button in Xcode to see what's going on.Second, you will need to call
self.addSubview(_:)
in your UIView subclass in order to have them appear. Once you add them as subviews, you can add constraints withNSLayoutConstraint
.For more info on the visual format language used to create the constraints, see the VFL Guide