I have a downloader class that downloads a file based on a given URL which then calls a completion passing it the contents of the file as NSData.
For the project that I'm using this in, the URL will be a JPEG image. The downloader works perfectly; I can use the result into NSImage and show it in a Image View Controller.
I would like to be able to save that NSData object to file.
After quite some time researching the internet on Google, StackOverflow, etc. and trying many suggestions, I cannot get the file to save.
Here is a playground of the Downloader class and my attempt to save the file:
//: Playground - noun: a place where people can play
import Cocoa
class NetworkService
{
lazy var configuration: URLSessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = URLSession(configuration: self.configuration)
let url: NSURL
init(url: NSURL)
{
self.url = url
}
func downloadImage(completion: #escaping ((NSData) -> Void))
{
let request = NSURLRequest(url: self.url as URL)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
if let data = data {
completion(data as NSData)
}
default:
print(httpResponse.statusCode)
}
}
} else {
print("Error download data: \(error?.localizedDescription)")
}
}
dataTask.resume()
}
}
let IMAGE_URL = NSURL(string: "https://www.bing.com/az/hprichbg/rb/RossFountain_EN-AU11490955168_1920x1080.jpg")
let networkService = NetworkService(url: IMAGE_URL!)
networkService.downloadImage(completion: { (data) in
data.write(to: URL(string: "file://~/Pictures/image.jpg")!, atomically: false)
})
The playground console show nothing at all. Can anyone spot why its not working?
NOTE: The target is macOS, not iOS. Also, I'm a swift noob...
I did try this:
networkService.downloadImage(completion: { (imageData) in
let imageAsNSImage = NSImage(data: imageData as Data)
if let bits = imageAsNSImage?.representations.first as? NSBitmapImageRep {
let outputData = bits.representation(using: .JPEG, properties: [:])
do {
try outputData?.write(to: URL(string: "file://~/Pictures/myImage.jpg")!)
} catch {
print("ERROR!")
}
}
})
It could be a permission issue. You may try:
let picturesDirectory = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
let imageUrl = picturesDirectory.appendingPathComponent("image.jpg", isDirectory: false)
try? data.write(to: imageUrl)
It does work for me:
Related
I am trying to create an app that creates random pictures when a button is clicked. The app is working fine but I see this message which I have never seen before."Publish changes from background threads is not allowed; make sure to publish values from the main thread.".
I am new to SwiftUI, help is appreciated.
import Foundation
import SwiftUI
class ImageviewModel{
var image: UIImage? = nil
//let url = URL(string: "https://source.unsplash.com/random/600x600")!
let url = URL(string: "https://picsum.photos/600/600")!
func responseHandler(data: Data?, response: URLResponse?) ->
UIImage?{
guard let data = data,
let image = UIImage(data: data),
let response = response else {return nil}
return image
}
func loadImageWithAsync() async throws -> UIImage?{
do{
let (data, response) = try await URLSession.shared.data(from: url,delegate: nil)
return responseHandler(data: data, response: response)
} catch{
throw error
}
}
}
class ViewModel: ObservableObject{
#Published var image: UIImage? = nil
var loader = ImageviewModel()
func fetchImage() async {
let image = try? await loader.loadImageWithAsync()
self.image = image
}
}
You need to add the MainActor wrapper to the class to guarantee that updates are done on Main
#MainActor
class ViewModel: ObservableObject{
I have used the below implementation to play a video with cookies content from the server, but it shows play icon with cross line. I have refer the link and do following implementation in swift. but I didn't get any output :(
func showVideo(url: String) {
let videoURL = NSURL(string: url)
var cookiesArray = [HTTPCookie]()
guard let cookieArray = UserDefaults.standard.array(forKey:
Constants.Object.kCookie) as? [[HTTPCookiePropertyKey: Any]] else {
return }
for cookieProperties in cookieArray {
if let cookie = HTTPCookie(properties: cookieProperties) {
cookiesArray.append(cookie)
}
}
let cookieArrayOptions = [AVURLAssetHTTPCookiesKey: cookiesArray]
let assets = AVURLAsset(url: videoURL! as URL, options: cookieArrayOptions)
let item = AVPlayerItem(asset: assets)
videoPlayer = AVPlayer(playerItem: item)
self.playerController.player = self.videoPlayer
self.playerController.view.frame = self.view.frame
self.present(self.playerController, animated: true, completion: nil)
self.playerController.player?.play()
}
Please help me on that, what is wrong in that implementation.
Thanks in advance! :)
After going through so many ways finally I have got the solution which worked for me :
func showVideo(url: String) {
let videoURL = NSURL(string: url)
let cookiesArray = HTTPCookieStorage.shared.cookies! //Stored Cookies of your request
let values = HTTPCookie.requestHeaderFields(with: cookiesArray)// Returns a dictionary of header fields corresponding to a provided array of cookies.ex.["Cookie":"your cookies values"]
let cookieArrayOptions = ["AVURLAssetHTTPHeaderFieldsKey": values]
let assets = AVURLAsset(url: videoURL! as URL, options: cookieArrayOptions)
let item = AVPlayerItem(asset: assets)
videoPlayer = AVPlayer(playerItem: item)
self.playerController.player = self.videoPlayer
self.playerController.view.frame = self.view.frame
self.present(self.playerController, animated: true, completion: nil)
self.playerController.player?.play()
}
I'm using swift 3. I want to add timeout to URLSession when doing download task. I did use configuration to change my setting, however, it doesn't work. The code didn't perform timeout... If the server didn't response quickly, it will fail.
Here is my code:
import Foundation
import UIKit
extension UIImageView {
func loadImage(url: URL) -> URLSessionDownloadTask {
let session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 70
configuration.timeoutIntervalForResource = 70
return URLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
}()
let downloadTask = session.downloadTask(with: url, completionHandler: { [weak self] url, response, error in
if error == nil, let url = url, let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
if let strongSelf = self {
strongSelf.image = image
}
}
}
})
downloadTask.resume()
return downloadTask
}
}
Any comment is appreciated!!
Checking the documentation and the migration guide, I should be able to set a new image using this code:
imageView.kf.setImage(with:url ...)
but actually I cannot find this method in the library, I only see:
imageView.kf.setImage(with:Resource... )
I don't know exactly how this resource shoud work though since I cannot find anything in the documentation.
Resource is a protocol. URL has been extended to conform to this protocol. So you can do:
let url = URL(string: ...)!
imageView.kf.setImage(with: url)
If you want some control over what Kingfisher uses for the key in its cache, you can use ImageResource:
let identifier = "..."
let url = URL(string: "http://example.com/images/identifier=\(identifier)")!
let resource = ImageResource(downloadURL: url, cacheKey: identifier)
imageView.kf.setImage(with: resource)
For Swift 4.2
import Kingfisher
extension UIImageView {
func setImage(with urlString: String){
guard let url = URL.init(string: urlString) else {
return
}
let resource = ImageResource(downloadURL: url, cacheKey: urlString)
var kf = self.kf
kf.indicatorType = .activity
self.kf.setImage(with: resource)
}
}
How to use
self.imgVw.setImage(with: your image url)
I fixed that issue using this:
PhotoHelper.shared.imagePickedBlock = { [weak self] (image, url) in
self?.imageView.kf.setImage(with: url, placeholder: image, options: nil, progressBlock: nil, completionHandler: { imageResult, error, type, cache in
self?.imageView.image = image
})
}
PhotoHelper is wrapper on native Image Picker:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
currentVC.dismiss(animated: true, completion: {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
let url = info[UIImagePickerControllerReferenceURL] as! URL
self.imagePickedBlock?(image, url)
}
})
}
Thanks to Apple my iOS 9 Project 'Swift 2.3' is completely unusable with iOS 10's 'Swift 3'...
I fixed almost everything except that I am having issue with using NSURLSession, Xcode is telling me that it has been renamed to URLSession, if I rename it Xcode will tell me:
use of undeclared type URLSession
Foundation is imported.
What is the issue?!
For example I am using it this way...
lazy var defaultSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "reCoded.BGDownload")
configuration.sessionSendsLaunchEvents = true
configuration.isDiscretionary = true
let session = URLSession(configuration: configuration, delegate: self, delegateQueue, queue: nil)
return session
}()
and even with the delegate methods the same issue.
Try using Foundation.URLSession where ever you use URLSession.
/Got it to work/ In some cases try to copy your code somewhere else then remove everything in your class that uses URLSession then type the session methods again and put back your copied code you should be fine.
Update your URLSessin functions with;
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.data.append(data as Data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
I can explain how but by playing around with the code I got this to work in SWIFT 3 after two days of frustration. I guess SWIFT 3 removed a lot of unnecessary words.
let task = Foundation.URLSession.shared.dataTask(with: <#T##URL#>, completionHandler: <#T##(Data?, URLResponse?, Error?) -> Void#>)
Here's where I am right now. It's not perfect but works maybe half of the time.
First, in the class where my URLsession is defined:
import Foundation
class Central: NSObject, URLSessionDataDelegate, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
I don't think all of that is necessary, but there it is. Then here is the function that is called by my background fetch:
func getWebData() {
var defaults: UserDefaults = UserDefaults.standard
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myBGconfig")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
urlString = "https://www.powersmartpricing.org/psp/servlet?type=dayslider"
if let url = URL(string: urlString) {
let rateTask = backgroundSession.downloadTask(with: URL(string: urlString)!)
rateTask.taskDescription = "rate"
rateTask.resume()
}
When the task comes back:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) {
if downloadTask.taskDescription == "rate" { // I run 2 web tasks during the session
if let data = NSData(contentsOf: location) {
var return1 = String(data: data as! Data, encoding: String.Encoding.utf8)!
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 0.2){
var defaults: UserDefaults = UserDefaults.standard
defaults.set(myNumber, forKey: "electricRate") // myNumber is an extract of the text in returned web data
defaults.set(Date(), forKey: "rateUpdate")
defaults.synchronize()
self.calcSetting() //Calls another function defined in the same class. That function sends the user a notification.
let notificationName = Notification.Name("GotWebData")
NotificationCenter.default.post(name: notificationName, object: nil)
} // Closes the Dispatch
}
if session.configuration.identifier == "myBGconfig" {
print("about to invalidate the session")
session.invalidateAndCancel()
}
}
I haven't figured out yet how to kill the session when BOTH tasks have completed, so right now I kill it when either one is complete, with invalidateAndCancel as above.
And finally, to catch errors:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCompleteWithError: Error?) {
if downloadTask.taskDescription == "rate" {
print("rate download failed with error \(didCompleteWithError)")
}
if downloadTask.taskDescription == "other" {
print("other download failed with error \(didCompleteWithError)")
}
downloadTask.resume() // I'm hoping this retries if a task fails?
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
if let error = error as? NSError {
print("invalidate, error %# / %d", error.domain, error.code)
} else {
print("invalidate, no error")
}
}