I am doing a app in which i need to upload videos taken from iPhone Camera and upload it to a server. When the app is in foreground the video gets uploaded but i don't know how to do it in background when the app is inactive. I used AFNetworking to do the multipart data upload. Here is the code i tried
var task:NSURLSessionUploadTask!
let FILEPICKER_BASE_URL = "My server Url"
var isUploading : Bool = false
var videoPath : String!
func upload(dictMain: NSMutableDictionary)
{
isUploading = true
let uuid = NSUUID().UUIDString + "-" + (getUserId().description)
let request = AFHTTPRequestSerializer().multipartFormRequestWithMethod("POST", URLString: FILEPICKER_BASE_URL, parameters: nil, constructingBodyWithBlock: { (fromData) in
do {
try fromData.appendPartWithFileURL(NSURL(fileURLWithPath: videoPath) , name: "fileUpload", fileName: uuid, mimeType: "video/quicktime")
}catch{
print(error)
}
}, error: nil)
let manager:AFURLSessionManager = AFURLSessionManager(sessionConfiguration: NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("Your video is uploading"));
task = manager.uploadTaskWithStreamedRequest(request, progress: nil , completionHandler: { (response, responseObject, error) in
NSLog("Resposne Object: \(responseObject)")
post(dictMain,response: responseObject!)
})
task.resume()
}
Also I don't know how to know my upload progress. I tried to use the following block in the progress parameter
{ (progress) in
print("\((progress))")
}
But it does not work the complier shows error
Cannot convert value of type '(_) -> _' to expected argument type 'AutoreleasingUnsafeMutablePointer<NSProgress?>' (aka 'AutoreleasingUnsafeMutablePointer<Optional<NSProgress>>')
Could any one share a snippet that really works. As i googled there are very very few basic tutorials on NSURLSession.uploadTaskWithStreamedRequest and all of them i tried didn't work on swift 2.2
I am using Xcode 7.3 Swift 2.2
Points I Know:
Background upload works only with File paths and not NSData. so i took the video url path from the UIImagePickerController function func video(videoPath: NSString, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject)
To do background file transfer i have to enable them in the Target->Capabilities -> Background Modes -> Background fetch
Must set the Session manager with NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(""). But i don't know why and where that identifier is being used.
I know this is a long post. But if it is answered this will surely be helpful to many developers.
I fixed it by calling the upload function inside a DispatchQueue Background queue
DispatchQueue.global(qos: .background).async { upload() }
Related
I have an Instagram scheduling app and I am trying to open this (see image below) in Swift 5.x. The goal is simple: save Image to Firebase, once it is time to post, notification!, user clicks on the notification and this (image below) opens up with the appropriate image/video to post. Everything works except for opening Instagram with the appropriate photo/video. I have tried this:
func postToInstagram(image: URL) {
let videoFileUrl: URL = image
var localId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
localId = request?.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
// completion handler is called on an arbitrary thread
// but since you (most likely) will perform some UI stuff
// you better move everything to the main thread.
DispatchQueue.main.async {
guard error == nil else {
// handle error
print(error)
return
}
guard let localId = localId else {
// highly unlikely that it'll be nil,
// but you should handle this error just in case
return
}
let url = URL(string: "instagram://library?LocalIdentifier=\(localId)")!
guard UIApplication.shared.canOpenURL(url) else {
// handle this error
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
})
}
and this:
func postToInstagram(image: URL, igURL: String) {
let urlStr: String = "instagram://app"
let url = URL(string: igURL)
if UIApplication.shared.canOpenURL(url!) {
print("can open")
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
To no avail. The latter code works, but only opens the Instagram app itself, which is fine, but I would like to open the View in the image below rather than Instagram's home screen. I also tried changing the URL to "instagram://share" and this works but goes to publish a regular post, whereas I want the user to decide what they want to do with their image.
This is where I want to go:
Note: For everyone who will be telling me this and whoever will wonder: Yes, my URL schemes (LSApplicationQueriesSchemes) are fine. And, just to clarify, I need to fetch the image/video from Firebase before posting it.
I am working on an application that requires to download a certain number of files to be able to work offline. Obviously, download tasks are preferred to be done with the app in the background. I implemented an URLSession with a background configuration following Apple's documentation available here : https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background. I also followed a tutorial on raywenderlich: https://www.raywenderlich.com/3244963-urlsession-tutorial-getting-started.
Basically, what I've done looks like this (I've made my class a Singleton but I have the same problem either way):
public final class DownloadService: NSObject {
static let shared = DownloadService()
static let identifier = "downloadService"
private var urlSession: URLSession!
var backgroundCompletionHandler: (() -> Void)? // This is attributed in the handleEventsForBackgroundURLSession delegate method in the AppDelegate
private override init() {
super.init()
let config = URLSessionConfiguration.background(withIdentifier: DownloadService.identifier)
config.isDiscretionary = true
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
}
extension DownloadService: URLSessionDelegate {
// Delegate method called when the background session is finished.
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
guard let completionHandler = self.backgroundCompletionHandler else {
Logger.fault("No completion for bg session", category: .network)
return
}
Logger.log("Complete background session", category: .network)
// This must be executed on the main thread
// Executes things such as updating the app preview in recent apps view
completionHandler()
}
}
}
extension DownloadService: URLSessionDownloadDelegate {
// Delegate method called when a download task is finished
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// Perform
guard let sourceUrl = downloadTask.originalRequest?.url else {
return
}
Logger.log("Received file: %#", sourceUrl.lastPathComponent, category:.network)
// Check and save file
saveFile(originalFileURL: sourceUrl, downloadedTo: location)
}
}
And I start the download using:
/// Download file using a previously created URLSession.
/// - parameter filename: Name of the file.
/// - parameter baseURL: URL where the files are located.
/// - parameter size: Expected filesize in Bytes.
private func download(file filename: String, from baseURL: String, size: Int64) {
guard let url = URL(string: baseURL)?.appendingPathComponent(filename) else { return }
let task = urlSession.downloadTask(with: url)
task.countOfBytesClientExpectsToSend = 0
task.countOfBytesClientExpectsToReceive = size
task.resume()
}
My problem is that everything works fine when the app is in foreground, but whenever I put the app in the background or lock the screen, I have an error saying:
Task <46648342-7D13-4D1F-96A1-FDAE4C1F8475>.<362> finished with error [22] Error Domain=NSPOSIXErrorDomain Code=22 "Invalid argument"
I have tried playing a bit with the URLSessionConfiguration, specifically the isDiscretionary parameter which is set to false by default, and it seems that setting it to true, as advised by Apple's documentation, even blocks the download from proceeding with the app in the foreground, resulting to the same error 'Invalid argument'.
I wonder if this parameter has anything to do with my problem, or if there's something I've misunderstood?
The exemple on raywenderlich provided above also works the same way, using isDiscretionary seems to make the download fail everytime.
I am using Xcode 11.3.1 with Swift 5 and targeting iOS13.
Let me know if any other information is needed and thank you for your help!
So, I was trying to do it with a simulator. Either by running from Xcode with the debugger, or by installing the app into the simulator (without the debugger since it affects the application lifecycle).
I tried to run it on a real device (iPad), and there's no sign of this error whatsoever! Setting isDiscretionary seems to work as intended so I'm not sure that this parameter was causing the issue on a simulator.
I have a problema with this method:
func DownloadImages(uid: String, indice: Int) {
DispatchQueue.global(qos: .background).async {
let refBBDD = FIRDatabase.database().reference().child("users").child(uid)
refBBDD.observeSingleEvent(of: .value, with: { snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let profileImageUrl = snapshotValue?.value(forKey: "profileImageUrl") as! String
let storage = FIRStorage.storage()
var reference: FIRStorageReference!
if(profileImageUrl == "") {
return
}
print("before")
reference = storage.reference(forURL: profileImageUrl)
reference.downloadURL { (url, error) in
let data = NSData(contentsOf: url!)
let image = UIImage(data: data! as Data)
print("image yet dowload ")
self.citas[indice].image = image
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadRows(at: [IndexPath(row: indice, section: 0)], with: .none)
//self.tableView.reloadData()
print("image loaded")
})
}
print("after")
})
}
}
I want to download images in background mode. I want follow using app, but the UI has frozen until methods not entry in reloadRows.
Is it possible run in true background mode and can i follow using the app??
Trace program:
before
after
before
after
...
before
after
before
after
image yet dowload --> here start UI frozen
image loaded
image yet dowload
image yet dowload
...
image yet dowload
image yet dowload
image loaded
image loaded
...
image loaded
image loaded
image yet dowload ------> here UI is not frozen
image loaded
The problem is caused by this line: let data = NSData(contentsOf: url!).
That constructor of NSData should only be used to read the contents of a local URL (a file path on the iOS device), since it is a synchronous method and hence if you call it to download a file from a URL, it will be blocking the UI for a long time.
I have never used Firebase, but looking at the documentation, it seems to me that you are using the wrong method to download that file. You should be using func getData(maxSize size: Int64, completion: #escaping (Data?, Error?) -> Void) -> StorageDownloadTask instead of func downloadURL(completion: #escaping (URL?, Error?) -> Void), since as the documentation states, the latter only "retrieves a long lived download URL with a revokable token", but doesn't download the contents of the URL itself.
Moreover, you shouldn't be force unwrapping values in the completion handler of a network request. Network requests can often fail for reasons other than a programming error, but if you don't handle those errors gracefully, your program will crash.
Your problem is in this line let data = NSData(contentsOf: url!) and let's now see what apple says about this method below
Don't use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
So it clearly states that this method will block your User Interface.
I'm currently building a share extension that accepts URLs. As part of this I've customised my share screen as outlined in a previous question to create a full screen view controller. This is all working fine. However in the default share composer view I noticed there was a preview image of the web page. I'm trying to access this in my extension but I can't seem to get hold of it.
Specifically I've been trying to use the method
loadPreviewImage
https://developer.apple.com/reference/foundation/nsitemprovider/1403925-loadpreviewimage
You will note in the docs that this says the following for the completion handler
completionHandler
A completion handler block to execute with the results. The first parameter of this block must be a parameter of type NSData, NSURL, UIImage (in iOS), or NSImage (in macOS) for receiving the image data. For more information about implementing the block, see CompletionHandler.
However if I try to set this as a UIImage in my completion block I get an error of
Cannot convert value of type '(UIImage, _) -> ()' to expected argument
type 'NSItemProvider.CompletionHandler!'
example code where itemProvider is confirmed to be an instance of NSItemProvider via guard statements
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
The docs for the completion Handler say to set this to what type you want and it will attempt to coerce the data to the type you specify. Has anyone seen this before? I'm not sure what to do here as I can't see what I'm doing wrong.
https://developer.apple.com/reference/foundation/nsitemprovider/completionhandler
If all else fails I'll look at using some Javascript to get an image from the dom but I would have liked the preview image that Apple seemed to provide
I don't know why the code in
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
not called when Post button tapped.
My round way is saving the preview image in method
override func configurationItems() -> [Any]! {
}
as
let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
let itemProvider = inputItem.attachments![0] as! NSItemProvider
if (itemProvider.hasItemConformingToTypeIdentifier("public.url")) {
itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in // 画像を取得する
if let image = item as? UIImage {
if let data = UIImagePNGRepresentation(image) {
self.photoNSURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("preview.png") as NSURL!
do {
try data.write(to: self.photoNSURL as URL, options: .atomic)
} catch {
print("\(#file)#\(#function)(\(#line)): error: \(error.localizedDescription)")
}
}
}
})
}
I'm having this weird issue in which a newly created URLSessionUploadTask gets cancelled instantly. I'm not sure if it's a bug with the current beta of Xcode 8.
I suspect it might be a bug because the code I'm about to post ran fine exactly once. No changes were made to it afterwards and then it simply stopped working. Yes, it literally ran once, and then it stopped working. I will post the error near the end.
I will post the code below, but first I will summarize how the logic here works.
My test, or user-exposed API (IE for use in Playgrounds or directly on apps), calls the authorize method. This authorize method will in turn call buildPOSTTask, which will construct a valid URL and return a URLSessionUploadTask to be used by the authorize method.
With that said, the code is below:
The session:
internal let urlSession = URLSession(configuration: .default)
Function to create an upload task:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data? = nil
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
The authentication function, which uses a task created by the above function:
public func authorize(withCode code: String?, completion: AccessTokenExchangeCompletionHandler) {
// I have removed a lot of irrelevant code here, such as the dictionary building code, to make this snippet shorter.
let obtainTokenTask = buildPOSTTask(onURLSession: self.urlSession, appendingPath: "auth/access_token", withPostParameters: nil, getParameters: body, httpHeaders: nil) { (data, response, error) in
if let err = error {
completion(error: err)
} else {
print("Response is \(response)")
completion(error: nil)
}
}
obtainTokenTask.resume()
}
I caught this error in a test:
let testUser = Anilist(grantType: grant, name: "Test Session")
let exp = expectation(withDescription: "Waiting for authorization")
testUser.authorize(withCode: "a valid code") { (error) in
if let er = error {
XCTFail("Authentication error: \(er.localizedDescription)")
}
exp.fulfill()
}
self.waitForExpectations(withTimeout: 5) { (err) in
if let error = err {
XCTFail(error.localizedDescription)
}
}
It always fails instantly with this error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED}
Here's a few things to keep in mind:
The URL used by the session is valid.
All credentials are valid.
It fails instantly with a "cancelled" error, that simply did not happen before. I am not cancelling the task anywhere, so it's being cancelled by the system.
It also fails on Playgrounds with indefinite execution enabled. This is not limited to my tests.
Here's a list of things I have tried:
Because I suspect this is a bug, I first tried to clean my project, delete derived data, and reset all simulators. None of them worked.
Even went as far restarting my Mac...
Under the small suspicion that the upload task was getting deallocated due to it not having any strong pointers, and in turn calling cancel, I also rewrote authorize to return the task created by buildPOSTTask and assigned it to a variable in my test. The task was still getting cancelled.
Things I have yet to try (but I will accept any other ideas as I work through these):
Run it on a physical device. Currently downloading iOS 10 on an iPad as this is an iOS 10 project. EDIT: I just tried and it's not possible to do this.
I'm out of ideas of what to try. The generated logs don't seem to have any useful info.
EDIT:
I have decided to just post the entire project here. The thing will be open source anyway when it is finished, and the API credentials I got are for a test app.
ALCKit
After struggling non-stop with this for 6 days, and after googling non-stop for a solution, I'm really happy to say I have finally figured it out.
Turns out that, for whatever mysterious reason, the from: parameter in uploadTask(with:from:completionHandler) cannot be nil. Despite the fact that the parameter is marked as an optional Data, it gets cancelled instantly when it is missing. This is probably a bug on Apple's side, and I opened a bug when I couldn't get this to work, so I will update my bug report with this new information.
With that said, everything I had to do was to update my buildPOSTTask method to account for the possibility of the passed dictionary to be nil. With that in place, it works fine now:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
} else {
postParameters = Data()
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
Are you by any chance using a third party library such as Ensighten? I had the exact same problem in XCode 8 beta (works fine in XCode 7) and all of my blocks with nil parameters were causing crashes. Turns out it was the library doing some encoding causing the issue.
For me, this was a weak reference causing the issue, so I changed
completion: { [weak self] (response: Result<ResponseType, Error>)
to
completion: { [self] (response: Result<ResponseType, Error>)