In my today extension with my device unlocked, this line of code works as expected, returning the data from the image path:
let imageData = NSData(contentsOfFile: path)
However when my device is locked with a passcode, it returns nil. Is there any way to access images in the file system when the device is locked? I can access UserDefaults just fine, but not files in the directory for my shared group. Here is how I am creating the path, calling imagePath, which is correctly populated with the path I expect in both cases:
func rootFilePath() -> String? {
let manager = NSFileManager()
let containerURL = manager.containerURLForSecurityApplicationGroupIdentifier(GROUP_ID)
if let unwrappedURL = containerURL {
return unwrappedURL.path
}
else {
return nil
}
}
func imagePath() -> String? {
let rootPath = rootFilePath()
if let uPath = rootPath {
return "\(uPath)/\(imageId).png"
}
else {
return nil
}
}
I just figured it out! You need to set the file permissions accordingly:
NSFileManager *fm = [[NSFileManager alloc] init];
NSDictionary *attribs = #{NSFileProtectionKey : NSFileProtectionNone};
NSError *unprotectError = nil;
BOOL unprotectSuccess = [fm setAttributes:attribs
ofItemAtPath:[containerURL path]
error:&unprotectError];
if (!unprotectSuccess) {
NSLog(#"Unable to remove protection from file! %#", unprotectError);
}
In many cases you wouldn't normally want to do this, but because the information is intended to be viewed from the lock screen, I'm OK with removing file protection.
Related
After updating iOS native app with an app written on Flutter I want to read a file from filesystem on iOS device using Dart. The file I want to read has been previously written to filesystem using this ObjectiveC code:
- (void)setAccount:(FTAccountModel *)account {
_account = account;
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
path = [path stringByAppendingPathComponent:AccountModelPath];
if (account) {
NSArray * array = #[account];
[array writeToFile:path atomically:YES];
[NSKeyedArchiver archiveRootObject:array toFile:path];
}
}
I've tried the following approach in Flutter using path_provider package:
final appDocDir = await getApplicationDocumentsDirectory();
final accountDataFile = File('${appDocDir.path}/$_iosAccountDataFile');
String contents = await accountDataFile.readAsString();
print("contents: $contents");
But I get an error when calling readAsString() method:
FileSystemException: Failed to decode data using encoding 'utf-8', path = '/var/mobile/Containers/Data/Application/FBCB4862-E5EA-4C93-8C2E-3DF1F00A8645/Documents/AccountModel.data'
How to read file on iOS device using Dart and Flutter, that has been written using NSKeyedArchiver?
As of writing this answer, there are no plugins to read the file, that has been previously written to the filesystem using NSKeyedArchiver in iOS. The way to read the file is to write custom platform-specific code.
So the iOS platform code on Swift will be something like the following:
private func callGetUserIdFromNativeApp(result: #escaping FlutterResult) {
var account: FTAccountModel?
let fm = FileManager.default
let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
if (!urls.isEmpty) {
let file = urls[0].appendingPathComponent("Accounts.data", isDirectory: false)
if (fm.fileExists(atPath: file.path)) {
if let accountArray: [FTAccountModel] = NSKeyedUnarchiver.unarchiveObject(withFile: file.path) as? [FTAccountModel] {
if (!accountArray.isEmpty) {
account = accountArray[0]
}
}
}
}
if let userId: Int = account?.userId {
result(String(userId))
} else {
result(nil)
}
}
And the flutter part will use MethodChannel to invoke the native code:
static const MethodChannel _channel = const MethodChannel("CHANNEL_NAME");
static Future<String> getUserIdFromNativeIos() async {
try {
return await _channel.invokeMethod("METHOD_NAME");
} catch (e){
return _failedString();
}
}
I am using CocoaLumberjack V2.4 to save log into file.
This is the default code to set saving log into file:
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
[DDLog addLogger:fileLogger];
Log is saved in default place: /AppData/Library/Caches/Logs/
I want implement an API to remove the saved log manually. I check they have Manually clear logs as the open issue there. Anyone has suggestion?
This should work for you:
[fileLogger rollLogFileWithCompletionBlock: ^{
for (NSString *filename in fileLogger.logFileManager.sortedLogFilePaths) {
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
}
}];
Swift Answer:
fileLogger.rollLogFile(withCompletion: {
for filename: String in self.fileLogger.logFileManager.sortedLogFilePaths {
do {
try FileManager.default.removeItem(atPath: filename)
} catch {
print(error.localizedDescription)
}
}
})
Try this one in Swift.
func clearLogs(){
let logHelper = LogHelper(subsystem: "Logs", category: "Clear")
logHelper.i("Clearing logs...")
for logger in DDLog.allLoggers {
if let fileLogger = logger as? DDFileLogger {
fileLogger.rollLogFile{
for path in fileLogger.logFileManager.sortedLogFilePaths {
let fileURL = URL(fileURLWithPath: path)
logHelper.i("\(fileURL)")
do {
try FileManager.default.removeItem(at: fileURL)
logHelper.i("Done!")
}
catch {
logHelper.e(error.localizedDescription)
}
}
}
}
}
}
I'm trying to persist an array of objects to file. That has failed. So to test my process, I've tried writing a simple string to file and that hasn't worked either. Below is the code I'm using to test the simple writing of a string to file.
var directoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var path = directoryPath.stringByAppendingPathComponent(archivePath)
var checkFile = NSFileManager.defaultManager()
NSLog("Path: %#", path)
var testString = "Hi my name is ken"
var error : NSError?
var written = testString.writeToFile(path, atomically: true, encoding: NSASCIIStringEncoding, error: &error)
if ( written == false ) {
if ((error ) != nil) {
var errString = error?.localizedDescription
NSLog( "Fail: %#", errString! )
var isDir : ObjCBool = false
var isFile = checkFile.fileExistsAtPath(directoryPath, isDirectory: &isDir)
if ( isDir ){
NSLog("The directory %#, exists", directoryPath )
} else {
NSLog("The directory %#, does not exist", directoryPath )
}
}
}
This is the result from the NSLog statements:
2015-02-25 08:37:29.280 ...App Name... [14596:3911337] Path: /Users/cs/Library/Developer/CoreSimulator/Devices/1747CFD7-5252-4221-AA89-BDEC1A57BA5A/data/Containers/Data/Application/5DC24B8F-A667-4ECD-B7E6-E139E65525E1/Documents/data/appdata.txt
2015-02-25 08:37:29.284 ...App Name... [14596:3911337] Fail: The operation couldn’t be completed. (Cocoa error 4.)
2015-02-25 08:37:29.285 ...App Name... [14596:3911337] The directory /Users/cs/Library/Developer/CoreSimulator/Devices/1747CFD7-5252-4221-AA89-BDEC1A57BA5A/data/Containers/Data/Application/5DC24B8F-A667-4ECD-B7E6-E139E65525E1/Documents, exists
You don't include the archive path, but I assume it is /data/appdata.txt.
The documents directory exists, but there isn't a /data folder in it, unless you create it. Trying to write the file to /data/appdata.txt doesn't automatically create the data directory. Either first create the data directory in the .DocumentzDirectory, or just try saving the file to the root (without the /data/).
var directoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var path = directoryPath.stringByAppendingPathComponent(archivePath)
var checkFile = NSFileManager.defaultManager()
NSLog("Path: %#", path)
var testString = "Hi my name is ken"
var error : NSError?
//1. if your archivePath is just a file name with extension
//remove exist file first
var canWrite : bool = true
if (checkFile.fileExistsAtPath(path)) {
canWrite = checkFile.removeItemAtPath(path, error:&error)
}
if (!canWrite) {
//show alert
//and return
return
}
//2. if your archivePath is a path with some folder, besure to create if folder does not exist and continue with //1.
var written = testString.writeToFil
e(path, atomically: true, encoding: NSASCIIStringEncoding, error: &error)
if ( written == false ) {
if ((error ) != nil) {
var errString = error?.localizedDescription
NSLog( "Fail: %#", errString! )
var isDir : ObjCBool = false
var isFile = checkFile.fileExistsAtPath(directoryPath, isDirectory: &isDir)
if ( isDir ){
NSLog("The directory %#, exists", directoryPath )
} else {
NSLog("The directory %#, does not exist", directoryPath )
}
}
}
Creating the directory helped, but it turns out, the key to the problem was the array I was writing to disk had dictionaries which contained values that weren't compatible with plists. Array.writeToFile... writes to Plists and if data in the array isn't compatible with a plist, it won't write. I ended up using NSKeyArchiver and NSKeyUnArchiver to handle the writing and reading. Once I used that and handled the directory issue, it all worked. Thanks to all for your suggestions.
how to rewrite this objective-c language to swift?
NSString *filePath = #"/Applications/MySample.app";
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// avoid open add friend
}
regards.
Equivalent Swift 3 Code:
let filePath = "/Applications/MySample.app"
if (FileManager.default.fileExists(atPath: filePath)) {
// avoid open add friend
}
Swift 2
let filePath = "/Applications/MySample.app"
if (NSFileManager.defaultManager().fileExistsAtPath(filePath))
{
// avoid open add friend
}
Some years after the question has been asked I recommend to take rewrite literally and use the URL related API
let fileURL = URL(fileURLWithPath:"/Applications/MySample.app")
if let _ = try? fileURL.checkResourceIsReachable() {
// file exists
}
let path = "/Applications/MySample.app"
let hasFile = FileManager().fileExists(atPath: path)
if hasFile {
// use file
}
else {
// possibly inform user the file does not exist
}
I am trying to create a Application which lists all contacts from the iPhone address book with the following code (coffeescript)
listContacts: ->
options = new ContactFindOptions()
options.filter = '';
options.multiple = true
fields = ["id", "photos", "name", "phoneNumbers"]
navigator.contacts.find(fields, #onSuccess, #onError, options)
onSuccess: (contacts) ->
console.log contacts.length
onError: (error) ->
console.log error
this seems to work nice for a bunch of contacts. but with 3000 the contacts will never return. the funny thing though this works perfectly on the iOsSimulator.
are there any limitations to the number of contacts which can be retrieved?
I had the same problem with 300 contacts, it took around 5 minutes. After I patched it only takes 10 seconds.
Here is my pull request : https://github.com/phonegap/phonegap/pull/19
They have to generate a temp file for each picture and they are using a crazy loop to find a free file path. Something like :
do {
filePath = [NSString stringWithFormat:#"%#/photo_%03d.jpg", docsPath, i++];
} while ([fileMgr fileExistsAtPath:filePath]);
Now I use mktemp and everything is faster.
If you don't need full res pictures, you can also replace :
CFDataRef photoData = ABPersonCopyImageData(self.record);
by :
CFDataRef photoData = ABPersonCopyImageDataWithFormat(self.record, kABPersonImageFormatThumbnail);
I hope that'll help you !
Edit :
IOS'll flush the temp directory each time you start the application:
You are responsible for deleting any temporary files that you created.
The system will clean them up at startup, but that could be a very
long time away.
From: http://cocoadev.com/wiki/NSTemporaryDirectory
If you don't want to slow down the bootstrap of your application, you should use always the same filepath based on the contact id. You'll save cleanup and write time if the file already exists :
- (NSObject*)extractPhotos
{
NSMutableArray* photos = nil;
if (ABPersonHasImageData(self.record)) {
//CFDataRef photoData = ABPersonCopyImageDataWithFormat(self.record, kABPersonImageFormatThumbnail);
CFDataRef photoData = ABPersonCopyImageData(self.record);
NSData* data = (__bridge NSData*)photoData;
// write to temp directory and store URI in photos array
// get the temp directory path
NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath];
NSError* err = nil;
int recordId = ABRecordGetRecordID(self.record);
NSFileManager* fileMgr = [[NSFileManager alloc] init];
NSString* filePath = [NSString stringWithFormat:#"%#/photo_%03d.jpg", docsPath, recordId];
BOOL hasImage = NO;
if ([fileMgr fileExistsAtPath:filePath]) {
hasImage = YES;
} else if ([data writeToFile:filePath options:NSAtomicWrite error:&err]) {
hasImage = YES;
}
if (hasImage) {
photos = [NSMutableArray arrayWithCapacity:1];
NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:2];
[newDict setObject:filePath forKey:kW3ContactFieldValue];
[newDict setObject:#"url" forKey:kW3ContactFieldType];
[newDict setObject:#"false" forKey:kW3ContactFieldPrimary];
[photos addObject:newDict];
}
CFRelease(photoData);
}
return photos;
}
Edit (08/01/2013):
FYI : merged in cordova : http://git-wip-us.apache.org/repos/asf/cordova-ios/commit/c6a1dbe3
First you have to add plugin from terminal command line
$ cordova plugin add org.apache.cordova.contacts
onDeviceReady you can call a method to open contact list
function chooseContact() {
var options = new ContactFindOptions();
options.fields = ["displayName", "name", "emails", "phoneNumbers"];
navigator.contacts.chooseContact(onSuccess, options);
}
function onSuccess(id, contact) {
console.log(JSON.stringify(contact));
}