Gallaugher Gallaugher - 22 days ago 7
Swift Question

Checking approach: Managing UIPageViewController page data using UITableView cells

I've implemented a UIPageViewController that allows the user to add/remove/delete new "pages" via a separate UITableView, similar to how Apple implements Cities/Weather Locations in the iPhone Weather app. Project is posted below. There is no weather or fancy UI in it at this point – I'm just trying to focus on the UIPageViewController management below. Hopefully this is useful if anyone wondering how to implement UIPageViewController with pages managed via UITableView:
https://github.com/gallaugher/PageViewControllerDemo

It seems to work fine, but I'm new to this & quite uncertain if I've done this using recommended approaches or if it's "Swifty".

enter image description here

Right now I have:
PageViewController [Initial View Controller]
- Sets delegate & data source to self
- Instantiates first UIViewController (imagine a CityViewController for local weather, even though details & UI not added in this example) & sets initial values.
- Cities (weather locations) are kept in a [String] array: citiesArray

In CityViewController - when "Cities" button is clicked in lower-right (created in interface builder), like Apple Weather, it opens a UITableView (CityListViewController).

- To get to this CityListViewController, I trigger a segue drawn directly via the interface builder from the "Cities" button in CityViewController to the CityListViewController, presenting modally.
- preapareForSegue passes citiesArray to the destination CityListViewController (UITableView).

In CityListViewController (the UITableView)
- User can add cities, move, delete in UITableView updating tableView & array
- Clicking a tableView row (a city's name or "Local Weather") triggers a perform segue, unwinding to CityViewController, getting source CityListViewController and using this to pass data back to the CityViewController (e.g. citiesArray = controller.citiesArray).
- This @IBAction unwind function calls an unwind to the PageViewController (really does nothing more than pass data through from the TableView in CityListViewController to the UIPageViewController PageViewController).

Unwind in PageViewController
- Grabs source (as CityViewController)
- Passes key data back (e.g. citiesArray = controller.citiesArray)
- Calls a function to instantiate view controller for the current page & set PageControl index, etc.

Q1:
While this seems to work & I haven't managed to break it during testing, is it a sound approach to go from UITableView, unwinding to a ViewController that simply triggers another unwind to the UIPageViewController, with nothing done other than pass data through?

Q2:
I've implemented the UIPageControl by building it programmatically in the PageViewController, but the button that segues to the CityListViewController (the UITableView) was created in the CityViewController using Interface Builder. Is this the proper approach? I couldn't seem to get both of these created within the same VC.

Thanks so much for those who had the patience to wade through this convoluted explanation. Still trying to get a handle on data passing among VCs, and how this relates to PageControllers & the TableViews.

Answer

For this interested in the solution I've posted to GitHub at: https://github.com/gallaugher/PageViewControllerDemo I've posted with more generic names - PageVewController, DetailViewController, and ListViewController, so this is easier to reuse and perhaps follow.

Thanks to Sazan Dauti for answering this question outside of StackOverflow. It is possible to segue directly between the PageController & the ListController that contains a TableView for managing pages an array of similar pages (e.g. like Apple does in the Weather app where you can add/delete/move cities), keeping the Detail (Cities in original example) free of any knowledge that it's in a PageView. - Drag a segue directly from the UIPageViewController to the UIViewController with the TableView that contains, in my case, a list of locations (not the detail that's managed by the PageView). The segue should present modally. Key variables should be passed to the ListViewController in a prepare for segue in the PageViewController like this:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
 if segue.identifier == "ToListViewController" {
  let controller = segue.destination as! ListViewController
  controller.listArray = listArray
  controller.currentPage = currentPage
 }
}
  • Be sure the values passed (listArray & currentPage in the case above) are declared in ListViewController.

  • Add an Unwind method to the PageViewController similar to this:

    @IBAction func unwindFromListViewController(sender: UIStoryboardSegue) { pageControl.numberOfPages = listArray.count pageControl.currentPage = currentPage setViewControllers([createDetailViewController(currentPage)], direction: .forward, animated: false, completion: nil) }

setViewControllers above refers to the function written in the PageViewController that contains the logic to instantiate the DetailViewController for the current page.

  • Return segue is created by dragging from a TableViewCell in the ListView to the "Exit' button (far right of the three buttons at the top/title view of this UIViewController). You'll be asked the name for an unwind method that should be in the PageViewController (in the example above I've called it unwindFromListViewController), so be sure to add this method before trying to make the segue.

  • Add a prepare for segue function to the ListViewController:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ToPageViewController" { let controller = segue.destination as! PageViewController controller.currentPage = currentPage controller.listArray = listArray } }

  • and in tableView didSelectRowAt, trigger the perform segue:

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { currentPage = indexPath.row // set current page to row selected performSegue(withIdentifier: "ToPageViewController", sender: self) }

I couldn't find any decent examples online demonstrating how this was done, so hopefully this is understandable and efficient. Any corrections are welcome. Cheers!