Implement protocol through extension [duplicate] - swift

This question already has answers here:
Swift: Using protocol extension results in "unrecognized selector sent to instance"
(2 answers)
Closed 6 years ago.
I'm trying to create a protocol that wraps the process of using the UIImagePickerController to make it more stream-lined in my apps. I essentially have something like this:
public protocol MediaAccessor : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func mediaCaptured(title: String, fileData: NSData, fileType: String)
}
and an extension that does all the heavy lifting of requesting the permission and handling the delegate methods:
public extension MediaAccessor where Self : UIViewController {
public func captureMedia() {
//All sorts of checks for picker authorization
let picker = UIImagePickerController()
picker.delegate = self
self.presentViewController(picker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
//implementation of the delegate in extension
//even though everything compiles, this method is not called on picker completion
}
}
So everything compiles, but implementing the UIImagePickerControllerDelegate through the extension doesn't seem to register. When I show the picker, it allows me to take a picture, but the didFinishPickingImage call never happens. If I move that call directly into the controller everything works fine, but the idea of this was to hide this stuff from the view controller for a very clean interface into allowing a controller to access media from the device. Is implementing protocol methods through an extension like this something that can't work? Is there something I can change to allow this to work, without having to implement the protocol directly in my view controller?

Cocoa is written in Objective-C. Objective-C can't see Swift protocol extension code. So it's unaware of that implementation of imagePickerController:didFinishPickingImage:. If you want a delegate method to be called by Cocoa, you need to put it where Cocoa can see it.

Related

Non-'#objc' method 'didFinish' does not satisfy requirement of '#objc' protocol 'YPSignatureDelegate'

We are using YPDrawSignature from here to draw a signature on the IOS App.
We are getting the following error on the UIViewController.
Non-'#objc' method 'didFinish' does not satisfy requirement of '#objc' protocol 'YPSignatureDelegate'
The UIViewController is this.
class SignatureViewController: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource, YPSignatureDelegate {
func didStart(_ view : YPDrawSignatureView) {
// print("Started Drawing")
}
func didFinish(_ view : YPDrawSignatureView){
// func didFinish (){
}
}
UPDATE.
We tried the following.
#objc func didStart(_view : YPDrawSignatureView) {
// print("Started Drawing")
}
But still get the same error.
The YPDrawSignatureView has the following Delegate
#objc
public protocol YPSignatureDelegate: class {
func didStart(_ view : YPDrawSignatureView)
func didFinish(_ view : YPDrawSignatureView)
}
extension YPSignatureDelegate {
func didStart(_ view : YPDrawSignatureView) {}
func didFinish(_ view : YPDrawSignatureView) {}
}
I've downloaded YPSignatureView, and did similar to what you have. All I implemented was:
func didStart(_ view : YPDrawSignatureView) {}
func didFinish(_ view : YPDrawSignatureView) {}
and of-coarse assigning a delegate to the view, with those methods implemented by the delegate. And I don't get any errors. Make sure you haven't accidentally changed the YPSignatureView.swift file by clicking on the red button that says Fix as a suggestion. Before I implemented didStart and didFinish, I did get the same error as you have, with a button that says Fix in YPSignatureView. Clicking on that silently changes code within YPSignatureView.swift. Make sure YPSignatureView.swift is pristine and implement the above two functions and you should be fine. Re-download the file to be safe, implement those two methods as above, and thats it.
My didStart() and didFinish() functions are called when touches begins and finishes respectively. Let me know how you go.
Update: as mentioned above, you probably clicked on these two error messages here:
Do not do that. If you did then get a fresh copy of that file then just implement the protocol methods without any #objc. Hit Command + Shift + k to clean build folder and then build again. I did not get any errors after that, runs fine, and yours should too.
In your implementation, you forgot to put the space between _ and view;
Change
func didFinish(_view : YPDrawSignatureView) to
func didFinish(_ view : YPDrawSignatureView)

Sending data using protocols

