IOS FacebookShare Error 'reserved' being returned - ios

I've tried searching but could not find an answer. I have written an app and I am trying to share content to facebook. Basically I would like to share a URL and maybe a quote or title.
I keep getting an error called 'reserved' but I am not sure what it means or how to fix it. Any help would be great!
func fbClick() {
let content = LinkShareContent(url: URL(string: "www.google.com")!)
showShareDialog(content, mode: .native)
}
func showShareDialog<C: ContentProtocol> (_ content: C, mode: ShareDialogMode = .automatic) {
let dialog = ShareDialog(content: content)
dialog.presentingViewController = self
dialog.mode = mode
do {
try dialog.show()
} catch (let error) {
self.view.makeToast("Invalid share content. Failed to present share dialog with error \(error)", duration: 3.0, position: .top)
}
}

Figured it out.
This line...
let content = LinkShareContent(url: URL(string: "www.google.com")!)
Should have been like this...
let content = LinkShareContent(url: NSURL(string: "https://www.google.com")! as URL)
or like this
let content = LinkShareContent(url: NSURL(string: "https://www.google.com")! as URL, quote: quote)

Had the same reserved error but while using VideoShareContent. Spent 5 hours to find the issue and finally found. Really hope someone finds this helpful too.
Solution: when you are retrieving the url of your video from info param from the UIImagePickerController delegate method make sure you use the key "UIImagePickerControllerReferenceURL".
Example:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
picker.dismiss(animated: true)
if let videoURL = info["UIImagePickerControllerReferenceURL"] as? URL {
let video = Video(url: videoURL)
let content = VideoShareContent(video: video)
do {
try ShareDialog.show(from: self, content: content)
} catch {
print(error)
}
}
}
Additional info: initially I did not use this key "UIImagePickerControllerReferenceURL". Why: it's deprecated. According to Apple, you should use UIImagePickerControllerPHAsset instead. But the url from there also returns reserved error. Another try was to use key "UIImagePickerControllerMediaURL", but it also didn't succeed.

Related

Sharing Rich Links in Swift

I am trying to implement rich link sharing on posts within my app. Sharing a link in iMessage should show the video or image if there's one associated with the post. This swift implementation is not working, not sure if its my code or and issue with the website meta data
What do I need in my website to make this work? Is the current swift implementation
static func addURLPreview(urlString: String) {
let metaDataProvider = LPMetadataProvider()
let url = URL(string: urlString)
metaDataProvider.startFetchingMetadata(for: url!) { (metaData, error) in
guard let _ = error, let metaData = metaData else {
return
}
let vc = UIActivityViewController(activityItems: [metaData], applicationActivities: nil)
UIApplication.shared.getKeyWindow()?.rootViewController?.present(vc, animated: true)
}
}

How to add file picker to the app on iOS 14+ and lower

