UISearchController doesn't work properly with a non-translucent UINavigationBar - ios

Currently I am trying to embed a UISearchController into my application. But the UISearchBar, which is a property of the UISearchController, doesn't get displayed properly, if the UINavigationBar is non-translucent. Usually after tapping the UISearchBar property, the UINavigationBar moves up to make room for the UISearchBar. You can see the result on the following screenshot:
https://www.dropbox.com/s/172k63zr2bhj84t/Normal_behaviour.png?dl=0
But if the "translucent" property of the UINavigationBar is set to "NO", the UISearchBar doesn't get displayed properly, because the background of the status bar remains transparent, as you can see on the following screenshot:
https://www.dropbox.com/s/v5cnxoj9ms6976r/Wrong_behaviour.png?dl=0
To demonstrate this weird behaviour, I have modified the sample project provided by Apple:
https://developer.apple.com/library/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html
Here you can download the modified version:
https://www.dropbox.com/s/7icfe6kap98g1e8/TableSearchwithUISearchControllerObj-CandSwift_MODIFIED.zip?dl=0
The modification is in file "APLMainTableViewController.m" line 33.

It's clearly a bug (rdar://20942583).
My workaround is to set
self.edgesForExtendedLayout = UIRectEdgeAll;
self.extendedLayoutIncludesOpaqueBars = YES;
This allows you to keep the navigation bar opaque. The downside is that the content flows below the bar even if it can't be seen, creating some overhead.

All I needed was:
func viewDidLoad() {
extendedLayoutIncludesOpaqueBars = true
}

One workaround for this is to make the status bar translucent just before the search is going to become active, and remove the translucency when the search is about become inactive.
You can do this by registering your view controller as a delegate of UISearchController, and implementing the willPresentSearchController and willDismissSearchController methods. For example (in Swift):
Declare your view controller as a delegate of UISearchController:
class MyViewController: UITableViewController, UISearchControllerDelegate
Don't forget to actually set it as the delegate, for instance in viewDidLoad add:
searchController.delegate = self
And finally:
func willPresentSearchController(searchController: UISearchController) {
navigationController?.navigationBar.translucent = true
}
func willDismissSearchController(searchController: UISearchController) {
navigationController?.navigationBar.translucent = false
}

Ok, this one is a SUPER pain to debug but not that bad to fix. It's all down to the way Apple changed the appearance of navigation bars. It can be fixed by creating a UINavigationBarAppearance object, configuring it with the visual properties you want (i.e. background colour etc) and then assigning it to standardAppearance and scrollEdgeAppearance on UINavigationBar.appearance() - you can have two different instances with different settings if you want.
A simple implementation might look like this:
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
appearance.backgroundColor = barColor
appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
(Naturally replace barColor and textColor with the colours of your choice!)

if someone have a problem like non-translucent hidden the search bar u can just had this :
self.definesPresentationContext = true
Regards

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

UISearchBarController iOS 11 issue - SearchBar and scope buttons overlap

