NSAttributed string from html without hanging UI thread - html

I am trying to load some html text into an NSAttributedString and I am seeing the UI hang.
Looking around I am not sure if that is possible as its looks like it may have to run on the main thread: NSAttributedString from HTML in the background thread
In swift 3 iOS 10 I am able to run this without exception
let myString = // some html content
DispatchQueue.global(qos: .background).async {
let data = myString.data(using: .utf8)
let options: [String: Any] = [
NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue
]
do {
let attributedString = try NSAttributedString(data: data!, options: options, documentAttributes: nil)
DispatchQueue.main.async {
() -> Void in
self.label.attributedText = attributedString
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
A couple things.
Not sure why it doesn't crash. But I suspect that its still running on the main thread as the UI still locks up for a few seconds.
There are images in the html. Wondering if that is was is causing most of the delay. I still want to display the images but wondering if I can pull them out and just display the text initially and load the images on a background thread. Not sure if there is anything built into NSAttributedString to pull out images, or doI have to parse them out manually.
Is there any way at all to get this data to load on a background thread so I can not lock up the UI when using an NSAttributedString initialized with html data?

Just came across the same situation, I just figure out setting value for "Timeout" key can reduce the Unresponding time.
var options = [NSAttributedString.DocumentReadingOptionKey : Any]()
options[.documentType] = NSAttributedString.DocumentType.html
options[.characterEncoding] = String.Encoding.utf8.rawValue
options[.timeout] = 5.0 //Important!You can adjust this value to suitable value.
I think the following line of code always runs in the main queue.There is no way to avoid UI hanging completely while using this function.
NSAttributedString(data: data!, options: options, documentAttributes: nil)

Related

Using CDATA in html leaves the brackets at the end for iOS

I have a simple html snippet that loks like this:
let mystring = """
<![CDATA[
<body>
<font size=+2>
<p><b>Before proceeding:</b></p>
<ul type="dash">
<li>Save a backup copy of the door configuration file</li><li>Disconnect all other Bluetooth devices</li></ul><br />
<p><b>During upgrade:</b></p>
<ul type="dash">
<li>Do not turn off phone screen, take calls, use other apps, or in any other way push the app to the background</li>
</ul><br />
<p><b>Are you sure you want to upgrade %1$# firmware?</b></p>
</font>
</body>]]>
"""
I parse it like this:
var htmlToAttributedString: NSMutableAttributedString? {
guard let data = String(format: mystring, doorName).data(using: .utf8) else { return nil }
do {
return try NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return nil
}
}
And it ends up like this (note the ]]> at the end):
Why are the brackets still there at the end while everything else works fine? It works fine when testing for Android and it's also for the sake of Android that CDATA is used in the first place. How do I make this work for iOS?
The problem most likely is that CDATA is xml and not html and that is why NSAttributedString can't handle it properly. Unfortunately your string is not a proper xml string so we can't use XMLParser for this.
One way is to strip out the xml (CDATA) from the string using a regular expression
let htmlString = mystring.replacingOccurrences(of: "(<\\!\\[CDATA\\[|\\]\\]>)",
with: "",
options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)

parsing Json Array swift 2 error

this code was function correctly but when i tried on swift 2, i start having this errors, does anyone know how to fix them? i already add the NSAppTransportSecurity to info list.
The app may be able to download a json array with images from server, but i'm not really sure how to do it on swift 2
I tried a lot of tutorials about swift 2 but didn't work
Try this....Use do catch instead of if let
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(dataObject, options:.MutableContainers) as? NSArray
self.images = jsonResult
} catch let error as NSError {
print(error)
}

Swift 2.0 How to parse JSON?

I am coding a hangman game and am loading the possible words into my app using json text files. I tried to follow the examples of others on this website but I am getting errors from Xcode.
I tried the following code based on another answer:
import Foundation
var error: NSError?
let jsonData: NSData = /* get your json data */
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &error) as NSDictionary
But I got an errors on line 4 with jsonDict that said "call can throw but is not marked with try, and the error is not handled" and "Type JSONReadingOptions does not conform to protocol NilLiteralConvertible".
Here is the JSON File I would like to parse:
{
“wordList” : {
“difficulty” : “Easy”
“list” : [
“fireplace”,
“apple”,
“january”,
“tooth”,
“cookies”,
“mysterious”,
“essential”,
“magenta",
“darling”,
“pterodactyl”
]}}
I would like to be able to go into my list array and get values. Thank you very much for any help!
In Swift 2 you need to use the new error handling API instead of passing a reference to an NSError:
do {
let jsonDict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(rawValue: 0)) as? NSDictionary
if let jsonDict = jsonDict {
// work with dictionary here
} else {
// more error handling
}
} catch let error as NSError {
// error handling
}
You also can't pass nil as value to the options parameter, you need to pass a value of type NSJSONReadingOptions.
That said, the most common approach for parsing JSON in Swift is currently using third party libraries, such as Argo because they can save you a lot of code that is necessary to validate and safely cast the content of your JSON data to the correct Swift types.

Decoding HTML in Swift causes BAD_ACCESS on iOS device