I have issues with using protocols to send data back to previous controller. I have studied SO questions and guides, but for some reason my data doesn't get transferred back.
In my second class I create data, that is later being sent back to first class:
protocol ImageEditorDelegate {
func sendImage(image: UIImage, id: String)
}
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didPressSave(_ sender: UIButton) {
delegate?.sendImage(image: finalImage, id: imageThatWasSelected)
self.dismiss(animated: true, completion: nil)
}
}
And in my receiving class I have:
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
override func viewDidLoad() {
super.viewDidLoad()
imageEditor?.delegate = self
}
func sendImage(image: UIImage, id: String) {
print("Receiving images", image, id)
switch id {
case "1":
selectedImages[1] = image
productImage1.image = image
case "2":
selectedImages[2] = image
productImage2.image = image
case "3":
selectedImages[3] = image
productImage3.image = image
default:
break
}
}
}
But nothing happens, this func never gets called. I think my delegate is nil, or so, but how could I fix this issue? I have Also, I'm using VIPER as architecture with slightly customized segues, may this be the issue? I have tried simple segues, but had same issue.
I understand that this is rather simple question, but I couldn't understand what I doing wrong after I have read articles about protocols.
Thanks for your help!
What you're doing is very wrong. You have two view controllers with property references to one another:
class PhotoEditorViewController: UIViewController {
var delegate: ImageEditorDelegate?
}
class NewProductViewController: UIViewController, ImageEditorDelegate {
var imageEditor: PhotoEditorViewController?
}
Those are not weak references, so if you ever do get this to work — that is, if you ever arrange things so that the NewProductViewController's imageEditor is a PhotoEditorViewController whose delegate is that NewProductViewController — you will have a nasty retain cycle and a memory leak.
This suggests that you have not understood the protocol-and-delegate pattern. Only the presented view controller should have a delegate property pointing back to the presenter, and it should be weak. The presenter does not need any property pointing to the presented view controller, because it presents it.
you need to instantiate your photoEditor, like
photoEditor = PhotoEditorViewController()
before attempting to set its delegate.
you dont' have to do this next part, but I'd suggest making the delegate variable a weak variable to avoid any retain issues, like so
weak var delegate: ImageEditorDelegate?
and you'll need to mark the protocol as class like so
protocol ImageEditorDelegate : class {

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.

Swift Delegation

I'm having trouble wrapping my head around delegation in Swift. After reading some guides, I was able to set it up delegation between two ViewControllers, but I'm not understanding how it works. In my first view controller, I have a a label that displays what has been entered in the second view controller which contains a text field and a button (that returns to the first view controller). Here is the code for the first view controller:
#IBOutlet weak var labelText: UILabel!
func userDidEnterInformation(info: String) {
labelText.text = info;
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "transition"){
let secondVC: SecondViewController = segue.destinationViewController as! SecondViewController;
secondVC.delegate = self;
}
}
Here's the code for the second view controller:
protocol DataEnteredDelegate{
func userDidEnterInformation(info: String);
}
#IBOutlet weak var userText: UITextField!
var delegate: DataEnteredDelegate? = nil;
#IBAction func buttonPressed(sender: AnyObject) {
let information = userText.text!;
delegate!.userDidEnterInformation(information);
self.navigationController?.popToRootViewControllerAnimated(true);
}
My understanding is that in the text inside the text field gets stored in the information constant, then the userDidEnterInformation method from the protocol is called, with the method being defined inside the first view controller. This method then changes the label inside the first view controller. The thing is, I'm not sure what is happening in the prepareForSegue function. Specifically, I'm not sure what's the purpose of secondVC.delegate = self.
I would appreciate any sort of clarity on delegation.
The diagram is simple but can help you understand what's going on.
FirstViewController must conform to the DataEnteredDelegate protocol you have defined (see Sumit's answer). When using secondVC.delegate = self, you are saying that for the segue transition with the destination being a SecondViewController, the attribute delegate of that SecondViewController instance will be set to this instance of FirstViewController, thus delegating things from SecondViewController to your FirstViewController as made possible by the DataEnteredDelegate protocol.
The protocol you created in second viewcontroller is an Interface. You must implement your first view controller with the DataEnteredDelegate protocol.
class FirstViewController:UIViewController, DataEnteredDelegate{
func userDidEnterInformation(info: String) {
//stub
}
}
If the delegate of the second VC is not set in prepareForSegue() it remains nil. The second VC is then unable to call the first VC.
On a side note, if the delegate is nil your code will crash because delegate! is trying to unwrap an optional binding with the value of nil. It's better to first unwrap the delegate variable:
if let handler = delegate {
handler.userDidEnterInformation(information)
}
Alternatively, you could use Swift's Optional Chaining, calling userDidEnterInformation only if delegate is not nil.
delegate?.userDidEnterInformation(information);
In addition it is recommended to declare the delegate weak, to prevent retain cycles:
weak var delegate: DataEnteredDelegate?
Delegates and Protocols
Do not try to figure out how the dictionary definition of “delegate” fits with the concept of delegation in Swift. It doesn't.
Delegation in Swift is an agreement between two players—a sensing object and a requesting object. The “delegate” is the “requesting object.” Just think “asker” or “requester” every time you see “delegate” and it will make a lot more sense. Here is their agreement...
The Sensing Object (Second View Controller):
I have data from some event that took place. I will publish instructions (a protocol) on how you may access that data. If you want it, you must do three things.
You must declare in your class type that your class abides by my protocol.
You must write the functions that I describe in my protocol. I don't care what those functions do but the function type must match what I publish.
In YOUR code, you must set MY “delegate” (think “asker”) property to point to you. {secondVC.delegate = self} That way I can call one of YOUR functions to deliver the data.
After that, when I get some data, I will call one of the functions in your object that I told you to write. My call to your function will contain the data you are looking for as one of the arguments. {delegate!.userDidEnterInformation(information)} Note: delegate! (asker!) is YOU.
The Delegate (Requesting) Object (First View Controller):
O.K. You've got a deal.