37

When running the App on iOS 13 beta 6, using Xcode 11 beta 5 I'm encountering the strange gap when presenting search results view controller:

enter image description here

Here's a bit of how this is set up:

let searchResultsController = BLSearchResultsController()

let ret = UISearchController(searchResultsController: searchResultsController)
ret.searchResultsUpdater = self
ret.delegate = self
ret.searchBar.delegate = self;
ret.searchBar.autocapitalizationType = .none
ret.searchBar.placeholder = NSLocalizedString("SearchMsg", comment: "")
        ret.searchBar.enablesReturnKeyAutomatically = true

if #available(iOS 13.0, *) {
    ret.searchBar.showsScopeBar = false
    ret.searchBar.backgroundColor = .white

    let searchTextField = ret.searchBar.searchTextField
    searchTextField.font = UIFont.tuttiRegularFont(16)
    searchTextField.accessibilityIdentifier = "Main Search Field"
    if let searchImageView = searchTextField.leftView as? UIImageView {
        searchImageView.image = UIImage(named: "home-search-icon")
     }
}

The results search controller is a normal UITableViewController and is just added to the navigationItem.searchController. There is no fancy presentation code. When building on latest live Xcode and running on the iOS 11/12 device this issue is not present which lead me to believe some underlying iOS 13 change might be causing this glitch.

When debugging the view hierarchy it looks like the result view controller does not reach to the top of the moved search bar.

I've tried fiddling with the modalPresentationModes trying to exclude the possibility that the changes to the presentation could be the cause, had no luck there.

Has anyone encountered this issue and had luck fixing it?

Mo Abdul-Hameed
  • 6,030
  • 2
  • 23
  • 36
UrosMi
  • 383
  • 1
  • 3
  • 11
  • Possible duplicate of [UISearchController searchbar in a tableview header leaves status bar sized gap](https://stackoverflow.com/questions/37198442/uisearchcontroller-searchbar-in-a-tableview-header-leaves-status-bar-sized-gap) – emmics Aug 16 '19 at 09:27
  • unfortunately I don't have any segment controls or anything. My search controller is just embedded into `navigationItem.searchController`. I'm thinking this is in context of some underlying iOS 13 change, since it does not happen when running on current Xcode with current iOS versions – UrosMi Aug 16 '19 at 09:46
  • still double checked and none of those answers helped – UrosMi Aug 16 '19 at 10:12
  • I'm having this issue as well and not sure what causes it. – SlashDevSlashGnoll Aug 26 '19 at 19:11
  • I have the same problem, even in an existing version built with Xcode 10.3 on iOS 13 – Igor Kulman Sep 05 '19 at 08:25
  • Sorry to add a "me too" but... me too. – Nostradamus Sep 17 '19 at 03:34
  • Hey man did you find solution – Swift Sharp Oct 03 '19 at 20:44
  • Nothing that's not hackish @SwiftSharp...at this point removing UISearchController looks like the only good solution – UrosMi Oct 07 '19 at 15:16

14 Answers14

24

Setting

extendedLayoutIncludesOpaqueBars = true

in the UIViewController used to show the search results, fixed the issue for me.

DrS
  • 326
  • 1
  • 4
15

We had the same issue and the solution was to set Under Opaque Bars (since we use opaque bars) Under Opaque Bars

We already had Top and Bottom checked, adding the third moved the search results controller to the correct location.

TodayMorning
  • 201
  • 2
  • 8
  • 1
    yup, I've tried that but no luck... Had to do it through code since this project uses .xibs so I did ```searchResultsController.extendedLayoutIncludesOpaqueBars = true searchResultsController.edgesForExtendedLayout = .all``` – UrosMi Aug 29 '19 at 13:57
  • 9
    I had to set it on the viewcontroller that hosts the searchcontroller, not on the searchcontroller itself – Hons Sep 23 '19 at 09:32
  • This left a weird gap above my navigation bar. I had to use @mirkobraic 's answer. As ugly as it seems – PJayRushton Oct 31 '19 at 06:11
4

For me the problem was that UISearchController didn't update it's frame when search bar moved upwards. I fixed it by setting frame of UISearchController to the frame of it's presenting view controller.

extension UISearchController {
    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if let presentingVC = self.presentingViewController {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                self.view.frame = presentingVC.view.frame
            }
        }
    }
}

