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.
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)
}
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
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
.
User taps a segmented control.
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
override func viewDidLoad()
: once the view did load, you call the function addTableView
.
func addTableView()
: in this custom function you instantiate your table view and place it within the SegmentOneViewController, which is itself I assume a UIViewController
.
override func viewDidAppear(_ animated: Bool)
: you call the custom function loadData
and set your table view data source and delegate.
Back Button
User taps the back button.
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...
UIViewController
lifecycle; andSome reading recomendations:
This SO question titled :Looking to understand the iOS UIViewController lifecycle" presents a lot of good information, but understand that some of the information is incorrect due to deprecation of viewDidUnload
from iOS 6.
Which is why you should always go to the Apple documentation for UIViewController to refer to the latest API reference.
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.