I have a searchBar with two scope titles. When I pick one of two, I want to display some data for this category in a UIView.
I try to make this because the simple conclusion did not work. It works but the data is superimposed on top of each other when I saw with a scope titles.
I know about UITableView and reloadData() but I need a UIView because I make a cloud of tags (use label in code).
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.navigationBar.barTintColor = UIColor.white
self.navigationController?.navigationBar.shadowImage = UIImage()
searchBar.delegate = self
checkData(data)
if index == 0 {
createTagsLabel(main: City)
view.setNeedsDisplay()
}
else {
createTagsLabel(main: Contractor)
//listTags.clearsContextBeforeDrawing
}
}
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
index = selectedScope
searchBar.text = nil
viewDidLoad()
}
If you mean to remove all child views, you can use subviews property, like so:
view.subviews.forEach { $0.removeFromSuperview() }
But I would suggest that you remove the main view itself and initialize a new one
Related
I would like to add a button to the left of the search bar. However, I don't want the the scope titles to shift over as well, leaving me with a gap somewhere. I believe my options to be:
remove scope bar, use UISegmentedControl below, and add a button in
modify the UISearchBarClass with a button
?? container view as header, includes button and search controller ??
Here is my code, refactored to use a UIViewController for easy modification. Note that I am using the SnapKit libary for constraints (table cell logic removed). How can I accomplish this?
class TeamSearchController: UIViewController {
let tableView = UITableView()
var searchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
initUI()
}
func initUI() {
self.view.addSubview(tableView)
initSearchController()
initTableView()
}
func initSearchController() {
// searchController.dimsBackgroundDuringPresentation = false
searchBar.placeholder = "Search for a team here..."
searchBar.delegate = nil
searchBar.sizeToFit()
searchBar.showsScopeBar = true
searchBar.scopeButtonTitles = ["Cross Country", "Track"]
// searchController.definesPresentationContext = true
}
func initTableView() {
tableView.dataSource = nil
tableView.delegate = nil
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.tableHeaderView = searchBar
tableView.estimatedRowHeight = 40
tableView.rowHeight = UITableView.automaticDimension
}
}
Current Status:
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.
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)
])
Using Swift 3 and testing on my device, I've tried several code that should remove the black/grey border but it's not being removed. The weird thing is the border is there but once I click on the searchBar ready to type, the border isn't there anymore until the view is loaded again. So the border is just showing until I click the searchBar.
This is my code:
// Coloring TableView
myTableView.backgroundColor = UIColor.white
myTableView.sectionIndexBackgroundColor = UIColor.black
myTableView.sectionIndexColor = UIColor.black
// Search Bar
searchController.searchBar.backgroundColor = UIColor.white
searchController.searchBar.barTintColor = UIColor.white
// Coloring SearchBar Cancel button
let cancelButtonAttributes: NSDictionary = [NSForegroundColorAttributeName: UIColor.black]
UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes as? [String : AnyObject], for: UIControlState.normal)
// Scope: Selected text
let titleTextAttributesSelected = [NSForegroundColorAttributeName: UIColor.white]
UISegmentedControl.appearance().setTitleTextAttributes(titleTextAttributesSelected, for: .selected)
// Scope: Normal text
let titleTextAttributesNormal = [NSForegroundColorAttributeName: UIColor.black]
UISegmentedControl.appearance().setTitleTextAttributes(titleTextAttributesNormal, for: .normal)
// Coloring Scope Bar
UISegmentedControl.appearance().tintColor = UIColor.black
UISegmentedControl.appearance().backgroundColor = UIColor.white
// Search Bar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
myTableView.tableHeaderView = searchController.searchBar
// Scope Bar
searchController.searchBar.scopeButtonTitles = ["All", "Released", "Unreleased", "Open Beta"]
searchController.searchBar.delegate = self
let searchController = UISearchController(searchResultsController: nil)
// SEARCH BAR: Filtering Content
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredFollowedArray = followedArray.filter { Blog in
let categoryMatch = (scope == "All") || (Blog.blogType == scope)
return categoryMatch && (Blog.blogName.lowercased().contains(searchText.lowercased()))
}
filteredBlogArray = blogArray.filter { Blog in
let categoryMatch = (scope == "All") || (Blog.blogType == scope)
return categoryMatch && (Blog.blogName.lowercased().contains(searchText.lowercased()))
}
myTableView.reloadData()
}
// SEARCH BAR: Updating Results
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {}
// SEARCH BAR: Scope
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchText: searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
// SEARCH BAR: Updating Scope
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
}
// Deallocating Search Bar
deinit{
if let superView = searchController.view.superview {
superView.removeFromSuperview()
}
}
This is how the searchBar looks when the view is first loaded, the line on the top is grey (I don't why here the grey looks so faded, you can't see it but a very thin line of grey is there):
and after you click the searchBar, the line turns black:
And this happens when I add this code which was referred to me:
// Search Bar Border
let searchBar = searchController.searchBar
searchBar.backgroundImage = UIImage()
and this is after I click on the searchBar with that code, and how it's supposed to look. No border:
Are you checking this behavior in the small sized simulator only? The black line in your second screen shot doesn't look connected to your search bar in the first screenshot. It looks like the bottom of the status bar instead. Sometimes simulator can't display thin lines properly. I would increase the simulator size to the biggest or better yet, test it on a phone.
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