Swift: Unable to make UIApplication conform to my protocol - swift

I'm trying to make UIApplication conform to the following protocol:
protocol URLOpenerProtocol {
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
}
I've added the following extension:
extension UIApplication: URLOpenerProtocol {}
However, I am getting a conforming error.
Given the UIApplication already has the following method on it:
UIApplication.shared.open(_ url: URL, completionHandler: ((Bool) -> Void)?)
why won't this conform? The two seem to be indentical to me...
Error is as follows:
Type UIApplication does not conform to protocol URLOpenerProtocol

The actual open method in UIApplication is declared like this with 3 parameters, and the second and third ones happen to be optional (source):
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:],
completionHandler completion: ((Bool) -> Void)? = nil
)
UIApplication has no two-parameter method with this signature:
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
It only has the three-parameter one. Therefore, it does not conform to your protocol.
You should add the options parameter into your protocol too, so that UIApplication can conform to it, and I would suggest that you add a protocol extension with the two-parameter version:
protocol URLOpenerProtocol {
func open(_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any],
completionHandler: ((Bool) -> Void)?)
}
extension URLOpenerProtocol {
func open(_ url: URL,
completionHandler: ((Bool) -> Void)?) {
open(url, options: [:], completionHandler: completionHandler)
}
}

Related

ParseSwift.initialize shows Cannot convert value of type error

I figure that it is time to start using Swift so I am working on a complete rewrite of an obj C app. This app uses Parse Server and CocoaPods. Following the ParseSwift documentation example in my AppDelegate.swift I added
import ParseSwift
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
ParseSwift.initialize(applicationId: "AppID", clientKey: "", serverURL: URL(string: "https://server")!, liveQueryServerURL: URL(string: "https://server")!, authentication: ((URLAuthenticationChallenge,(URLSession.AuthChallengeDisposition,URLCredential?) -> Void) -> Void))
and so on ... But the initialize line is showing an error
Cannot convert value of type '((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void).Type' to expected argument type '((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?'
Authentication is an optional field so you don't need to pass a value.
If you are not doing anything with authentication (most cases), you shouldn't pass anything:
ParseSwift.initialize(applicationId: "AppID",
clientKey: "",
serverURL: URL(string: "https://server")!)
When you plan to use authentication for certificate pinning, you should use it as a trailing closure:
ParseSwift.initialize(applicationId: "AppID",
clientKey: "",
serverURL: URL(string: "https://server")!) { (challenge, credential) in
credential(.performDefaultHandling, nil)
}

Completion handler in Swift requires odd underscore characters