Following the great example of How do I decode HTML entities in swift? I have managed to decode a HTML entity. However, running my app in iOS simulator causes no errors while testing it on a real device do.
I'm getting the following error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0xc)
On this line:
let attributedString = NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil, error: nil)!
How do you go about solving this?
Probably you are not running the code in the main thread. I am not sure how it is working in the simulator. Anyway try putting that code in a main thread block like
dispatch_async(dispatch_get_main_queue(),
{
let attributedString = NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil, error: nil)
let decodedString = attributedString?.string
})
It should work properly.

How do I access the data when I am using NSURLSession?

I am new to iOS development. I am using Swift and I have very little experience with Objective-C, so some of the other possibly related answers are tricky to understand. I am trying to understand how to use NSURLSession to get some data from a JSON file on the Web. I found some useful information about getting a file from a URL, but like this other StackOverflow user (NSURLSessionDataTask dataTaskWithURL completion handler not getting called), I heard that NSURLConnection was not the current way to get data, so I'm trying to use NSURLSession.
When I am getting my JSON from the bundle, I am using this extension to
Dictionary (I am pretty sure I got this code from a tutorial):
static func loadJSONFromBundle(filename: String) -> Dictionary<String, AnyObject>? {
let path = NSBundle.mainBundle().pathForResource(filename, ofType: ".json")
if !path {
println("Could not find level file: \(filename)")
return nil
}
var error: NSError?
let data: NSData? = NSData(contentsOfFile: path, options: NSDataReadingOptions(),
error: &error)
if !data {
println("Could not load level file: \(filename), error: \(error!)")
return nil
}
let dictionary: AnyObject! = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions(), error: &error)
if !dictionary {
println("Level file '\(filename)' is not valid JSON: \(error!)")
return nil
}
return dictionary as? Dictionary<String, AnyObject>
}
I'd like to do something similar for getting a dictionary from a JSON file that is on the web because I don't anticipate wanting to include all of my JSON files in the bundle. So far, I have this:
static func loadJSONFromWeb(urlstring: String) -> Dictionary<String, AnyObject>? {
let url = NSURL(string: urlstring)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config, delegate: nil, delegateQueue: NSOperationQueue())
var error: NSError?
//I think I am using the completionHandler incorrectly. I'd like to access the data from the download
let task = session.downloadTaskWithRequest(NSURLRequest(URL: url), {(url, response, error) in println("The response is: \(response)")
})
task.resume()
//Isn't this contentsOfURL thing supposed to go with the connection stuff rather than the session stuff?
//How can I do this with a session? How can I create and use a completionHandler? This way seems clunky.
let data: NSData? = NSData(contentsOfURL: url)
if !data {
println("Could not load data from file: \(url), error: \(error!)")
return nil
}
println("The data is: \(data)")
let dictionary: AnyObject! = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions(), error: &error)
if !dictionary {
println("The file at '\(url)' is not valid JSON, error: \(error!)")
return nil
}
return dictionary as? Dictionary<String, AnyObject>
}
I think that my actual question that most needs answering is this: Where
is the data? I don't think I am using sessions and tasks correctly. I feel like I'm
starting a session to connect to a specific URL and using resume() to
start the download task I want to make happen, but I don't know how to
get the data from that JSON file.
If I need to use a completionHandler and a request in a way similar to what I found here:
(popViewControllerAnimated work slow inside NSURLSessionDataTask) can someone please explain how the 'data' in the completionHandler relates to the data in the fie I am trying to read/download? I am a bit baffled by the completionHandler and how to use it properly.
I looked at the documentation for NSData as well, but I didn't see anything that helped me understand how to get data from my session (or how to initialize an instance of NSData given my session). As far as I can tell form the documentation for NSURLDownloadTask, the task itself is not how I can access the data. It looks like the data comes from the session and task through the completionHandler.
EDIT:
I also looked at the documentation for NSURLSessionDownloadDelegate, but I could really use an example in Swift with some explanation about how to use the delegate. This led me to the URL Loading System Programming Guide. I'm guessing the benefits of using a session must be huge if the documentation is this complicated. I'm going to keep looking for more information on the URL Loading System.
I found this answer helpful (but I'm so new I can't upvote anything yet): https://stackoverflow.com/a/22177659/3842582 It helped me see that I am probably going to need to learn to use a delegate.
I also looked at the URL Loading System Programming Guide. I think what I really need is help with a completionHandler. How can I get my data? Or, am I already doing it correctly using NSData(contentsOfURL: url) (because I don't think I am).
Thank you in advance for any help you can offer!
First, let data: NSData? = NSData(contentsOfURL: url) will return your JSON synchronously. Did you try that and get this working simply? That way you can get started with the rest of your processing while figuring out NSURLSession.
If you're going to use NSURLSession, or a lot of other things in iOS, you need to learn delegates. Fortunately, they're easy. In terms of syntax you just add it to your class declaration like you were inheriting from it. What that does is say that you are going to implement at least the required functions for the delegate. These are callback functions which are generally pretty well documented. It is quite straightforward once you understand it.
If this is not a "heavyweight" project that really needs NSURLSession, you should look at this Swift library. Besides being a really nice way to deal with JSON there is a synchronous call to directly load the JSON from a url. https://github.com/dankogai/swift-json/
Why is NSURLConnection not the correct way to get data? You just should be careful with synchronous requests. Here is an example of how to get data from an url.
func synchronousExampleRequest() -> NSDictionary {
//creating the request
let url: NSURL! = NSURL(string: "http://exampledomain/apiexample.json")
var request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
var response: NSURLResponse?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
error = nil
let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
return resultDictionary
}