Problems converting code to Swift from obj-c [duplicate] - ios

This question already has an answer here:
Cannot convert value of type '()?' to specified type Bool
(1 answer)
Closed 7 years ago.
Im trying to convert to swift code, which sets files property to "not to backup to iCloud"
Original code looks like this
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString {
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
I came with the such code
func addSkipBackupAttributeToItemAtURL(URL: NSURL) -> Bool{
let fileManager = NSFileManager.defaultManager()
assert(fileManager.fileExistsAtPath(URL.absoluteString))
var error:NSError?
let success:Bool = try? URL.setResourceValue(NSNumber(bool: true),forKey: NSURLIsExcludedFromBackupKey)
if !success {
print("Error excluding \(URL.lastPathComponent) from backup \(error)")
} else {
print("File at path \(URL) was succesfully updated")
}
return success
}
but it throws error at line with let success:Bool
Cannot convert value of type '()?' to specified type 'Bool'
How to fix that ?

setResourceValue is not returning a Bool to indicate the success, but throws an error when it fails. So you should wrap it in a try catch block.
̶W̶e̶l̶l̶,̶ ̶t̶h̶e̶ ̶d̶o̶c̶u̶m̶e̶n̶t̶a̶t̶i̶o̶n̶ ̶l̶o̶o̶k̶s̶ ̶o̶u̶t̶d̶a̶t̶e̶d̶, the docs describes error handling in Swift at the bottom, but if you jump it's defenition, you would see that the method does not return anything:
public func setResourceValue(value: AnyObject?, forKey key: String) throws

Related

How do I get around NSCocoaErrorDomain:257 when pulling a file from the Files app?

I'm trying to access a file to pull a copy into my app so that users can associate it with relevant information. It used to work just fine up until recently, and now I suddenly am getting the following message:
Failed to read file, error Error Domain=NSCocoaErrorDomain Code=257 "The file “[File name]” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/[File name], NSUnderlyingError=0x281b88690 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
This is the code that's throwing the error:
//AppDelegate.m
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
if (![url.pathExtension isEqualToString:#"pdf"] && ![url.pathExtension isEqualToString:#"png"] && ![url.pathExtension isEqualToString:#"jpg"] && ![url.pathExtension isEqualToString:#"jpeg"]){
return false;
}
NSError* error = nil;
NSString *path = [url path];
NSData *data = [NSData dataWithContentsOfFile:path options: 0 error: &error];
if(data == nil) {
NSLog(#"Failed to read file, error %#", error);
}
//Do stuff with the file
return true;
}
I did update to xcode 11 and iOS 13, so there may have been a change there that I wasn't aware of.
It turns out there's a "using" function that tells the app its accessing files outside of it's sandbox. The methods startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource on NSURL need to be wrapped around the code using the url, like so:
BOOL isAcccessing = [url startAccessingSecurityScopedResource];
NSError* error = nil;
NSString *path = [url path];
NSData *data = [NSData dataWithContentsOfFile:path options: 0 error: &error];
if(data == nil) {
NSLog(#"Failed to read file, error %#", error);
}
if (isAccessing) {
[url stopAccessingSecurityScopedResource];
}
I'm not sure if there's anything specific to iOS 13 that requires this when it didn't previously, but that is the only real change between it working and not working.
Jordan has a great answer! Here's the version translated to Swift
let isAccessing = url.startAccessingSecurityScopedResource()
// Here you're processing your url
if isAccessing {
url.stopAccessingSecurityScopedResource()
}
As I encountered this myself and the comment to Jordan's answer confirmed this happens only on the real device. Simulator has no such an issue

CallDirectory Handler extension error while using Realm

I'm using Callkit extension to identify the numbers. All my contacts (around 30k+) are stored in Realm.
I have stored the Realm file in AppGroup which can be shared between my app and its extensions.
I get the error when I try to reload the extension.
Error Domain=com.apple.CallKit.error.calldirectorymanager Code=7
"(null)"
When this error occurred , the setting for Call Blocking & Identification for my app shows a spinner (while other apps show the switch to toggle)
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[CXCallDirectoryManager sharedInstance] reloadExtensionWithIdentifier:#"com.j2x.handheldcontact.CallerID" completionHandler:^(NSError *error){
if(error) {
NSLog(#"CallerID - refresh failed. error is %#",[error description]);
}
}];
}
I see that the error happens only when I try to use access the Realm in the app group directory.
In my extension subclass:
- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context
{
context.delegate = self;
NSString *appGroupId = #"group.com.j2x.handheldcontact.CallerID";
NSURL *appGroupDirectoryPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
NSURL *dataBaseURL = [appGroupDirectoryPath URLByAppendingPathComponent:#"default.realm"];
[[[RLMRealm defaultRealm]configuration]setFileURL:dataBaseURL];
RLMResults *temp = [self getContactArray]; //This gives the callKit error
RLMResults *temp ; //This doesn't give any error
[context completeRequestWithCompletionHandler:nil];
}
-(RLMResults *)getContactArray{
RLMResults *res = [[RealmContact allObjects]objectsWithPredicate:[NSPredicate predicateWithFormat:#"phone <> nil or homePhone <> nil or mobilePhone <> nil or altPhone <> nil or fax <> nil"]];
return res;
}
Why does accessing the Realm data gives the error ? The predicate format does look ok to me.
With some research I found the following code:
public enum Code : Int {
public typealias _ErrorType = CXErrorCodeCallDirectoryManagerError
case unknown
case noExtensionFound
case loadingInterrupted
case entriesOutOfOrder
case duplicateEntries
case maximumEntriesExceeded
case extensionDisabled
#available(iOS 10.3, *)
case currentlyLoading
#available(iOS 11.0, *)
case unexpectedIncrementalRemoval
}
In my case , the error says case currentlyLoading (code 7). I also tried this on realm with only 250 contacts . But I got the same error.
Edit:
If I hardcode the contact, it works fine. But if I bring Realm into scene, it fails.
CXCallDirectoryPhoneNumber phoneNumber = strtoull([#"14xxxxxx86" UTF8String], NULL, 0);
if (phoneNumber > 0) {
[context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:#"Test Test"];
}
Workaround:
For now, I'm storing all my data into a file and saving that file in the app group.
NSString *appGroupId = #"group.xxx.CallerID";
NSURL *appGroupDirectoryPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
NSURL *appFile = [appGroupDirectoryPath URLByAppendingPathComponent:#"contacts.txt"];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:[appFile path]];
if(exists) {
[[NSFileManager defaultManager]removeItemAtPath:[appFile path] error:nil];
}
[NSKeyedArchiver archiveRootObject:uniqueCallDirectory toFile:[appFile path]];
and accessing this array in the callID extension subclass.
The list you provide to addIdentificationEntryWithNextSequentialPhoneNumber must be ordered by ascending phone Number. Retrieve the list from realm in ascending order. (else it will break and keep the loading icon when you activate the extension)

Unable to remove SQLite Database file

I am new to Xcode and ios. I am using Xcode 7.3 version and swift2.2
I have removed a per-populated SQlite Db from my project. Later, I used Add file menu in Xcode to add the same name SQLite Db with only modified the content of the field. Example , fieldname :addr , the content :123, 5th ave , now the same filedname :addr ,the content :12, Broadway
After added the modified content SQLite DB, the code STILL using the old content that is 123,5th ave!
let querySQL = "select Sid, location, Addr from tblPlaces where Sid =" + myId
let result: FMResultSet? = MyDB.executeQuery(querySQL, withArgumentsinArray:nil)
if result?.next() == true {
let strAddr = results!stringForColumn("Addr")
}else {
}
I have created a class to handle the creation of SQLite DB in AppDelegate:
Util.copyFile("SqliteDB filename")
here the code:
class func copyFile(fileName: NSString) {
var dbPath: NSString = getPath(fileName)
var fileManager = NSFileManager.defaultManager()
if !fileManager.fileExistsAtPath(dbPath) {
let documentsURL = NSBundle.mainBundle().resourceURL
let fromPath = documentsURL!.URLByAppendingPathComponent(filename as string)
var error : NSError?
do {
try filemanager.CopyItemAtPath(fromPath.path!, toPath: dbPath)
}catch let error1 as NSError {
error = error1
}
}
// GetPath of the SQLite file
class func getPath(filename : String) -> string {
let documentDirectory = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,inDomain:
UserDomainMask, appropriateURLURL:nil, create: true)
return documentDirectory.URLbyAppendingPathComponent(filename).path!
}
What this happened? How to do the right way?
If I remove the SQlite Db file say MyDB.sqlite, and later I add in the same name Db file with content modified, I should get the new content. But this is not the case.
You are putting your file in the application bundle, i.e., to be distributed with your app. That file is read only. To update the file (including scheme restructuring) you would need to copy the the file to your documents folder, and update it there. Otherwise, your update will fail, leaving you with the same file as before.
I think this is what is happening.
For distribution it is also necessary that any such file be flagged NOT to backup to icloud or the app is rejected.
I use the following method (sorry, obj c) to copy the file from the bundle to the documents directory (sorry this code is sloppy but it may convey the basic idea).
+ (void)copyBundleFileToStoresDirectory:(NSString *)filename
{
NSError *error;
NSURL *fileURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:filename ofType:nil]];
NSURL *pathURL = [SVOFileSystemMethods documentsSubdirectory:#"Stores" skipBackup:YES];
if (fileURL)
{
if([[NSFileManager defaultManager] copyItemAtURL:fileURL toURL:pathURL error:&error])
{
// NSLog(#"File successfully copied");
}
else
{
[[[UIAlertView alloc]initWithTitle:NSLocalizedString(#"error", nil) message: NSLocalizedString(#"Failed to copy database from bundle.", nil)
delegate:nil cancelButtonTitle:NSLocalizedString(#"OK", nil) otherButtonTitles:nil] show];
NSLog(#"Error description-%# \n", [error localizedDescription]);
NSLog(#"Error reason-%#", [error localizedFailureReason]);
}
}
}
// // flags URL to exclude from backup //
+ (BOOL) addSkipBackupAttributeToItemAtURL:(NSURL *)URL {
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success)
{
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success; }

Get the attribute of NSURLIsExcludedFromBackupKey of the file

To exclude files from backups to iCloud and iTunes, I have used the code below :
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
The value of the success is YES, but I still want to check the corresponding attribute of the file. I try to use the code below, but it fails:
NSLog(#"%#",[URL valueForKey:NSURLIsExcludedFromBackupKey]);
what is the right way to achieve my purpose? thanks!
As Shashank suggested, if you're setting a resource value via setResourceValue, you need to access it via getResourceValue. valueForKey is for KVC, and is not related to resource values.
In order to do this, you need to pass in the object you want to hold the result in.
NSNumber* backupKeyResult = nil;
NSError* error = nil;
BOOL result = [URL getResourceValue:&result forKey:NSURLIsExcludedFromBackupKey error:&error];
if (result && !error) {
if (backupKeyResult) {
BOOL backupKeySet = [backupKeyResult boolValue];
// backupKeySet has the value you've set previously
}
else {
// The requested resource value is not defined for the URL.
}
}
else {
if (error) {
// An error occurred whilst trying this, check your NSError object to see what's up
}
else if (!result) {
// The value was not successfully populated
}
}

Clean (remove) a database in MagicalRecord

I have an app that is using MagicalRecord for its Core Data handling and this works nice. However I have different users that can login in the app and when another user logs in, the core data database must be emptied so that the different user can have his own data. The database can be emptied completely as the data is also stored on a webservice and therefore can always be synced again after logging in again the first user.
So far I cannot seem to find a helper method (that works) for this purpose. I have tried
[MagicalRecord cleanUp];
whenever the user is logging out, but this does not do the trick.
This is how I did it. It is essential to have this line: [MagicalRecord cleanup]. Without it, [self setupDB] won't work.
UPDATE: Deletes the -wal and -shm files. #thattyson pointed out an issue in iOS 9. Also, see the answer of #onmyway133.
- (void)setupDB
{
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:[self dbStore]];
}
- (NSString *)dbStore
{
NSString *bundleID = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey];
return [NSString stringWithFormat:#"%#.sqlite", bundleID];
}
- (void)cleanAndResetupDB
{
NSString *dbStore = [self dbStore];
NSError *error1 = nil;
NSError *error2 = nil;
NSError *error3 = nil;
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:dbStore];
NSURL *walURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-wal"];
NSURL *shmURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-shm"];
[MagicalRecord cleanUp];
if([[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error1] && [[NSFileManager defaultManager] removeItemAtURL:walURL error:&error2] && [[NSFileManager defaultManager] removeItemAtURL:shmURL error:&error3]){
[self setupDB];
}
else{
NSLog(#"An error has occurred while deleting %#", dbStore);
NSLog(#"Error1 description: %#", error1.description);
NSLog(#"Error2 description: %#", error2.description);
NSLog(#"Error3 description: %#", error3.description);
}
}
Here's the Swift version:
func setupDB() {
MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed(self.dbStore())
}
func dbStore() -> String {
return "\(self.bundleID()).sqlite"
}
func bundleID() -> String {
return NSBundle.mainBundle().bundleIdentifier!
}
func cleanAndResetupDB() {
let dbStore = self.dbStore()
let url = NSPersistentStore.MR_urlForStoreName(dbStore)
let walURL = url.URLByDeletingPathExtension?.URLByAppendingPathExtension("sqlite-wal")
let shmURL = url.URLByDeletingPathExtension?.URLByAppendingPathExtension("sqlite-shm")
var removeError: NSError?
MagicalRecord.cleanUp()
//Swift 1
//let deleteSuccess = NSFileManager.defaultManager().removeItemAtURL(url, error: &removeError)
//Swift 2
let deleteSuccess: Bool
do {
try NSFileManager.defaultManager().removeItemAtURL(url)
try NSFileManager.defaultManager().removeItemAtURL(walURL!)
try NSFileManager.defaultManager().removeItemAtURL(shmURL!)
deleteSuccess = true
} catch let error as NSError {
removeError = error
deleteSuccess = false
}
if deleteSuccess {
self.setupDB()
} else {
println("An error has occured while deleting \(dbStore)")
println("Error description: \(removeError?.description)")
}
}
To expand on #yoninja 's answer, this will make reset CoreData stack explicitly, plus dealing with wal and shm files
- (void)setupDB
{
[MagicalRecord setDefaultModelNamed:#"Model.momd"];
[MagicalRecord setupCoreDataStack];
}
- (void)cleanAndResetupDB
{
[MagicalRecord cleanUp];
NSString *dbStore = [MagicalRecord defaultStoreName];
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:dbStore];
NSURL *walURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-wal"];
NSURL *shmURL = [[storeURL URLByDeletingPathExtension] URLByAppendingPathExtension:#"sqlite-shm"];
NSError *error = nil;
BOOL result = YES;
for (NSURL *url in #[storeURL, walURL, shmURL]) {
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
result = [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
}
}
if (result) {
[self setupDB];
} else {
NSLog(#"An error has occurred while deleting %# error %#", dbStore, error);
}
}
MagicalRecord does not provide this functionality for you. The cleanUp method is provided for you to reinitialize your CoreData stack in memory and cleaning up any contexts, queues and other related objects. However, it is not that difficult to do yourself given that MagicalRecord does provide a handy method to get the path for your library.
Check out the -[NSPersistentStore MR_urlForStoreName:] method. This will give you the file url for your store. You can then delete it with an NSFileManager instance. Be careful to do this before you set up the Core Data stack or you'll crash when you save because you'd have yanked out the store from under a properly initialized stack.
The following will completely delete the MagicalRecord CoreData sqlite files, as well as the -wal and -shm files. MagicalRecord puts them all in the Library folder; this will simply remove all files from the folder. This will not work if you have other data you need to persist in the Library folder, I did not:
- (void)resetCoreDataDB
{
[MagicalRecord cleanUp];
[self deleteFilesInLibrary];
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"YourDBName.sqlite"];
}
- (void)deleteFilesInLibraryDirectory
{
NSString* folderPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSError *error = nil;
for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:&error])
{
[[NSFileManager defaultManager] removeItemAtPath:[folderPath stringByAppendingPathComponent:file] error:&error];
if(error)
{
NSLog(#"Delete error: %#", error.description);
}
}
}
If you are using the iOS Simulator and deleted the database file, you may probably notice that the data is still there. However, if tested on an actual device (which should be), the file is deleted and the context is reset as should be.
[MagicalRecord cleanUp];
// delete database file
NSError *error;
NSURL *fileURL = [NSPersistentStore MR_urlForStoreName:#"db.sqlite"];
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
if(error) {
// Hanldle error
}
// reset setup.
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"db.sqlite"];
A bit rewritten answer of #yoninja for Swift 4:
private var dbStore : String? {
get {
if let bundleId = Bundle.main.bundleIdentifier {
return bundleId + ".sqlite"
}
return MagicalRecord.defaultStoreName()
}
}
func setupCoreDataStack() {
MagicalRecord.setupCoreDataStack(withAutoMigratingSqliteStoreNamed: self.dbStore)
}
func cleanUp() {
MagicalRecord.cleanUp()
var removeError: NSError?
let deleteSuccess: Bool
do {
guard let url = NSPersistentStore.mr_url(forStoreName: self.dbStore) else {
return
}
let walUrl = url.deletingPathExtension().appendingPathExtension("sqlite-wal")
let shmUrl = url.deletingPathExtension().appendingPathExtension("sqlite-shm")
try FileManager.default.removeItem(at: url)
try FileManager.default.removeItem(at: walUrl)
try FileManager.default.removeItem(at: shmUrl)
deleteSuccess = true
} catch let error as NSError {
removeError = error
deleteSuccess = false
}
if deleteSuccess {
self.setupCoreDataStack()
} else {
print("An error has occured while deleting \(self.dbStore)")
print("Error description: \(removeError.debugDescription)")
}
}

Resources