iOS 11 UINavigationBar Transparency in pushed ViewController - ios

I've been trying to implement Apple Music like transparent navigation bar for pushed view controller. There are a lot of solutions on Internet saying place the code below into viewDidLoad:
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
But the thing is that it only works for root controller, e.g. UITableViewController with a list of items. When I tap on an item and open it's details I expect to see transparent navigation bar, but after appearing it becomes solid (not even translucent). Even setting barTintColor does not help.
What am i doing wrong? Or is it a known issue in iOS 11? It used to work before...

I confirm that transparent navigation bar is not working in iOS 11 for pushed viewcontroller, instead just appears black without translucent #screenshot.
Firstly, I have filed this bug report, lastly :) I found a quick workaround that presenting and dismissing a UIViewcontroller fixes this issue, as following:
if (self.navigationController!.viewControllers.count > 1) {
if #available(iOS 11.0, *) {
self.present(UIViewController(), animated: true, completion: {
self.dismiss(animated: false)
})
self.scrollView.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
self.extendedLayoutIncludesOpaqueBars = false
}
I am using above code in viewWillAppear and my UI is being generated programatically without storyboard or xib, so it works seamlessly :) and glad I get expected result #screenshot

Related

Visible UISearchBar changes UINavigationBar background color

A tableview controller is embedded into a navigation controller.
I programmatically added a search bar to the tableview controller's navigation bar. I only changed the navigation bar Background color into something different than Default (purple) - all the rest I left default.
class TableViewController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesSearchBarWhenScrolling = true
navigationItem.searchController = searchController
}
}
Code above is reduced to bare minimum for demonstration purpose.
All done with Xcode 11 (11A420a).
I ran the project in iOS 12.0 and 13.0 simulators and devices.
iOS 13.0
The search bar shows upon start.
Navigation bar background color is correctly presented.
While scrolling, navigation bar background color remains correct.
With iOS 13.0, all works as expected!
iOS 12.0
The search bar doesn't show upon start.
Navigation bar background color is correctly presented.
While scrolling, navigation bar background color goes white as soon search bar is visible.
I tried to change all kind of color setting in storyboard as well as properties programmatically. I didn't succeed in changing the navigation bar background color when search bar is visible.
It seems (?!) that the navigation bar foreground looses transparency when search bar becomes visible.
If I use a Bar Tint color of the navigation bar (!= Default), all works as expected (and as with iOS 13.0), but I loose the gradient effect, which I would like to keep.
What did I miss?
How can I avoid this?
Bug?
I had some luck with the navigationItem.scrollEdgeAppearance property when facing a similar problem. For example:
vc.navigationItem.scrollEdgeAppearance?.backgroundColor = .red
This is only available on iOS 13 though.
Here's what I ended up doing to get correct colors in the navigation bar while allowing the search controller's scroll bar to hide during scroll:
if #available(iOS 13.0, *) {
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
navbar.standardAppearance.backgroundEffect = blurEffect
navbar.standardAppearance.backgroundColor = appMainColor.withAlphaComponent(0.75)
navbar.standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
navbar.compactAppearance = nil
navbar.scrollEdgeAppearance = navbar.standardAppearance.copy()
navitem.standardAppearance = nil
navitem.compactAppearance = nil
navitem.scrollEdgeAppearance = nil
}
I don't know if it is the look you're going for but I found if you disable hideSearchBarWhenScrolling the background stops changing color. However, the search bar is always there.
Add this to viewDidLoad():
navigationItem.hidesSearchBarWhenScrolling = false

iOS13 UIDocumentPickerViewController - open directory does not display select/open/cancel buttons

