Ambigous reference to initializer 'init(_:)' when trying to parse Any? object to String in Swift - swift

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
}

Related

executeJavascript does not call completionHandler when inside a DispatchQueue

I've written a function that's supposed to return the HTML string that makes up a WKWebview. However, the completion handler is never called, and the project freezes indefinitely. I've also already adopted the WKScriptMessageHandler protocol so that's not the problem.
public func getHTML() -> String {
var result = ""
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
self.webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: {(html: Any?, error: Error?) in
if (error != nil) {
print(error!)
}
result = html as! String
group.leave()
})
}
group.wait()
print("done waiting")
return result
}
I've found several examples on how to get the html, like here, but I don't want to merely print, I want to be able to return its value. I'm not experienced with DispatchQueues, but I do know for that WKWebView's evaluateJavaScript completion handler always runs on the main thread

Extend completionHandler of Document.close

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)
}
}

How to get data from NSDocument's read method

I'm having difficulty trying to put 'data' onto NSPasteboard. By 'data', I mean a type other than the specific PasteboardType formats: text, HTML, image, etc. (It's actually MIDI data.)
override func copy() -> Any {
let pboard = NSPasteboard.general
pboard.clearContents()
pboard.setData(data, forType: .typeMidi)
return true
}
When I try to put my data in, I get:
Cannot convert value of type '(String) throws -> Data' to expected element type 'NSPasteboardWriting'.
That's probably because I've been trying to use NSDocument's data method, but it turns out that's only for writing out to disk.
The data needs to come from the read function:
override func read(from data: Data, ofType typeName: String) throws {
self.theMIDIPlayer = try AVMIDIPlayer.init(data: data, soundBankURL: nil)
if self.theMIDIPlayer == nil {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
But it seems that the read function's arguments are not NSDocument's data function (which only relates to writing). I've no idea where the arguments come from.
Adding something like self.myData = data to the read function (in an attempt to get the data in a useful property) produce "Expected pattern" errors.
SOLVED: The problem was a schoolboy error of using copy() instead of copy(_:).
Vadian's now deleted answer was helpful in creating a custom Pasteboard type.
The complete (relevant) code is as follows:
extension NSPasteboard.PasteboardType {
static let typeMidi = NSPasteboard.PasteboardType(rawValue: "public.midi-audio")
}
class Document: NSDocument {
var theMIDIPlayer: AVMIDIPlayer?
var myData: Data?
#IBAction func copy(_: Any) {
let pboard = NSPasteboard.general
pboard.clearContents()
pboard.setData(myData, forType: .typeMidi)
}
override func read(from data: Data, ofType typeName: String) throws {
self.theMIDIPlayer = try AVMIDIPlayer.init(data: data, soundBankURL: nil)
self.myData = data
if self.theMIDIPlayer == nil {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

How can a closure be used to only complete when data is loaded? (swift)

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)
}

Subclass AFNetworking JSON Response Serialization to insert JSON response body in to error data in Swift 2

AFNetworking does not return the JSON response body when it fails (e.g. 400 status code), therefore you must subclass AFJSONResponseSerializer and fill in the error object with such. This is the recommended here and an example is here
Up until Swift 2, I used the following code to achieve such:
import Foundation
let JSONResponseSerializerWithDataKey : String = "JSONResponseSerializerWithDataKey"
let JSONResponseUndefinedAPIFailureReason : String = "UNKNOWN_ERROR"
class JSONResponseSerializerWithData: AFJSONResponseSerializer
{
override func responseObjectForResponse (response: NSURLResponse, data: NSData, error: AutoreleasingUnsafeMutablePointer
<NSError?>) -> AnyObject
{
var json : AnyObject = super.responseObjectForResponse(response, data: data, error: error) as AnyObject
if (error.memory? != nil)
{
var errorValue = error.memory!
var userInfo = errorValue.userInfo
if let errorDetail = json["detail"] as? String
{
userInfo![JSONResponseSerializerWithDataKey] = errorDetail
}
else
{
userInfo![JSONResponseSerializerWithDataKey] = JSONResponseUndefinedAPIFailureReason
}
error.memory = NSError(domain: errorValue.domain, code: errorValue.code, userInfo: userInfo)
}
return json
}
}
Start with Swift 2, a new type of Error handling was introduced.
The signature of the above function is now:
override func responseObjectForResponse(response: NSURLResponse!, data: NSData!) throws -> AnyObject
I am having trouble achieving the same as above inside a do-catch statement as it seems the failure does not invoke the catch statement, and thus there is no access to the error object. Further, new ErrorTypes are essentially empty and don't contain
This is what I've tried, but the catch statement is never called:
class JSONResponseSerializerWithData: AFJSONResponseSerializer
{
override func responseObjectForResponse(response: NSURLResponse!, data: NSData!) throws -> AnyObject
{
do
{
return try super.responseObjectForResponse(response, data: data)
}
catch
{
let nsError = (error as NSError)
var userInfo = nsError.userInfo
if let errorDetail = userInfo["detail"] as? String
{
userInfo[JSONResponseSerializerWithDataKey] = errorDetail
}
else
{
userInfo[JSONResponseSerializerWithDataKey] = JSONResponseUndefinedAPIFailureReason
}
throw NSError(domain: nsError.domain, code: nsError.code, userInfo: userInfo)
}
}
}
I've tried stepping through the AFNetworking2 Library an the body of the response is there, so I could sub-class it in Objective-C rather than Swift, but would prefer doing such if possible.
Am I handling this incorrectly with a do-catch statement? Any pointers in the right direction would be greatly appreciated.
After more digging, I have found that the issue is as described here.
The error is not thrown, only filled, therefore catch is never called. To induce this old behaviour you can add "NS_SWIFT_NOTHROW" to the end of the Objective-C signature in the header file as described here
This will change the signature to:
override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject?
Which can then be used like before.