Referred here and here. No answer in first link. In the second link, though the answer is not accepted, but the link to apple developer forum gives error.
Before iOS 11 :
iOS 11 :
Note : Same device same code.
Also, this would mean, all apps using this feature have to be republished ?
Adding these lines fixed it for me:
override func viewDidLayoutSubviews() {
self.searchController.searchBar.sizeToFit()
}
I can get the initial appearance to display correctly in iOS11 using the following code (as per greg's answer):
[self.searchController.searchBar sizeToFit];
if (#available(iOS 11.0, *)) {
self.navigationItem.searchController = self.searchController;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
} else {
// Fallback on earlier versions
self.tableView.tableHeaderView = self.searchController.searchBar;
}
However, if the app is backgrounded then restored while the search bar was active, the appearance would end up overlapped as shown in Nitish's second screenshot above.
I was able to fix that with the following workaround:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
self.searchController.searchBar.showsScopeBar = NO;
[self.searchController.searchBar sizeToFit];
self.searchController.searchBar.showsScopeBar = YES;
[self.searchController.searchBar sizeToFit];
}];
(I'm still working on how to workaround the layout issues following an interface orientation change while the search bar is active - that still ends up overlapped.)
In the radar that Ray Wenderlich filed, #benck posted this answer from WWDC, which, if I'm not mistaken, hasn't been posted yet.
Per your comments, your UISearchController's UISearchBar has been assigned to your UITableView's tableHeaderView. In iOS 11, you should instead be assigning your UISearchController to the searchController property of your view's navigationItem. You no longer need to assign the UISearchBar anywhere. See Apple's documentation on this new property.
I met the same issue on my app, my solution is in iOS 11, using apple suggested new way for searchBar which is in navigationItem, otherwise, using the old way. My code in viewDidLoad() as below:
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchViewHeight.constant = 0
} else {
searchView.addSubview(searchController.searchBar)
}
I have two IBOutlets: searchView and searchViewHeight:
#IBOutlet var searchView: UIView!
#IBOutlet var searchViewHeight: NSLayoutConstraint! // new added for iOS 11
Before iOS 11, my viewController's hierarchy as below:
I have a searchView which height is 44 to contains my searchController's searchBar view. It's under navigation bar.
In iOS 11, I add a new IBOutlet for searchView's height constraint, and set its constant to 0, hide this container view. And add searchController as a part of navigation item.
See apple's document:
https://developer.apple.com/documentation/uikit/uinavigationitem/2897305-searchcontroller
One more thing is under iOS 11, the searchBar's textField background color is little darker than navigation bar color by default. For consistency, you can change it to white, the below code will work both for iOS11 and its prior:
if let textField = searchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundView = textField.subviews.first {
// Search bar textField background color
backgroundView.backgroundColor = UIColor.white
// Search bar textField rounded corner
backgroundView.layer.cornerRadius = 10
backgroundView.clipsToBounds = true
}
}
I think that the solution is to add the Search Bar in the Navigation Bar:
navigationController?.navigationBar.prefersLargeTitles = true // Navigation bar large titles
navigationItem.title = "Contacts"
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor(displayP3Red: 0/255, green: 150/255, blue: 136/255, alpha: 1.0)
let searchController = UISearchController(searchResultsController: nil) // Search Controller
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController
You can find an example for UISearchBarController - SearchBar and scope buttons overlap here.
I had the same issue in iOS 11.
Contrary to some of the comments here, if I look at your screenshots you DONT want to set it as the navigationItem because you don't have a UINavigationController setup.
Neither do you want to add the searchBar in the header of the tableView because for some reason it can't cope with the scopeBar
So what I did to fix it:
To get a UISearchBar with scopes over your tableView, use a UIViewController in interface builder not a UITableViewController.
Place a UISearchBar and a UITableView inside the view controller and wire them up properly (delegates, dataSource, etc).
Don't forget to change your swift file to UIViewController instead of UITableViewController as well and change it accordingly. (add a tableView property and connect it via IBOutlet, change the delegates for the tableView etc)
Then in interface builder, use autoLayout guides so the searchBar sits on top of the tableView
In interface builder when you activate the scope bar it will look totally weird but don't panic, it will be fine. I guess Apple screwed the rendering n interface builder when they changed the behavior to work with UINavigationController... anyway...
Then everything works as it should and look like this (in my case I present it the vc in a popover but that doesn't matter)

UISearchController and UISearchBar are not animating properly and are changing the Status Bar

I'm working in a fresh project with Xcode 7.2, built against iOS 9.2, and testing on an iPhone 6. This project is less than a week old with a minimal framework primarily defined in a storyboard.
The storyboard has a float of UINavigationController > UITabBarController > UITableViewController, which is the one with the search code, defined below.
In my Info.plist, I have Status bar style set to UIStatusBarStyleLightContent, Status bar is initially hidden set to YES, and View controller-based status bar appearance set to No.
In the storyboard, I do not have a UISearchBar or anything else on the UITableViewController other than the prototype UITableViewCell.
Nowhere in my simple app do I override preferredStatusBarStyle and on the storyboard I have made sure all the Status Bar values are set to Light Content for every controller, just to be safe.
The UITableViewController has the following code in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
searchResultsController = MovieSearchResultsController()
searchResultsController.tableView.delegate = self
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.delegate = self
searchController.dimsBackgroundDuringPresentation = true
searchController.hidesNavigationBarDuringPresentation = true
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.searchBar.tintColor = UIColor.whiteColor()
searchController.searchBar.sizeToFit()
self.tableView.delegate = self
self.tableView.tableHeaderView = searchController.searchBar
self.definesPresentationContext = true
}
When the UISearchBar is selected, the entire view shifts upwards, covering the UINavigationBar. This is great, except the status bar changes to black text and the UISearchBar disappears.
I'm pulling my hair out and have been pouring over this all day. I've read so many answers on Stack and nothing seems to work.
When I run Apple's UISearchController demo, it works exactly as intended. I've tried to replicate this exactly and I'm having no luck. I've also tried picking apart the demo and inserting my own code and it seems to work fine, short of completely redoing the project in this container, which seems highly unnecessary.
For the time being, I've just set searchController.hidesNavigationBarDuringPresentation to false and removed self.definesPresentationContext.
My Goal:
The status bar will remain with a white text color and the background color of my UINavigationBar.
The UINavigationBar will hide as the UISearchBar animates, shifting the view up.
The animation will flow smoothly, just like in the Apple demo. No jumpiness or strange delay.

