So, I'm making an importing system to bring text files from email into the app to read there contents. I am very new to swift, and app programming at that (mainly do backend), and I am having an issue with the code below. It is most likely very inefficient and there is probably a better way to do this, but currently I have the func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool with some other code to assign variables to the URL to send to the view controller (haven't worked with notifications/rootviewcontrollers yet). however, after running this code, the result, instead of the contents of the file, is ("matrixFile4197009889-26.text", Unicode (UTF-8)). What should I do? Please explain in "baby language."
My view controller code:
let delegate = UIApplication.shared.delegate as! AppDelegate
if delegate.importFileIndicator == true {
let filemgr = FileManager.default
let docsDirURL = try! filemgr.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let inboxURL = docsDirURL.appendingPathComponent("Inbox")
print(inboxURL)
do{
var directoryContents = try FileManager.default.contentsOfDirectory(at: inboxURL, includingPropertiesForKeys: nil, options: [])
var fileSearchBoolCounter = false
var fileSearchCounter = 0
var fileURL: URL
while fileSearchBoolCounter == false {
if (String(describing: directoryContents[fileSearchCounter].lastPathComponent).range(of: String(describing: NSURL(string: delegate.urlString)!.lastPathComponent!)) != nil) {
fileURL = directoryContents[fileSearchCounter]
fileSearchBoolCounter = true
print(fileURL)
let path = inboxURL.appendingPathComponent((NSURL(string: delegate.urlString)?.lastPathComponent!)!)
encryptedMessageField.text = try String(contentsOfFile: String(describing: path), encoding: String.Encoding.utf8)
}else{
print(directoryContents[fileSearchCounter])
fileSearchCounter += 1
print(NSURL(string: delegate.urlString)!.lastPathComponent!)
}
}
delegate.importFileIndicator = false
fileSearchBoolCounter = false
fileSearchCounter = 0
}catch let error as NSError{
print(error)
}
}
My AppDelegate code:
var importFileIndicator = false
var urlString = ""
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
urlString = String(describing: url)
print(urlString)
importFileIndicator = true
return true
}
I think you've already good with some parts, but I'm going to include them too in the whole process.
1. Make your app available to open a TXT file
To let the system know that your app is prepared to receive a TXT file, you need to configure the Info.plist itself, or the simplest way is to configure via TARGETS/"Info tab"/"Document Types section":
At this point your app becomes available to handle the TXT files coming from other external applications. So when you're about to open a TXT file that is attached to a mail, you should see your app available in the list:
2. Prepare app to receive incoming TXT file
In order to handle the supported file type, you need to implement the application:openURL:options: method you've already mentioned in your AppDelegate. Here you receive the file path as url, that you can easily send towards your ViewController for further processing. This url should looks something like this:
(lldb) po url
▿ file:///private/var/mobile/Containers/Data/Application/42D78E58-C7EC-4F3B-9100-B731AF7A4E45/Documents/Inbox/sample.txt
3. Handle the TXT file
Here you can also store the file's content in a String using the appropriate String initializer.
String(contentsOf: url, encoding: String.Encoding.utf8)
and then you can pass that String to your ViewController.
So your application:openURL:options: in your AppDelegate should looks something like this (depends on your actual view controller hierarchy) :
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
do {
let contentString = try String(contentsOf: url, encoding: .utf8)
if let window = self.window, let viewController = window.rootViewController as? ViewController {
viewController.displayText(text: contentString)
// here you pass the actual content as String to your custom ViewController which implements a displayText: function that receives a string
}
}
catch {
// contents could not be loaded
}
return true
}
Related
I'm trying to get real file name from file picker. I used two ways, such:
let fileName = file.lastPathComponent
and such:
let fileName = FileManager.default.displayName(atPath: file.absoluteString)
where file is let file = urls[0]. In any time I received strange letters set:
%D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.rtf
the real file name is: Без названия.rtf. The real file name is on Russian language , when I tried to get file with file name on English, everything was OK, so as I see all problems are connected with Russian file names. Maybe someone faced with similar problems and knows how to solve them? I also tried utf-8 encoding but it didn't help me :(
UPDATE
I send it to a chat server via websocket task:
func documentPicker(_ controller: UIDocumentPickerViewController,
didPickDocumentsAt urls: [URL]) {
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file)
let encodedString = String.init(data: fileData, encoding: .isoLatin1)!
let fileName = FileManager.default.displayName(atPath: file.path)
let time = Int64(NSDate().timeIntervalSince1970)
print(encodedString.count)
wsSendString(message: ChatMStrings.init().sendFilePart(fileContent: encodedString, fileName: fileName, fileSize: encodedString.count))
}catch{
print("contents could not be loaded")
}
}
sending object:
func sendFilePart(fileContent: String, fileName: String, fileSize: Int) -> String {
let sendFile:[String:[String:Any]] = ["chat":["a":"send_file",
"body":fileContent,
"filename":fileName,
"total":fileSize]]
let jsonData = try? JSONSerialization.data(withJSONObject: sendFile, options: [])
return String(data: jsonData!, encoding: String.Encoding.ascii)!
}
sending method:
func wsSendString(message:String) {
self.webSocket!.send(URLSessionWebSocketTask.Message.string(message)) { error in
if let error = error {
print("WebSocket sending error: \(error)")
}
}
self.listenWS()
}
websocket creation:
webSocket = urlSession.webSocketTask(with: request)
webSocket!.resume()
The issue there is that you are using the wrong URL property. file.absoluteString is wrong. you should use file.path.
let fileURL = URL(fileURLWithPath: "Без названия.rtf")
print(fileURL) // %D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.rtf -- file:///private/var/folders/00/l311vw5s2550g5gz2h25b2vr0000gp/T/
print(fileURL.path) // /private/var/folders/00/l311vw5s2550g5gz2h25b2vr0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.MyPlayground-comparable-enumeration-AD2BDA8A-BF7B-4F92-B561-B080E72B4DF0/Без названия.rtf
print(fileURL.absoluteString) // file:///private/var/folders/00/l311vw5s2550g5gz2h25b2vr0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.MyPlayground-comparable-enumeration-AD2BDA8A-BF7B-4F92-B561-B080E72B4DF0/%D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.rtf
let fileNamePath = FileManager.default.displayName(atPath: fileURL.path)
print(fileNamePath) // "Без названия.rtf\n"
let fileNameAbsoluteString = FileManager.default.displayName(atPath: fileURL.absoluteString)
print(fileNameAbsoluteString) // %D0%91%D0%B5%D0%B7%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.rtf
I created a stop button that can collect data, which will be saved to the defined path after clicking the stop button. However, if I want to continue collecting after clicking the stop button, the data will be added to the original text file. (This makes senses as I only know how to define one path)
My question is: Would it be possible to ask the user and input a new file name and save as a new text file after each stop so that the data is not added to the original file?
Below is what I have for one defined path and stacking up the data:
#IBAction func stopbuttonTapped(_ btn: UIButton) {
do {
let username:String = user_name.text!
fpsTimer.invalidate() //turn off the timer
let capdata = captureData.map{$0.verticesFormatted}.joined(separator:"") //convert capture data to string
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("testing.txt") //name the file
try capdata.appendLineToURL(fileURL: url as URL)
let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
}
catch {
print("Could not write to file")
}
}
And the extension I use for string and data:
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self).appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: String.Encoding.utf8)!
try data.append(fileURL: fileURL)
}
func trim() -> String
{
return self.trimmingCharacters(in: CharacterSet.whitespaces)
}
}
extension Data {
func append(fileURL: URL) throws {
if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
}
else {
try write(to: fileURL, options: .atomic)
}
}
}
Do I need to set a default file name (maybe texting.txt) and then popped up a user input for saving the text file? (That's where I am not too sure how to integrate to what I already have). I thank you so much for your time and suggestions in advance.
You could generate unique names.
For example:
let url = dir.appendingPathComponent("testing-\(Date.timeIntervalSinceReferenceDate).txt")
or
let url = dir.appendingPathComponent("testing-\(UUID().uuidString).txt")
I know we can pass key/value pairs in deep link URL. but can we pass an image as string as a value for a particular key ? I know about inter app communication through shared container. In my case there is a framework created by us which other developer can integrate in their apps. Through framework the user can send an image to our application(if its installed). So shared container will not work here.
Any help will be appreciated.
Is there any limit on the length of the url?
Thanks
Pass base64StrImage from source application
func gotToApp() {
let data = UIImagePNGRepresentation(#imageLiteral(resourceName: "img"))
let base64Str = data!.base64EncodedString()
if UIApplication.shared.canOpenURL(URL(string: "deep://")!) {
UIApplication.shared.open(URL(string: "deep://?img=\(base64Str)")!, options: ["img": #imageLiteral(resourceName: "img")]) { (finish) in
}
}
}
Get Image In Destination Application.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print(url.queryParameters!["img"])
return true
}
extension URL {
public var queryParameters: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else {
return nil
}
var parameters = [String: String]()
for item in queryItems {
parameters[item.name] = item.value
}
return parameters
}
}
I enabled Document Types to import or copy files from other apps to my application. I have some questions :
1- Where should create the method of moving files form Inbox to Document directory ? is this the right place ?
func applicationWillEnterForeground(_ application: UIApplication)
2- On first view controller I am getting files from Document directory :
func getFileListByDate() -> [String]? {
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
if let urlArray = try? FileManager.default.contentsOfDirectory(at: directory,
includingPropertiesForKeys: [.contentModificationDateKey],
options:.skipsHiddenFiles) {
return urlArray.map { url in
(url.lastPathComponent, (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast)
}
.sorted(by: { $0.1 > $1.1 }) // sort descending modification dates
.map { $0.0 } // extract file names
} else {
return nil
}
}
But when a file imports to my app there is Inbox folder(item) in my table view , how can I automatically move files from Inbox to Document directory and remove Inbox folder ?
If your app needs to open a file coming from another App you need to implement delegate method
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
and move the url to the folder of your choice inside your App.
let url = url.standardizedFileURL // this will strip out the private from your url
// if you need to know which app is sending the file or decide if you will open in place or not you need to check the options
let openInPlace = options[.openInPlace] as? Bool == true
let sourceApplication = options[.sourceApplication] as? String
let annotation = options[.annotation] as? [String: Any]
// checking the options info
print("openInPlace:", openInPlace)
print("sourceApplication:", sourceApplication ?? "")
print("annotation:", annotation ?? "")
Moving the file out of the inbox to your destination URL in your case the documents directory appending the url.lastPathComponent:
do {
try FileManager.default.moveItem(at: url, to: destinationURL)
print(url.path)
print("file moved from:", url, "to:", destinationURL)
} catch {
print(error)
return false
}
return true
I have a project that is associated with opening PDF files. This is set in the Info.plist. When I get a PDF attachment in email, I can hold my finger on the PDF attachment and then 'Open in' in my app. In my AppDelegate, I have the following added:
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
incomingTransfer = URL
return true
}
incomingTransfer is a Global Variable declared in another ViewController as an NSURL. This ViewController also has a UIWebView and the incomingTransfer loads into it and I'm able to see the new PDF file. My goal is to have a button that allows the user to save the incoming PDF as a PDF. I'm having trouble with this. I thought I had it all figured out, but it wasn't saving as a PDF at all, but rather as a String. Can someone help me please? My goal is to save the incoming PDF file as a PDF to the app memory, preferably in DocumentDirectory. I have a hard time trying to convert Objective C to Swift. My original code to save it was:
let html = String(incomingFileTransfer)
let fmt = UIMarkupTextPrintFormatter(markupText: html)
let render = UIPrintPageRenderer()
render.addPrintFormatter(fmt, startingAtPageAtIndex: 0)
let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
let printable = CGRectInset(page, 0, 0)
render.setValue(NSValue(CGRect: page), forKey: "paperRect")
render.setValue(NSValue(CGRect: printable), forKey: "printableRect")
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil)
for i in 1...render.numberOfPages() {
UIGraphicsBeginPDFPage();
let bounds = UIGraphicsGetPDFContextBounds()
render.drawPageAtIndex(i - 1, inRect: bounds)
}
UIGraphicsEndPDFContext();
recipeFileName = fileName.text!
print("File Name Entered: \(recipeFileName)")
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
pdfData.writeToFile("\(documentsPath)/\(recipeFileName).pdf", atomically: true)
I figured it out. I created a class called 'PDFFile'. Within 'PDFFile' are two variables, named var name: String and var url: NSURL. Within my 'IncomingTransfer' ViewController, I have the 'save' button create and save the new file with a typed name from the UITextField and the incomingURL specified in my AppDelegate is assigned to the url variable. Both are then saved to the PDFFile class using NSCoding. I then set a UITableView for it's dataSource from the PDFFile Class array data. I created a segue when the user clicks on the UITableViewCell and that goes to a new ViewController with a UIWebView. This WebView loads the PDF from a urlRequest using the specified url variable, saved from the NSCoding.
AppDelegate Code:
// Allows incoming file access (PDF)
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
// Transfer incoming file to global variable to be read
if url != "" {
// Load from Mail App
incomingFileTransfer = url
incomingStatus = "Incoming"
} else {
// Regular Load
print("App Delegate: No incoming file")
incomingFileTransfer = nil
}
return true
}
IncomingFile code and save button code:
// MARK: Properties
var file: PDFFile?
// MARK: Actions
// Save Button
let name = fileName.text ?? ""
let url = incomingFileTransfer
file = PDFFile(name: name, url: url)
// MARK: NSCoding
func saveFiles() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(pdfFiles, toFile: PDFFile.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save PDF file")
}
}
Viewing Saved Incoming PDF Later ViewController code:
// MARK: Properties
#IBOutlet weak var pdfItemWebView: UIWebview!
var incomingURL: NSURL!
// Within ViewDidLoad
if let file = file {
pdfFileName.text = file.name
incomingURL = file.url
print("Saved URL: \(incomingURL)")
print("Pending load...")
let request = NSURLRequest(URL: incomingURL!)
pdfItemWebView.loadRequest(request)
}
It was complicated but worked for me. There may be an easier way, but this is what I figured out and its works for my needs.