How do I make a UITableViewController conform to protocol UISearchResultsUpdating? - swift

I have a UITableViewController class in which I am implementing a UISearchController. I've added the following delegates:
class EmployeesTableView: UITableViewController, NSFetchedResultsControllerDelegate,UISearchResultsUpdating{
I'm importing both UIKit and CoreData. I'm getting the following error:
"Type 'CustomTableViewController' does not conform to protocol UISearchResultsUpdating"
What do I need to do to make the controller conform to the protocol?

Swift 3:
func updateSearchResults(for searchController: UISearchController) {
// code here
}

When you add protocols to your class definition, the easiest way is to mouse over the protocol name and command click its name. This will pull up its definition. With protocol definitions, they usually have methods immediately following them. If a method is required it will be at the top, if it has optional in front, then it isn't required in order to conform.
In the case of `UISearchResultsUpdating, it only has one method and it is required. Just copy the method, or multiple methods and click the back arrow to get back to your class. Paste the methods into your class, and implement them. If they were optional methods (in this case there are no optional methods), remove optional from the front. This is what I copied from the definition.
func updateSearchResultsForSearchController(searchController: UISearchController)
Then you update it to do what you want to do.
func updateSearchResultsForSearchController(searchController: UISearchController) {
//do whatever with searchController here.
}
As an additional example, command click on NSFechedResultsControllerDelegate. You will see that it has no required methods, but lots of optional ones. This information is usually found in the documentation as well, but I've found command + click to be the fastest way to find what I'm looking for.

Swift 3.0
//Make sure to import UIKit
import Foundation
import UIKit
class ViewController: UIViewController, UISearchBarDelegate {
var searchController = UISearchController()
override func viewDidLoad() {
//Setup search bar
searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
//Set delegate
searchController.searchResultsUpdater = self
//Add to top of table view
tableView.tableHeaderView = searchController.searchBar
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
print(searchController.searchBar.text!)
}
}

Related

Weak and delegate fail-warnings when trying to update tableview through delegate method

I been struggling to update my tableview through another class I made.
I then found this stackoverflow solution:
How to access and refresh a UITableView from another class in Swift
But when I follow it step by step and implement all the codes, I get the following errors:
My line:
weak var delegate: UpdateDelegate?
Gets the warning
'weak' may only be applied to class and class-bound protocol types, not 'UpdateDelegate'
And my line:
self.delegate.didUpdate(self)
Gets warning:
Instance member 'delegate' cannot be used on type 'APIgetter'
Could this be because the code is old and I'm using swift 4? else I cannot see why this should be failing. I hope you can help me :)
Update:
My Protocol:
protocol UpdateDelegate: AnyObject {
func didUpdate(sender: APIgetter)
}
Snippet from my ViewController containing the tableview:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UpdateDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
APIgetter.addDataFromSQL()
let updates = APIgetter()
updates.delegate = self
}
//update func
func didUpdate(sender: APIgetter) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
My APIgetter class in APIgetter.swift:
class APIgetter {
weak var delegate: UpdateDelegate?
class func addDataFromSQL (){
//Code to fetch data from API
//Code that comes after DispatchQueue.global & DispatchQueue.main and my result being executed
//result
self.delegate.didUpdate(self)
just update your protocol definition.
protocol UpdateDelegate: class {
// protocol body
}
or
protocol UpdateDelegate: AnyObject {
// protocol body
}
This is needed (as of Swift 4 I think) because classes are reference types and you can only use a weak reference on reference types. Not value types like structs.
UPDATE: You cannot access a property/instance member from a static function the way that you currently are. Remove the class keyword from the function and it should work.
If you want/need to use a single instance of this class throughout your application you can use a static property to make it a Singleton
class APIgetter {
static let shared: APIgetter = APIgetter()
}
Then you would be able to access it like this:
APIgetter.shared.addDataFromSQL()
You could also update the delegate in the same way before calling your function.
APIgetter.shared.delegate = self
I think in this case though I would use a Singleton without the delegate. Just use a completion handler in your function. Setting and changing the delegate on a shared instance could have some side effects if not managed carefully.

Editor Placeholder Error

I am trying to add search capability to a table view controller.
I am getting an error saying I have an editor placeholder in my source code. It is in the updateSearchResults method where I am implementing the UISearchResultsUpdating protocol.
Here is the code generating this error:
import UIKit
class SearchTable : UITableViewController {
}
extension SearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
}
}
I have not implemented this method just yet but will later - I am just wondering why this error is surfacing as I have provided the stub for the method and will return to it later?

Using protocol extension to dismiss keyboard on outside tap

In my project I have few view controllers which are subclasses of UITableViewController, UIViewController, on each I want to implement this behavior:
When user taps outside of a text field it should dismiss the keyboard which was visible when user tapped inside it.
I can easily implement it by defining a tap gesture recognizer and associating a selector to dismiss the keyboard:
class MyViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureToDismissKeyboard()
}
private func configureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
form.addGestureRecognizer(tapGesture)
}
func hideKeyboard() {
form.endEditing(true)
}
}
Since I have to implement same behavior in multiple view controllers, I am trying to identify a way to avoid using repetitive code in multiple classes.
One option for me is to define a BaseViewController, which is subclass of UIViewController, with all above methods defined within it and then subclass each of my view controller to BaseViewController. The problem with this approach is that I need to define two BaseViewControllers one for UIViewController and one for UITableViewController since I am using subclasses of both.
The other option which I am trying to use is - Protocol-Oriented Programming. So I defined a protocol:
protocol DismissKeyboardOnOutsideTap {
var backgroundView: UIView! { get }
func configureToDismissKeyboard()
func hideKeyboard()
}
Then defined its extension:
extension DismissKeyboardOnOutsideTap {
func configureToDismissKeyboard() {
if let this = self as? AnyObject {
let tapGesture = UITapGestureRecognizer(target: this, action: "hideKeyboard")
tapGesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(tapGesture)
}
}
func hideKeyboard() {
backgroundView.endEditing(true)
}
}
In my view controller I confirmed to the protocol:
class MyViewController: UITableViewController, DismissKeyboardOnOutsideTap {
var backgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// configuring background view to dismiss keyboard on outside tap
backgroundView = self.tableView
configureToDismissKeyboard()
}
}
Problem is - above code is crashing with exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyViewController hideKeyboard]: unrecognized selector sent to instance 0x7f88c1e5d700'
To avoid this crash I need to redefine hideKeyboard function within MyViewControllerclass, which is defeating my purpose of avoiding repetitive code :(
Please suggest if I am doing any thing wrong over here or is there any better way to implement my requirement.
I think there are two possible problems: casting Self to AnyObject, and not using the new #selector syntax.
Instead of casting Self to AnyObject, define the protocol as a class-only protocol:
protocol DismissKeyboardOnOutsideTap: class {
// protocol definitions...
}
Then use type constraints to apply your extension to only subclasses of UIViewController, and use Self directly in your code, rather than casting to AnyObject:
extension DismissKeyboardOnOutsideTap where Self: UIViewController {
func configureToDismissKeyboard() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(Self.hideKeyboard()))
gesture.cancelsTouchesInView = true
backgroundView.addGestureRecognizer(gesture)
}
}
Edit: I remembered the other problem I ran into when doing this. The action argument for UITapGestureRecognizer is an Objective-C selector, but Swift extensions to classes aren't Objective-C. So I changed the protocol to an #objc protocol, but that was a problem because my protocol included some Swift optionals, and it also introduced new crashes when I tried to implement the protocol in my VC.
Ultimately, I discovered an alternative method that didn't require an Objective-C selector as an argument; in my case, I was setting an NSNotification center observer.
In your case you might be better off simply extending UIViewController, as UITableViewController is a subclass, and subclasses inherit extensions (I think).
As pointed out by the user ConfusedByCode, even though a protocol oriented approach starts out as a nice one, it becomes un-Swifty as the compiler forces you to use the keyword #objc.
Therefore extending UIViewController is a better approach; at least in my opinion.
In order to maintain a clean project structure, create a file named UIViewController+DismissKeyboard.swift and paste the following content inside:
import UIKit
extension UIViewController {
func configureKeyboardDismissOnTap() {
let keyboardDismissGesture = UITapGestureRecognizer(target: self,
action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(keyboardDismissGesture)
}
func dismissKeyboard() {
// to be implemented inside your view controller(s) wanting to be able to dismiss the keyboard via tap gesture
}
}
Afterwards, any one of your view controllers or other base classes from Apple inheriting from UIViewController such as UITableViewController, etc. for that matter, will have access to the method configureKeyboardDismissOnSwipeDown().
Therefore, merely calling configureKeyboardDismissOnSwipeDown() inside viewDidLoad in each of your view controllers will be automatically injecting a swipe down gesture to dismiss the keyboard.
One caveat still remaining is that, every view controller will be in need to call configureKeyboardDismissOnSwipeDown() separately. Unfortunately, this is a bummer as you can't simply override viewDidLoad() in your extension. Moreover, it's still a mystery to me as to why Apple haven't implemented this directly into the keyboard so that us developers would not need to code around it.
Anyways, this issue can be solved by a technique called Method Swizzling. Basically, it's overriding methods given by Apple so that their behaviour change at runtime. I won't go into any more detail about method swizzling any more than saying that it can be highly dangerous to play around as you would be modifying battle-tested, solid code provided by Apple and used by the system.
Afterwards, when you implement the above provided dismissKeyboard() method in a view controller where you want to be able to dismiss the keyboard, you'll be able to do so.
TapGestureDismissable.swift
#objc protocol TapGestureDismissable where Self: UIViewController {
func hideKeyboard()
}
extension TapGestureDismissable {
func configureTapGestureToDismissKeyboard() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = true
view.addGestureRecognizer(tapGesture)
}
}
Inside your ViewController
extension myViewController: TapGestureDismissable {
func hideKeyboard() {
view.endEditing(true)
}
}

Implementing a Delegate Method in a Subclass whose Superclass conforms to that protocol Swift

I have incorporated the GoogleMaps API into my app and all is well with that. However after a bit more development I realized I will need to have two different ViewControllers, both showing a GMSMapView but each having slightly different functionality. I decided to make a base class which has the common functionality and that base class conforms to GMSMapViewDelegate. In this base class, among other things I have:
class BaseMapViewController: UIViewController {
var mapView = GMSMapView()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
loadMap()
}
func loadMap() {
mapView = GMSMapView(frame: CGRectZero)
mapView.mapType = kGMSTypeHybrid
self.view = mapView
}
}
extension BaseMapViewController: GMSMapViewDelegate {}
One of the subclasses needs to implement func mapView(mapView: GMSMapView!, didTapAtCoordinate coordinate: CLLocationCoordinate2D) so I just implement that in the subclass (shown below), but it doesn't register any taps.
class SellerMapViewController: BaseMapViewController {
override func viewDidLoad(){
super.viewDidLoad()
}
func mapView(mapView: GMSMapView!, didTapAtCoordinate coordinate: CLLocationCoordinate2D) {
print("\(coordinate.latitude,coordinate.longitude)")
}
}
I then tried putting that delegate method in the base class and it still didn't register any taps. Any ideas as to what I am doing wrong or what I could try?
Thanks!
It sounds like there could be a couple things going on:
Make sure you are setting the superclass as the delegate, not just conforming to the protocol.
I have done a similar things in my own code, and what I do is implement a dummy function in the superclass that if called prints a message that its subclass needs to implement it, so that the superclass fully implements the delegate method.
Implement the methods that you need to in your subclass. If the relationship was properly set up in your superclass, the methods will be called in your subclass, overriding the same call in the superclass.

How to update search results when scope button changed. Swift UISearchController

How to update search results when scope button changed (after my click on scope)?
Search results changed (with new scope) when I type again!
searchControl - config
import UIKit
class ProductTableView: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating
{
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
var friendsArray = [FriendItem]()
var filteredFriends = [FriendItem]()
override func viewDidLoad()
{
super.viewDidLoad()
searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.sizeToFit()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.scopeButtonTitles = ["Title","SubTitle"]
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
self.tableView.reloadData()
}
Update Func
When I type text NSLog print my text and scope number.
When I change scope - nothing!!!
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = searchController.searchBar.text
let scope = searchController.searchBar.selectedScopeButtonIndex
NSLog("searchText - \(searchText)")
NSLog("scope - \(scope)")
filterContents(searchText, scope: scope)
tableView.reloadData()
}
Filter func
func filterContents(searchText: String, scope: Int)
{
self.filteredFriends = self.friendsArray.filter({( friend : FriendItem) -> Bool in
var fieldToSearch: String?
switch (scope){
case (0):
fieldToSearch = friend.title
case(1):
fieldToSearch = friend.subtitle
default:
fieldToSearch = nil
}
var stringMatch = fieldToSearch!.lowercaseString.rangeOfString(searchText.lowercaseString)
return stringMatch != nil
})
}
Help me, please!
The behaviour you're expecting is logical and seems on the surface to be correct, but actually isn't. Thankfully, there's an easy workaround.
Here's Apple's description of the method:
Called when the search bar becomes the first responder or when the user makes changes inside the search bar.
A scope change is a change in the search bar, right? Makes sense to me. But if you read the discussion, Apple makes it clear the behaviour isn't what you expect:
This method is automatically called whenever the search bar becomes the first responder or changes are made to the text in the search bar.
Not included in that: Changes to the scope. Weird thing to overlook, isn't it? Either the method should be called when the scope is changed, or the summary should be clear it isn't.
You can get the behaviour you want by adding the UISearchBarDelegate protocol to your view controller and setting searchController.searchBar.delegate to your view controller.
Then add this:
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResultsForSearchController(searchController)
}
This will cause updateSearchResultsForSearchController to be fired whenever the scope changes, as you're expecting. But instead, you might want to factor the meat of updateSearchResultsForSearchController into a new method that both updateSearchResultsForSearchController and selectedScopeButtonIndexDidChange call.