I just created a method with a completion handler. I am unable to compile my application, unless I add annoying underscore characters to my method like so:
func searchForLocation(name: String, completion: #escaping (_ results: Array<Any>, _ error: NSError) -> ()) { }
Why can I just define my method as:
func searchForLocation(name: String, completion: #escaping (results: Array<Any>, error: NSError) -> ()) { }
This certainly makes no sense, because Apple are able to create their own completion handlers without the annoying underscore characters, for example:
open func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask
Just change your function declaration as:
func searchForLocation(name: String, completion: #escaping (Array<Any>, NSError) -> ()) { ... }
This is because, you can't use named parameters in the closure.

Error: Generic parameter 'T' could not be inferred.

I have a problem with calling this method:
func setUpFeedbackForm<T:UIViewController>(viewController: T,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ())
where T:FeedbackFormDelegate { ... }
inside this wrapper function:
public class func setUpFeedbackFormWrapper(viewController: UIViewController,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
setUpFeedbackForm(viewController: viewController,
viewForScreenshot: viewForScreenshot,
completionHandler: completionHandler)
}
I am getting error: Generic parameter 'T' could not be inferred. I do understand what the error means, but I don't know how to implement this call correctly. Also the reason why I have this wrapper is that I want to expose func setUpFeedbackForm to obj-c code and I can't import it directly to obj-c because of swift's generics.
Could you please show me the correct way of calling it?
You have two constraints on the viewController parameter that need to be satisfied when calling setUpFeedbackForm:
inheritance from UIViewController
conformance to FeedbackFormDelegate
, and setUpFeedbackFormWrapper satisfies only one, thus the compiler doesn't know what to do with the other one.
The problem is caused by a limitation of Swift which can't directly express variables/parameters that satisfy both class inheritance and protocol conformance, unless using generics, which breaks the Objective-C compatibility.
Thus a valid UIViewController<FeedbackFormDelegate> construct in Objective-C doesn't have a direct equivalent in Swift.
A workaround to this limitation is to declare a 3rd method that exposes the class inheritance and protocol conformance arguments as two distinct parameters, and call that method from both the Objective-C compatible and the Swift-only versions.
func setUpFeedbackForm<T:UIViewController>(viewController: T,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ())
where T:FeedbackFormDelegate {
setupFeedbackFormImpl(viewController: viewController,
feedbackFormDelegate: viewController,
viewForScreenshot: viewForScreenshot, completionHandler: completionHandler)
}
func setupFeedbackFormImpl(viewController: UIViewController,
feedbackFormDelegate: FeedbackFormDelegate,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
// actual code here
}
public func setUpFeedbackFormWrapper(viewController: UIViewController,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
guard let feedbackFormDelegate = viewController as? FeedbackFormDelegate else {
// you can also report errors here, if you want to
// forbid runtime calls with controllers that are not FeedbackFormDelegate
return
}
setupFeedbackFormImpl(viewController: viewController,
feedbackFormDelegate: feedbackFormDelegate,
viewForScreenshot: viewForScreenshot,
completionHandler: completionHandler)
}
If we think in terms of SOLID programming, then this workaround follows the Interface Segregation Principle, as we receive one argument for the view controller stuff, and another one for the delegate stuff, even if they point to the same object behind.

Unable to compile Siri extension in Swift 2.3

I want to integrate the Siri/Intent extension in my App, currently based on Swift 2.3. I am using the Xcode 8 beta 6 build.
The app fails to even compile, giving out errors as
Type 'IntentHandler' does not conform to protocol 'INSendMessageIntentHandling'
Protocol requires function 'handle(sendMessage:completion:)' with type '(sendMessage: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) -> Void'
Objective-C method 'handleWithSendMessage:completion:' provided by method 'handle(sendMessage:completion:)' does not match the requirement's selector ('handleSendMessage:completion:')
Even the sample app from Apple, UnicornChat fails to compile with the same errors. I did change the SWIFT_VERSION flag to Yes, to support Swift 2.3
Sample Code:
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .Success, userActivity: userActivity)
completion(response)
}
This fails with the error above.
Anyone has an idea on how to make Siri work in Swift 2.3?
So, this helped
https://forums.developer.apple.com/message/174455#174455
We need to explicitly define the #objc method signatures with the Swift ones. As in the following works.
#objc (handleSendMessage:completion:)
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
//..
}
Apparently Xcode 8 beta 6 has been updated to Swift 3 and the call back needs #escaping added to the method:
func handle(sendMessage intent: INSendMessageIntent, completion: #escaping (INSendMessageIntentResponse) -> Swift.Void) {
//..
}
If the Siri delegate methods have been updated to Swift 3 (as appears to be the case) then I am afraid you will have to use Swift 3 as well, or an older version of the SDK.
While compiling your code with Swift 2.3, you need to set Use Legacy Swift Language Version to YES for Siri and SiriUI targets.
Also, when IntentHandler and IntentViewController are created, methods might have been created in Swift 3 (as happened to me). So convert the classes to Swift 2.3, and use #objc method signatures along with the delegates.
For IntentHandler,
#objc (handleSendMessage:completion:)
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
}
#objc (handleSearchForMessages:completion:)
func handle(searchForMessages intent: INSearchForMessagesIntent, completion: (INSearchForMessagesIntentResponse) -> Void) {
}
#objc (handleSetMessageAttribute:completion:)
func handle(setMessageAttribute intent: INSetMessageAttributeIntent, completion: (INSetMessageAttributeIntentResponse) -> Void) {
}
For IntentViewController,
#objc (configureWithInteraction:context:completion:)
func configureWithInteraction(interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
}
Hope this helped.

"Does not conform to protocol" error when extending different class

I'm attempting to test my own class by injecting objects that adapt the URLSession and URLSessionDataTask protocols. I'm extending NSURLSession and NSURLSessionDataTask to adopt those protocols so that I can use the existing objects normally, but use test objects in unit tests.
I have the following code, with the error commented:
typealias SessionHandler = (NSData?, NSURLResponse?, NSError?) -> Void
protocol URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask
}
protocol URLSessionDataTask {
}
// Type 'NSURLSession' does not conform to protocol 'URLSession'
extension NSURLSession : URLSession {}
extension NSURLSessionDataTask : URLSessionDataTask {}
I understand the error, my protocol doesn't exactly match the method as implimented by NSURLSession. How do I fix this?
What I ended up doing was creating a protocol extension that creates the necessary method that NSURLSession requires.
extension NSURLSession : URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask {
return dataTaskWithURL(url, completionHandler: completionHandler) as NSURLSessionDataTask
}
}