I have been adding search boxes and search tab bars to my Swift 3 project based on the Ray Wenderlich tutorial (https://www.raywenderlich.com/113772/uisearchcontroller-tutorial)
This has worked well for most tables. However, one of my tables has a header that has been defined in the storyboard. If I use the code as shown below, it replaces the header that is defined in the storyboard, and also has some other side-effects.
I therefore want to know how to do one of the following:
a)append the search bar to the existing header using code
b)add a searchbar object in the storyboard and then set-up the outlet correctly so that my other search code references it.
I have the following code
In didload
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.scopeButtonTitles = ["All", "Active", "Not Active"]
searchController.searchBar.delegate = self
tableView.tableHeaderView = searchController.searchBar
Extension above main class
extension ItemsViewController: UISearchResultsUpdating{
func updateSearchResults(for: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
extension ItemsViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
//filterContentForSearchText(searchText: searchController.searchBar.text!)
filterContentForSearchText(searchText: searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
Filter Code
func filterContentForSearchText(searchText: String, scope: String =
... filter logic ..
tableView.reloadData()
}
any thoughts would be much appreciated
Related
I tried to solve this problem since hours and didn't find a proper solution. I want to have a custom UICollectionView with a segmented control in the header. Changing the segmented control index should render the cells differently. So far I can display the segmented control in the header of my collectionView but changing the content of the collectionView inside of my UserSearchHeader doesn't work.
I created a custom UICollectionView called UserSearchController which is a Subclass of UICollectionView and conforms the UICollectionViewDelegateFlowLayout protocol.
class UserSearchController: UICollectionViewController,
UICollectionViewDelegateFlowLayout, UISearchBarDelegate { ....
My custom collectionView UserSearchController has a custom header (UserSearchHeader) and custom cells (UserSearchCell).
collectionView?.register(UserSearchCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(UserSearchHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
The UserSearchHeader contains the segmented control.
class UserSearchHeader: UICollectionViewCell {
var segmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["Username", "Hashtag"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.black
sc.selectedSegmentIndex = 0 // automatically highlight
// sc.addTarget(self, action: #selector(segmentedChange), for: .valueChanged)
return sc
}() .....
If the segmentedIndex is 0 I want to render the cells with the UserSearchCell class if the segmentedIndex is 1 I want to render it with a different cellClass (Clicking the segmented control). How can I achieve this behavior with these different classes. shall I use the cellForItem at method inside of the UserSearchController to check the status of the segmented control. But how can I do this when the segmented control is defined in the UserSearchHeader class .
override func collectionView(_ collectionView:
UICollectionView, cellForItemAt indexPath: IndexPath) ->
UICollect. ionViewCell { if segmentedControl.segmentedIndex = 0 ....}
tried using Use the scopeButtonTitles property?
mySearchController.searchBar.scopeButtonTitles = ["Username", "Hashtag"]
Have a look at this question for more details. Basically you use the
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
// respond to your scopeBar selection here
switch selectedScope {
case 0:
print("Username tab selected")
// do your stuff for Username here
case 1:
print("Hashtag tab selected")
// do your stuff for Hashtag here
default:
print("Someone added a tab!")
// you shouldn't have more than 2 tabs
}
EDIT:
Please find below the missing pieces described in the link provided initially. There is no need to add the header from a NIB.
First, you add properties for your search & results controllers:
typealias ResultVC = UserResultController //The class to show your results
var searchController: UISearchController!
var resultController: ResultVC?
Then, in viewDidLoad you add this:
// Search Results
resultController = ResultVC()
setupSearchControllerWith(resultController!)
if #available(iOS 9.0, *) {
searchController?.loadViewIfNeeded()
}
Then you need to add this function to your class:
func setupSearchControllerWith(_ results: ResultVC) {
// Register Cells
results.tableView.register(TableCell.self, forCellReuseIdentifier: "\(TableCell.self)")
results.tableView.register(UINib(nibName: "\(TableCell.self)", bundle: Bundle.main), forCellReuseIdentifier: "\(TableCell.self)")
// We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
results.tableView.delegate = self
searchController = UISearchController(searchResultsController: results)
// Set Scope Bar Buttons
searchController.searchBar.scopeButtonTitles = ["Comics only", "Digital too"]
// Set Search Bar
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
// Set delegates
searchController.delegate = self
searchController.searchBar.delegate = self // so we can monitor text & scope changes
// Configure Interface
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = true
if #available(iOS 9.1, *) {
searchController.obscuresBackgroundDuringPresentation = false
}
// Search is now just presenting a view controller. As such, normal view controller
// presentation semantics apply. Namely that presentation will walk up the view controller
// hierarchy until it finds the root view controller or one that defines a presentation context.
definesPresentationContext = true
}
Finally, you add the function I was describing initially: searchBar:selectedScopeButtonIndexDidChange:
I want implement an UISearchBar on my tableView.
My code (in viewDidLoad) :
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.searchBar.autocapitalizationType = .None
self.searchController.searchBar.autocorrectionType = .No
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = false
self.tableView.tableHeaderView = self.searchController.searchBar
When I click on the searchBar, this one move to top, like it wants hide the navigationBar:
I searched on many posts for an answer but nothing works. I want to disable this animation so that the searchBar doesn't move.
You should be able to prevent this by setting:
self.searchController.hidesNavigationBarDuringPresentation = false
You can think of the UISearchController as being presented modally when you start searching. This would work fine if you had a usual UINavigationController setup, however in a more "customized" UI like yours you may run into issues like the search controller attaching to a wrong view, etc.
I would suggest not to use UISearchController in your case and use a separate UISearchBar initialised with the rest of your interface (probably in a storyboard?), and then do the search manually. Implement the UISearchBarDelegate and add your own UITableView for the search results, if you can't simply filter your content in place.
Although looks like you have a tableView var — you could simply add another property, similar to the one you use as a UITableViewDataSource and store filtered data there. So whenever you have something in the UISearchBar you simply use a filtered data source when reloading table view data. For instance:
var data: [String]
var filteredData: [String]
And then use filteredData in the UITableViewDataSource:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
...
And then do something like this in the UISearchBarDelegate:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
// Filter the data you have. For instance:
filteredData = data.filter({$0.rangeOfString(searchText).location != NSNotFound})
tableView.reloadData()
}
Paste this line in your viewDidLoad of presenting controller:
self.extendedLayoutIncludesOpaqueBars = true
I am trying to write a simple UITableView with a UISearchBar. I have no problem when I drag and drop a UITableViewController. Everything works! (see code below)
override func viewDidLoad() {
super.viewDidLoad()
self.searchResultsController = UISearchController(searchResultsController: nil) //initialize the search controller
self.searchResultsController.searchResultsUpdater = self //the search controller updater is this view
self.searchResultsController.dimsBackgroundDuringPresentation = false
self.searchResultsController.searchBar.sizeToFit()
self.searchResultsController.searchBar.searchBarStyle = UISearchBarStyle.Minimal
self.searchResultsController.searchBar.showsCancelButton = true
self.tableView.tableHeaderView = searchResultsController.searchBar
self.tableView.reloadData()
}
But now, for experimental purpose, I used UIViewController, and added a UITableView, no problem with showing my records in the table.
Then, added a UISearchBar from the storyboard, set its delegate to the UIViewController, but the updateSearchResultsForSearchController method is not called when the user type in something.
It's like my UISearchController has no idea there is a UISearchBar, and what ever I type in, does not evoke the updating method. Do I have to tell the UISearchController that hey this is your UISearchBar?
So here's top of my code:
class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate
{
#IBOutlet weak var mySearchBar: UISearchBar!
#IBOutlet weak var myViewTable: UITableView!
let allElements = ["H", "Li", "Na", "K", "Rb", "Cs", "Fr"]
var filteredElemetns = [String]()
var searchResultsController = UISearchController() //create a new search controller
override func viewDidLoad()
{
super.viewDidLoad()
self.searchResultsController = UISearchController(searchResultsController: nil) //initialize the search controller
self.searchResultsController.searchResultsUpdater = self //the search controller updater is this view
self.searchResultsController.dimsBackgroundDuringPresentation = false
self.searchResultsController.searchBar.sizeToFit()
self.searchResultsController.searchBar.searchBarStyle = UISearchBarStyle.Minimal
self.searchResultsController.searchBar.delegate = self
//self.myViewTable.tableHeaderView = self.searchResultsController.searchBar
}
If I uncomment the last line, then I'll have two UISearchBar, which one is added by the storyboard and the other one with the last line code. The one I added, does not work, but the one at top of the myViewTable does.
Ok, I found the solution.
I used my filtering algorithm inside searchBar:textDidChange function. Then everything worked, and I don't need updateSearchResultsForSearchController function anymore. Here is my code:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String)
{
print("filtering...")
self.filteredElemetns.removeAll(keepCapacity: false) //remove all the elements
let searchPredict = NSPredicate(format: "SELF CONTAINS [c] %#", self.mySearchBar.text!)
print(searchPredict)
let foundElements = (self.allElements as NSArray).filteredArrayUsingPredicate(searchPredict)
self.filteredElemetns = foundElements as! [String]
self.myViewTable.reloadData()
}
Of course, don't forget to make sure your class conforms to UISearchBarDelegate protocol.
I find this approach better than using a UITableViewController. One reason, if you search, you will find that many people have a problem with making UISearchBar the first responder. The only solution to that, I found, is calling becomeFirstResponder after a delay, which is really not a good programming approach.
But, with this approach, you can make an outlet of your UISearchBar and then easy make it the first responder in viewDidAppear.
I know some people might say no you can easily make the UISearchBar the first responder even if you use UISearchResultsController by doing something like:
self.searchResultsController.searchBar.becomeFirstResponder()
self.searchResultsController.active = true
But, believe me in iOS 8/9 it is not that simple, it won't work. Try it...
I want to use search bar in my app. But I couldn't find any tutorial for this.
My question is simple: How can I get search bar text when user preses to enter button ?
I need something like this in my view controller:
override func userPressedToEnter(text: String) {
println("User entered: \(text)")
}
How can I do this in swift ?
Assuming you have a simple search bar in your storyboard, make sure you have it connected as an outlet. Then use this as an example. Use UISearchBarDelegate the reference to learn more about delegate methods available to you.
import UIKit
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet var searchBar:UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
searchBar.delegate = self
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("searchText \(searchText)")
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("searchText \(searchBar.text)")
}
}
I would take a look at the UISearchBarDelegate protocol:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UISearchBarDelegate_Protocol/index.html
Make your view controller class conform to this protocol and you will have everything you need to interact with your search bar. Alternatively you can get at the search bar text field but Apple gives you a much cleaner, nicer, event driven way via this protocol.
Assuming you have a tableview that you're searching, add a Search Bar and Search Controller to the tableview in the storyboard. That'll hook up all the data source / delegate connections that you need.
Then in your tableview you can use:
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
doStuffWithSearchText(searchBar.text, scope: 0)
}
which will get called whenever they change the text in the search bar. It's common to update the data that's displayed every time they change the text but if you need to do it only when they tap on the search button use this function instead:
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
doStuffWithSearchText(searchBar.text, scope: 0)
}
And you can get the text from the search results controller:
controller.searchBar.text
Or from the search bar:
searchBar.text
If you're not using a tableview controller:
Add a search bar
Hook up your view controller as the search bar's delegate
Then use the searchBarSearchButtonClicked: function to handle when they tap the "Search" button or searchBar(searchBar: UISearchBar, textDidChange searchText: String) to handle w
I wrote a tutorial on doing it with a table view controller that has all the gritty details: Adding a Search Bar to a Table View in Swift
As part of an assignment for a iOS development class, I'm tasked with adding a UISearchBar to a Core Data project written in Swift.
I can't figure out how to get the search bar to compare the text to the objects in my managedObjectContext. I know I'll need to set a predicate and maybe add a few variables. It's the "how" and "where" that's crushing me.
What I THINK I need to do
The managedObjectContext is instantiated in code and there are objects that display in the TVC.
I need to set an NSPredicate based on searchBar.text to search the managedObjectContext and dump the objects in filteredNotes. This shouldn't be that hard to do, but I find it difficult.
Problems I've encountered:
Apple's documentation tells me what UISearchBar can do, but not how to do
it.
Apple's sample code won't compile in Xcode 6.3 (Problem partially solved)
Update: To update sample code from Apple's website to Swift 1.2 when using Xcode 6.3 beta, you can click Edit/Convert within Xcode to update it. Even though the code compiles without crashing, I still get a console error on Apple's code.
Existing examples of how to do it are:
...written in Objective-C and/or
...for filtering static arrays and/or
...using deprecated features
My Setup So Far
This post could be a Swift Rosetta Stone for adding a UISearchBar in a Core Data project's ViewController. Thank you for reading. If you've got a thought, here are the details of what I've got set up so far.
My Core Data Stack is setup in AppDelegate.swift using "canned code".
My project is setup as a Master-Detail ViewController.
My sole NSManagedObject entity has 4 attributes.
I added UISearchBarDelegate to the top of my "canned code" MasterVC:
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchBarDelegate
// These get set up in the canned "TableView" code, which I've left out
var detailViewController: DetailViewController? = nil
var addNoteViewController:AddNoteViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
#IBOutlet weak var searchBar: UISearchBar! // <- Search Stuff
var searchActive : Bool = false // <- Property for UISearchBar delegate methods
var filteredNotes:[Note] = [] // <- Property for Notes (my NSManagedObject)
Below is my section for UISearchBarDelegate optional methods (and the searchBar)
// UISearchBarDelegate methods
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
searchActive = true;
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchActive = false;
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filteredNotes = data.filter({ (text) -> Bool in // <- 'data' should be the Note objects in my managed object context
let tmp: NSString = text
let range = tmp.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range.location != NSNotFound
})
if(filteredNotes.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}