Cache URL Request in WKWebView with an HTML string - ios

Problem:
How can I cache the existing function from loading an HTML string to a URL request.
fileprivate func loadWebViewWithParameters(_ parameters: YouTubePlayerParameters) {
// Get HTML from player file in bundle
let rawHTMLString = htmlStringWithFilePath(playerHTMLPath())!
// Get JSON serialized parameters string
let jsonParameters = serializedJSON(parameters as AnyObject)!
// Replace %# in rawHTMLString with jsonParameters string
let htmlString = rawHTMLString.replacingOccurrences(of: "%#", with: jsonParameters)
// Load HTML in web view
webView.loadHTMLString(htmlString, baseURL: URL(string: baseURL))
}
What I need:
I need to implement the above function to handle the cache but using the html string. I need to save the webpages to cache but with the above function of loading an HTML string using a system like the below function.
let url = NSURL(string: load_url1)
let request = NSURLRequest(url: url as! URL,cachePolicy: NSURLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 60)
self.webView.loadRequest(request as URLRequest);

Related

Swift Sending a cookie with an URL Request to get past Cookie Authentification

I'm trying to get HTML data from a website using swift to parse some tournament-results within an iPhone app.
However if I'm trying to access the site inside the code I get redirected to a page where normally you have to accept cookies.
I know that this cookie is stored in the HTTPCookieStorage but I can't seem to find a way to send it with my URLRequest. No matter what I do I always get the contents of the cookie-site instead of the page I'm trying to get.
Here is my approach:
let cookies = HTTPCookieStorage.shared.cookies! as [HTTPCookie]
var cookie = [HTTPCookie]()
for i in cookies {
if i.domain == "www.turnier.de" {
cookie.append(i)
}
}
HTTPCookieStorage.shared.setCookies(cookie, for: URL(string: "https://www.turnier.de/sport/winners.aspx?id=44325544-0A05-4330-BC8D-114AB3ECD25D"), mainDocumentURL: URL(string: "https://www.turnier.de/sport/winners.aspx?id=44325544-0A05-4330-BC8D-114AB3ECD25D"))
var request = URLRequest(url: URL(string: "https://www.turnier.de/sport/winners.aspx?id=44325544-0A05-4330-BC8D-114AB3ECD25D")!)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let responseString = String(data: data!, encoding: .utf8)
print(responseString)
}
task.resume()

Add parameters to URL (resource from path, WKWebView)

I am using Swift 4 & WKWebView. The HTML pages are included in the app (not downloaded from server).
For configuration purpose I would like to add parameters to the URL (e.g. ...index.html?debug=true).
Currently I am using the following approach to load the page:
let indexHTMLPath = Bundle.main.path(forResource: "f7Shoma/index", ofType: "html")
let url = URL(fileURLWithPath: indexHTMLPath!)
let request = URLRequest(url: url)
...
appWebView!.load(request)
How can parameters be added/passed to the page?
You can use URLComponents to create a URL with a query component:
var components = URLComponents(string: indexHTMLPath)
components?.queryItems = [URLQueryItem(name: "debug", value: "true")]
if let result = components?.url {
let request = URLRequest(url: url)
appWebView!.load(request)
}

Obtaining CKAsset URL without downloading data