UISearchController with UIBarPositionTopAttached throws UISearchBar offscreen; impossible to have standard UISearchController with UITableView?

I'm trying to create a similar experience to the Contacts apps in iOS 8. The primary components of this are:
Keep the search bar fixed below the navigation bar
Have the search bar attach to the top of the view (standard functionality) when presenting.
This, however, is easier said than done. After a while of struggling with tableHeaderView (which didn't allow for interaction in front of the table view, and was complex with the viewDidLayoutSubviews positioning), I decided to embed a UITableView within a UIViewController, so I could add the UISearchBar as a subview. This worked pretty well, and allowed interaction with the search bar at all scroll positions, and the insets weren't hard to calculate.
But, the search bar gets cut off below the status bar, when activated. Seems like a straightforward issue—even if I didn't experience it with the exact same implementation in a UITableViewController. So, I tried making sure all my properties were set up for alignment, in every possible view controller.
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = YES;
self.automaticallyAdjustsScrollViewInsets = NO;
I've tried every possible combination of these, in viewWillAppear as well as viewDidLoad, as well as translucent navigation bars and different starting frames for the table view and search bar. No luck. So, I tried to adjust the frames or constraints, perhaps using the topLayoutGuide or just 0. Unfortunately, adjusting the frame in any of the UISearchControllerDelegate methods didn't actually adjust its presented position, and adding constraints crashed immediately when the active animation begins (due to super.top not existing in the view hierarchy at the time; removing the constraints in willPresent did absolutely nothing).
After struggling for a bit longer, I tried implementing positionForBar as the UISearchBar's delegate:
- (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar {
return UIBarPositionTopAttached;
}
This seemed to adjust the height, but the search bar flies off the top of the view. When activated, the search bar seems to appear directly above the visible area. This is even worse than it getting clipped behind or below the UIStatusBar. I also tried to just hide the status bar when the search controller becomes active, but conditionally implementing prefersStatusBarHidden didn't work at all (returning YES works great without UISearchController active, but when active it shows the status bar again or it gets shown beneath it). I assume this is because UISearchController refuses to obey any standards or rules, as is now painfully clear.
I've been trying to figure this out for a few days now, and I can't think of a solution besides reimplementing the UISearchController class/animation entirely. Please help!
For the behavior you described, this works great for me 😊
var resultSearchController = UISearchController()
And the following in your viewDidLoad function:
//Search Bar
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.searchBar.sizeToFit()
controller.dimsBackgroundDuringPresentation = false
self.tableView.tableHeaderView = controller.searchBar
self.definesPresentationContext = true
return controller
})()
What worked for me was to subclass UISearchController and override the prefersStatusBarHidden in the subclass.
Also make sure View Controller Based Status Bar Appearance is set to YES
And in your viewcontroller containing the tableview / search bar:
self.definesPresentationContext = YES;
I have the same problem. I add a UISearchController to UIViewController,not using tableView.header. And the UISearchBar.superview.frame.origin.y is -44. So you cannot see the searchBar.
Method 1: You can change the frame of UISearchBarWrapperView in the didPresentSearchController,the delegate of UISearchControllerDelegate。But you will see the animation of this process.
Method 2: like JBlake say. Set self.definesPresentationContext = YES; after the UISearchController is init. It works!
What i understood is if u need searchbar under navigationbar as u mentioned in #1 its very easy just set a boolean (Solution for #1) searchController.hidesNavigationBarDuringPresentation = true
Unfortunately i couldnt fix the searchbar-offscreen-issue while navigationbar is hidden.

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