I am using UIDocumentPickerViewController to browse through and let the user select a directory but on iOS 13 when this UIDocumentPickerViewController is displayed, the buttons that should be displayed like select/cancel and open/done are not displayed but when you tap on that location it does behaves like how it would if the buttons were visible. Also this problem is seen only on iOS 13. With the same code, the buttons are displayed on iOS 12. Any help is appreciated
I do have the navigation bar's tint color set to nil for an instance of UIDocumentBrowserViewController in AppDelegate didFinishLaunchingWithOptions.
if #available(iOS 11.0, *) {
UINavigationBar.appearance(whenContainedInInstancesOf: [UIDocumentBrowserViewController.self]).tintColor = nil
}
//Here is how UIDocumentPickerViewController is created and presented
let documentPickerViewController = UIDocumentPickerViewController(documentTypes:["public.folder"], in: .open)
...
...
...
self!.documentPickerViewController.delegate = self!
self!.documentPickerViewController.allowsMultipleSelection = true
self!.documentPickerViewController.modalPresentationStyle = .fullScreen
self!.navigationController?.present(self!.documentPickerViewController, animated: true, completion:nil)
Here is a screenshot
Edit :
Here is the View Hierarchy - Not sure why DOCExportModeViewController on iOS 13. On iOS 12, it is a UIDocumentBrowserViewController for the same code. Any ideas how this can be fixed?
I found this quick fix, if the document ViewController or any other picker is used at multiple places:
if #available(iOS 11.0, *) {
UINavigationBar.appearance(whenContainedInInstancesOf: [UIDocumentBrowserViewController.self]).tintColor = nil
}
If you are worried for one place or have different colors at different places you can set tint color in ViewWillAppear and reset it in ViewWillDisappear method.
I have observed that UIDocumentPickerController is a subclass of UIViewController Where UIImagepickerController is a subclass of UINavigationController.
if you try to set tintColor of navBar for UIImagePickerController , you can directly access it like imagePicker.navigationBar.tintColor = .red, coming to the document picker, you Cann't access navigationBar directly.you can access it by imagePicker.navigationController?.navigationBar.tintColor = .red. Here navigationController is Optional.thats why we are unable to access navbar directly and make changes.
Apple created an app with document picker. Refer source code here: Particles

iOS 13 Status Bar Glitch

There seems to be a glitch on iOS 13 when trying to change the status bar color from one view controller to the other. The previous view controller overrides the preferred status bar style to light content. When navigating to a child view, I call the following code to set the status bar based on the interface style.
override var preferredStatusBarStyle: UIStatusBarStyle {
if #available(iOS 13, *) {
if self.traitCollection.userInterfaceStyle == .dark {
return .darkContent
} else {
return .lightContent
}
}
return .default
}
The status bar looks like so, where half of it is light and the time is dark (as it should be). After an arbitrary amount of time the status bar will draw properly. Ive tried calling setNeedsStatusBarDisplay(). Which does get called but does not fix the problem after a re-render.
This only happens on iOS 13. Tested across multiple devices
Status bar glitch. Time is light where as battery and network icons are dark:
Thanks in advance!
Here is what I do to fix this issue:
#interface AHTabBarController : UITabBarController
- (UIViewController *)childViewControllerForStatusBarStyle {
UINavigationController *navigationController = self.selectedViewController;
navigationController.navigationBar.barStyle = UIBarStyleDefault; // status bar style
return navigationController;
}
I got the same status bar glitch every time when I was changing UIWindow.rootViewController. It was reproducible even in empty project, created from scratch in Xcode 11 and already configured correctly by it. But on iOS 12 and below everything works fine.
I've found the solution for iOS 13 that works for me. If you update your project from Xcode 10 / iOS 12, you should add SceneDelegate to the project first (I've done it according to this manual). Then, right after changing root view controller you should call makeKeyAndVisible:
if (#available(iOS 13, *)) {
id<UIWindowSceneDelegate> sceneDelegate = (id<UIWindowSceneDelegate>) UIApplication.sharedApplication.connectedScenes.allObjects.firstObject.delegate;
[sceneDelegate.window makeKeyAndVisible];
}
Sorry for Objective-C code, but Swift version is pretty similar.
this isn't a glitch this set it back to Default
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.style
}
var style: UIStatusBarStyle = .default
Fixed it by setting the View controller-based status bar appearance in the info.plist to NO.
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

Responding to navigation bar height changes during push transition