I'm using CloudKit in my app as a way to persist data remotely. One of my records has an CKAsset property that holds an image. When I'm fetching the records, I realized that it's taking so much time to finish the query. After multiple testings, I concluded that when you query records, CloutKit downloads the entire Asset file with the record object. Hence, when you obtain the Asset from the record object and request it's fileURL, it gives you a local file path URL and not an HTTP kind of URL. This is as issue to me because you have to let the user wait so much time for the entire records to be downloaded (with their associated images) before the query ends. Coming from a Parse background, Parse used to give you the HTTP URL and the query is fast enough to load the UI with the objects while loading the images async. My question is, how can I achieve what Parse does? I need to restrict the queries from downloading the Assets data and obtain a link to load the images asynchronously.
You can use CloudKit JavaScript for accessing url of asset. Here is an example;
(Used Alamofire and SwiftyJSON)
func testRecordRequest() -> Request {
let urlString = "https://api.apple-cloudkit.com/database/1/" + Constants.container + "/development/public/records/query?ckAPIToken=" + Constants.cloudKitAPIToken
let query = ["recordType": "TestRecord"]
return Alamofire.request(.POST, urlString, parameters: ["query": query], encoding: .JSON, headers: nil)
}
JSON response contains a "downloadURL" for the asset.
"downloadURL": "https://cvws.icloud-content.com/B/.../${f}?o=AmVtU..."
"${f}" seems like a variable so change it to anything you like.
let downloadURLString = json["fields"][FieldNames.image]["value"]["downloadURL"].stringValue
let recordName = json["recordName"].stringValue
let fileName = recordName + ".jpg"
let imageURLString = downloadURLString.stringByReplacingOccurrencesOfString("${f}", withString: fileName)
And now we can use this urlString to create a NSURL and use it with any image&cache solutions such as Kingfisher, HanekeSwift etc.
(You may also want to save image type png/jpg)
Make two separate calls for the same record. The first call should fetch all the NON-asset fields you want, and then second request should fetch the required assets.
Something like:
let dataQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
dataQueryOperation.desiredKeys = ["name", "age"] // etc
database.addOperation(dataQueryOperation)
let imageQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
imageQueryOperation.desiredKeys = ["images"]
database.addOperation(imageQueryOperation)
If need, refactor this into a method so you can easily make a new CKQueryOperation for every asset-containing field.
Happy hunting.
Like others have said you cannot get the url(web) of the CKAsset. So your best options are
1. Use a fetch operation with progress per individual UIImageView. I have built a custom one that shows a progress to the user. Cache is not included but you can make a class and adopt NSCoding and save the entire record to cache directory. Here you can see a fetch that i have a completion on to send the asset back to where i call it from to combine it with my other data.
// only get the asset in this fetch. we have everything else
let operation = CKFetchRecordsOperation(recordIDs: [myRecordID])
operation.desiredKeys = ["GameTurnImage"]
operation.perRecordProgressBlock = {
record,progress in
dispatch_async(dispatch_get_main_queue(), {
self.progressIndicatorView.progress = CGFloat(progress)
})
}
operation.perRecordCompletionBlock = {
record,recordID,error in
if let _ = record{
let asset = record!.valueForKey("GameTurnImage") as? CKAsset
if let _ = asset{
let url = asset!.fileURL
let imageData = NSData(contentsOfFile: url.path!)!
dispatch_async(dispatch_get_main_queue(), {
self.image = UIImage(data: imageData)
self.progressIndicatorView.reveal()
})
completion(asset!)
}
}
}
CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
The other option is to store images on an AWS server or something comparable and then you can use something like SDWebImage to do all of the cache or heavy lifting and save a string in the CKRecord to the image.
I have asked several people about a CKAsset feature to expose a url. I don't know about the JS Api for CloudKit but there might be a way to do it with this but i will let others commment on that.
Using the Post method from #anilgoktas, we can also get the download URL without using Alamofire and SwiftyJSON, although it may be a little more complicated and not as neat.
func Request() {
let container = "INSERT CONTAINER NAME"
let cloudKitAPIToken = "INSERT API TOKEN"
let urlString = "https://api.apple-cloudkit.com/database/1/" + container + "/development/public/records/query?ckAPIToken=" + cloudKitAPIToken
let query = ["recordType": "testRecord"]
//create the url with URL
let url = URL(string: urlString)! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: ["query": query], options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
DispatchQueue.main.async {
for i in json.values {
if let sub = i as? NSArray {
for j in sub {
if let dict = j as? NSDictionary, let subDict = dict["fields"] as? NSDictionary, let titleDict = subDict["title"] as? [String:String], let title = titleDict["value"], let videoDict = subDict["video"] as? NSDictionary, let vidVal = videoDict["value"] as? NSDictionary, let url = vidVal["downloadURL"] as? String {
let downloadURL = url.replacingOccurrences(of: "${f}", with: "\(title).jpg")
print(title)
print(downloadURL)
}
}
}
}
// handle json...
}
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
Here is my approach.
Let's say I have Record Type : Books
BookID (auto-id or your unique id)
BookName string
BookImage CKAsset
BookURL string
Incase I use CKAssest I store in BookURL :
"Asset:\BookID.png "
Incase I used external server to store the images, I use normal URL in BookURL
"http://myexternalServer\images\BookID.png"
Queries :
with desiredKeys I query all fields without BookImage(CKAsset) field.
if BookURL is empty , there is no image for the book.
if BookURL start with "Asset:\" I query for BookImage from cloudkit
if BookURL is normal URL I download the image from external server (NSUrlSession)
This way , any time I can change and decide how to store image, on cloudkit(CKAssets) or on external server (http downloads)

How to save UIWebView content for offline display?

I'm using Xcode 7.2 and Swift to create an iOS app, on this app I display the content of my website, however if I was offline the content will not be shown. So I want to cache the webpage and display for offline.
After I declared everything I'm using the following code :
var URLPATH="http://google.com"
let requestURL = NSURL(string: URLPATH)
let request = NSURLRequest(URL: requestURL!)
WB.loadRequest(request)
HTML to Data
if let url = URL(string: urlString) {
person.setValue(try? Data(contentsOf: url), forKey: "content_article")
}
Data to WebView
if let savedObject = fetchedObjects?.first,
let data = savedObject.content_article as? Data,
let baseStringUrl = savedObject.content_url,
let baseURL = URL(string: baseStringUrl) {
webView.load(
data, mimeType: "text/html",
textEncodingName: "",
baseURL: baseURL
)
}

How to use Alamofire to send bytes?

I'm creating an application to download my university timetable. I've done the REST calls in Java first to demonstrate a prototype which works nicely. And now I'd like to do it in Swift using Alamofire (or anything else which works).
Below is the REST call in Java that I'm trying to replicate.
Client client = Client.create();
String authString = "UID=XXXXX&PASS=XXXXX";
byte[] authBytes = authString.getBytes();
WebResource webResouce = client.resource("https://access.adelaide.edu.au/sa/login.asp");
ClientResponse response = webResource.post(ClientResponse.class, authBytes);
if (response.getStatus != 302) {
throw new RuntimeException("Failed: HTTP code: " + response.getStatus());
}
However I'm having trouble sending the bytes properly. The server will actually accept any byte data (so you can see if it works without a UID or PASS) and respond with 302, which indicates that it works. Otherwise it'll send a 200 which means it didn't.
I've had a few attempts of sending the UID and PASS in a parameter, getting their bytes and then putting them in a parameter etc etc. but nothing seems to work so far.
Any help would be great, thanks!
You should use Alamofire's custom encoding technique (something like this). This is my 3rd hour of Swift so bear with me.
struct ByteEncoding: ParameterEncoding {
private let data: Data
init(data: Data) {
self.data = data
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
urlRequest.httpBody = data
return urlRequest
}
}
Alamofire.request(url: "url", method: .post, parameters: nil, encoding: ByteEncoding(data: authBytesAsData)
Documentation
https://github.com/Alamofire/Alamofire#custom-encoding
If you use a regular NSURLRequest you can just set the request body:
let URL = NSURL(string: "https://access.adelaide.edu.au/sa/login.asp")!
let request = NSMutableURLRequest(URL: URL)
request.HTTPBody = // NSData you want as your body
Edit
As pointed out by #mattt himself, you can pass an NSURLRequest to Alamofire. No need for the hassle with custom parameter encoding as I answered first. (See below)
I don't exactly know how to do this using Alamofire, but it seems you can use a Custom parameter encoding with a closure. I didn't test this but took it from the Alamofire unit test source:
let encodingClosure: (URLRequestConvertible, [String: AnyObject]?) -> (NSURLRequest, NSError?) = { (URLRequest, parameters) in
let mutableURLRequest = URLRequest.URLRequest.mutableCopy() as NSMutableURLRequest
mutableURLRequest.HTTPBody = parameters["data"]
return (mutableURLRequest, nil)
}
let encoding: ParameterEncoding = .Custom(encodingClosure)
let URL = NSURL(string: "https://access.adelaide.edu.au/sa/login.asp")!
let URLRequest = NSURLRequest(URL: URL)
let data: NSData = // NSData you want as your body
let parameters: [String: AnyObject] = ["data": data]
let URLRequestWithBody = encoding.encode(URLRequest, parameters: parameters).0
Here's a quick example of how you could make this type of request.
import Alamofire
class BytesUploader {
func uploadBytes() {
let URLRequest: NSURLRequest = {
let URL = NSURL(string: "https://access.adelaide.edu.au/sa/login.asp")!
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.HTTPMethod = "POST"
let authString = "UID=XXXXX&PASS=XXXXX"
let authData = authString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
mutableURLRequest.HTTPBody = authData
return mutableURLRequest.copy() as NSURLRequest
}()
let request = Alamofire.request(URLRequest)
request.response { request, response, data, error in
if let response = response {
println("Response status code: \(response.statusCode)")
if response.statusCode == 302 {
println("Request was successful")
} else {
println("Request was NOT successful")
}
} else {
println("Error: \(error)")
}
}
}
}
You need to encode your authorization string as an NSData object and then set that as the HTTPBody of the NSURLRequest. This should match your Java code that you posted.

Resources