UISearchController expands off screen when tapped - ios

We're having a weird issue with using a UISearchController as an item in a navigation bar. It's set as the left bar button item and when a user taps to start searching, the search bar expands to the right off the side of the screen.
Below is the code for creating the UISearchController:
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.hidesNavigationBarDuringPresentation = false
controller.searchBar.delegate = self
controller.delegate = self
controller.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
let searchBarView = UIView(frame: controller.searchBar.frame)
searchBarView.addSubview(controller.searchBar)
controller.searchBar.backgroundImage = UIImage(named: "searchBarBG")
controller.searchBar.barTintColor = .white
controller.searchBar.subviews[0].subviews.flatMap(){ $0 as? UITextField }.first?.tintColor = Constants.Colors.Red
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchBarView)
return controller
})()
When the screen first loads, it looks like this.
After tapping on the search bar, it changes to look like this.
I have no idea what's causing this. After lots of searching, I tried changing self.definesPresentationContext = false; and self.extendedLayoutIncludesOpaqueBars = true as well as adjusting similar checkboxes in the storyboard. Any tips?
Edit: This only seems to happen on iOS 11. In 10.3, the search bar actually shrinks a little to accommodate the Cancel button but ultimately takes up the same amount of space.

I ended up fixing this by overriding the didPresentSearchController and didDismissSearchController methods as part of the UISearchControllerDelegate.
extension ContactUsViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController)
{
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
}
func didDismissSearchController(_ searchController: UISearchController)
{
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 266, height: 44.0)
}
}

If you don't need the added functionality of UISearchBarController you can directly use UISearchBar, which has a more predictable resizing behavor:
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
searchBar.delegate = self
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchBar)
Note that if you use the built-in cancel button or other similar features you'll have to manually handle them in the delegate methods.

Related

Custom View in Navigationbar titleView disappear iOS Swift

I'm having a problem with the titleView in navigation bar in iOS. I've created a custom view for the titleView, but every time I push another controller, the titleView immediately appear and disappear when I go back to the first view controller. Here's my code.
override func viewDidLoad() {
super.viewDidLoad()
let logo = UIImage(named: "img_appbar_logo")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 180, height: 40))
logoContainer.backgroundColor = UIColor.clear
animatedLogo = UIImageView(frame: CGRect(x: 0, y: 0, width: logoContainer.frame.width, height: logoContainer.frame.height))
animatedLogo.contentMode = .scaleAspectFit
animatedLogo.clipsToBounds = true
animatedLogo.image = logo
logoContainer.addSubview(animatedLogo)
navigationItem.titleView = logoContainer
}
I've already fixed the issue related to titleView in Navigation bar. I found out that after I push another controller and pop back, the titleView will be replaced by the view of empty UILabel in the NavigationItem. That's why the titleView appear then disappear.
How I fixed the issue?
I added the custom view directly to the navigation bar. Here's the code
let logo = UIImage(named: "img_appbar_logo")?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 180, height: 40))
logoContainer.backgroundColor = UIColor.red
animatedLogo = UIImageView(frame: CGRect(x: 0, y: logoContainer.frame.origin.y, width: logoContainer.frame.width, height: logoContainer.frame.height))
animatedLogo.contentMode = .scaleAspectFit
animatedLogo.clipsToBounds = true
animatedLogo.image = logo
logoContainer.addSubview(animatedLogo)
navigationController?.navigationBar.addSubview(logoContainer) <--- new
//navigationItem.titleView = logoContainer <---- old
Then remove the view on viewWillDisappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
logoContainer.removeFromSuperview()
}
Set the titleView in viewWillAppear since the titleView gets replaced by the title in the previous controller

Header of viewcontroller doesn't fit

So the original setup of the app that I inherited has the navigation bar set like so (this is in my AppDelegate):
private func configureNavigationController(_ navigationController: UINavigationController) {
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController.topViewController?.view.frame.width)! / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
UINavigationBar.appearance().barTintColor = UIColor.navBackground
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().addSubview(titleView)
}
This creates the nav bar across every view controller correctly with the image in the center, however I have some new functionality that needs to be on top of everything, and this logo file - logo-white - is still showing up over top.
That's the real problem I want to solve - so if my attempted solution below is wrong, let me know and tell me the correct way.
Anyway, I tried commenting out the code above in my AppDelegate, and putting it in the specific viewcontrollers that I need it for
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController!.topViewController?.view.frame.width)!// / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
navigationItem.titleView = imageView
However this doesn't work - I can either get the logo to show up on the left side of the screen, or slightly off of center, but never center.
I am guessing that this is because the bar has a back button and a little settings icon as well on either side.
So, how do I do this correctly?
Is there a way to make it so that something can cover the logo? Is the solution to move it into my individual view controllers?
Here's a picture of the overlap here. The logo, "Pinyada" should not be covering this up at all - this is a third party library that should be on top of everything.
You may try this :
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = .scaleAspectFit
imageView.frame = CGRect(x: 0, y: 0, width: 88, height: 44)
navigationItem.titleView = imageView
}
If you have navigationItem.titleView , another titleview is not necessary.
Sometimes, if you need a much more precise control of the titleView, you can add a customTitleView. Add the following codes in the viewController and you can get what you want.
let imageView = UIImageView(image: UIImage(named: "logo-white"))
private func addTitleView(){
let nbar = (navigationController?.navigationBar)!
let width = nbar.frame.width
imageView.contentMode = UIView.ContentMode.scaleAspectFit
imageView.frame = CGRect.init(x: (width - 88) / 2.0 , y: 0, width: 88, height: 44)
nbar.addSubview(imageView)
}
private func removeTitleView(){
imageView.removeFromSuperview()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addTitleView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeTitleView()
}
BTW, UINavigationBar.appearance().addSubview(titleView)
This method will result in all navigationBar with the same titleView, which is not what you want.
I figured it out.
One of the other modifications to my header was taking up too much space. I made it smaller and the image fits perfectly.
I moved all of the code generating the header out of the AppDelegate and into my navigation protocol, where I then popped it into each and every viewcontroller.

