0

Say I create a tableView in navigation controller programmatically. After removing storyboard file and its reference. I put all UI initialization and constraints code into loadView() as below code.

Running with real device, but the table view is not showed up, and soon this waring pops up.

If I put those code in viewDidLoad, everything works fine. So, how could I track down this issue? I have searched some similar threads but without fixing my problem.

warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    let table = UITableView()
    var pictures = [Picture]()
    let defaults = UserDefaults.standard

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // if put code in loadView() into here, everything works fine.
        loadData()
    }
    
    override func loadView() {
        title = "My Pictures"
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addPicture))
        
        view.addSubview(table)
        table.delegate = self
        table.dataSource = self
        table.register(PictureCell.self, forCellReuseIdentifier: "picture")
        table.translatesAutoresizingMaskIntoConstraints = false
        table.rowHeight = 120
        
        NSLayoutConstraint.activate([
            table.topAnchor.constraint(equalTo: view.topAnchor),
            table.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            table.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            table.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }
    
    func loadData() {
        DispatchQueue.global().async { [weak self] in
            if let savedPcitures = self?.defaults.object(forKey: "pictures") as? Data {
                let jsonDecoder = JSONDecoder()
                do {
                    self?.pictures = try jsonDecoder.decode([Picture].self, from: savedPcitures)
                } catch {
                    print("Failed to load pictures.")
                }
            }
            DispatchQueue.main.async {
                self?.table.reloadData()
            }
        }
    }
    
    @objc func addPicture() {
        let picker = UIImagePickerController()
        if UIImagePickerController.isSourceTypeAvailable(.camera) {
            picker.sourceType = .camera
        } else {
            fatalError("Camera is not available, please use real device")
        }
        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
    }

...
    

enter image description here

Zhou Haibo
  • 1,681
  • 1
  • 12
  • 32
  • why dont you put the viewDidLoad then ? you wonder why this happen or fix the your problem ? – zeytin Nov 06 '20 at 12:01

2 Answers2

1

If you override loadView(), it is up to you to create and set the controllers "root" view.

If you add this to your code:

override func loadView() {
    title = "My Pictures"
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addPicture))

    // add these three lines
    let newView = UIView()
    newView.backgroundColor = .white
    view = newView
    
    view.addSubview(table)
    // ... rest of your original code

You should be able to navigate to your controller.

That said --- unless you have a specific reason to override loadView(), you probably shouldn't. All the code in your example would normally go in viewDidLoad().


Edit - for some clarification...

I think Apple's documentation is a bit ambiguous...

As I understand it, any view controller that is a descendant of UIViewController created via code only (UIViewController, UITableViewController, UICollectionViewController, etc) generates its own "root" view unless we override loadView() in which case it is up to us to instantiate and provide that root view.

So, why would we want to provide that view? We may want to provide a custom UIView subclass. For example, if I have a "Gradient" view that sets its base layer class to a CAGradientLayer - so I don't have to add and manage a sublayer. I can override loadView() and set the root view of my controller to that Gradient view.

I've put up a complete example on GitHub. The only Storyboard in the project is the required LaunchScreen --- everything else is done via code only.

It starts with 3 buttons in a navigation controller:

enter image description here

  • The first button pushes to a simple UIViewController with a label
  • The second button pushes to a UITableViewController, where selecting a row pushes to a "Detail" UIViewController
  • The third button pushes to a simple UIViewController with a label and a gradient background

The third example is the only one where I override loadView() to use my custom Gradient view. All the others handle all the setup in viewDidLoad()

Here's the link to the project: https://github.com/DonMag/CodeOnly

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks, I need to give a value to `view` as it is nil by default. As I create my views manually(without IB), then I should use `loadView()` here. One question, could I just set `view = table` to set tableView as root view, is that a proper way to do it? – Zhou Haibo Nov 07 '20 at 05:26
  • @ChuckZHB - see the edit to my answer for clarification and a link to an example project. – DonMag Nov 07 '20 at 15:05
  • Nice example, I have gone through it. Clearly, we could use both of way to set the view, either `viewDidLoad` or `loadView`. And the best practice to use `loadView` is when we need to use some custom `UIView` as root view. Meantime, I find a good article about this topic. http://roopc.net/posts/2015/loadview-vs-viewdidload/ – Zhou Haibo Nov 07 '20 at 17:58
  • @ChuckZHB - one more thought... *"if using IB ... you must not override loadView()"* -- So, if I started using `loadView()` for VC initializations, and then later decide to use those classes in a Storyboard / IB environment -- or if someone else wants to use my code -- changes will be required. Since the root view is **automatically created anyway**, personally I think it's safer to go with `viewDidLoad()` ***unless*** using a custom view class. I'm not an Apple engineer, so my understanding could be wrong, but I haven't seen any other docs or discussion to convince me otherwise. – DonMag Nov 08 '20 at 13:50
  • Yeah, there is no true or wrong. We should use them flexibel as my understand. Creating UI with IB or pure coding way, or maybe mixing them up according your conditions. And by creating UI/views programtically makes me feel confident, and I could know some underlying things. Thus, I prefer to use coding way. – Zhou Haibo Nov 09 '20 at 17:41
0

As DonMag said above, I need to give view a value here as it is nil by default(without using interface builder).

And here is a reference I found could explain when to use viewDidLoad or loadView, in my case I used the 2nd way to create all the views programatically.

Reference: https://stackoverflow.com/a/3423960/10158398

When you load your view from a NIB and want to perform further customization after launch, use viewDidLoad.

If you want to create your view programtically (not using Interface Builder), use loadView.

Update:

Per DonMag's example. It is clearly that we could use both of way to set the view, either in viewDidLoad or in loadView. And the best practice to use loadView is when we need to use some custom UIView as root view. Meantime, I find a good article about this topic. It is an old one, though I think the concepts will not change.

http://roopc.net/posts/2015/loadview-vs-viewdidload/

To conclude, we can set up the view hierarchy in either loadView or viewDidLoad, as long as we’re aware of when which method gets called. The most important thing to remember is that if you override loadView, you should set the view property, while if you override viewDidLoad, you should only read the view property, which should have been already set.

Zhou Haibo
  • 1,681
  • 1
  • 12
  • 32