I'm newbie in iOS development, so some things which I will show and ask here can be stupid and please don't be angry :) So, I need to add support of picking files from local storage in my app. This feature will be used for picking file -> encoding to Base64 and then sending to remote server. Right now I have some problems with adding this functionality to my app. I had found this tutorial and did everything what was mentioned here:
added import - import MobileCoreServices
added implementation - UIDocumentPickerDelegate
added this code scope for showing picker:
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText),String(kUTTypeContent),String(kUTTypeItem),String(kUTTypeData)], in: .import)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
and also added handler of selected file:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
}
In general file chooser appears on simulator screen, but I see warning in XCode:
'init(documentTypes:in:)' was deprecated in iOS 14.0
I visited the official guideline and here also found similar info about deprecation some method. So, how I can solve my problem with file choosing by the way which will be fully compatible with the latest iOS version. And another question - how I can then encode selected file? Right now I have an ability of file choosing and printing its location, but I need to get its data like name, content for encoding and some others. Maybe someone faced with similar problems and knows a solution? I need to add it in ordinary viewcontroller, so when I tried to add this implementation:
UIDocumentPickerViewController
I saw such error message:
Multiple inheritance from classes 'UIViewController' and 'UIDocumentPickerViewController'
I will be so pleased for any info: tutorials or advice :)
I decided to post my own solution of my problem. As I am new in ios development my answer can contain some logical problems :) Firstly I added some dialogue for choosing file type after pressing Attach button:
#IBAction func attachFile(_ sender: UIBarButtonItem) {
let attachSheet = UIAlertController(title: nil, message: "File attaching", preferredStyle: .actionSheet)
attachSheet.addAction(UIAlertAction(title: "File", style: .default,handler: { (action) in
let supportedTypes: [UTType] = [UTType.png,UTType.jpeg]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
documentPicker.shouldShowFileExtensions = true
self.present(documentPicker, animated: true, completion: nil)
}))
attachSheet.addAction(UIAlertAction(title: "Photo/Video", style: .default,handler: { (action) in
self.chooseImage()
}))
attachSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(attachSheet, animated: true, completion: nil)
}
then when a user will choose File he will be moved to ordinary directory where I handle his selection:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
var selectedFileData = [String:String]()
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file.absoluteURL)
selectedFileData["filename"] = file.lastPathComponent
selectedFileData["data"] = fileData.base64EncodedString(options: .lineLength64Characters)
}catch{
print("contents could not be loaded")
}
}
as you can see in scope above I formed special dicionary for storing data before sending it to a server. Here you can also see encoding to Base64.
When the user will press Photo/Video item in alert dialogue he will be moved to gallery for picture selecting:
func chooseImage() {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImageData = [String:String]()
guard let fileUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else { return }
print(fileUrl.lastPathComponent)
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
selectedImageData["filename"] = fileUrl.lastPathComponent
selectedImageData["data"] = pickedImage.pngData()?.base64EncodedString(options: .lineLength64Characters)
}
dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
via my method all file content will be encoded to base64 string.
P.S. Also I'm so pleased to #MaticOblak because he showed me the initial point for my research and final solution. His solution also good, but I have managed to solve my problem in way which is more convenient for my project :)
As soon as you have file URL you can use that URL to retrieve the data it contains. When you have the data you can convert it to Base64 and send it to server. You gave no information about how you will send it to server but the rest may look something like this:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
and you would use it as
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urls.forEach { sendFileWithURL($0) { <#Your code here#> } }
}
To break it down:
To get file data you can use Data(contentsOf: url). This method even works on remote files so you could for instance use an URL of an image link anywhere on internet you have access to. It is important to know that this method will pause your thread which is usually not what you want.
To avoid breaking the current thread we create a new queue using DispatchQueue(label: "DownloadingFileData." + UUID().uuidString). The name of the queue is not very important but can be useful when debugging.
When data is received we convert it to Base64 string using data.base64EncodedString() and this data can then be sent to server. You just need to fill in the TODO: part.
Retrieving your file data can have some errors. Maybe access restriction or file no longer there or no internet connection... This is handled by throwing. If the statement with try fails for any reason then the catch parts executes and you receive an error.
Since all of this is done on background thread it usually makes sense to go back to main thread. This is what the finish function does. If you do not require that you can simply remove it and have:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
completion(nil)
} catch {
completion(error)
}
}
}
There are other things to consider in this approach. For instance you can see if user selects multiple files then each of them will open its own queue and start the process. That means that if user selects multiple files it is possible that at some point many or all of them will be loaded in memory. That may take too much memory and crash your application. It is for you to decide if this approach is fine for you or you wish to serialize the process. The serialization should be very simple with queues. All you need is to have a single one:
private lazy var fileProcessingQueue: DispatchQueue = DispatchQueue(label: "DownloadingFileData.main")
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
fileProcessingQueue.async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
Now one operation will finish before another one starts. But that may only apply for getting file data and conversion to base64 string. If uploading is then done on another thread (Which usually is) then you may still have multiple ongoing requests which may contain all of the data needed to upload.

Check if URL is valid in Swift 4

