UISplitViewController: Deinit DetailView in collapsed mode - ios

I've been struggling on this for a while now, but I wasn't able to find a solution:
I've got an iOS 9 app that supports all device families, uses size classes and is programmed with Swift 2.0.
I'm using a UISplitViewController and everything works as I want, except in a collapsed environment (e.g. on an iPhone).
The Master-ViewController is a UITableViewController that triggers a replace segue when a cell is selected. In a collapsed environment this means, that the detailViewcontroller gets pushed onto the screen. The UISplitViewController visually behaves kind of like a UINavigationController.
However, when I dismiss the detailViewController with the back button or the swipe gesture it does not get deallocated until the a new replace segue is triggered in the Master-ViewController.
I assume that this is kind of a feature of UISplitViewController, since it was originally designed to show both contents next to each other. Nevertheless, in a collapsed environment I would like my UISplitViewController to behave like a simple UINavigationController, which deallocates the previously pushed detailviewController when popped.
I've been trying to manually change the splitViewController's viewControllers attribute after the detailViewController is popped:
if let firstVc = self.splitViewController?.viewControllers.first {
self.splitViewController?.viewControllers = [firstVc]
}
But that does not help. Simply replacing the detailViewController with an empty "Dummy"-ViewController doesn't work neither, since it automatically animates the transition.
Playing around with the UISplitViewControllerDelegate didn't help me neither...
Is there a solution for this (maybe simple? :)), that I'm too blind to see?

Related

My UINavigationBar seems to become out of sync with UINavigationController (see gif below)

When I try to back out of my Navigation stack using either the back button or swiping back from the left side of the screen, the navigation bar changes but does not dismiss the ViewController (or, in the case of swiping, the navbar animation is not interruptible). Please see the gif below.
I'm implementing my NavigationController with storyboard. It's just a UIViewController embedded into a UINavigationController. I've tried detaching the UIViewController and reattaching it to a different NavControl, manually embedding it, removing the TabBar controller that was also embedded originally. All of these have led to the same result.
Edit: Also relevant is how I'm pushing these ViewControllers to the nav stack. To present these VCs, I'm just using navigationController?.pushViewController(vc, animated: true).
present(_: UIViewController,animated: Bool) gives a modal presentation which is not what I'm looking for.
Any ideas as to what would be causing this odd behavior?
I had the following in my rootViewController's viewDidLoad from some earlier experimentation that wasn't being used:
// Setting as delegate might not be necessary right now,
// could become useful in the future?
navigationController?.navigationBar.delegate = self
Evidently it did not become useful in the future. Seems like doing this causes the delegate to become responsible for some of the work navBar usually does for free? Removing it immediately fixed the issue.

How can a user change tabs without directly tapping the tab?

