I had this code in a UITableViewController and it worked perfectly.
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
tableView.setContentOffset(point, animated: true
}
Now I'm refactoring my code to fit more of an MVC style architecture. What I did is create a UITableView in the View class:
class View: UIView {
lazy var tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func configureView() {
// tableView
addSubview(tableView)
tableView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
tableView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}
and then use the View class in my ViewController:
class ViewController: UIViewController {
var newView: View! { return self.view as! View }
override func loadView() {
view = View(frame: UIScreen.main.bounds)
newView.configureView()
}
override func viewDidLoad() {
super.viewDidLoad()
setupSearchBar()
}
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
newView.tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
newView.tableView.setContentOffset(point, animated: true)
}
The tableView shows up no problem and everything else is fine. The only thing that's not working is the setContentOffset is being called, but it's not offsetting the content. I want the searchbar to be hidden by default when the user first opens this viewController (similar to iMessage), but after I moved the code from a UITableViewController to separate files (UIView + UIViewController) like in this example, the searchbar always shows by default.
I'm not sure why it's not working. Any help would be greatly appreciated.
It's probably a timing problem relative to layout. Instead of calling setUpSearchBar in viewDidLoad, do it later, in viewDidLayoutSubviews, when initial layout has actually taken place. This method can be called many times, so use a flag to prevent it from being called more than once:
var didSetUp = false
override func viewDidLayoutSubviews() {
if !didSetUp {
didSetUp = true
setUpSearchBar()
}
}
Also: Your animated value is wrong:
newView.tableView.setContentOffset(point, animated: true)
You mean false. You don't want this movement to be visible. The table view should just appear with the search bar out of sight.
Related
I have a tableView. I set the all settings about searchController ( Search Bar in Large Navigation Bar ) - ( open / close when scroll tableview ). I implemented rightBarButtonItem which name is 'Close' . I want to hide/close tableView and Search Bar with programmatically. I can hide tableView but not SearchBar.
When I do isHidden for SearchBar , The Large Navigation Bar doesnt shrink to normal size.
Pic 1. Opened search bar with scroll down.
Pic 2. Not Hidden Large Navigation Bar with programmatically ( searchar.isHidden not implemented here )
Thanks in advance.
I tried this before but not run
tableView.setContentOffset(.zero, animated: false)
navigationController?.navigationBar.prefersLargeTitles = false
I tried to find a proper way to hide search bar, but I didn't find. But I found a workaround to hide your search bar which is change content offset your table view.
You may try this function to hide your table view and search bar.
func hide() {
tableView.isHidden = true
let point = tableView.contentOffset
let searchBarFrame = self.navigationItem.searchController?.searchBar.frame
let newPoint = CGPoint(x: point.x, y: point.y + searchBarFrame!.height)
tableView.setContentOffset(newPoint, animated: true)
}
Just try this:
navigationItem.searchController = nil
This is all my test code:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var leftBarButtonItem: UIBarButtonItem!
var isHidden = false
var searchController: UISearchController {
let search = UISearchController(searchResultsController: nil)
search.searchBar.placeholder = "hello world"
search.obscuresBackgroundDuringPresentation = false
return search
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Test"
tableView.delegate = self
tableView.dataSource = self
showSearchController()
}
#IBAction func isHiddenAction(_ sender: UIBarButtonItem) {
isHidden = !isHidden
self.tableView.isHidden = isHidden
if isHidden {
leftBarButtonItem.title = "Show"
hiddenSearchController()
} else {
leftBarButtonItem.title = "Hidden"
showSearchController()
}
}
func hiddenSearchController() {
navigationItem.searchController = nil
}
func showSearchController() {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = true
definesPresentationContext = true
}
I have implemented searchBar using UISearchController using following code -
var searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search here..."
definesPresentationContext = true
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
if #available(iOS 11.0, *) {
self.navigationItem.searchController = searchController
} else {
// Fallback on earlier versions
navigationItem.titleView = searchController.searchBar
navigationItem.titleView?.layoutSubviews()
}
Now I have two issues-
SearchBar comes below the navigationBar(See the image attached), how do I get the searchBar on top of NavigationBar that used to come when we implement searchBar with UISearch bar.
The cancel button is not coming on the right side of search bar.
I don't think you can do this natively. But you can activate the search bar when you open the menu (dont forget to set searchController.hidesNavigationBarDuringPresentation to true):
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
But it will hide the UINavigationBar so this is not what you really want. So, maybe better, you can create a custom navigation bar and hide the native one. Here is a quick example:
1 - Create a swift a xib file NavigationBarView with an horizontal UIStackView, a back UIButton with a fixed width and a UISearchBar:
class NavigationBarView: UIView {
var backAction: (()->Void)?
#IBOutlet weak var searchBarView: UISearchBar!
override func awakeFromNib() {
super.awakeFromNib()
// Customize your search bar
self.searchBarView.showsCancelButton = true
}
#IBAction func backButtonPressed(_ sender: Any) {
self.backAction?()
}
}
2 - Instead of using a UITableViewController, create a UIViewController with a vertical UIStackView which contains a view with a fixed height of 64 and a UITableView:
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var containerView: UIView!
let navigationBarView: NavigationBarView = NavigationBarView.viewFromNib() // Custom helper to instantiate a view, see below
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.isHidden = true // hide the native UINavigationBar
self.navigationBarView.backAction = {
self.navigationController?.popViewController(animated: true)
}
self.navigationBarView.searchBarView.delegate = self
self.navigationBarView.add(in: self.containerView) // Custom helper to put a view in a container view, see below
// Other stuff
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
Here is my helpers:
extension UIView {
static public func viewFromNib <GenericView: UIView> () -> GenericView {
let className = String(describing: self)
guard let instance = UINib(nibName: className, bundle: nil)
.instantiate(withOwner: nil, options: nil).first as? GenericView else {
// If this happens, it means the xcodeproj is broken
fatalError("Ho no its broken!")
}
return instance
}
func add(in superView: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
superView.addSubview(self)
self.topAnchor.constraint(equalTo: superView.topAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: superView.bottomAnchor).isActive = true
self.leftAnchor.constraint(equalTo: superView.leftAnchor).isActive = true
self.rightAnchor.constraint(equalTo: superView.rightAnchor).isActive = true
}
}
Yo can try below code and please let me know if you are facing any issue.
if self.searchController != nil {
self.searchController.isActive = false
}
isSearching = true
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = false
self.navigationItem.titleView = searchController.searchBar
self.definesPresentationContext = false
self.searchController.searchBar.returnKeyType = .done
There is a property for this
searchController.hidesNavigationBarDuringPresentation = true
There is a gap, so it might be a white text Canel button. ou can know it for sure in Debugger Navigator (Cmd+7) -> View UI Hierarcy. White button text might be caused by custom navigation bar style
I'm having a really frustrating problem that I'm sure has a simple solution but for the life of me, I can't figure it out.
I have a UITableView within a UIViewController. On the toolbar, I have a button that can show/hide a Search Bar. Everything works great except for the annoying fact that the search bar, upon selection, shifts up 8 pixels (the original margin between the UITableView and the SuperView) and expands in width to equal the full superview.
I have kind of fixed the width issue with the function searchBarFrame(), however, it cuts the "Cancel" button in half, so it isn't perfect (See Below). I'd really appreciate any thoughts on these two problems. I have tried every combination of Extend Edges and Scroll View Insets based on other solutions I've found, but nothing is working for me. I really don't want to use the navigation bar as the search bar nor do I want to convert completely to a UITableViewController. There must be a way to make this work!
Here is my (relevant?) code:
class ListVC: UIViewController UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBtn: UIBarButtonItem!
let searchController: UISearchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
func searchBarFrame() {
var searchBarFrame = searchController.searchBar.frame
searchBarFrame.size.width = tableView.frame.size.width
searchController.searchBar.frame = searchBarFrame
}
func showSearchController() {
searchController.isActive = true
searchBarFrame()
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.placeholder = "Search Places"
searchController.searchBar.roundCorners(corners: [.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 5.0)
searchController.searchBar.barTintColor = UIColor.blurColor
tableView.tableHeaderView = searchController.searchBar
}
func hideSearchController() {
tableView.tableHeaderView = nil
searchController.isActive = false
}
#IBAction func onSearchBtnPress(sender: UIBarButtonItem) {
if !searchController.isActive {
showSearchController()
} else {
hideSearchController()
}
}
Following up, in case anyone else experienced this issue. After a lot of time and effort, I never got my original setup to work. Instead, I started from scratch and approached it differently.
In storyboard (you can do this programmatically too but I went the easier route because I was fed up), I put a UISearchBar inside a UIView inside a UIStackView. I set the Stackview's leading and trailing constraints to the uitableview, the bottom to the top of the uitableview and the top to the bottom of the top layout view. The UIView's only constraint is a height of 56 (the typical search bar height) with a priority of 999 (if you want to show and hide).
This fixed everything and the code was really simple too.
class MyVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
searchView.isHidden = true
}
#IBAction func onSearchBtnPress(sender: UIBarButtonItem) {
if searchView.isHidden {
searchView.isHidden = false
} else {
searchView.isHidden = true
}
}
}
extension MyVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// do something
}
I imagine that the use of NSLayoutConstraint to position and size your views would solve this issue.
For example:
private let margin: CGFloat = 15.0
tableView.translatesAutoresizingMaskIntoConstraints = false
searchBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: margin),
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: margin),
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
searchBar.widthAnchor.constraint(equalTo: tableView.widthAnchor)
])
Here I have a simple example. im calling FirstResponder in viewDidLoad. But accessory view only shows up after tapping the screen. Why isn't it showing from the start?
class TestViewController: MainPageViewController {
private let accessoryView = UIView() //TextInputView() // MessageInputAccessoryView()
override var inputAccessoryView: UIView {
return accessoryView
}
override var canBecomeFirstResponder: Bool { return true }
override func viewDidLoad() {
super.viewDidLoad()
accessoryView.backgroundColor = .red
accessoryView.frame = CGRect(x: 0, y: 0, width: 300, height: 50)
self.becomeFirstResponder()
// Do any additional setup after loading the view.
let tap = UITapGestureRecognizer(target: self, action: #selector(tappo))
self.view.isUserInteractionEnabled = true
self.view.addGestureRecognizer(tap)
tappo()
}
func tappo() {
self.becomeFirstResponder()
}
}
viewWillAppear is a better place to put the becomesFirstResponder. Try that.
So something was resigning my first responder (as I was using UIPageViewController). So I've added this to my UIViewControler :
override var canResignFirstResponder: Bool { return false }
That's it. Cheers!
I created a UISearchController in a table view controller. I segue to this table view controller using a push segue from another view controller. I want the keyboard to show up with the cursor in the search bar as soon as the table view controller is pushed.
I made the search controller active in the viewDidLoad method using
self.mySearchController.active = true
It does make the search controller active but this does not bring up the keyboard nor is the cursor placed in the search bar. I also tried
self.mySearchController.searchBar.becomeFirstResponder()
This line does not seem to have any effect.
How do I bring up the keyboard automatically/programmatically? Below is a more detailed version of my code
class PickAddressViewController: UITableViewController, UISearchResultsUpdating {
var searchText = ""
var mySearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.mySearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.searchBar.text = self.searchText
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.mySearchController.active = true
self.mySearchController.searchBar.becomeFirstResponder()
}
BecomeFirstResponder is the way to go, but you should do it not in viewDidLoad. Look at following discussion for details - Cannot set searchBar as firstResponder
I also tried the suggestions listed in the link mentioned by Nikita Leonov. I needed to add make the class a UISearchControllerDelegate & UISearchBarDelegate and then it worked. I don't u
class PickAddressViewController: UITableViewController, UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating {
override func viewDidLoad() {
super.viewDidLoad()
self.mySearchController = ({
controller.searchBar.delegate = self
})()
self.mySearchController.active = true
self.mySearchController.delegate = self
}
func didPresentSearchController(searchController: UISearchController) {
self.mySearchController.searchBar.becomeFirstResponder()
}
…
}
Swift 5
in viewDidLoad:
searchViewController.delegate = self
in viewDidAppear:
searchViewController.isActive = true
This activates the SearchController
Define a delegate method:
extension MyViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
}
Swift 3 solution in my case:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = mySearchController.searchBar
mySearchController.searchResultsUpdater = self
mySearchController.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.mySearchController.isActive = true
}
}
func presentSearchController(_ searchController: UISearchController) {
mySearchController.searchBar.becomeFirstResponder()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.navigationItem.titleView = searchController!.searchBar
dispatch_async(dispatch_get_main_queue(), {
self.searchController?.active = true
self.searchController!.searchBar.becomeFirstResponder()
})
}
and this code
func presentSearchController(searchController: UISearchController) {
searchController.searchBar.becomeFirstResponder()
}
make sure you give
searchController?.delegate = self in viewDidLoad(). Tested on iOS 9.* device
Besides doing what the other users suggested, I also did the following, and it worked:
searchController.definesPresentationContext = true