Swift Firebase Append Image for UIActivityViewController - ios

I need help appending a image I downloaded from firebase so I can use it to be share over the UIActivityViewController. I get the right image but unsure how to set up the data pass to let it be shared. The current set up throws a "Thread 1 error" on "objectsToShare = [self.image!]".
let shareAction = UIAlertAction(title: "Share", style: UIAlertActionStyle.default, handler: {(alert: UIAlertAction!) in
var objectsToShare: [AnyObject]?
let titlePost = self.feeds[sender.tag].downloadURL
if let postURL = URL(string: titlePost) {
let postRequest = URLRequest(url: postURL)
self.image?.setImageWith(postURL, placeholderImage: nil, options: SDWebImageOptions.progressiveDownload, completed: { (imageRequest, imageResponse, error) -> Void in
// failure downloading image
print("Error downloading Firebase post image")
print(error)
})
}
objectsToShare = [self.image!]
let activityViewController = UIActivityViewController(activityItems: objectsToShare!, applicationActivities: nil)
// present the view controller
self.present(activityViewController, animated: true, completion: nil)
})

In place of the below:
objectsToShare = [self.image!]
You have to use like this :
if let image = self.image {
objectsToShare.append(image)
}

You should check what a completion handler is first.
You should check for error in the completion, and if there's not, do what you have to do with your image. As the request is asynchronous the image isn't there yet on the line just after.
Do something like that :
self.image?.setImageWith(postURL, placeholderImage: nil, options: SDWebImageOptions.progressiveDownload, completed: { (imageRequest, imageResponse, error) -> Void in
// This closure is called when the request is done
if error == nil {
objectsToShare = [self.image!]
let activityViewController = UIActivityViewController(activityItems: objectsToShare!, applicationActivities: nil)
// present the view controller
self.present(activityViewController, animated: true, completion: nil)
} else {
// failure downloading image
print("Error downloading Firebase post image")
print(error)
}
})

Related

Sharing text and Url to Facebook Messenger with UIActivityViewController but failing

I want to share both text and url to Facebook Messenger using UIActivityViewController.
But when i success send and open my Messenger, i only get url.
Is it possible send text and url to Facebook Messenger using UIActivityViewController at the same time?
Here is my code
#IBAction func sharedLink(_ sender: Any) {
let url = NSURL(string: "https://www.google.com.tw")!
let text = "test" as AnyObject
let shareObject: [AnyObject] = [text, url as AnyObject]
let vc = UIActivityViewController(activityItems: shareObject, applicationActivities: [])
vc.completionWithItemsHandler = { (type,completed,items,error) in
if completed { vc.dismiss(animated: true, completion: nil) }
}
present(vc, animated: true, completion: nil)
}
Here is my screenshot
In my case,
#IBAction func sharedLink(_ sender: Any) {
let urlString = "https://www.google.com.tw"
let url = NSURL(string: urlString)!
let text = "test" as AnyObject
let shareObject: [AnyObject] = ["\(text), \(urlString)"]
let vc = UIActivityViewController(activityItems: shareObject, applicationActivities: [])
vc.completionWithItemsHandler = { (type,completed,items,error) in
if completed { vc.dismiss(animated: true, completion: nil) }
}
present(vc, animated: true, completion: nil)
}

Swift - Downloading files on ios 13 error

