Search code examples
iosarraysswiftuiviewcontrolleruipageviewcontroller

Why can't I set view controllers in UIPageViewController?


I am trying to populate a page view controller with 5 events that I am pulling from an API. The idea is that the user can page through the top 5 latest events and click on them to see more details about the event. The issue I am running into is that once I get the events I want to reload the view controller for the page view controller but when I try setting up the view controllers I get an error: fatal error: unexpectedly found nil while unwrapping an Optional value. I set a property observer for my array of events so that once it's set I reload the view controllers. This is how my class looks like:

class EventPageViewController: UIPageViewController {

var eventViewControllers = [EventViewController]()

var events: [Event] = []{
    didSet {
        self.reloadViewControllers()
    }
}


func reloadViewControllers(){

    self.dataSource = nil
    self.dataSource = self
    // Get top 5 events
    let topFiveEvents = Array(events.prefix(5))
    print(self.events.count)
    self.eventViewControllers.removeAll()
    print(eventViewControllers.count)

    // Set up view controllers
    for event in topFiveEvents{
        if let controller = self.storyboard?.instantiateViewController(withIdentifier: "event") as? EventViewController{
            print(event.title)
            // Just this line alone is what crashes the app
            controller.eventTitle.text = event.title
            self.eventViewControllers.append(controller)
        }
    }

    if self.eventViewControllers.count != 0 {
        let first = [self.eventViewControllers[0]]
        self.setViewControllers(first, direction: .forward, animated: false, completion: nil)

    }


}

override func viewDidLoad() {
    super.viewDidLoad()
    let timeMin = GTLRDateTime(date: Date()).rfc3339String
    let params = ["maxResults": "250",
                  "singleEvents": "true",
                  "timeMin": timeMin]
    NetworkManager.events(forPark: .all, withParameters: params, query: nil) { (events, error) in
        if error == nil {
            DispatchQueue.main.async {
                self.events = events
            }
        }
    }
}

override func viewDidLayoutSubviews() {
    for subView in self.view.subviews {
        if subView is UIScrollView {
            subView.frame = self.view.bounds
        } else if subView is UIPageControl {
            let pageControl = subView as! UIPageControl
            pageControl.currentPageIndicatorTintColor = UIColor(red:0.00, green:0.15, blue:0.29, alpha:1.0)
            pageControl.pageIndicatorTintColor = UIColor(red:0.00, green:0.15, blue:0.29, alpha:0.30)
            self.view.bringSubview(toFront: subView)


        }
    }
    super.viewDidLayoutSubviews()
}



}

If I don't set anything in the view controller then I get the page view controller to show view controllers with static information. I'm not sure what I'm doing wrong.


Solution

  • It looks to me like your page view controller is trying to manipulate it's child view controllers' views directly. Don't do that.

    This line:

    controller.eventTitle.text = event.title
    

    Is likely the cause, assuming that eventTitle is an outlet.

    Instead of doing that, add a string property to your child view controllers:

    public var eventTitleString: String
    

    And then set that instead:

    controller.eventTitleString = event.title
    

    And in your child view controller, add code to copy that value into your field:

    func viewWillAppear(_: animated: Bool) {
      super.viewWillAppear(animated)
      eventTitle.text = eventTitleString
    }
    

    @MrSaturn suggests calling controller.loadViewIfNeeded(), which would likely fix your crash, but it's bad practice. You should treat a view controllers' views as private, and never try to manipulate them from outside. Instead add a public property or function that updates your view controller's views directly.