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...
Related
I'm new to Xcode development and very new to MessageKit. I'm currently following an iOS Academy video to get the messaging section of my app working (Great series of videos by the way).
I have installed the MessageKit cocoa pod (and reinstalled it like 3 times to make sure I did it right) but yet, I can't seem to use the messagesCollectionView property.
I know it exists because first, the tutorial uses it, and second, after further investigation, ITS THE FIRST ATTRIBUTE OF THE VIEW CONTROLLER
open class MessagesViewController: UIViewController,
UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UIGestureRecognizerDelegate {
/// The `MessagesCollectionView` managed by the messages view controller object.
open var messagesCollectionView = MessagesCollectionView()
/// The `InputBarAccessoryView` used as the `inputAccessoryView` in the view controller.
open lazy var messageInputBar = InputBarAccessoryView()
when I start typing out "messagesCollectionView" there is no autocomplete and I get an error that says that it cannot be found in scope. Here is my view controller
class ChatViewController: MessagesViewController {
let currentUser = Sender(senderId: "self",displayName: "Yianni Zavaliagkos")
let otherUser = Sender(senderId: "other",displayName: "Ezra Taylor")
var messages: [MessageType] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "Chat"
messages.append(Message(sender: currentUser,
messageId: "1",
sentDate: Date().addingTimeInterval(-86400),
kind: .text("Hello World")))
messages.append(Message(sender: otherUser,
messageId: "2",
sentDate: Date().addingTimeInterval(-70000),
kind: .text("How is it going")))
//Errors on these next couple lines
messagesCollectionView.messagesDataSource = self
messagesCollectionView.messagesLayoutDelegate = self
messagesCollectionView.messagesDisplayDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
and yes, I have implemented the MessegesDataSource and Layout and Display Delegates as well.
My first guess is that I installed the cocoa pod somehow incorrectly but I've tried everything and was hoping to be blessed by the StackOverflow Gods with an easy solution to this frustrating problem. Thanks!
You need to reload your collection, so put: messagesCollectionView.reloadData() to your viewDidLoad method. It works for me
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
Full code for this branch here
View controller "MovieDetailsVC" is presented to the navigation controller when a cell is selected.
The presenting view controller, "ViewController", stores the row of the tableView to display in NSUserDefaults as an Int.
"MovieDetailsVC" reads the row ok. It then pulls the whole array of custom class info from CoreData and stores the array row in a property.
The data is displayed ok at first. The IBOutlet connections are setup ok. I've disconnected and reconnected twice all outlets on MovieDetailsVC, so that should be ok.
"viewDidLoad" is called a successive time. Not sure from where. When it is called, the coredata entity and row number are pulled ok.
The issue is at line "lblTitle.text = self.movieRecord.title". I'm assuming any of the IBOutlets would cause the same issue.
The error thrown is what you would see if the outlets were not connected:
fatal error: unexpectedly fond nil while unwrapping Optional value.
code for the MovieDetailsVC is below. Any ideas why this outlet link would break after working ok would be greatly appreciated.
import UIKit
import CoreData
class MovieDetailsVC: UIViewController {
#IBOutlet var lblTitle: UILabel!
#IBOutlet var lblDescr: UILabel!
#IBOutlet var lblLink: UILabel!
#IBOutlet var imgMovie: UIImageView!
var movieRecord:FavMovie!
var favMovies = [FavMovie]()
override func viewDidLoad() {
super.viewDidLoad()
fetchAndSetResult()
}
func fetchAndSetResult() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "FavMovie")
do {
let results = try context.executeFetchRequest(fetchRequest)
self.favMovies = results as! [FavMovie]
} catch let err as NSError {
print(err.description)
}
if let row = NSUserDefaults.standardUserDefaults().objectForKey("movieRow") as? Int {
self.movieRecord = self.favMovies[row]
configureCellDescr()
}
}
func configureCellDescr() {
lblTitle.text = self.movieRecord.title
lblDescr.text = self.movieRecord.descrWhyGood
lblLink.text = self.movieRecord.linkImdb
imgMovie.image = self.movieRecord.getImg()
}
}
I just have a look at your source code in github and find the problem. There are two issues and I will explain it following.
it does that the second time that the app overrides viewdidload
The reason that your code would call the viewDidLoad method twice is because you have a segue in your storyboard that connect the tableViewCell to movieDetailVC. This will present the movieDetailVC once you click the cell.
And in your code for didSelectCell method, you create another movieDetailVC object and present it.
So actually movieDetailVC would be presented twice when you click the cell. This cause the issue.
Any ideas why this outlet link would break after working ok would be greatly appreciated
The reason why the IBOutlet is nil is because of the way you present movieDetailVC in your code. You create the movieDetailVC object using: let movieDetailsVC = MovieDetailsVC(). Doing it this way, the IBOutlets will not be connected correctly, because ios dont know about the storyboard information.
The correct way to create a MovieDetailVC object is to instantiate from storyboard. See this post for difference.
Solution
There is a very simple solution for your code design:
just remove let movieDetailsVC = MovieDetailsVC() and self.navigationController?.presentViewController(movieDetailsVC, animated: true, completion: nil) from your ViewController class. Since you save the selection data in NSUserDefault, the cell segue will present movieDetailVC and your movieDetailVC can also get the selection data from userDefault.
I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.
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()
}