I'm using new integrated UISearchBar available in iOS 11, the problem is as soon as I tap on the searchbar and start typing text, the SearchResultsController obscures the whole screen, including the searchbar. It is impossible to dismiss the results controller or cancel search afterwards.
To demonstrate the issue, I've configured a minimal reproducible example:
import UIKit
let reuseid = "reuseIdentifier"
class TableViewController: UITableViewController, UISearchResultsUpdating {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseid)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseid, for: indexPath)
return cell
}
func updateSearchResults(for searchController: UISearchController) {
print(searchController.searchBar.text)
}
}
class ViewController: UIViewController {
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: TableViewController())
navigationItem.searchController = searchController
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = UIColor.white
window?.makeKeyAndVisible()
let nc = UINavigationController(rootViewController: ViewController())
window?.rootViewController = nc
return true
}
}
Initial state
Tapping on the SearchBar
The screen is obscured by the UITableView - colored in green
What could cause this bug and how can it be avoided?
Solved by adding this to the SearchResultsUpdater:
edgesForExtendedLayout = []
With default edges, the ResultsController tries to extend its view beyond the UINavigationBar and hence, obscures it:
Related
I have UIViewController with NavigationBar that is a part of UITabController.
Inside UIViewController I have only UITableView.
NavigationBar is transparent and blurred.
TableView top constraint is to superview, so when I scroll content it goes behind navigation bar.
I have large titles enabled:
Problem:
loadData data triggered immediately when I start to scroll down.
Right after I scroll few pixels down.
All works fine if I remove largeTitles,
but with large titles it feels like refreshControl already at position when it ready to trigger .valueChanged
Also it works fine if to remove tab bar and load navigationController directly as root. But I need tabbar.
Full code:
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
self.window = window
window.rootViewController = TabBarController()
return true
}
}
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let navigationController = UINavigationController(rootViewController: ViewController())
navigationController.navigationBar.prefersLargeTitles = true
viewControllers = [navigationController]
}
}
class ViewController: UIViewController {
private let tableView = UITableView()
private let refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Test"
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.delegate = self
tableView.dataSource = self
tableView.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(loadData), for: .valueChanged)
}
#objc func loadData() {
print("loadData triggered")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.refreshControl.endRefreshing()
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell
if let defaultCell = tableView.dequeueReusableCell(withIdentifier: "defaultCell") {
cell = defaultCell
} else {
cell = UITableViewCell(style: .value1, reuseIdentifier: "defaultCell")
}
cell.textLabel?.text = "Test"
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
}
BUT
All works fine if it's from StoryBoard:
I am trying to get a smooth Searchbar on navigation item on iOS 9, which means I can't use navigationItem.searchController property since its only iOS 11 only.
class SearchContainerViewController: UITableViewController {
let dataSource = ["1", "2", "3", "4", "5"]
override public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
override public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = dataSource[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true, completion: nil)
}
}
class SearchViewController: UISearchController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class MyViewController : UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
lazy var searchButton = UIBarButtonItem(title: "Search", style: UIBarButtonItem.Style.plain, target: self, action: #selector(showSearchBar))
var searchViewController: SearchViewController = {
let container = SearchContainerViewController()
let searchController = SearchViewController(searchResultsController: container)
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
setupSearchButton()
}
func setupSearchController() {
searchViewController.searchResultsUpdater = self
searchViewController.searchBar.delegate = self
searchViewController.dimsBackgroundDuringPresentation = false
searchViewController.hidesNavigationBarDuringPresentation = false
searchViewController.searchBar.searchBarStyle = .minimal
searchViewController.searchBar.showsCancelButton = true
definesPresentationContext = true
}
#objc func showSearchBar() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView = self.searchViewController.searchBar
self.navigationItem.rightBarButtonItem = nil
self.searchViewController.searchBar.becomeFirstResponder()
}
}
func setupSearchButton() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView = nil
self.navigationItem.rightBarButtonItem = self.searchButton
}
}
// MARK: Conforms to UISearchResultUpdating
public func updateSearchResults(for searchController: UISearchController) { }
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
setupSearchButton()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layoutSubviews()
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let newWindow = UIWindow(frame: UIScreen.main.bounds)
let mainViewController = MyViewController()
let navigationController = UINavigationController(rootViewController: mainViewController)
newWindow.backgroundColor = .white
newWindow.rootViewController = navigationController
newWindow.makeKeyAndVisible()
window = newWindow
return true
}
}
Though the result is kinda disappointing since the textview with StatusBar is clipping out of the navigation item context, there is anything im doing wrong and could've done better?
Appreciate your support.
In before people down voting the question for no reasons, I am going to do something different and answer my own question.
The clipping happened because in both situations the height of the navigationItem were different because of they are somewhat stretchable if put big content in titleView (like a searchBar).
I've set the searchBar from the start on the navigationItem and just toogle their isHidden property when it should be done.
#objc private func activateSearch() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView?.isHidden = false
self.navigationItem.rightBarButtonItem = nil
self.searchController.isActive = true
}
}
private func deactivateSearch() {
UIView.animate(withDuration: 0.75) {
self.navigationItem.titleView?.isHidden = true
self.navigationItem.rightBarButtonItem = self.searchButton
self.searchController.isActive = false
}
}
I have a question about safe area.
And I write a code and build in two simulators of iOS version 10.2 and 11.4.
It shows different part of red rectangle area like following image.
What's wrong with me?
Thanks.
code here:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: ViewController())
window?.makeKeyAndVisible()
return true
}
}
class ViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "TITLE HERE"
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.left.equalTo(10)
make.top.equalTo(self.topLayoutGuide.snp.bottom)
make.right.bottom.equalTo(-10)
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "\(indexPath) TEST"
return cell
}
}
The problem:
Instead of adding a search bar(with a search and results controller) to a table view controller, I have added it to a regular view controller's navigation bar. At first everything seems fine, but when I click on the search bar the screen turns gray.
This is my code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating, UISearchBarDelegate{
var schools = ["Saratoga", "Fremont", "Argonaut", "Redwood", "Foothill", "Miller", "Rolling Hills"].sorted()
var filteredSchools = ["Saratoga", "Fremont", "Argonaut", "Redwood", "Foothill", "Miller", "Rolling Hills"].sorted()
var searchController: UISearchController!
var resultsController: UITableViewController!
override func viewDidLoad() {
super.viewDidLoad()
resultsController = UITableViewController()
searchController = UISearchController(searchResultsController: resultsController)
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
searchController.searchResultsUpdater = self
self.view.addSubview(searchController.searchBar)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchController.searchBar)
}
func updateSearchResults(for searchController: UISearchController) {
let currText = searchController.searchBar.text ?? ""
filteredSchools = schools.filter({ (school) -> Bool in
if school.contains(currText){
return true
}
return false
})
resultsController.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSchools.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = filteredSchools[indexPath.row]
return cell
}
}
Add these lines in viewDidLoad:
resultsController.tableView.backgroundColor = UIColor.clear
searchController.hidesNavigationBarDuringPresentation = false
Your Navigation Bar is hiding that's all.
If you don't want the gray tint:
searchController.dimsBackgroundDuringPresentation = false
In Xcode 6, is it possible to use a .swift file as the 1st view the user will see when they open my app?
The file below is for a table view as I prefer to use swift instead of IB (too many views makes IB look messy).
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
let items = ["Hello 1", "Hello 2", "Hello 3"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
self.tableView = UITableView(frame:self.view.frame)
self.tableView!.dataSource = self
self.tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel.text = "\(self.items[indexPath.row])"
return cell
}
}
var ctrl = ViewController()
According to Apple's View Controller Programming Guide, "When working with view controllers directly, you must write code that instantiates the view controller, configures it, and displays it." However it also states "You gain none of the benefits of storyboards, meaning you have to implement additional code to configure and display the new view controller." That being said if you still desire to do this, this is how you do it (in your app delegate):
var window: UIWindow?
var viewController: ViewController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.backgroundColor = UIColor.whiteColor()
viewController = ViewController()
// Any additional setup
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}