I've been having a very tricky bug where after a significant amount of time on the device and simulator (it's unclear when), tapping anywhere on the screen brings me back to the very first/top tab in the tab bar's tabs. The rough layout for the app is below:
For the first/top tab, the user is taken through three scenes, and after completing the third the last two are popped off and the user is returned to the root Scene (the first in the Navigation Controller's stack). I can confirm that there are no memory leaks, at least regarding these controllers. The latter two are popped off and are clearly de-allocated.
In the second/middle tab, the controller holds a container view that holds a UIPageViewController with several controllers.
The third tab is the most straightforward and is a TableViewController with a single detail view.
All three tabs in some way reference the base tab bar controller, which is where I have put a common store object that each of the tabs may read/write to.
It does not make sense in the app to press the first tab to clear the cyclic Scene, so I have implemented a check in the base UITabBarController that blocks against clearing it when already at that tab and returning true otherwise:
func tabBarController(
_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController
) -> Bool {
guard let vc = selectedViewController else {
fatalError(
"TabBarController should always have a non-nil selectedViewController.")
}
var result = true
if let currentVC = vc as? UINavigationController {
// Block clearing to the base Controller in the Cycle if already in the Cycle.
if let cvc = currentVC.viewControllers[0] as? CyclicViewController
{
let areEqual = viewController.isEqual(currentVC as UIViewController)
result = !areEqual
}
}
return result
}
As stated, after a certain amount of time (usually after at least one or two iterations of the cyclic View Controllers in the first tab, although sometimes at launch as well), the app eventually and permanently starts to return to the the first tab when tapping the screen, regardless of which controller or where you tap. You can still tap back to the other tabs, but tapping anywhere brings you back to the first, even when tapping on the current tab. It's like the first tab hasn't really been switched out, even though it clearly does as the above method is called.
I can confirm that the above shouldSelect is called with these unintentional tab switches, but this is the only code that ever talks about switching between tabs. Nowhere else in the codebase is there code that says to switch to another tab, conditionally or otherwise.
There is no threaded code anywhere in my codebase. Everything runs on the main thread.
The selectedViewController property is assigned to the View Controller at first/top tab in the viewDidLoad of the Tab Bar Controller.
I would like to be able to reproduce this, but can't seem to, or else I'd have used a simpler example. I'm aware I'm not showing much, but I'd rather not show more than what I have. Setting break points is a huge pain, even with a generous limit - I don't know how to reliably reproduce this error, so it's very difficult to put a number and the breakpoint is very annoying to use.
Regarding tap gesture recognizers - the middle tab with its PageViewController does page through several ViewControllers with objects that have tap gesture recognizers. I can confirm with grep -R "ecognizer" in my workspace that I have zero tap gesture recognizers myself. Crucially, the issue affects the third tab too, which never uses tap gesture recognizers.
I am going slightly insane looking at this, so anyone that can clarify why tabs might be switching despite very clearly not tapping another tab would be hugely appreciated.
For specs, I am running on a MacBook Pro (macOS Catalina), with Xcode 12.4 and running iOS 14.1 on an iPhone SE 2nd Gen and iPhone 12 (both simulated) and an iPhone 7 (real).

Switching between UIViewControllers in story board

As someone who usually used separate xibs in the past I thought I'd give storyboard a go as it seemed a lot simpler to use and much easier to develop with. I've been writing an application where the essential set up is this:
At the top of all this is a UINavigationController (first level). Then I have Multiple UIViewControllers (second level) with buttons in them which you can tap to switch between the second level UIViewControllers.
However a problem occurs when I start switching between the second level UIViewControllers. I first thought this was an initialisation problem with the NSMutableArrays because in my code I have a NSTimer set to loop periodically and found when I set a breakpoint during it, when I went forward to the next timer tick event there appeared to be different instances of the same NSMutableArrays and it seemed a gamble to try and insert new values into these array with it sometimes working, sometimes not (as it may or may not insert into the correct instance).
Then, looking at the memory usage under Debug Navigator I found the issue. Each time I "switched" between the UIViewControllers a new UIViewController was being initiated, along with all new variables.
The code I am using to switch between them is
-(void) perform {
[[[self sourceViewController] navigationController] pushViewController:[self destinationViewController] animated:NO];
}
Or essentially a push segue transition. This also explains why when I tried to switch back to my view, the data on that view was lost as it is a complete new view.
Does anyone know how to switch between multiple ones of these UIViewControllers (say 5) essentially like a UITabViewController would except without the tab bar being present?
First option you can do this: You can use a tabbarcontroller for switching viewcontroller and hidden the tabbar. Then on buttonclick setthe tabbar index.
Second option you can do this: Create one more view controller and in this viewcontroller subview the all switching viewController and when you want to switch viewcontroller just bring that viewcontroller view to front by delegate.
Do you need the navigation bar and other features provided by your top level navigation controller?
If not, you could use a UIPageViewController instead.
You set up all your second level view controllers and then just have to tell the page view controller which one to display.
If you implement the associated delegate methods, it will automatically provide swipe gestures to switch between them and nice animations to get them on and off screen.
You can also get it to put a UIPageControl at the bottom showing a dot for each VC with the dot for the current VC highlighted.

Change starting point for UINavigationController to detail UIViewController without loosing its context?

How can I change the starting point for a UINavigationController using a Storyboard without loosing its context?
I have a simple Storyboard with a UINavigationController including a UICollectionView as overview and a UIViewController as detail viewcontroller.
As long as I have the starting arrow pointing to the UINavigationController, it works fine. However I want to launch directly with latest detail UIViewController (on the right) opened skipping the UICollectionView. But when I drag the starting arrow to the detail controller I run into issues:
the actual detail ViewController content that is being populated in the prepareForSegue method in the UICollectionViewis then missing
the [self.navigationController popViewControllerAnimated:YES] in the detail view controller is not working as well. So it's not possible to go back to the overview UICollecionView
What's the best way to change the start up UIViewController without loosing its navigationControl context and preparefForSegue population method?
[self.mavigationController setViewControllers:#[…] animated:YES];
// just set any UIViewControllers array you want, you can replace or filter it. The last one in your array will be shown on the screen.

Memory management of view while using NavigationController

I changed navigation in my application from using UITabBarController to u UINavigationController. I.e. former solution (1st version) was based only on the TabBarController - 4 ViewControllers (one simple TableView, one simple custom view and one MapView with many overlays). The second version is based only on the UINavigationController.
In case of TabBarController it was clear and simple, everything worked fine, especially MapView. I mean: the MapView was loaded once (with a significant number of overlays) and when I went to another view and back to the MapView the MapView was still there with its overlays already loaded and displayed (simple check: MapView`s viewDidLoad was called just once per app run, I had some debug messages there).
Now I changed navigation logic to the UINavigationController. Everything works fine for the first look - but: the viewDidLoad (for each view) is called everytime I navigate to the view. It is annoying especially in the case of the MapView - the loading of overlays is performed everytime, it takes some time and it causes app crash in some cases.
OK, my questions:
Is it some kind of "common" behavior of NavigationController?
Can I change this behavior so viewDidLoad will be called just once?
And more - How can I influence the "display sequence" of some view?
I understand the logic is probably more complicated but I appreciate any answer or hint ;)
Some related circumstances:
TabBar and Navigation controllers are not combined.
I use storyboards, segues are designed in the UIB, no manual calling like perfomSegue or prepareForSegue in my code. One button triggers segue to MapView.
I use push segues.
I also tried to use modal segues but without any change of that behavior.
any of viewDidUnload is never called during segues among the views.
No memory warning received.
No memory leaks measured both on simulator and iPhone 4.
I tried to build a very simple temporary project / app that is concerned just about the Nav. Controller and other views without ANY coding, just storyboard. It was the same behavior.
There was an issue that causes app crash when I fast and periodically tapped to navigation button and back button between one view and the MapView. In most cases the app crashed when I tapped the back button on the MapView before it was fully displayed (i.e. its overlays). It was fixed when I added a 1 sec. delay method call in the viewDidDisappeared in the MapView. It is not a fair fix, I know ;)
A UITabBarController and UINavigationController are based on fundamentally different paradigms.
The UITabBarController is intended for the UIViewController on each tab to exist independently of each other and for the user to choose which they want to view. viewDidLoad only gets called once for each UIViewController because it is intended that each tab still exists in memory even as the user switches to a different tab.
The UINavigationController is a stack of UIViewControllers where each is related to the one above and beneath itself. The top UIViewController in the stack is always the one that is visible to the user. When a UIViewController is pushed to the stack, its viewDidLoad gets called because it is being loaded into memory. When the top UIViewControllergets poped off the stack, it is unloaded from memory, and viewDidUnload gets called on the way out (viewDidUnload is deprecated in iOS6 and won't get called, but the controller will still get dumped from memory). This is why viewDidLoad gets called every time that the user pushes a particular UIViewController onto the UINavigationController stack.

Resources