0
votes

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

2
why dont you put the viewDidLoad then ? you wonder why this happen or fix the your problem ? - zeytin

2 Answers

1
votes

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

0
votes

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.