In viewWillAppear animation of search bar did not start so you have to wait for a split second. When animation starts, frame of presenting VC is set to the correct value and then you can update frame of your UISearchController. The solution is a hack but it works fine for me.

mirkobraic
  • 49
  • 4
  • 5
    I literally skipped over this answer and tried every single other one. Finally came back to this 2 hours later as last ditch effort. Unfortunately it worked. What is the world coming to?! – PJayRushton Oct 31 '19 at 06:13
3

Finally get through the tough. Just to make the first controller contains the UISearchController to have a translucent navigationBar. Work for me perfectly!

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.isTranslucent = true
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.navigationController?.navigationBar.isTranslucent = false
    }
Raydemo
  • 39
  • 2
  • Here setting isTrunslucent to true value plays important role to not have search controller's search bar all black. But any idea why it needs to be set. It is a little bit counterintuitive why setting navigation bar to being transulcent makes it not being black (transparent?) – Michał Ziobro Sep 24 '19 at 10:07
1

extendedLayoutIncludesOpaqueBars = true did help to some extent.

Along with this, I had to update

navigationController?.navigationBar.prefersLargeTitles = false

when we start searching and set it back to true when search bar is dismissed.

Baby Groot
  • 4,637
  • 39
  • 52
  • 71
1

FYI I filed a bug report to apple - according to the WWDC presentation that describes the newly re-written SearchController and some other UI updates, it sounds like the architecture of the SearchController has been re-written from the ground up. I can't believe that this gap we are seeing is expected behavior - I've wasted the better part of two days trying to move past this, and I'm not bothering any further - my app store app is a free app that has a number of users and I wasn't able to devote time to tracking changes/behavior in the API during the beta period, I'm somewhat tired of Apple doing this kind of thing on a yearly basis.

Nostradamus
  • 1,497
  • 3
  • 20
  • 34
1
  1. As it was mentioned in above answers, set Extend Edges Under Opaque Bars flag as On for UIViewController which presents search results. For me it was not enough because I use NOT translucent navigation bar.
  2. So I added the following implementation for methods of UISearchControllerDelegate:
    - (void)willPresentSearchController:(UISearchController *)searchController
    {
        if (@available(iOS 13.0, *))
        {
            self.navigationController.navigationBar.translucent = YES;
        }
    }

    - (void)willDismissSearchController:(UISearchController *)searchController
    {
        if (@available(iOS 13.0, *))
        {
            self.navigationController.navigationBar.translucent = NO;
        }
    }
  • His https://stackoverflow.com/questions/58427943/ios-13-when-search-active-push-to-other-vc-then-that-vc-uitableview-goes-under Can you pls check this I have same issue but none of the solution is working for me – Yogesh Patel Oct 25 '19 at 10:49
1

Using .asyncAfter(deadline: .now() + 0.1) will cause a glitch in the UI. To get rid of that, get rid of the deadline! Using DispatchQueue.main.async is enough.

extension UISearchController {
    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if let presentingVC = self.presentingViewController {
            DispatchQueue.main.async {
                self.view.frame = presentingVC.view.frame
            }
        }
    }
}
Tiziano Coroneo
  • 640
  • 6
  • 18
1

The easiest solution is to set this on your searchResultsController:

searchResultsController.edgesForExtendedLayout = UIRectEdgeNone;
Bms270
  • 1,586
  • 15
  • 18
0

You must set yours navigationBar.standardAppearance to an UINavigationBarAppearance object that describes a white background.

if #available(iOS 13.0, *) {
        let appearance = UINavigationBarAppearance()
        appearance.backgroundColor = .white
        self.navigationController?.navigationBar.standardAppearance = appearance
}
0

I finally solved this by replacing the UISearchController with a simple(r) UISearchBar.

Maybe not the answer you wanted to hear, but the UISsearchController was already a mess on iOS12, the same code on iOS13 works but give horrible UI artifacts. Like disapearing or overlapping searchbar with the header, white space between the searchbar and the first element of the table, or hiding the first list-item under the scope buttons, ... All different issues between iOS12 and 13, but never looking good.

So overall I spent 6 hours trying to fix the searchcontroller, failed, then spent 30 mins migrating to the Searchbar.

I added the UISearchBar simply using Interface Builder in Xcode10.3. For the refactoring, mostly I had to simply replace searchController.searchBar.xx by searchBar.xx . The main effort was to reimplement the UISeachBarDelegates. Just to only show the scopebuttons and cancel button while the user is searching, and removing them afterwards. The code below gives a good overview of what I did:

class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {

  var fetchedItemsController: NSFetchedResultsController<Item>! = NSFetchedResultsController()

  @IBOutlet weak var searchBar: UISearchBar! //hooked up to IB
  //GONE IS: let searchController = UISearchController(searchResultsController: nil)


  override func viewDidLoad() {
    super.viewDidLoad()

    initializeFetchedResultsControllerForItems()

    //Enable search controller
    searchBar.scopeButtonTitles = [NSLocalizedString("Name", comment: ""),
                                   NSLocalizedString("Birthdate", comment: ""),
                                   NSLocalizedString("Employer", comment: "")    ]
    searchBar.placeholder = NSLocalizedString("Search", comment: "")
    searchBar.delegate = self
    searchBar.showsScopeBar = false
    searchBar.showsCancelButton = false

    tableView.contentInsetAdjustmentBehavior = .automatic
    self.tableView.tableHeaderView = searchBar //add the searchbar as tableheader view

    self.initializeFetchedResultsControllerForItems()

  }

  // MARK: - Data loading from CoreData
  private func initializeFetchedResultsControllerForItems(searchText: String = "", scopeIndex: Int = 0) {
    //print("FETCH RESULTS WITH FILTER: \(searchText) en SCOPE: \(scopeIndex)")
    //Do whatever searches you need to do to update the FetchedResultsController
    //..
    self.tableView.reloadData()
  }
}

extension MasterViewController: UISearchBarDelegate {  //the delegates for the searchbar
  func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
    searchBar.showsScopeBar = true  //show the scopebar when users adds text to searchbar
    searchBar.showsCancelButton = true //also show the cancel button
    searchBar.sizeToFit()
    self.tableView.reloadData() //since the scopebar is there, the table needs to move a bit down

  }
  func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
  }
  func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    initializeFetchedResultsControllerForItems(searchText: searchBar.text!, scopeIndex: searchBar.selectedScopeButtonIndex)
  }
  func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
    switch (selectedScope) {
    case 0: searchBar.placeholder = NSLocalizedString("Seach on name", comment: "")
    case 1: searchBar.placeholder = NSLocalizedString("Search on birthdate", comment: "")
    case 2: searchBar.placeholder = NSLocalizedString("Search on employer", comment: "")
    default: searchBar.placeholder = NSLocalizedString("Search", comment: "")

    searchBar.showsScopeBar = true
    searchBar.sizeToFit()
    }

    initializeFetchedResultsControllerForItems(searchText: searchBar.text!, scopeIndex: selectedScope)
  }
  func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
    searchBar.placeholder = NSLocalizedString("Search", comment: "")
    searchBar.showsScopeBar = false
    searchBar.showsCancelButton = false
    searchBar.endEditing(true)
    searchBar.text = ""
    searchBar.sizeToFit()
    initializeFetchedResultsControllerForItems(searchText: searchBar.text!, scopeIndex: searchBar.selectedScopeButtonIndex)
  }
}
Rodge
  • 101
  • 4
0

Just bringing my solution. In my case:

edgesForExtendedLayout = .all

on the UIViewController that contains the UISearchController worked.

//MARK: - Properties
var presenter: ExplorePresenting?
var searchController: UISearchController?
var searchUpdater: SearchUpdating?


//MARK: - Lifecycle methods
public override func viewDidLoad() {
    super.viewDidLoad()

    headerTitle = "explore".localised
    tableView.allowsSelection = false
    registerCell(cellClass: ExploreTableViewCell.self, with: tableView)

    if let searchController = searchController {

        searchController.searchBar.delegate = self
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "explore_search_placeholder".localised

        definesPresentationContext = true
        navigationItem.hidesSearchBarWhenScrolling = false
        navigationItem.searchController = searchController
        edgesForExtendedLayout = .all

    }

    presenter?.viewReady()

}
Reimond Hill
  • 4,278
  • 40
  • 52
0

Setting Extend Edges > Under Opaque Bars in Interface Builder eliminated the gap for me, setting this on the UITableViewController that hosts the UISearchController (not the UISearchController nor the results UITableViewController).

LouK
  • 1
  • 1
0

Changing navigationController?.navigationBar.isTranslucent property value from false to true helped me to get rid of this gap.

Ilya Biltuev
  • 166
  • 1
  • 7