Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set UIViewController view property to custom UIView class without storyboard / nib

Tags:

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:

Result

like image 491
Hapeki Avatar asked Dec 31 '16 00:12

Hapeki


People also ask

What is the difference between UIView and UIViewController?

They are separate classes: UIView is a class that represents the screen of the device of everything that is visible to the viewer, while UIViewController is a class that controls an instance of UIView, and handles all of the logic and code behind that view.

Is UIViewController a subclass of UIView?

Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller's view hierarchy. A view controller's main responsibilities include the following: Updating the contents of the views, usually in response to changes to the underlying data.

What is Loadview in iOS?

Creates the view that the controller manages. iOS 2.0+ iPadOS 2.0+ Mac Catalyst 13.0+ tvOS 9.0+


2 Answers

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:

You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. The views you create should be unique instances and should not be shared with any other view controller object. Your custom implementation of this method should not call super.

This would look something like this:

class LoginViewController: UIViewController {         override func loadView() {         // Do not call super!         view = LoginView()     } } 

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 call window.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 with NSLayoutConstraint.

private func setupLabels(){     // Initialize labels and set their text     usernameLabel = UILabel()     usernameLabel.text = "Username"     usernameLabel.translatesAutoresizingMaskIntoConstraints = false // Necessary because this view wasn't instantiated by IB     addSubview(usernameLabel)      passwordLabel = UILabel()     passwordLabel.text = "Password"     passwordLabel.translatesAutoresizingMaskIntoConstraints = false // Necessary because this view wasn't instantiated by IB     addSubview(passwordLabel)      NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[view]", options: [], metrics: nil, views: ["view":usernameLabel]))     NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-20-[view]", options: [], metrics: nil, views: ["view":passwordLabel]))      NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]", options: [], metrics: nil, views: ["view":usernameLabel]))     NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[view]", options: [], metrics: nil, views: ["view":passwordLabel])) } 

For more info on the visual format language used to create the constraints, see the VFL Guide

like image 183
Jadar Avatar answered Oct 13 '22 15:10

Jadar


Override the layoutSubviews method to update the frames of the subviews inside your custom view.

And never call super.loadView(). This is documented for the loadView method.

like image 42
rmaddy Avatar answered Oct 13 '22 16:10

rmaddy