How can you check to see if a URL is valid in Swift 4? I'm building a simple web browser for personal use and even though I know to enter the full URL each time I'd rather get an alert instead of the app crashing if I forget.
import UIKit
import SafariServices
class MainViewController: UIViewController {
#IBOutlet weak var urlTextField: UITextField!
#IBAction func startBrowser(_ sender: Any) {
if let url = self.urlTextField.text {
let sfViewController = SFSafariViewController(url: NSURL(string: url)! as URL)
self.present(sfViewController, animated: true, completion: nil)
}
print ("Now browsing in SFSafariViewController")
}
}
For example, if I was to type in a web address without http:// or https:// the app would crash with the error 'NSInvalidArgumentException', reason: 'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported.'
Reading the comments on the accepted answer, I could see that you actually want to validate the URL, to check if it's valid before trying to open with Safari to prevent any crash.
You can use regex to validate the string(I created an extension, so on any string, you can check if it is a valid URL):
extension String {
func validateUrl () -> Bool {
let urlRegEx = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?"
return NSPredicate(format: "SELF MATCHES %#", urlRegEx).evaluate(with: self)
}
}
You're probably crashing because you're using the ! operator and not checking that it will work. Instead try:
#IBAction func startBrowser(_ sender: Any) {
if let urlString = self.urlTextField.text {
let url: URL?
if urlString.hasPrefix("http://") {
url = URL(string: urlString)
} else {
url = URL(string: "http://" + urlString)
}
if let url = url {
let sfViewController = SFSafariViewController(url: url)
self.present(sfViewController, animated: true, completion: nil)
print ("Now browsing in SFSafariViewController")
}
}
}
This should give you the idea of how to handle the different cases, but you probably want something more sophisticated which can deal with https and strips whitespace.

Open URL from UIButton in CollectionViewCell

I have a UIButton in my UICollectionViewCell and it's getting data from JSON. Now I need to open a URL from each button (each button have a different url that also comes from JSON).
I managed to open the URL with:
let weburl = "http://example.com"
UIApplication.shared.openURL(URL(string: weburl)!)
But now I need to kinda pass an url to each button. Any ideas of how can i achieve this?
You can have an array of urls:
let urls = [url1, url2, ...]
And then assign the tag property of each button to the index of its corresponding url. Now you can easily manage what you want:
#IBAction func handleTouch(_ sender: UIButton) {
// assumes that the buttons' tags start at 0, which isn't a good idea.
// see #rmaddy comment bellow
let url = urls[sender.tag]
// use the version of the open method shown bellow because the other one becomes deprecated in iOS 10
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
}
EDIT
Other solution would be to just store the url in the cell itself, and in the button handler open the url corresponding to its cell.
FYI openURL is deprecated in iOS 10. I suggest the following if you need to support older versions of ios:
let url = URL(string: "alexa://")!
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:], completionHandler: {
(success) in
guard success else {
//Error here
}
//Success here
})
} else {
if let success = UIApplication.shared.openURL(url) {
//Success here
} else {
//Error here
}
}
Otherwise just use UIApplication.shared.open. Also I would add a URL field to the data model you are passing to your tableViewCell and just look up the URL from the model.

Import PDF into own App with iOS Action Extension

I'm looking for a possibility to import a PDF in order to do some further tasks with it, just like described in this Question: Importing PDF files to app
After two days of looking around in the inter webs I found that an action extension might be the solution, this is how far I got:
override func viewDidLoad() {
super.viewDidLoad()
let fileItem = self.extensionContext!.inputItems.first as! NSExtensionItem
let textItemProvider = fileItem.attachments!.first as! NSItemProvider
let identifier = kUTTypePDF as String
if textItemProvider.hasItemConformingToTypeIdentifier(identifier) {
textItemProvider.loadItemForTypeIdentifier(identifier, options: nil, completionHandler: handleCompletion)
}
}
func handleCompletion(pdfFile: NSSecureCoding?, error: NSError!) {
print("PDF loaded - What to do now?")
}
The completion handler is called properly so I assume the PDF is loaded - but then I don't now how to proceed. If the action extension only handles images or text it could easily be downcasted, but the only way to work with files I know is with path names - which I do not have and don't know how to obtain. Plus, I'm pretty sure Sandboxing is also part of the party.
I guess I only need a push in the right direction which Class or Protocol could be suitable for my need - any suggestions highly appreciated.
For anyone else looking for an answer - I found out by myself, and it's embarrassingly easy:
func handleCompletion(fileURL: NSSecureCoding?, error: NSError!) {
if let fileURL = fileURL as? NSURL {
let newFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory().stringByAppendingString("test.pdf"))
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtURL(fileURL, toURL: newFileURL)
// Do further stuff
}
catch {
print(error)
}
}
}

Resources