I want to use Optional #escaping closures to execute something when the closure is called in the function, but if i make it optional like that:
func checkForFavorites(_ completion: (#escaping (Bool) -> Void)?) {
}
i get this error
#escaping attribute may only be used in function parameter position
This may be an easy question so please have understanding
Related
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)
}
}
I'd like to extend the Document.close function to first run some of my own code when the completionHandler triggers before calling the completionHandler that was passed to the function by the user. So something like this:
override func close(completionHandler: ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
So far so good, but I'm not able to call the completionHandler that was passed to the function. Using completionHandler() causes the compiler to complain about Cannot call value of non-function type '((Bool) -> Void)?'. A few StackOverflow searches later it sounds like we have to use the #escaping annotation:
override func close(completionHandler: #escaping ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
but the compiler complains again: #escaping attribute only applies to function types. This leads us to other questions like Swift 3 optional escaping closure parameter, but we are not able to change the function definition because it's overriding an (Objective-C) system library.
Am I misunderstanding something? If not, is there a way to workaround this limitation?
You can pass completion as below,
class Document: UIDocument {
override func close(completionHandler: ((Bool) -> Void)? = nil) {
let completion: ((Bool) -> Void)? = { success in
print("I got \(success)")
// TODO: do something AFTER writeContents has completed
//....
//....
//....
// TODO: call completionHandler
completionHandler?(success)
}
super.close(completionHandler: completion)
}
}
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.
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.
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.