Resizing UISearchBar in UINavigationBar titleView doesn't work - ios

Resizing UISearchBar in UINavigationBar titleView doesn't work.
I also got two navigation items on each side of the UINavigationBar.
#IBAction func searchButtonPressed(sender: UIButton) {
searchWrap.frame = self.resultSearchController.searchBar.bounds
searchWrap .addSubview(self.resultSearchController.searchBar)
self.resultSearchController.active = true
self.resultSearchController.searchBar.becomeFirstResponder()
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad)
{
self.tableView.tableHeaderView = searchWrap
}
else
{
self.navigationItem.titleView = searchWrap
}
searchIsOn = true
searchButton.hidden = true
filterButton.hidden = true
favButtonStar.hidden = true
settingsButton.hidden = true
}
even if set the frame it automatically resizes.

Here I have created a Demo project for the above solution which you can get it from here.
Download Demo Project
What I have done?
First, I added two buttons to UINavigationBar. First button is for demo and second is for search.
When you tap on search a UISearchBar will be added to the titleView of UINavigationBar and the buttons will be removed from the UINavigationBar
I have implemented the cancel delegate of UISearchBar, when it is called I removed UISearchBar and added the earlier two buttons again.
This how you can get the full UISearchBar

I have met this issue once. And confirm that searchBar that is added as subview on titleView does not work.
titleView belongs to self.navigationController, and your searchDelegate you're using belong to self controller, that's not self.navigationController. So it can not reach into delegate even in debug.
I don't know what your aim, but if you really want to on the navigationBar, you could pretend that, make a view look like a navigationBar, and add searchBar like subview and so on.
Hope this could help.

Related

Custom position for UISearchBar

I'm currently trying to implement UISearchController and its associated UISearchBar.
I aim to have a UISearchBar just above my UITableView which goes top as soon as it has the focus, like the picture below (screenshot from my old UI with UISearchDisplayController, now deprecated):
I already tried to set my UISearchBar as header of the UITableView, it works well but I have 2 issues :
The search bar logically scrolls with the table view content
When the search bar takes the focus, it remains at the same position
Here is the way I configured my UISearchBar:
func configureSearchController() { // Method called in the viewDidLoad()
searchController = UISearchController(searchResultsController: nil)
searchController!.searchResultsUpdater = self
searchController!.dimsBackgroundDuringPresentation = false
searchController!.searchBar.placeholder = "searchfood.searchbar.placeholder".localized
searchController!.searchBar.sizeToFit()
searchController!.hidesNavigationBarDuringPresentation = false
print(searchController!.searchBar.frame)
// Place the search bar view to the tableview headerview.
foodsTableView.tableHeaderView = searchController!.searchBar
// Search bar
searchController!.searchBar.searchBarStyle = .prominent
searchController!.searchBar.barTintColor = ColorLSDP.corail
searchController!.searchBar.tintColor = ColorLSDP.beige
}
So I tried to remove the following lines:
foodsTableView.tableHeaderView = searchController!.searchBar
But when I did this, I was not able to see the UISearchBar anymore.
After checking some other SO topics, I just spotted that my UISearchBar was maybe hidden below my UINavigationBar. So I tried to add the following lines in the viewDidLoad() method:
self.navigationController?.navigationBar.isTranslucent = false
self.edgesForExtendedLayout = []
It changed nothing.
Anyway, it did not solve my main issue which is having this search bar initially positioned like the screenshot above.
Is there a way to do this properly with the brand new UISearchController ?
Thanks a lot for your upcoming answers,
Regards,
Seb R.

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)

UISearchBar of UISearchController moves off screen when active

