Search code examples
swifttableviewios11container-viewlarge-title

How to handle iOS 11 large title animation when using multiple container views?


I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.

I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.

I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.

enter image description here

Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?

I tried the following

1) Change Container view zPosition when changing segments

2) Move the container view to the front by calling view.bringSubview(toFront: ...)

3) Looping through the subviews and calling view.exchangeSubview(at: 0, withSubviewAt: ...)

I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.

Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.


Solution

  • So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/

    Essentially what I did is

    1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.

    2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them

     lazy var viewController1: LibraryViewController = {
            let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
            // do other set up if required.
            return viewController
        }()
    
    lazy var viewController2: LibraryViewController = {
                let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
                // do other set up if required.
                return viewController
            }()
    

    3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.

    extension UIViewController {
    
        func add(asChildViewController viewController: UIViewController) {
            // Add Child View Controller
            addChildViewController(viewController)
    
            // Add Child View as Subview
            view.addSubview(viewController.view)
    
            // Configure Child View
            viewController.view.frame = view.bounds
            viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    
            // Notify Child View Controller
            viewController.didMove(toParentViewController: self)
        }
    
        func remove(asChildViewController viewController: UIViewController) {
            // Notify Child View Controller
            viewController.willMove(toParentViewController: nil)
    
            // Remove Child View From Superview
            viewController.view.removeFromSuperview()
    
            // Notify Child View Controller
            viewController.removeFromParentViewController()
        }
    }
    

    4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.

    func didPressSegmentedControl() {
    
       if segmentedControl.selectedSegmentIndex == 0 {
            remove(asChildViewController: viewController2)
            add(asChildViewController: viewController1)
        } else {
            remove(asChildViewController: viewController1)
            add(asChildViewController: viewController2)
        }
    }
    

    5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.

    func viewDidLoad() {
        super.viewDidLoad()
    
        didPressSegmentedControl()
    }
    

    This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.

    Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.

    Hope this helps somebody trying to do the same.