I've noticed that my NSLog (and I've also tried it with os_log) statements are not showing up consistently in the console application.
Here's some code that runs when my app starts up.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
NSLog("xyz didFinishLaunchingWithOptions")
FirebaseApp.configure()
NSLog("xyz FirebaseApp.configure() done")
let db = FirestoreDelegate.db()
let now = Date()
NSLog("xyz about to write")
db.collection("atest").document(DateUtils.formatTimestampGMT(now)).setData(["ts": now, "note":"delegate startup"]) {
err in
if let err = err {
NSLog ("xyz error ats \(err)")
return
}
NSLog("xyz wrote to ats \(DateUtils.formatTimestampGMT(now))")
}
NSLog("xyz after write")
... extraneous code removed
}
When I run, sometimes I see "xyz didFinishLaunchingWithOptions" and "wrote to ats", but none of the other log statements. Sometimes I see them all, but it's pretty inconsistent. Are they getting filtered or optimized out somehow?
I'm not debugging through xcode, I'm just running the app on my phone and viewing the logs through the console app on my computer.
I didn't figure out why the logging is inconsistent. Instead I created a custom logger that writes to a file.
public class FileWriter {
static func getTs() -> String {
let timestampFormat = DateFormatter()
timestampFormat.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return timestampFormat.string(from: Date())
}
static func write(_ text: String) {
do {
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("log.txt")
if !FileManager.default.fileExists(atPath: url.path) {
FileManager.default.createFile(atPath: url.path, contents: nil, attributes: nil)
}
let fileHandle = try FileHandle(forWritingTo: url)
let line = "\(getTs()) \(text)\n"
let data = line.data(using: String.Encoding.utf8)!
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
} catch let error as NSError {
print ("error \(error)")
}
}
}
To read the file, I added the following keys to my Info.plist "UIFileSharingEnabled"
, "LSSupportsOpeningDocumentsInPlace" and then I could open the file on my phone with the Files app.
Related
I'm trying to share file originally generated in watch App Extension but it's not working. I am using iOS 11.3 and Watch 4.3. I am also using Simulator.
I have done like this
Enable App domain in Main app and Watch App Extension.
App domain in selected in both with identifier.
REF : https://www.techotopia.com/index.php/Sharing_Data_Between_a_WatchKit_App_and_the_Containing_iOS_App
Code added:
In Watch App Extension:
do {
let fileManager = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppDomain)
guard let url = fileManager?.appendingPathComponent("TabCareLogs.txt") else { return }
try SOME_STRING.appendLineToURL(fileURL: url)
print("WatchKit: FILE PATH \(url.path)")
let result = try String(contentsOf: url, encoding: .utf8)
debugPrint("############# FILE: \(result) #############")
} catch let error {
print("Could not write to file : \(error.localizedDescription)")
}
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self + "\n").appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: .utf8)!
try data.append(fileURL: fileURL)
}
}
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)
}
}
}
WatchKit: FILE PATH
/Users/abhishek/Library/Developer/CoreSimulator/Devices/D515C513-3975-4C8F-B07D-6A0CCCD1A8EC/data/Containers/Shared/AppGroup/90DC2A1A-61D6-440B-A499-9EA608CE0C2E/TabCareLogs.txt
I'm getting file as well as its content.
In Main App:
let fileManager = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: AppDomain)
guard let url = fileManager?.appendingPathComponent("TabCareLogs.txt") else { return nil }
print("FILE PATH \(url.path)")
print("FILE PATH ABSOLUTE \(url.absoluteString)")
let exists = FileManager.default.fileExists(atPath: url.path)
print("FILE EXIST : \(exists)")
FILE PATH
/Users/abhishek/Library/Developer/CoreSimulator/Devices/D5CED7C4-B674-4BF7-A72C-C063F733F4E7/data/Containers/Shared/AppGroup/77C1EAC2-2A86-4763-973F-33929C909619/TabCareLogs.txt
FILE PATH ABSOLUTE
file:///Users/abhishek/Library/Developer/CoreSimulator/Devices/D5CED7C4-B674-4BF7-A72C-C063F733F4E7/data/Containers/Shared/AppGroup/77C1EAC2-2A86-4763-973F-33929C909619/TabCareLogs.txt
FILE EXIST : false
I have tried to print the path I'm getting path bit if I'm checking file exist then its is showing false.
Same issue : NSFileManager.defaultManager().fileExistsAtPath returns false instead of true
Also the paths are different I don't know why it is going to different path after using shared container?
I'm still trying to setup proper migration of existing NSSQLiteStore to SMStore.Type (Seam3) and getting strange issue. Cannot understand why it happens. I have raised the same for Paul on github, but probably somebody will noticed the cause in my code and will be able to help, while Paul is busy.
What do I have:
App running on iOS 10+ with NSSQLiteStore located in default folder.
Steps of testing:
Remove new store (if exists) and it's supporting files located in new path:
file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Data/Application/29B60BBC-0D17-4D6C-8107-0135C45B20BA/Documents/CafeManagerSeam3.sqlite
Run app. App should check if new store exists. If not - perform migration from default one.
App tries to migrate store and fails with EXC_BAD_ACCESS.
But in the same time I can see that new store appeared in new path and if I run app once again, it uses new store with no visible issues.
My code:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var smStore: SMStore?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 10.0, *) {
let storeDescriptionType = AppDelegate.persistentContainer.persistentStoreCoordinator.persistentStores.first?.type
if storeDescriptionType == SMStore.type {
print("Store is SMStore")
print()
self.smStore = AppDelegate.persistentContainer.persistentStoreCoordinator.persistentStores.first as? SMStore
}
} else {
let storeDescriptionType = AppDelegate.managedObjectContext.persistentStoreCoordinator?.persistentStores.first?.type
if storeDescriptionType == SMStore.type {
print("Store is SMStore")
print()
self.smStore = AppDelegate.managedObjectContext.persistentStoreCoordinator?.persistentStores.first as? SMStore
}
}
...
// MARK: - Core Data stack for iOS 10+
#available(iOS 10.0, *)
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CafeManager")
let persistentStoreCoordinator = container.persistentStoreCoordinator
//MARK: Initializing Seam3
SMStore.registerStoreClass()
SMStore.syncAutomatically = true
//MARK: Preparing URL
let applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
let newURL = applicationDocumentsDirectory.appendingPathComponent("CafeManagerSeam3.sqlite")
//MARK: Check if SQLite store has been already migrated by checking if CafeManagerSeam3.sqlite exists.
let seamStoreExists = FileManager.default.fileExists(atPath: newURL.path)
if seamStoreExists {
//If exists, then use it because it has been already migrated to Seam3 storage
print("Already migrated, using \(newURL)")
let storeDescription = NSPersistentStoreDescription(url: newURL)
storeDescription.type = SMStore.type
storeDescription.setOption("iCloud.iGlock.CafeManager.com" as NSString, forKey: SMStore.SMStoreContainerOption)
storeDescription.setOption(NSNumber(value:SMSyncConflictResolutionPolicy.clientTellsWhichWins.rawValue), forKey:SMStore.SMStoreSyncConflictResolutionPolicyOption)
container.persistentStoreDescriptions=[storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
} else {
//If does not exist, then migrate old storage to Seam3.
print("Not yet migrated, migrating to \(newURL)")
//Loadig default store
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Failed to load default store \(error), \(error.userInfo)")
}
})
let defaultPersistentStore = container.persistentStoreCoordinator.persistentStores.last
print("Default store is located here: \(defaultPersistentStore!.url!)")
//Adding new Seam3 store
do {
try persistentStoreCoordinator.addPersistentStore(ofType: SMStore.type, configurationName: nil, at: newURL, options: nil)
print("Seam store was added to the new url: \(newURL)")
} catch {
fatalError("Failed to add new Seam store: \(error)")
}
//Migrating default store to new Seam store
do {
try persistentStoreCoordinator.migratePersistentStore(defaultPersistentStore!, to: newURL, options: nil, withType:SMStore.type)
//Removing old store
if defaultPersistentStore != nil {
do {
try persistentStoreCoordinator.remove(defaultPersistentStore!)
} catch {
fatalError("Failed to remove default store \(error)")
}
}
}
catch {
fatalError("Failed to migrate to Seam store: \(error)")
}
//Setting additional parameters to Seam store to make it able to use CloudKit
let storeDescription = NSPersistentStoreDescription(url: newURL)
storeDescription.type = SMStore.type
storeDescription.setOption("iCloud.iGlock.CafeManager.com" as NSString, forKey: SMStore.SMStoreContainerOption)
storeDescription.setOption(NSNumber(value:SMSyncConflictResolutionPolicy.clientTellsWhichWins.rawValue), forKey:SMStore.SMStoreSyncConflictResolutionPolicyOption)
container.persistentStoreDescriptions=[storeDescription]
return container
}
Output:
CoreData: annotation: Failed to load optimized model at path '/Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Bundle/Application/3E4EB624-227E-4BD0-99F1-AA975D86BDA8/CafeManager.app/CafeManager.momd/CafeManager v2.omo'
Not yet migrated, migrating to file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Data/Application/100FB44C-C881-44C1-9D03-454FEBDB092B/Documents/CafeManagerSeam3.sqlite
Default store is located here: file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Data/Application/100FB44C-C881-44C1-9D03-454FEBDB092B/Library/Application%20Support/CafeManager.sqlite
Seam store was added to the new url: file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Data/Application/100FB44C-C881-44C1-9D03-454FEBDB092B/Documents/CafeManagerSeam3.sqlite
2017-10-31 17:49:47.762 CafeManager[52561:7472303] Access to CloudKit has not been verified by calling verifyCloudKitConnection
(lldb) po self
error: Trying to put the stack in unreadable memory at: 0x7ffee595ef80.
(lldb)
What am I doing wrong? Is it my misunderstanding of CoreData stack or something else?
Output when I rerun app after it's crash:
objc[52621]: Class _NSZombie_OS_xpc_endpoint is implemented in both ?? (0x618000048340) and ?? (0x600000047290). One of the two will be used. Which one is undefined.
CoreData: annotation: Failed to load optimized model at path '/Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Bundle/Application/0B098A31-C927-44C0-87C2-2F2B944DF66C/CafeManager.app/CafeManager.momd/CafeManager v2.omo'
Already migrated, using file:///Users/dj-glock/Library/Developer/CoreSimulator/Devices/3708F142-3BD0-4C70-8515-217B7785D285/data/Containers/Data/Application/932E6B3A-65B1-44F1-920B-DCE5F242C9C7/Documents/CafeManagerSeam3.sqlite
Store is SMStore
Sync Started
No more records coming
Sync Performed
Sync performed successfully
//some test insert that was loaded to Cloud.
Sync Started
No more records coming
Sync Performed
Sync performed successfully
Error was solved. Used example from Paul.
{
// If does not exist, then migrate old storage to Seam3.
print("Not yet migrated, migrating to \(newURL)")
SMStore.syncAutomatically = false
// Loadig default store
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Failed to load default store \(error), \(error.userInfo)")
}
})
let defaultPersistentStore = container.persistentStoreCoordinator.persistentStores.last
print("Default store is located here: \(defaultPersistentStore!.url!)")
//Migrating default store to new Seam store
do {
try persistentStoreCoordinator.migratePersistentStore(defaultPersistentStore!, to: newURL, options: nil, withType:SMStore.type)
}
catch {
fatalError("Failed to migrate to Seam store: \(error)")
}
//Setting additional parameters to Seam store to make it able to use CloudKit
let storeDescription = NSPersistentStoreDescription(url: newURL)
storeDescription.type = SMStore.type
storeDescription.setOption("iCloud.iGlock.CafeManager.com" as NSString, forKey: SMStore.SMStoreContainerOption)
storeDescription.setOption(NSNumber(value:SMSyncConflictResolutionPolicy.clientTellsWhichWins.rawValue), forKey:SMStore.SMStoreSyncConflictResolutionPolicyOption)
container.persistentStoreDescriptions=[storeDescription]
SMStore.syncAutomatically = true
return container
}
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
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
}
I just drag the Data.csv file to the application folder in the Navigator panel, I am trying to set the correct path of the file into the app. The code below I used for the simulator and works perfect, but to run in the device I changed to the second block of code, then I got this errors:
Data[399:157757] CFURLCopyResourcePropertyForKey failed because it was passed an URL which has no scheme
Error Domain=NSCocoaErrorDomain Code=256 "The file “Documents” couldn’t be opened." UserInfo={NSURL=/var/mobile/Containers/Data/Application/C7756542-6922-4C6F-A98E-C6F407B2063E/Documents}
//code to show the path in the simulator:
guard let remoteURL = NSURL(string: "/Users/mbp/Library/Developer/CoreSimulator/Devices/7F25FC7C-F2B2-464E-85B4-A2B96DB83F17/data/Containers/Bundle/Application/F285940D-7776-4EE2-83A1-D54DD3411E0E/Data.app/Data.csv") else {
return
}
Block to run the app in the device:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let sourcePath = NSBundle.mainBundle().pathForResource(“Data”, ofType: "csv")
print(sourcePath)
let filename = "Data.csv"
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let destinationPath = documentsPath + "/" + filename
do {
try NSFileManager().copyItemAtPath(sourcePath!, toPath: destinationPath)
} catch _ {
}
Try to load the file
let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "DataEntity")
fetchRequest.fetchLimit = 1
do {
let result = try managedObjectContext.executeFetchRequest(fetchRequest)
if result.count == 0 {
preloadData()
}
} catch let error as NSError {
print("Error: \(error.domain)")
}
func preloadData () {
guard let remoteURL = NSURL(string:NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]) else {
return
}
}
Process file path via NSURL can avoid the mismatch between device and simulator.
let srcURL = NSBundle.mainBundle().URLForResource("Data", withExtension: "csv")!
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
var toURL = NSURL(string: "file://\(documentsPath)")!
toURL = toURL.URLByAppendingPathComponent(srcURL.lastPathComponent!)
do {
try NSFileManager().copyItemAtURL(srcURL, toURL: toURL)
self.preloadData(toURL)
} catch let error as NSError {
print(error.localizedDescription)
}
func preloadData(toURL: NSURL) {
print("=== Success and print toURL ===")
print(toURL)
}