I need to have a UISearchBar above a UITableView (ie. the search bar is not part of the table view), and what I found is that when the search bar is activated, it moves off screen.
I did a fair bit of search and could not find a solution, there are ‘search bar off screen’ issues but they are adding search bar to table's header view and tweaking properties like definesPresentationContext fixed it.
My view hierarchy:
VC’s view
|— top view
|— segmented control
|— search bar
|— table view
Looks like UISearchController expects the search bar to be inside the table view, and always shifts the table view so that the search bar moves to the very top of screen.
Anyone having the same issue and found a solution?
Thanks a lot!
// Swift 5
// if you are using UISearch Controller then simply add line below to your
viewDidLoad() and it will fix the issue.
override func viewDidLoad() {
super.viewDidLoad()
// fixes the search moving to next screen when its active
self.definesPresentationContext = true
}
Updates
After further testing I decided to just implement the UISearchBar delegate and stop using the UISearchController since I was just reusing my UICollectionView for the results. Found a great guide on how three different methods to implement the SearchBar.
Edge Case #1
TLDR
Enable clips to bounds on the containing view. Or if you have a collection view cell that has a user-generated content view, enable clips to bounds on that content view.
Details:
An additional very important fact is that you may have the search bar embedded in a container view. This can cause issues if the container view does not have clips to bounds enabled. Other examples of this being a problem is the user-generated content views in UICollectionViewCells. Compare the setting on a UITableViewCell auto-generated content view to observe the difference.
Summary
A Boolean value that determines whether subviews are confined to the
bounds of the view. Declaration
var clipsToBounds: Bool { get set } Discussion
Setting this value to true causes subviews to be clipped to the bounds
of the receiver. If set to false, subviews whose frames extend beyond
the visible bounds of the receiver are not clipped. The default value
is false.
Swift 4.x Solution
In my case, this was happening both inside a UICollectionView and inside of a UIStackView. I was able to keep my searchBar in place after noticing the following info in the searchBar quick help.
Summary
The search bar to install in your interface. Declaration
var searchBar: UISearchBar { get } Discussion
Before presenting your searchable content, install the search bar in
this property somewhere into your view controller’s interface. The
search bar becomes the starting point for searching your contents.
Interactions with the search bar are handled automatically by the
UISearchController object, which notifies the object in the
searchResultsUpdater property whenever the search information changes.
To use a custom subclass of UISearchBar, subclass UISearchController
and implement this property to return your custom search bar.
After that I was able to resolve the issue successfully by doing the following:
let searchController = UISearchController(searchResultsController: nil)
var searchBar:UISearchBar!
Sometime later I assign the searchBar from the searchController to the searchBar in my UIViewController...
BTW - I embed my searchBar in a container UIView that has all the contraints I need to keep it in place.
func setupSearchController<T:UIViewController>(delegate vc:T) where T: UISearchResultsUpdating, T:UISearchBarDelegate {
searchBar = searchController.searchBar //this is the key
searchController.searchResultsUpdater = vc
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search".localized()
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = vc
searchController.searchBar.showsCancelButton = true
definesPresentationContext = true
self.searchMenu.addSubview(searchBar)
//searchMenu is a constrained UIView in my hierarchy
}
I believe putting searchBar inside tableView is not mandatory. I had same issue some time back. All you need to do is setting proper auto layout constraint. Adjust leading and trailing of searchView as align to leading and trailing of the tableView. Also release the margin property from constraints. Hope this may help you.
After a few tries, I've found the reason being that when the UISearchBar is activated, UISearchController removes it from your view and puts it on its own view with a table view for showing the results, since I use auto-layout for the search bar, by removing it, some of the constraints are screwed up. So my solution was to not use auto layout for the UISearchBar from UISearchController.
If you choose to use UISearchBar without an UISearchController, which I have found much easier for keeping UISearchBar within its initial bounds, here is the code template:
final class MyUIViewController: UIViewController {
// MARK: Properties
private let tableView = UITableView()
private let searchBar = UISearchBar()
...
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
...
searchBar.delegate = self
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
}
}
extension MyUIVewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
... // Your code filtering tableView dataSource goes here
tableView.reloadData()
}
}

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)
}
}
}

UISearchBar subview strange behaviour

I have added the UISearchDisplayController via Interface Builder, and then I add the UISearchBar to my navigationBar like this
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
It works and looks fine. What I then want to do is add a subview to the searchBar (to act as a placeholder image). I do it by
[self.searchDisplayController.searchBar addSubview:self.imageView];
The problem is, the imageView is not visible. I NSLog its superview and I get a confirmation that it's the searchBar. I can also see that the searchBar has 1 subview, but when I log the subview, it tells me that it's... drum-rolls... the searchBar itself.
How can this be?
EDIT
Interestingly, if I add the searchBar to navigationBar as a subview and the add my imageView, it's visible. The problem is only when the searchBar is being added to navigationBar by self.searchDisplayController.displaysSearchBarInNavigationBar = YES.
Here's how I solved it - I hope this helps someone. The strange thing about adding a searchBar to navigationBar like this
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
is that the 'didLayoutSubviews' is called twice in the viewController, i.e. the searchDisplayController adds the searchBar to the navigationBar with a delay that I was not able to capture through existing delegate methods.
What I ended up doing instead is setting the zIndex of my imageView to maximum
self.imageView.layer.zPosition = MAXFLOAT;
so even if the searchDisplayController adds the searchBar to the navigationBar with a delay, the imageView still stays on top.

Resources