I've got a Master-Detail project using Xcode 7 beta 4 (Swift 2.1), where both the MVC and DVC are UIViewControllers. I have a UISearchController property in the DVC, which is used to display a UISearchBar in the titleView of the DVC nav controller. This search bar is working fine on iPad, but on iPhone, when I push the DVC, I can see the UISearchBar, but it cannot be tapped or first responder.
The odd thing is if I instantiate a local UISearchController in viewDidLoad and set its searchBar as the titleView, I can interact with the UISearchBar just fine, but every time I push the view controller, I get a warning:
"Attempting to load the view of a view controller while it is deallocating is not allowed - UISearchController"
If I create a class instance of the UISearchController by creating a DVC property, then the UISearchBar isn't interactable:
self.detailViewController?.navigationItem.titleView = self.searchController.searchBar
But if I do a local variable, it's interactable (with that warning stated above):
let localSearchController = UISearchController(searchResultsController: nil)
self.detailViewController?.navigationItem.titleView = localSearchController.searchBar
This bug has completely dumbfounded me, and it only occurs on iPhone. It seems like a simple problem, and I've tried many different approaches to figure it out. Maybe someone here has an idea what's wrong. Thanks!
I was able to solve this issue by searching for definesPresentationContext in my app and deleting all the instances of it. I'm sure there may be a way to fix it without deleting.
Related
The Apple guy in the What's new in Cocoa Touch WWDC video said that the new large-title navigation bar will magically hook into the top-level scroll view of the underlying view controller and collapse/expand itself automatically while scrolling up and down. (And by "magically", he probably meant that they failed to monkey patch this functionality into the already embarassing UINavigationController-UINavigationBar-UINavigationitem APIs in a usable way, so they had to resort to hooking into some heuristically chosen scroll view behind the scenes)
Even though I was prepared that this "automatic" collapse/expand wouldn't work if I deviate the slightest from the basic UINavigationController + UITableView/UICollectionView setup, it seems that even in this simplest case it doesn't work as expected.
Here's what I have:
A UITabBarController which contains a UINavigationController, which contains a UIViewController, which has a UITableView as its view. Tapping the first cell in the table will push a second view controller on the navigation stack:
No code, just the storyboard.
I've checked "Prefers large titles" for the navigation bar to activate large titles. Now, if I run the app and scroll up/down on the table view, the navigation bar stays the same - large - size; it doesn't collapse:
However, I've found that if I set the second view controller's navigation item to use the small navigation bar (by setting "Large Title" to the value "Never"), then if I open that page and navigate back, the interactive collapse magically starts working on the first page:
Am I missing something here, or is this feature not working properly? Here's the sample project I'm using: https://github.com/tzahola/iOS-11-Large-Title-Navigation-Bar
And by the way, I'm using the officially released iOS 11, not the betas.
2017-09-23 Update: I've sent a bug report to Apple, and opened a ticket on openradar.me: http://www.openradar.me/radar?id=5017601935671296
If there is any other view in addition to tableView, also make sure tableView is on the top of that view(s), right under the Safe Area:
Good news! I've just figured out that if I set "Large Titles" to "Never" on the storyboard, and then set it via code, then it works:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic;
}
Seems like Apple forgot to handle the case when the navigation item has its largeTitleDisplayMode set via the Interface Builder.
So until they fix this issue, leave "Large Titles" as "Never" on storyboards, and set them via code in viewDidLoad.
You just need to do that to the first view controller. Subsequent view controllers honor the value in storyboard.
Or instead of changing anything in storyboard, do this:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {
self.navigationItem.largeTitleDisplayMode = .never
self.navigationItem.largeTitleDisplayMode = .always
}
}
No matter which language!
This is because large titles on navigation item decides whether or not to collapse on the basis of large title behaviour on previous screen navigation item title.
Make sure that addSubview(tableView) placed before others addSuview(someview)
Year 2020, iOS 13.0, this WAS NEVER mentioned here. I literally spent an hour or two for this.
Issue: Large title won't collapse when doing layout programmatically using Snapkit (an autolayout framework)
Solution: SETUP YOUR VIEWS (including navigationController stuff and tableView) inside loadView() NOT in viewDidLoad().
#TamasZahola #mohamede1945
Guys I had the same problem. I was able to resolve this issue by adding following snippet on my first View Controller of Navigation Controller
navigationController?.navigationBar.prefersLargeTitles = true
It's an odd bug. The fix is to toggle OFF prefersLargeTitles in the storyboard and to set this in viewDidLoad of your nav controller's root vc:
navigationController?.navigationBar.prefersLargeTitles = true
TableView of its container should be at the top of ViewController's view hierarchy (RootView on screenshot). Otherwise it won't work.
I am implementing an application that uses a UINavigationController. It currently shows 3 views, each a UITableViewController. The first view that is shown only has a title, while the other two have a title and prompt. All segues are created in the storyboard, which is rather straightforward:
The problem: As soon as I animate AWAY from a view that has a prompt, the animation glitches - the title (and sometimes the back button) "fly in" from the bottom, instead of from the right.
Example: https://youtu.be/N-K8piEJ1aY (recording with slow animations turned on)
Here you can see that the animation from first to second view works fine, but from second to third view is glitchy. animating back works.
This issue seems similar to Weird animations when changing NavigationItem prompt . The conclusion in that thread was that this only occurs on iOS 7, I am running on iOS 10.0/10.1, though. The issue occurs both in simulator and on the real device.
Any ideas?
The only solution I found was an absolute hack inspired by an absolute hack inspired by Catalina T. over on this post:
Either in viewWillAppear: on the appearing VC or after calling pushViewController:animated: on the navigation controller (or I'm guessing after calling performSegueWithIdentifier:sender: add the following code:
ObjC
// This is a hack that's because UINavigationBar with prompts is broken
navigationController.navigationBarHidden = YES;
navigationController.navigationBarHidden = NO;
Swift
// This is a hack that's because UINavigationBar with prompts is broken
navigationController.isNavigationBarHidden = true
navigationController.isNavigationBarHidden = false
where navigationController is a reference to the UINavigationController that's doing all the pushing (e.g. viewController.navigationController)
EDIT: It appears now there're other weird animations that occur when popping and repushing a VC, so this answer isn't a true solution. Leaving it up so as to help someone else down a similar rabbit hole.
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?
I modified AAPLSearchBarEmbeddedInNavigationBarViewController in Apple's UICatalog sample code so that it pushes another instance of AAPLSearchBarEmbeddedInNavigationBarViewController onto the navigation stack when a cell is selected. In the second view controller the UISearchBar set as the title view of the UINavigationBar (just like the first one) isn't tappable (unlike the first the one). This seems like a bug. How do I fix it? Here is my modified UICatalog code:
https://github.com/stevemoser/UICatalog
Also I tested with with Xcode 6 and 7. It's broken in both.
The solution is to set the first VC self.definesPresentationContext = NO when navigating away from it and making sure to call self.definesPresentationContext=YES in the view did appear so that the visible VC allows defines the presentation context.
Thanks goes to Rory McKinnel who put me on the right track.
UIPopoverController *popCtrl = [[UIPopoverController alloc] initWithContentViewController:self.rootViewController.navigationController];
popCtrl.delegate = self;
[popCtrl presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
This code is in a button action, where the button is the "sender".
The line with presentPopoverFromBarButtonItem causes an exception to be thrown with the reason: Application tried to present modally an active controller DetailViewController: 0x15a54c00. DetailViewController is "self" in this case and it is only a delegate to popCtrl, so I don't see how it could be trying to present modally. It's supposed to be presenting rootViewController.navigationController.
As you may have guessed from the names, rootViewController and detailViewController are inside a SplitViewController, but prior to trying to present rootViewController with the the popover, it is removed from the SplitViewController.
This only happens on iOS 8 when built with the iOS 8 SDK. It's also not 100% reproducible. Most of the time this exception occurs, but sometimes after I restart the app it does not occur at all until I rerun the app, then it starts happening all the time again. (I put it in a try/catch so I know it can occur more than once per run.)
I'm almost positive this is yet another iOS 8 bug in the SDK, but has anyone come up with a workaround?
I faced the same problems while updating some app, which was initially developed at the times of iOS 5.0. Removing the controller from the UISplitViewController right before using it in the popover did not work, neither did it help to switch to the newer UIPopoverPresentationController.
However, I was able to swipe-in my (master) controller from the left side. More or less, I discovered that "feature" by accident, so I looked up where this came from and found this in Apple's iOS SDK 5.1 release notes:
In 5.1 the UISplitViewController class adopts the sliding presentation style when presenting the left view (previously only seen in Mail). This style is used when presentation is initiated either by the existing bar button item provided by the delegate methods or by a swipe gesture within the right view. No additional API adoption is required to obtain this behavior, and all existing API, including that of the UIPopoverController instance provided by the delegate, will continue to work as before. If the gesture cannot be supported in your app, set the presentsWithGesture property of your split view controller to NO to disable the gesture. However, disabling the gesture is discouraged because its use preserves a consistent user experience across all applications.
(Source: iOS 5.1 Release Notes, requires Apple Developer Login)
I didn't test what happens if you set the mentioned property to NO and if it releases the controller, but I wouldn't put too much hope on that.
So even after removing it manually from the UISplitViewController, my view controller was still active on that hidden swipeable pane, which appears to happen internally in the SDK. I'm aware of the fact that this still worked fine until iOS 7.x, but I actually consider that as tolerated bug now, closed with iOS 8.0.
I ended up abandoning the popover completely and using the default UISplitViewController behaviour of iOS 5.1 and above. For some extra tweaking, you can change UISplitViewController.preferredDisplayMode to fit your needs, this saved me a lot of time to upgrade old code which never heard of auto layout.
I am using a popover in iOS 8 programmatically in an IBAction. I don't know if this is a bug or not but I do know that they did make some changes to modal views and presentations. There is a good WWDC video on it, see if you can find it. The way I am doing it (keep in mind this is Swift, so you will need to do a little bit of translation) is the following:
let controller = self.settingsVC
controller.preferredContentSize = CGSizeMake(345, 234)
controller.modalPresentationStyle = UIModalPresentationStyle.Popover
var settingsPopController = controller.popoverPresentationController
settingsPopController?.delegate = self
settingsPopController?.sourceView = self.view
settingsPopController?.sourceRect = sender.frame
controller.modalPresentationStyle = UIModalPresentationStyle.Popover
self.presentViewController(controller, animated: true, completion: nil)
In this code, self.settingsVC is a property of the ViewController I set which is initialized to another ViewController in the storyboard, but you can replace controller with the ViewController you need to present as a popover. Also, please note that your UIViewController class must implement UIPopoverPresentationControllerDelegate.
If you need any help with the translation, I'd be happy to give you a hand.