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)
}
}
Related
I'm trying to parse an Any? object to String which contains an HTML Document that comes from a WKWebView after JS Execution.
If I try to print the Html: Any? object everything shows on console, but when I try to store it in a String var to do another stuff with it, appears the error
Ambigous reference to initializer 'init(_:)'
Here is my code:
func getHTML() {
miWEBVIEW.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: { (html: Any?, error: Error?) in
print(html) // -> This works showing HTML on Console, but need in String to manipulate
return html
})
}
Here is where I call the function in a button event:
let document: Any? = getHTML()
var documentString = String(document) // -> error appears in this line
The problem is that your getTML method returns Void. You can't initialize a String object with it. Besides that WKWebView evaluateJavaScript is an asynchronous method. You need to add a completion handler to your getHTML method:
func getHTML(completion: #escaping (_ html: Any?, _ error: Error?) -> ()) {
webView.evaluateJavaScript("document.documentElement.outerHTML.toString()",
completionHandler: completion)
}
Then you need call your getHTML method like this:
getHTML { html, error in
guard let html = html as? String, error == nil else { return }
self.doWhatever(with: html)
}
func doWhatever(with html: String) {
print(html)
// put your code here
}
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
Currently, I have a completion handler:
open func Start(completion: (() -> Void)) { ... }
but in this case I have to always call the completion.
How can I make an optional, so in some method I would use completion block but in others I would just skip them and do not add to my method calls?
For example, I want the same as in:
self.present(<#T##viewControllerToPresent: UIViewController##UIViewController#>, animated: <#T##Bool#>, completion: <#T##(() -> Void)?##(() -> Void)?##() -> Void#>)
I've tried
open func Start(completion: (() -> Void)? = nil) { ... }
adding question mark, but in this case I have to call an optional completion block
completion?()
and I cannot call simply
start()
where I do not need in completion block. It requires me to call it
You can make it an Optional Parameter with nil value by default:
open func Start(completion: (() -> Void)! = nil) {
guard completion != nil else {
return
}
completion()
}
Inside some other method:
func foo() {
Start()
Start(completion: nil)
Start(completion: {
// some code
})
Start {
// some code
}
}
You can use a default value that is not nil, e.g. a block that does nothing
open func start(completion: #escaping (() -> Void) = {}) {
}
However, I don't see what problem you have with calling completion?().
I'm slowly coming to terms with closures.
This is taken from the following post:
https://medium.com/#stasost/ios-three-ways-to-pass-data-from-model-to-controller-b47cc72a4336
I understand the function definition requestData is taking a closure which is called with completion(data):
class DataModel {
func requestData(completion: ((data: String) -> Void)) {
// the data was received and parsed to String
let data = "Data from wherever"
completion(data)
}
}
class ViewController: UIViewController {
private let dataModel = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataModel.requestData { [weak self] (data: String) in
self?.useData(data: data)
}
}
private func useData(data: String) {
print(data)
}
}
While I understand requestData is being called in viewDidLoad below and that (data:String) is being passed into requestData I don't quite get what is being done when completion(data) is being called.
Is completion(data) executing the code that is happening after the keyword "in"?
{ [weak self] (data: String) in
self?.useData(data: data)
}
And I had a question about the order in which things are happening. Is it:
a) let data = "Data from wherever"
b) completion(data)
c) self?.useData(data: data)
In the current app I'm working on when the user starts the app I make an api call to load data. But i'm still a tiny bit unsure about how to tell the ViewController that the data has finished loading.
Thanks.
You understand that a function can take, say, an Int as parameter?
func f(_ i:Int) {
// do something with i
}
Well, a function can also take a function as parameter:
func requestData(completion: ((data: String) -> Void)) {
// do something with completion
}
In that declaration, completion is a function — a function that takes one parameter, called data.
What can you do when you receive a function as a parameter? One obvious thing to do with it would be to call it:
func requestData(completion: ((data: String) -> Void)) {
completion(data: "well howdy there")
}
It remains only to talk about the syntax of passing the desired function to requestData as its completion parameter. Here is one way:
func output(data: String) {
print(data)
}
requestData(completion:output)
Here's a nicer way, one that avoids giving the passed function a name:
requestData(completion:{
data in
print(data)
})
Finally, since completion is the only parameter, we are allowed to use "trailing closure" syntax — we delete the completion: label and the parentheses of the call:
requestData {
data in
print(data)
}
Let's say that I have a NotificationManager with this function:
func receivedRemoteNotification(userInfo: [NSObject: AnyObject], fetchCompletionHandler: UIBackgroundFetchResult -> Void) {
guard isValidPayload(userInfo) else {
fetchCompletionHandler(.Failed)
return
}
doSomethingAsyncWithCompletionHandler(fetchCompletionHandler)
}
Is there a way to tell the compiler that fetchCompletionHandler has to always be called, whatever the code path is?
You can use a defer block to specify some code to be execute before the current function ends.
func foo() {
defer {
print("This will always be printed before the end")
}
print("Some code")
}
foo()
> Some code
> This will always be printed before the end
So you can update your function this way
func receivedRemoteNotification(userInfo: [NSObject: AnyObject], fetchCompletionHandler: UIBackgroundFetchResult -> Void) {
defer {
// call fetchCompletionHandler here
}
guard isValidPayload(userInfo) else {
fetchCompletionHandler(.Failed)
return
}
fetchCompletionHandler(.NewData)
}
Of course pay attention to avoid multiple calls.
Update
Swift has a mechanism to find out that every possible branch of your code do initialize a constant before being used.
You could take advantage of this technique declaring a constant that must be initialized
let result: UIBackgroundFetchResult
and adding a defer block where fetchCompletionHandler is invoked with the previous constant.
defer {
fetchCompletionHandler(result)
}
Now the compiler will force you to populate result in every possible branch before the current function ends.
func receivedRemoteNotification(userInfo: [NSObject: AnyObject], fetchCompletionHandler: UIBackgroundFetchResult -> ()) {
let result: UIBackgroundFetchResult
defer {
fetchCompletionHandler(result)
}
guard isValidPayload(userInfo) else {
result = .Failed // if you remove this you get a compiler error
return
}
result = .NewData // if you remove this you get a compiler error
}