Programmatically UISearchBar not showing up

I created a programatically UISearchBar (not UISearchController) and I set it as tableview header. However, it's not showing up. Anyone might know why is that?
Outside of the class:
var searchBar = UISearchBar()
Inside the viewDidLoad method.
searchBar.barTintColor = UIColor(red:0.54, green:0.77, blue:0.80, alpha:1.0)
searchBar.placeholder = "Cauta cheltuieli"
searchBar.backgroundImage = #imageLiteral(resourceName: "searchbarback")
tableView.tableHeaderView = searchBar
tableView.setContentOffset(CGPoint.init(x: 0, y: 44), animated: true)
You forget to give the searchbar any dimension so the size will be zero.
Try setting the dimension by setting the frame or use the constructor with a frame.
let searchbar = UISearchBar(frame: CGRect(x: 0.0, y: 0.0, height: 50.0, width: tableView.frame.width))

Is this a good way to show a view by touch a button at the middle of the navigation bar and remove it by touch anywhere else?

I'm trying to put a button at the middle of the navigation bar, it will show a list when I touch it (I added a pure UIView here instead of a UITabeView to just make the code simpler) . And then the additional view will be removed when I touch anywhere else. So I add a background view whose size is the same as the screen to response my touch. Although it still behind the navigation bar.
Here is my question:
Is this a good implementation?
class ViewController: UIViewController {
var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped() {
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(maskView)
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(alertView)
}
func handleGesture() {
optionView.removeFromSuperview()
backgroundView.removeFromSuperview()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Now it looks like the following.
Edit:
The following is my implementation of a popover view.
func buttonTapped() {
let popoverViewController = UIViewController()
popoverViewController.preferredContentSize = CGSize(width: 300, height: 300)
popoverViewController.modalPresentationStyle = .popover
let presentationController = popoverViewController.popoverPresentationController!
presentationController.delegate = self
presentationController.sourceView = view
presentationController.sourceRect = CGRect(x: 100, y: 100 , width: 100, height: 100)
present(popoverViewController, animated: true, completion: nil)
}
// delegate
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
It's a little bit different frrom the Apple documentation. They recommended that we'd better configure the presentation controller after calling present(:animated: completion:) method. But it doesn't work if I don't configure it before presentation. Maybe, because I set the delegate.
Configuring the popover presentation controller after calling present(_:animated:completion:) might seem counter-intuitive but UIKit does not create a presentation controller until after you initiate a presentation. In addition, UIKit must wait until the next update cycle to display new content onscreen anyway. That delay gives you time to configure the presentation controller for your popover.
For using a popover or not, it depends on the purpose of this pop over view. If it has lots of information, it will be better to separate it out to another view controller and make segue to it on button click. This will provides user the full screen to look at whatever it is.
For me, adding a button at the center of a navigation bar is not usual. You have to inform me about it for me to click on it.
In conclusion:
If you want a popover view to tell user hints or show them something, it will be better to use UIPopoverPresentationController so that you don't need to care about the styles.
If you want another view to show data, list of pictures etc, it will be better to use a segmented control or another view controller
var optionView: UIView!
var backgroundView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(ViewController.titleButtonTapped), for: .touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 20, height: 30)
button.backgroundColor = .red
navigationItem.titleView = button
}
func titleButtonTapped()
{
if backgroundView == nil
{
backgroundView = UIView(frame: UIScreen.main.bounds)
backgroundView.backgroundColor = .clear
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleGesture)) // add this gesture to response my touch
backgroundView.addGestureRecognizer(gesture)
view.addSubview(backgroundView)
}
if optionView == nil
{
optionView = UIView(frame: CGRect(x: -40, y: 30, width: 100, height: 100)) // x = button.wdith / 2 - optionView.width / 2
optionView.backgroundColor = .red
navigationItem.titleView?.addSubview(optionView)
}
}
func handleGesture()
{
if optionView != nil
{
optionView.removeFromSuperview()
optionView = nil
}
if backgroundView != nil
{
backgroundView.removeFromSuperview()
backgroundView = nil
}
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}

How to add a search bar to the navigation bar

I'd love to add a search bar managed by UISearchController to the right side of my navigation bar.
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.placeholder = "Search"
searchController.searchBar.searchBarStyle = .Minimal
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 200.0, height: 44.0)
searchController.hidesNavigationBarDuringPresentation = false
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: searchController.searchBar)
When I tap on the search bar (so it becomes active) it covers the whole navigation bar (while overlapping everything else) instead of the little part. How can I fix its frame?
Wrapping the search bar in a UIView seems to do the trick.
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: 200.0, height: 44.0)
let barContainer = UIView(frame: searchController.searchBar.frame)
barContainer.backgroundColor = UIColor.clearColor()
barContainer.addSubview(searchController.searchBar)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: barContainer)

Resources