In iOS 11 the search bar will now change the navigation bar height to 56dp when adding a search bar to the navigationItem.titleView
I like the height change and don't intend on forcing the height to stay at 44dp or lower.
unfortunately when transitioning from one view controller to another the pushed view will be drawn with the larger navigation bar in mind and then the bar height is changed after the transition is finished.
That looks a little like this:
I need a way of getting the navigation controller to recognise the height change during the transition so that it can animate to the smaller size and draw the view correctly.
I have one current fix which I don't like because it's a little jumpy and it's more work the app has to do and also it has to re-evaulate it's views regardless of which view controller it's being pushed from.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11, *) {
navigationController?.view.layoutSubviews()
}
}
So far I haven't found any similar questions on stack overflow, any comments from WWDC and nothing in the official apple documentation.
I've seen many apps deal with this however. The apple contacts app will create what looks like two navigation bars and will move between them without animating the height changes and the fb messenger app will perfectly transition between the heights and even allow for the interactive pop transition.
Using this one, it will keep your searchbar's height fixed
if #available(iOS 11.0, *) {
searchBar.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
}
I tried a lot of different functions.
This thing is the only one that really helps
OBJ C:
if (#available(iOS 11, *)) {
self.navigationController.view.layoutSubviews;
}
SWIFT:
if #available(iOS 11, *) {
self.navigationController.view.layoutSubviews()
}

UiSegmentedControl on NavigationBar below Title

I am new on iOS Development and during my works for an app I am building right now some doubts have appeared to me. I am trying to build a Screen that will be compounded by multiple ViewControllers, but on the NavigationBar I would like to have a UiSegmentedControl above the Title, something like a Scope Bar to control the navigation between the children ViewController. I wanted to build something similar to what we have on HealthKit Dashboard:
.
What kind of approach do you suggest to do that? I understand that some questions have already been done about it, but after a long research I have not got to a conclusion.
During my research I noticed that a UISearchBar on the NavigationBar ( to build the Scope Bar ) is only possible for UITableViewControllers, Am I right? So I think that can not be an approach.
My next idea was to use a UISegmentedControl placed manually below the NavigationBar and then use the Containment Api to change to the different ViewControllers for this Screen. The problem here, is I will have to duplicate the UISegmentedControl on all children ViewControllers. Is there any way to not have to duplicate that?
Another approach I tried was doing my own titleView for the NavigationBar with a NavigationBar and a UISegmentedControl below. I don’t like this idea, neither it went well trying to replicate the NavigationBar.
Finally, another approach I thought was using a UIPageViewController. Although this approach sounds a good idea to me, I think I will also have to duplicate the UISegmentedControl.
In the end I think the best solution is to have a UISegmentControl on the NavigationBar, but I am not seeing how to implement this.
What do you think is the best approach to accomplish my ideia? I thought that it would be easy because it is a pattern I see in many apps. Any suggestions?
I am doing this on XCode 6.1.1 using Swift for iOS 8.
Thanks a lot for your help.
You can get this effect by adding the segment as the title view and setting your desired prompt. In interface builder it looks like this:
Add a UIToolbar to your view. Doesn't matter if you add it through code or interface builder. Then you add your UISegmentedControl as custom view of an UIBarButtonItem
let toolbar = UIToolbar()
toolbar.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(toolbar)
NSLayoutConstraint.activate([
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
// Add SegmentedControl like this:
toolbar.setItems([UIBarButtonItem(customView: mySegmentedControl)], animated: false)
toolbar.delegate = self
Then implement this delegate method (See documentation for UIBarPosition.topAttached)
extension MyViewController: UIToolbarDelegate {
public func position(for bar: UIBarPositioning) -> UIBarPosition {
.topAttached
}
}
Then you have what you need. But there's still a separator line between the navigation bar and the toolbar. To get rid of it use this extension methods and call them in viewWillAppear and viewWillDisappear:
extension UINavigationBar {
func hideHairline() {
// Hide border line of navigation bar since we're showing a toolbar
if #available(iOS 13.0, *) {
standardAppearance.shadowColor = nil
standardAppearance.shadowImage = nil
} else {
shadowImage = UIImage()
setBackgroundImage(UIImage(), for: .default)
}
}
func restoreHairline() {
// Hide border line of navigation bar since we're showing a toolbar
if #available(iOS 13.0, *) {
standardAppearance.shadowColor = .separator
} else {
shadowImage = nil
setBackgroundImage(nil, for: .default)
}
}
}

Resources