I have a app that can download files from a webview. It was working fine in ios 12 but isnt working. I'm getting the error
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
and
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
This is my view.controller code:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
print(request.url as Any)
if request.url!.absoluteString.range(of: "/download/") != nil {
let extention = request.url!.absoluteString.slice(from: "&fileextension=", to: "&")?.lowercased()
var name = request.url!.absoluteString.slice(from: "&name=", to: "&")?.lowercased()
name = name?.replacingOccurrences(of: "+", with: " ")
DownlondFromUrl(request.url! as URL,name!, extention!)
return false
}
return true
}
func DownlondFromUrl(_ url: URL,_ name:String,_ extensionfile:String){
// Create destination URL
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let destinationUrl = documentsUrl!.appendingPathComponent(name + ".\(extensionfile)")
//Create URL to the source file you want to download
let fileURL = url
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
let dataFromURL = NSData(contentsOf: tempLocalUrl)
dataFromURL?.write(to: destinationUrl, atomically: true)
let alert = UIAlertController.init(title: "Download", message: "File download Successful. Do you want open file ", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: .default , handler:{ (UIAlertAction)in
let fileBrowser = FileBrowser()
self.present(fileBrowser, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Share", style: .default , handler:{ (UIAlertAction)in
let activityViewController = UIActivityViewController(activityItems: [destinationUrl], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler:{ (UIAlertAction)in
}))
self.present(alert, animated: true, completion: {
print("completion block")
})
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true;
}
}
I have found this post (Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread) but I'm very much a beginner and not sure how to implement this.
You have to run any code which accesses the UI on the main thread.
As URLSession tasks run on a background thread you have to add a DispatchQueue block
DispatchQueue.main.async {
let alert = UIAlertController.init(title: "Download", message: "File download Successful. Do you want open file ", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: .default , handler:{ action in
let fileBrowser = FileBrowser()
self.present(fileBrowser, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Share", style: .default , handler:{ action in
let activityViewController = UIActivityViewController(activityItems: [destinationUrl], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel)
self.present(alert, animated: true, completion: {
print("completion block")
})
}
Note:
The parameter in the UIAlertAction closure must be an instance not a type. If the parameter is not used you can replace it with an underscore character (for example handler:{ _ in)

When and where to dismiss UIAlertController in Swift?

I am calling a method that executes a URLSession but before it does anything, presents a UIAlertController blocking the UI until some sort of response from the request is achieved. Logic tells me that dismissing the UIAlertController in the completion block of that method where it is called on the main thread would be the best option. Am I wrong to assume this? Apparently so, as variably the presented UIAlertController will indeed display, but never dismiss. Help?
Block:
getCostandIV { output in
let cost = output["ask"] as! NSNumber
let IV = output["IV"] as! NSNumber
self.enteredCost = cost.stringValue
self.enteredIV = IV.stringValue
DispatchQueue.main.async {
self.progress.dismiss(animated: true, completion: nil)
self.tableView.reloadSections(IndexSet(integer: 1), with: UITableView.RowAnimation.none)
self.canWeSave()
}
}
Function:
func getCostandIV (completionBlock: #escaping (NSMutableDictionary) -> Void) -> Void {
DispatchQueue.main.async {
self.progress = UIAlertController(title: "Retrieving ask price and volatility...", message: nil, preferredStyle: UIAlertController.Style.alert)
self.present(self.progress, animated: true, completion: nil)
}
guard let url = URL(string: "https://api.tdameritrade.com/v1/marketdata/chains?apikey=test&symbol=\(symbol)&contractType=\(type)&strike=\(selectedStrike)&fromDate=\(selectedExpiry)&toDate=\(selectedExpiry)") else {
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
//print(error?.localizedDescription ?? "Response Error")
DispatchQueue.main.async {
self.presentedViewController?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "There was an error retrieving ask price and volatility.", message: "Please try again later.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
})
}
return }
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
// //print(jsonResponse) //Response result
guard let jsonDict = jsonResponse as? NSDictionary else {
return
}
// //print(jsonDict)
var strikeMap : NSDictionary = [:]
if self.type == "CALL" {
strikeMap = jsonDict["callExpDateMap"] as! NSDictionary
} else {
strikeMap = jsonDict["putExpDateMap"] as! NSDictionary
}
self.strikes.removeAllObjects()
let inner = strikeMap.object(forKey: strikeMap.allKeys.first ?? "<#default value#>") as! NSDictionary
let innerAgain = inner.object(forKey: inner.allKeys.first ?? "<#default value#>") as! NSArray
let dict : NSDictionary = innerAgain[0] as! NSDictionary
let dict2 = ["ask" : dict["ask"] as! NSNumber, "IV" : dict["volatility"] as! NSNumber] as NSMutableDictionary
completionBlock(dict2)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
Edit: Using self.presentedViewController?.dismiss(animated: true, completion: nil) did not fix the issue. In addition, the completion block of the dismiss function for self.progress is not being called.
Edit 2: presentedViewController right before dismiss code in the callback is nil, even though present is called on the alert controller before dismiss?
use this , for dismiss your alert you should add dismiss method in an async block and for setting timer for that you should tell async block to start being async from now to 5 seconds and after that do some thing :
alert.addAction(UIAlertAction(title: "ok", style: .default,
handler: nil))
viewController.present(alert, animated: true, completion: nil)
// change to desired number of seconds (in this case 5 seconds)
let when = DispatchTime.now() + 5
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alert.dismiss(animated: true, completion: nil)
}
If you call the getCostandIV method more than once, the second alert won't be presented and self.progress will have the reference of unpresented alert.
Change
self.progress.dismiss(animated: true, completion: nil)
To
self.presentedViewController?.dismiss(animated: true, completion: nil)
Your alert would be dismissed only if everything goes well.
I suggest you to change your function to something like this:
func getCostandIV (completionBlock: #escaping (NSMutableDictionary?, Error?) -> Void) -> Void
and make sure that your completionBlock is called when your guard statements fail or an error is thrown. In your current code the alert is only dismissed when it network request fails, but not when something goes wrong when parsing JSON.

URLSession and UI related tasks

I'm trying to authenticate login by retrieving a boolean from my web server using URLSession, and show an Alert Controller if the login fails.
func requestLogin() {
let url = URL(string: "http://mywebserver/login.php")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
let postString = "username=\(txtUsername.text!)&password=\(txtPassword.text!)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
guard data != nil else {
self.promptMessage(message: "No data found")
return
}
do {
if let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
let success = jsonData.value(forKey: "success") as! Bool
if (success) {
self.dismiss(animated: false, completion: { action in
//Move to next VC
})
return
} else {
self.dismiss(animated: false, completion: { action in
self.promptMessage(message: "The username or password that you have entered is incorrect. Please try again.")}
)
return
}
} else {
self.dismiss(animated: false, completion: { action in
self.promptMessage(message: "Error: Could not parse JSON!")
})
}
} catch {
self.dismiss(animated: false, completion: { action in
self.promptMessage(message: "Error: Request failed!")
})
}
})
showOverlayOnTask(message: "Logging in...")
task.resume()
}
func promptMessage(message: String) {
let alert = UIAlertController(title: "Login Failed", message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
func showOverlayOnTask(message: String) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
self.present(alert, animated: true, completion: nil)
}
The weird problem I'm getting is that my Logging In alert controller sometimes does not dismiss. It gets stuck until I tap on the screen, which then will dismiss and show the next alert controller. It's very annoying and I don't know where I'm doing wrong.
How do I fix this?
Maybe the problem is that you're trying to dismiss the controller without executing on the main thread, normally the UI changes/updates should be executed on the main thread.
Try this and check if works:
DispatchQueue.main.async {
self.dismiss(animated: false, completion: { action in
self.promptMessage(message: "Error: Could not parse JSON!")
})
}

Swift: save video from NSURL to user camera roll

I have a variable videoURL of type NSURL.
If I call println(videoURL) it would return something like this:
http://files.parsetfss.com/d540f71f-video.mp4
I have a button set up that should take this videoURL and save the video to the user's camera roll.
The best I have done is this:
UISaveVideoAtPathToSavedPhotosAlbum(videoPath: String!, completionTarget: AnyObject!, completionSelector: Selector, contextInfo: UnsafeMutablePointer<Void>)
While I'm not even sure if this will work or not, I can't figure out how to convert videoFile:NSURL into a videoPath.
Any help is appreciated on this.
Edit:
The following is unsuccessful:
UISaveVideoAtPathToSavedPhotosAlbum(videoURL.relativePath, self, nil, nil)
AssetsLibrary is deprecated
1: import Photos
import Photos
2: Use this code to save video from url to camera library.
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(nsUrlToYourVideo)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
}
Swift 3 & Swift 4
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlToYourVideo)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
The accepted answer no longer works with Swift 3.0 & iOS 10.
First, you need to set the following permission in your app's plist file:
Privacy - Photo Library Usage Description
Provide a string that is presented to the user explaining why you are requesting the permission.
Next, import photos:
import Photos
Finally, here is the updated code for Swift 3.0:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileURL)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}
To save video from NSURL to user camera roll
func video(videoPath: NSString, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject)
{
if let _ = error {
print("Error,Video failed to save")
}else{
print("Successfully,Video was saved")
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let conversationField = self.conversation {
if (mediaType?.isEqual((kUTTypeMovie as NSString) as String))!
{
let theVideoURL: URL? = (info[UIImagePickerControllerMediaURL] as? URL)
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum((theVideoURL?.path)!))
{
UISaveVideoAtPathToSavedPhotosAlbum((theVideoURL?.path)!, self, #selector(ConversationDetailsViewController.video(videoPath:didFinishSavingWithError:contextInfo:)), nil)
}
}
self.dismiss(animated: true, completion: nil)
}
Reference from:: https://www.raywenderlich.com/94404/play-record-merge-videos-ios-swift
Try this instead for saving video in photo library in swift 4.2 and above
func requestAuthorization(completion: #escaping ()->Void) {
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization { (status) in
DispatchQueue.main.async {
completion()
}
}
} else if PHPhotoLibrary.authorizationStatus() == .authorized{
completion()
}
}
func saveVideoToAlbum(_ outputURL: URL, _ completion: ((Error?) -> Void)?) {
requestAuthorization {
PHPhotoLibrary.shared().performChanges({
let request = PHAssetCreationRequest.forAsset()
request.addResource(with: .video, fileURL: outputURL, options: nil)
}) { (result, error) in
DispatchQueue.main.async {
if let error = error {
print(error.localizedDescription)
} else {
print("Saved successfully")
}
completion?(error)
}
}
}
}
Use of function
self.saveVideoToAlbum(/* pass your final url to save */) { (error) in
//Do what you want
}
Don't forgot to import Photos and add Privacy - Photo Library Usage Description to your info.plist
deprecated as of iOS 9
1: import AssetsLibrary
import AssetsLibrary
2: Use this code to save video from url to camera library.
ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(outputFileURL, completionBlock: nil)
Just use it and paste your video's url:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let createAssetRequest: PHAssetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(string: /* your url */)!)!
createAssetRequest.placeholderForCreatedAsset
}) { (success, error) -> Void in
if success {
//popup alert success
}
else {
//popup alert unsuccess
}
}

Resources