SDWebImage with JWT(JSON Web Token), how? [duplicate] - ios

I´ve just changed my code for caching images away from EGOImageCache to SDWebView. Unfortunately i don´t know how to set custom HTTP headers as i have to send authentification to be able to fetch images. It was easy done with EGOImageCache as i´ve extended the NSURLRequest at the appropriate place. But i don´t know how to do that with the SDWebView.framework. I see the headers and i´ve found methods in SDWebImageDownloader.h containing
/**
* Set a value for a HTTP header to be appended to each download HTTP request.
*
* #param value The value for the header field. Use `nil` value to remove the header.
* #param field The name of the header field to set.
*/
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
/**
* Returns the value of the specified HTTP header field.
*
* #return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
It seems that the lib does support HTTP headers. But as i use UIImageView+WebCache.h i can´t see there an option for setting the headers. In my code i call
[self.imageView setImageWithURL:[NSURL URLWithString:themeImageURL] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Can anybody tell me how to set HTTP headers?

I had the same problem, and I tried to make:
SDWebImageDownloader *manager = [SDWebImageDownloader sharedDownloader];
[manager setValue:username forHTTPHeaderField:#"X-Oauth-Username"];
But the header were not send. After some tries, I came across the problem, SDWebImageDownloader at sharedDownloader makes a new instance of SDWebImageDownloader, so when you put the header at that instance, the instance that really downloads the image don't has the header.
I've solved making this:
SDWebImageDownloader *manager = [SDWebImageManager sharedManager].imageDownloader;
[manager setValue:username forHTTPHeaderField:#"X-Oauth-Username"];

Swift Version
let imageDownloader = SDWebImageDownloader.shared()
imageDownloader.setValue("Username", forHTTPHeaderField: "X-Oauth-Username")

I know it's pretty old but couldn't help to share what worked for me. I needed to set a login token value for header logintoken. So, this piece of code did what I wanted -
NSString *loginToken = // Some method to fetch login token
[SDWebImageDownloader.sharedDownloader setValue:loginToken forHTTPHeaderField:#"logintoken"];

I am using Basic authentication and setting the username and password on the sharedDownloader helped:
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
downloader.username = #"username";
downloader.password = #"password";

Swift 4.1
let manager = SDWebImageManager.shared().imageDownloader
manager?.setValue("oAuthToken",forHTTPHeaderField: "AuthHeaderName")
manager?.downloadImage(with: imageURL, options: SDWebImageDownloaderOptions.useNSURLCache, progress:
{ (receivedSize, expectedSize , url) in
// progression tracking code
}, completed: { (image,data , error,finished) in
if error == nil && image != nil {
// here the downloaded image is cached, now you need to set it to the imageView
DispatchQueue.main.async {
imageView.image = image
self.maskCircle(anyImage: image!)
}
} else {
// handle the failure
DispatchQueue.main.async {
let defaultImage = UIImage(named: "defaultImage")
imageView.image = defImage
self.maskCircle(anyImage: defImage)
}
}
})

Related

Is this string object creation in swift?

I'm trying to parse csv file from my ios project(swift 2.3) and found this website. In the tutorial code, it has the following section of code :
if let content = String(contentsOfURL: contentsOfURL,
encoding: encoding, error: error) {
...........
}
And I'm not sure what it does. Does it create a String object?
Does it create a String object?
Yes, it creates a string from the contents of the URL given by contentsOfURL and using the character encoding given by encoding. It's analogous to the following Objective-C code:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
The if let part is a form of conditional statement. let is used to assign a value to an immutable variable. Using it in a conditional as in your example only allows the body of the conditional statement to execute if that assignment succeeds. So, if some error occurs while the data at the given URL is being fetched or if the string cannot be created for some reason, the condition fails and the body isn't executed. The whole snippet might be written like this in Objective-C:
NSString *content = [NSString stringWithContentsOfURL:contentsOfURL
encoding:encoding
error:&error];
if (content != nil) {
// do something with content
}
That code creates a string, but it does it by fetching the contents of a URL. Usually that URL points to a resource on the Internet. In that case it's a very bad way to fetch a string, since it is a synchronous network call that can hang or fail. It's a very bad idea to do synchronous networking calls on the main thread.
You could wrap that code in a GCD call to a background queue, but instead I'd suggest using NSURLSession and submitting a data task. Your search terms would be NSURLSession (or just URLSession in Swift 3) and the function func dataTask(with url: URL). (It might be easier search on it's Objective-C name, dataTaskWithURL since Google searches don't work very well with special characters.)
Take a look at a GitHub project I created called Async_demo. It has a singleton class called DownloadManager that downloads a blob of data from a specified URL. It's written to return the data as a Data object, but it would be a simple matter to convert that result from Data to a String.
The key bit of code is this:
typealias DataClosure = (Data?, Error?) -> Void
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}

AlamofireImage Usage

I'm trying to understand the way to use [AlamofireImage][1]
i have a few questions:
when i use:
Alamofire.request("https://httpbin.org/image/png").responseImage { response in
if let image = response.result.value {
//Do Somthing
}
}
is that all i need to do to use swift native URLCache?
Does it caching it automatically?
after the first request above, can i use AlamofiremImage fetch method?
// Fetch
let cachedImage = imageCache.image(withIdentifier: "https://httpbin.org/image/png")
or do i have to use URLCache cachedResponse(for: URLRequest) method?
(if the answer to 2 is "no") Do i need to actively save the image cache with alamfireImage Add method after the request response?
// Add
imageCache.add(image, withIdentifier: "https://httpbin.org/image/png")
(with relation to question 1) isn't it creating additional cache to the same image and url (once with URLCache and another with AlamofireImage add method)?
thanks!

Alamofire way for "setImageWithURLRequest: placeholderImage: success:" in AFNetworking

I'm trying to present images from url in a collection view. So far I was using AFNetworking for this, but now I'm moving to Alamofire.
I can not find a proper way to present the image like I was doing with the method 'setImageWithURLRequest: placeholderImage: success:'
Do I need to use AFNetworking in order to do this right? Is there a Alamofire way to do this that I'm skipping?
Thanks in advance
Edit
I'm currently using AlamofireImage
Here is the code I'm using:
Alamofire.request(.GET, urlString)
.responseImage { response in
if let image = response.result.value {
self.imageView.image = image
}
}
My problem is that this code is inside 'cellForItemAtIndexPath' method, and every time I scroll the images are recharged again and again. This wasn't happening with AFNetworking
You want to use the UIImageView extension in Alamofire to set the image from a URL. There is some very detailed documentation in the README.
let imageView = UIImageView(frame: frame)
let URL = NSURL(string: "https://httpbin.org/image/png")!
imageView.af_setImageWithURL(URL)
Best of luck!

How to check if Link is broken with WKWebview?

I use WKWebview to load a URL.
let webView = WKWebview()
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url!)
webView.loadRequest(request)
How can I detect if the link the webView should load is broken?
You can use canOpenUrl method:
UIApplication.sharedApplication().canOpenURL(url)
It will do the url validation and if the link is ok it returns true.
It's mostly use before you call:
UIApplication.sharedApplication().openURL(url)
to make sure this link can be open in safari but it should help you here too.
Make sure the link starts with http:// or https://.
Edited:
It will just check is the link is a correct url.
If you want to see the page is offline, authorisation issues, etc. you can implement WKNavigationDelegate protocol and check out this method:
- webView:didFailNavigation:withError:
this should give you more info.
It's always good idea to use: str.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAl‌​lowedCharacterSet())!
it make sure that you don't pass a character which are not allowed in URL.
Edited 2:
To detect the status code you can try to implement:
- webView:decidePolicyForNavigationResponse:decisionHandler:
the navigation response is an NSURLResponse instance but
whenever you make an HTTP request, the NSURLResponse object you get back is actually an instance of the NSHTTPURLResponse class so you should cast it to NSHTTPURLResponse. That should give you a statusCode.
In the last line in the method you should call handler, for example decisionHandler(WKNavigationResponsePolicyAllow).
Ref: Answer exists here
if let url = NSURL(string: yourUrlString) {
var canOpen = UIApplication.sharedApplication().canOpenURL(url)
}
If you want to check if url string is correct and valid - just create NSURL object, if path contains error it will cast to nil:
let string = "http://google.com"
let url = NSURL(string: string)
let brokenString = "http:\\brokenurl.12"
let brokenUrl = NSURL(string: brokenString) // nil - it's not valid!
If you have implemented NSURLConnectionDelegate then below solution can be used.
didFailWithError method of NSURLConnectionDelegate can be used for this.
This method get called if an error occurs during the loading of a resource.
Can refer to below link
https://developer.apple.com/documentation/foundation/nsurlconnectiondelegate/1418443-connection

AVAssetResourceLoaderDelegate - Only requests first two bytes?

I'm implementing an AVAssetResourceLoaderDelegate, and I'm having a bit of trouble getting to to behave correctly. My goal is to intercept any requests made by the AVPlayer, make the request myself, write the data out to a file, then respond to the AVPlayer with the file data.
The issue I'm seeing: I can intercept the first request, which is only asking for two bytes, and respond to it. After that, I'm not getting any more requests hitting my AVAssetResourceLoaderDelegate.
When I intercept the very first AVAssetResourceLoadingRequest from the AVPlayer it looks like this:
<AVAssetResourceLoadingRequest: 0x17ff9e40,
URL request = <NSMutableURLRequest: 0x17f445a0> { URL: fakeHttp://blah.com/blah/blah.mp3 },
request ID = 1,
content information request = <AVAssetResourceLoadingContentInformationRequest: 0x17ff9f30,
content type = "(null)",
content length = 0,
byte range access supported = NO,
disk caching permitted = NO>,
data request = <AVAssetResourceLoadingDataRequest: 0x17e0d220,
requested offset = 0,
requested length = 2,
current offset = 0>>
As you can see, this is only a request for the first two bytes of data. I'm taking the fakeHttp protocol in the URL, replacing it with just http, and making the request myself.
Then, here's how I'm responding to the request once I have some data:
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
//Make the remote URL request here if needed, omitted
CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)([self.response MIMEType]), NULL);
loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
loadingRequest.contentInformationRequest.contentType = CFBridgingRelease(contentType);
loadingRequest.contentInformationRequest.contentLength = [self.response expectedContentLength];
//Where responseData is the appropriate NSData to respond with
[loadingRequest.dataRequest respondWithData:responseData];
[loadingRequest finishLoading];
return YES;
}
I've stepped through this and verified that everything in the contentInformationRequest is filled in correctly, and that the data I'm sending is NSData with the appropriate length (in this case, two bytes).
No more requests get sent to my delegate, and the player does not play (presumably because it only has two bytes of data, and hasn't requested any more).
Does anyone have experience with this to point me toward an area where I may be doing something wrong? I'm running iOS 7.
Edit: Here's what my completed request looks like, after I call finishedLoading:
<AVAssetResourceLoadingRequest: 0x16785680,
URL request = <NSMutableURLRequest: 0x166f4e90> { URL: fakeHttp://blah.com/blah/blah.mp3 },
request ID = 1,
content information request = <AVAssetResourceLoadingContentInformationRequest: 0x1788ee20,
content type = "public.mp3",
content length = 7695463,
byte range access supported = YES,
disk caching permitted = NO>,
data request = <AVAssetResourceLoadingDataRequest: 0x1788ee60,
requested offset = 0,
requested length = 2,
current offset = 2>>
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
loadingRequest.contentInformationRequest.contentType = #"public.aac-audio";
loadingRequest.contentInformationRequest.contentLength = [self.fileData length];
loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
NSData *requestedData = [self.fileData subdataWithRange:NSMakeRange((NSUInteger)loadingRequest.dataRequest.requestedOffset,
(NSUInteger)loadingRequest.dataRequest.requestedLength)];
[loadingRequest.dataRequest respondWithData:requestedData];
[loadingRequest finishLoading];
return YES;
}
This implementation works for me. It always asks for the first two bytes and then for the whole data. If you don't get another callback it means that there was something wrong with the first response you have made. I guess the problem is that you are using MIME content type instead of UTI.
Circling back to answer my own question in case anyone was curious.
The issue boiled down to threading. Though it's not explicitly documented anywhere, AVAssetResourceLoaderDelegate does some weird stuff with threads.
Essentially, my issue was that I was creating the AVPlayerItem and AVAssetResourceLoaderDelegate on the main thread, but responding to delegate calls on a background thread (since they were the result of network calls). Apparently, AVAssetResourceLoader just completely ignores responses coming in on a different thread than it was expecting.
I solved this by just doing everything, including AVPlayerItem creation, on the same thread.

Resources