I am fairly new to Swift development, and am wondering why it is taking so long for the image to show. It's a 4.44kb image whose URL is stored in a Firestore document. It takes a second before it shows, which isn't very good for the UX. As I said, the image (a barcode) is just 4.44kb, which makes me wonder why loading for example an image from Instagram or Facebook is faster than loading this image.
My code:
In my viewDidLoad:
dbRef.getDocument { (document, err) in
if let e = err {
print("Error retrieving document: \(e)")
} else {
let data = document?.data()
if let imageURL = data?[K.User.barcodeImage] as? String {
// let url = URL(fileURLWithPath: imageURL)
let url = NSURL(string: imageURL)
self.downloadImage(from: url as! URL)
let number = data![K.User.barcodeNumber] as! String
self.cardNumber.text = number
UserDefaults.standard.set(number, forKey: K.User.barcodeNumber)
UserDefaults.standard.synchronize()
}
}
}
The rest:
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
func downloadImage(from url: URL) {
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() { [weak self] in
self?.cardCode.image = UIImage(data: data)
}
}
}
Does this have to do with DispatchQueue? Thank you in advance!
The 1 second latency is not unreasonable but you can try enabling cache headers from firebase storage / GCS bucket to improve performance.
Related
I'm using URLSessionDataTask to show an image, and using URLSessionDownloadTask to download the image on my app. Also I needed to observe download progress, so I use KVO at Progress's fractionCompleted.
The thing is fractionCompleted value just has 0.05 and 1.0. I think this issue happened for using same remote url. Because when I don't use URLSessionDataTask(I can't show the image though), I get right progress update.
Why is it happened? Is there any reference?
Edited
First, I don't want to use URLSession delegate to get progress. I'm using two VCs(first one is RemoteGridViewController using URLSessionDataTask to show image and second one is MediaViewerController). MediaViewerController delegate to first VC for downloading the image on camera roll. I know I can use URLSession delegate method to notify progress by using NotificationCenter or something like that, but I don't want. This way makes strong coupling(?) between the VCs(I guess...)
Second, RemoteGridViewController use URLSession to show an image by URLSessionDataTask and cache it.
guard let url = URL(string: url) else {
completionHandler(.failure(NetworkManagerError.urlError))
return
}
var req = URLRequest(url: url)
req.cachePolicy = .returnCacheDataElseLoad
if let cachedResponse = sharedCache?.cachedResponse(for: req) {
completionHandler(.success(cachedResponse.data))
} else {
sessionDataTask = session.dataTask(with: req) { [weak self] (data, response, error) in
if let error = error {
completionHandler(.failure(error))
print(error.localizedDescription)
return
}
guard let response = response as? HTTPURLResponse, let data = data else {
completionHandler(.failure(NetworkManagerError.dataError))
return
}
self?.sharedCache?.storeCachedResponse(CachedURLResponse(response: response, data: data), for: req)
completionHandler(.success(data))
}
sessionDataTask?.resume()
}
Third, MediaViewerController call MediaViewerDelegate method, which is downloadImage(itemAt:for:) to download an image.
extension RemoteGridViewController: MediaViewerDelegate {
func downloadImage(itemAt indexPath: IndexPath, for mediaViewer: MediaViewerController) {
let data = unsplashData[indexPath.item]
let urlString = data.regular
guard let url = URL(string: urlString) else { return }
mediaViewer.presentProgressView()
networkManager.downloadTaskForImage(with: url, progressHandler: mediaViewer.observe(_:)) { result in
switch result {
case .success(let image):
DispatchQueue.main.async {
mediaViewer.toastView(with: true)
}
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
case .failure(let error):
DispatchQueue.main.async {
mediaViewer.toastView(with: false)
}
print(error.localizedDescription)
}
}
}
}
class NetworkManager {
private let session: URLSession!
private var sessionDownloadTask: URLSessionDownloadTask?
func downloadTaskForImage(with url: URL, progressHandler: (Progress?) -> (), completionHandler: #escaping (Result<UIImage, Error>) -> ()) {
sessionDownloadTask = session.downloadTask(with: url) { (location, response, error) in
if let error = error {
completionHandler(.failure(error))
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
completionHandler(.failure(NetworkManagerError.responseError))
return
}
guard let location = location else {
completionHandler(.failure(NetworkManagerError.urlError))
return
}
do {
let data = try Data(contentsOf: location)
if let image = UIImage(data: data) {
completionHandler(.success(image))
} else {
completionHandler(.failure(NetworkManagerError.dataError))
}
} catch let error {
completionHandler(.failure(error))
}
}
progressHandler(sessionDownloadTask?.progress)
sessionDownloadTask?.resume()
}
}
You can track for the downloaded bytes to total expected bytes to track the progress of downloading image.
Use following delegate method:
URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
and following snippet:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// println("download task did write data")
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
//Your code to download and save image to camera role
}
}
I hope this may have resolved your query to check for exact progress with downloading.
I am using a REST API (https://restcountries.eu/) and want to download the flag image (which is an .svg) and show it as a UIImage. I tried the standard way with:
func requestData(at url: URL, success: #escaping (_ data: Data) -> Void, failure: ((_ error: NetworkError) -> Void)? = nil) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if responseError != nil {
failure?(.failedRequest)
} else if let data = responseData {
success(data)
} else {
failure?(.corruptedData)
}
}
}
task.resume()
}
and the data downloads fine, but when I try to show the image with UIImage(data: data), the image is nil. Am I missing something?
I am downloading and then reading a json file. this json contains a list of files and their address on the server.
Everything works fine but I want to get the size of all files to download.
but I have some trouble to set up a completionblock that would indicate that everything is finished.
here is the code.
jsonAnalysis {
self.sum = self.sizeArray.reduce(0, +)
print(self.sum)
} here
func jsonAnalysis(completion: #escaping () -> ()) {
let urlString = "xxxxxxxxxxxxxxxxxxxxx"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("error")
} else {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any]
self.i = -1
guard let array = json?["Document"] as? [Any] else { return }
for documents in array {
self.i = self.i + 1
guard let VersionDictionary = documents as? [String: Any] else { return }
guard let DocumentName = VersionDictionary["documentname"] as? String else { return }
guard let AddressServer = VersionDictionary["addressserver"] as? String else { return }
self.resultAddressServer.append(AddressServer)
self.addressServer = self.resultAddressServer[self.i]
self.resultDocumentName.append(DocumentName)
self.documentName = self.resultDocumentName[self.i]
let url1 = NSURL(string: AddressServer)
self.getDownloadSize(url: url1! as URL, completion: { (size, error) in
if error != nil {
print("An error occurred when retrieving the download size: \(String(describing: error?.localizedDescription))")
} else {
self.sizeArray.append(size)
print(DocumentName)
print("The download size is \(size).")
}
})
}
} catch {
print("error")
}
}
completion()
} .resume()
}
func getDownloadSize(url: URL, completion: #escaping (Int64, Error?) -> Void) {
let timeoutInterval = 5.0
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: timeoutInterval)
request.httpMethod = "HEAD"
URLSession.shared.dataTask(with: request) { (data, response, error) in
let contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
completion(contentLength, error)
}.resume()
}
I would like to get the sum of the array at the end when everything is done, right now print(self.sum) is running before and shows 0.
I am not familiar with the completion and I am sure I am doing everything wrong.
You need DispatchGroup.
Before calling the inner asynchronous task enter, in the completion block of the inner asynchronous task leave the group.
Finally when the group notifies, call completion
let group = DispatchGroup()
for documents in array {
...
let url1 = URL(string: AddressServer) // no NSURL !!!
group.enter()
self.getDownloadSize(url: url1!, completion: { (size, error) in
if error != nil {
print("An error occurred when retrieving the download size: \(String(describing: error?.localizedDescription))")
} else {
self.sizeArray.append(size)
print(DocumentName)
print("The download size is \(size).")
}
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
completion()
}
i have downloaded the twilio quickstarter project and followed the docx and create the function for chat and pass the url to the given place in project, now when i run the app the app crashes with this error,Thread 10: EXC_BREAKPOINT (code=1, subcode=0x1048d85b0). This error comes in their code when it try to seralize the data from the request. How can i get out from this issue?. This is the code where issue is occurring,
struct TokenUtils {
static func retrieveToken(url: String, completion: #escaping (String?, String?, Error?) -> Void) {
if let requestURL = URL(string: url) {
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: requestURL, completionHandler: { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String:String]
let token = json["token"]
let identity = json["identity"]
completion(token, identity, error)
}
catch let error as NSError {
completion(nil, nil, error)
}
} else {
completion(nil, nil, error)
}
})
task.resume()
}
}
}
This is what it shows in debugger,
In case I want to check if the file exists on my iPhone, just use the following code:
let filePath = fileName.path
let fileManager = FileManager.default
if fileManager.fileExists (atPath: filePath) {
}
How can I check if there is a pdf / jpg / png file at the URL:
www.myname.com/files/file1.jpg or www.myname.com/files/file2.pdf etc.?
Could I ask for an example of such a function - but for files on internet web servers?
UPDATE
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
}
}
task.resume()
}
Is it possible to check in this function whether the file is of JPG or PNG type? If so - then we also return true, and if not, false?
Updated:
After the discussion on the comments section, the code is updated to work in more correct way.
You should check for the mimeType of the URLResponse object rather than checking whether the image could be represented as UIImageJPEGRepresentation/UIImagePNGRepresentation or not. Because it doesn't guarantee that the resource is actually a jpg/jpeg or png.
So the mimeType should be the most reliable parameter that needs to considered here.
enum MimeType: String {
case jpeg = "image/jpeg"
case png = "image/png"
}
func remoteResource(at url: URL, isOneOf types: [MimeType], completion: #escaping ((Bool) -> Void)) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let mimeType = response.mimeType else {
completion(false)
return
}
if types.map({ $0.rawValue }).contains(mimeType) {
completion(true)
} else {
completion(false)
}
}
task.resume()
}
Verify with this:
let jpegImageURL = URL(string: "https://vignette.wikia.nocookie.net/wingsoffire/images/5/54/Panda.jpeg/revision/latest?cb=20170205005103")!
remoteResource(at: jpegImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) // true
}
let pngImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/6/69/Giant_panda_drawing.png")!
remoteResource(at: pngImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //true
}
let gifImageURL = URL(string: "https://media1.tenor.com/images/f88f6514b1a800bae53a8e95b7b99172/tenor.gif?itemid=4616586")!
remoteResource(at: gifImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //false
}
Previous Answer:
You can check if the remote data can be represented as UIImageJPEGRepresentation or UIImagePNGRepresentation. If yes, you can say that remote file is either JPEG or PNG.
Try this:
func remoteResource(at url: URL, isImage: #escaping ((Bool) -> Void)) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data, let image = UIImage(data: data) {
if let _ = UIImageJPEGRepresentation(image, 1.0) {
isImage(true)
} else if let _ = UIImagePNGRepresentation(image) {
isImage(true)
} else {
isImage(false)
}
} else {
isImage(false)
}
}
task.resume()
}
Usage:
let imageURL = URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL®ion=1")!
remoteResource(at: imageURL) { (isImage) in
print(isImage) // prints true for your given link
}
It’s about getting data from an URL. If the data is nil the file don’t exist.
Getting data from an URL in Swift
If you want to know the file exists on a server, then it requires sending a HTTP request and receiving the response.
Please try with following code,
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
} else {
completion(false)
}
}
task.resume()
}
UPDATE
remoteFileExistsAt(url: URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL®ion=1")!) { (success) in
print(success)
}