Search code examples
iosiphoneswiftuitableviewuisegmentedcontrol

UISegmented Control Disappears After Dismissing View


In working on this app with a TabBar at the bottom, NavBar at the top with a Segmented Control:

I have an issue where the View A (Segment One) with a UITableView, upon selecting a cell and displaying a new view with more details, when I click back, the Segmented control at the top will disappear and the TableView from View A will be pushed up.

This doesn't always happen - sometimes after many tries or sometimes just one. I haven't found any correlation to what's causing it.

I have found that if I select View B from the segmented Control, then back to View A, then click on one of the table cells to get to the details screen and then click back, 100% of the time the Top Nav Bar disappears with the segmented control.

enter image description here

TabBarItemOneViewController

let segmentOneVC: SegmentOneViewController
let segmentTwoVC: SegmentTwoViewController

var currentViewController: UIViewController

let viewControllerYLoc = 60 // statusBarHeight + topBarHeight
let viewWidth = Helper.getViewWidth()
let tabBarHeight = 40

func pressedSegItem(segControl: UISegmentedControl){

    let viewControllerHeight = Int(self.view.frame.height)

    let viewFrame = CGRect(x: 0, y: viewControllerYLoc, width: Int(viewWidth), height: viewControllerHeight)

    let selectedIndex = segControl.selectedSegmentIndex
    previouslySelectedMyLoadsIndex = selectedIndex

    self.currentViewController.removeFromParentViewController()

    if(selectedIndex == 0){
        currentViewController = segmentOneVC
    }
    else if(selectedIndex == 1){
        currentViewController = segmentTwoVC
    }

    self.view.addSubview(self.currentViewController.view)
    self.currentViewController.didMove(toParentViewController: self)
}

public init() {

    segmentOneVC = SegmentOneViewController(nibName: nil, bundle: nil)
    segmentTwoVC = SegmentTwoViewController(nibName: nil, bundle: nil)

    if(previouslySelectedIndex == 0){
        currentViewController = segmentOneVC
    }
    else{
        currentViewController = segmentTwoVC
    }

    super.init(nibName: nil, bundle: nil)

    self.calculateItems()

    self.addSegmentedControl()

    let viewControllerHeight = (Int(self.view.frame.height) - viewControllerYLoc) - tabBarHeight

    let viewFrame = CGRect(x: 0, y: viewControllerYLoc, width: Int(viewWidth), height: viewControllerHeight)

    self.currentViewController.view.frame = viewFrame
    self.addChildViewController(segmentOneVC)
    self.addChildViewController(segmentTwoVC)
    self.view.addSubview(self.currentViewController.view)
    self.currentViewController.didMove(toParentViewController: self)
}

SegmentOneViewController (note: SegmentTwoViewController is identical)

let cellReuseIdentifier = "ItemDetailTableViewCell"

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let row = indexPath.row

    let dataItem = self.dataArray[row]

    let itemDetailVC = ItemDetailViewController()
    itemDetailVC.dataItem = dataItem
    self.present(itemDetailVC, animated: true, completion: nil)
}

func addTableView(){
    self.tableView = UITableView()

    tableView.register(ItemDetailTableViewCell.self, forCellReuseIdentifier: self.cellReuseIdentifier)

    tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    tableView.frame = CGRect(x: 0, y: 0, width: Int(viewWidth), height: (Int(self.view.frame.height) - bottomOfTopNavBar) - heightOfTabBar)

    self.view.addSubview(tableView)
}

override func viewDidAppear(_ animated: Bool){
    super.viewDidAppear(animated)

    loadData()

    tableView.dataSource = self
    tableView.delegate = self
}

override func viewDidLoad() {
    super.viewDidLoad()

    addTableView()
}

ItemDetailViewController

// Connected to a back button in a top Navigation Bar
func goBack(){
    self.dismiss(animated: false, completion: nil)
}

Solution

  • Nice graphics BTW... that illustration make it much easier to understand your problem.

    Also BTW, I'm an Obj-C person, still learning the nuances of Swift, so please let me know if my syntax or otherwise is incorrect. I'm also relatively inexperienced in using container VC's.

    ​ I've written my response in two parts.

    The First Part is my attempt to solve your problem.

    The Second Part is my suggestion for an alternative for you to consider.

    First Part

    This is my understanding of the order/sequence of execution in your code...

    Parent View with Segmented Control

    1. public init () : on instantiation of the parent view controller, two child VCs (segmentOneVC and segmentTwoVC) are instantiated and depending on previous selection are assigned as currentViewController. Then you add a segmented control to the TabBarItemOneViewController.

    2. User taps a segmented control.

    3. Depending on user input, either the SegmentOneViewController or SegmentTwoViewController view is added as a subview to the TabBarItemOneViewController.view. (Note that this is also done when the VC is initialised.)

    Child View

    1. override func viewDidLoad() : once the view did load, you call the function addTableView.

    2. func addTableView() : in this custom function you instantiate your table view and place it within the SegmentOneViewController, which is itself I assume a UIViewController.

    3. override func viewDidAppear(_ animated: Bool) : you call the custom function loadData and set your table view data source and delegate.

    Back Button

    1. User taps the back button.

    2. Child VC is dismissed and the TabBarItemOneViewController becomes the active view on screen.

    Let's look at what does not happen in the view controller lifecycle when the back button is pressed... Item 1 in the list.

    This may explain the inconsistency.

    Try this... run the app, tap the tab control to take you to TabBarItemOneViewController. Don't tap the segmented control. Tap a line in your table view. Tap the back button in your child VC. I'd take a guess your segmented control is still there.

    Now try this... run the app, tap the tab control to take you to TabBarItemOneViewController. Tap the segmented control. Tap a line in your table view. Tap the back button in your child VC. I'd take a guess your segmented control is no longer there.

    Why? Because the custom function pressedSegItem that I assume has a target action assigned to the segmented control, will overwrite your init, which is where you add the segmented control into the tab bar view controller.

    So by way of example, try placing the code to instantiate your segmented control instead in an override function of viewWillAppear of the TabBarItemOneViewController VC.

    So a couple of concepts to think about...

    • lazy loading to save memory allocation - only instantiate the objects you need when the user specifically requests that function in the app;
    • order of execution of each function in the UIViewController lifecycle; and
    • which functions are executed once and which are executed each time your view becomes first responder / the active view.

    Some reading recomendations:

    Second Part

    By providing this alternative, I'm not suggesting that your approach is incorrect, however I am suggesting an alternative for you to consider.

    I've always used tab bar controllers to change views and segmented controls to filter data sets or change the appearance of the current view.

    Think about using a UISegmentedControl to manage or adjust the data set within only one table view. This will alleviate the need for multiple view controllers and the juggling act of managing these.

    For example, when writing your data source and delegate methods / functions for your tabel view, you can include the following code to ensure the table view loads and responds accordinagly:

    let selectedIndex = segControl.selectedSegmentIndex
    if(selectedIndex == 0) {
        rowHeight = 20 'for example
    } else {
        rowHeight = 30 'for example
    }
    

    Then you'd need to relaod your table view to effect the changes.