Insert a potentially null value into the sqlite database in iOS - ios

I have a class called Content, whose URL property is nullable (URL: String?).
I'd like to store this URL property in my sqlite database using FMDB, but Xcode complains I need to unwrap the optional with !
but the problem is when I do content.URL! it crashes because it's nil.
success = db.executeUpdate("INSERT INTO CONTENT(ID, Icon, Title, Description, URL, IsActive) VALUES(?,?,?,?,?,?,?,?,?)", withArgumentsInArray: [content.ID, content.icon, content.title, content.description, content.URL!, content.isActive])
How can I successfully insert URL both when it has and does not have a value?
Thanks!

One approach that I use for cases like this is to create a class extension.
For example:
class func databaseSafeObject(object: AnyObject?) -> AnyObject {
if let safeObject: AnyObject = object{
return safeObject;
}
return NSNull();
}
Then you can just use:
NSObject.databaseSafeObject(content.URL);
to get something that can be directly inserted in the db.

So this ended up working for me, although it seems kinda irking that this is how it has to be:
(content.URL == nil ? NSNull() : content.URL!)

There exists Swift wrappers for SQLite that may be a better fit that fmdb which can run in Swift but does not use Swift features such as optionals (that you miss here), type safety, and error handling. See for example my GRDB.swift http://github.com/groue/GRDB.swift which was heavily influenced by ccgus/fmdb.

The AnyObject type didn't work for me when working with variables of type Int and Double, so I created a similar function to handle optional Swift variables.
private func getOptionalOrNull(_ possibleValue:Any?)->Any {
if let theValue = possibleValue {
return theValue
} else {
return NSNull()
}
}

Related

Realmswift generic function call crashes when Result<Object> is returned

I have built a generic function to get any kind of data from realm, that seems to work just fine.
func getData<T: Object>(withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
I can't figure out though how to use this function to delete data.
My delete function is the following:
func removeData(withFilter: String) {
let dataToDelete: Results<Object>? = getData(withFilter: withFilter)
// *** The above line crashes the app ***
if let dataToDeleteUnwrapped = dataToDelete{
try? realm!.write {
realm!.delete(dataToDeleteUnwrapped)
}
}
}
This results to an error, attached bellow. Though Results<Object>? crashes the app, Results<MyCustomObject>? works fine, but then my remove data function is not generic anymore.
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'RealmSwiftObject' is not managed by the Realm. If using a custom `objectClasses` / `objectTypes` array in your configuration, add `RealmSwiftObject` to the list of `objectClasses` / `objectTypes`.'
I am sure there is a nice short way to solve this one, but I can't figgure it out, so any help is appreciated.
Results cannot hold instances of several classes, all of its elements must be from the same type, so Results<Object> is not a valid type for the collection.
Your current implementation of getData is wrong. You don't use the generic type T anywhere in your function. Generic types should be given to input arguments for generic functions. You need to call both getData and removeData on a specific Realm type inheriting from Object, since Result can only hold a single type and all your types have unique properties, so you can only use a predicate on a specific type.
This is the correct generic implementation of getData to query Realm for a specific class with the given predicate.
func getData<T:Object>(ofType: T.Type, withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
You call it like this (the following example was tested and is working with the playground in the examples project from the Realm website):
let people = getData(ofType: Person.self, withFilter: "cars.#count > 1 && spouse != nil")
people?.first?.name //prints Jennifer when called after adding the objects to realm, but before deleting them in the example project
Seeing how a generic function can be used on Realm with type constraints, you should be able to update your deletion function as well.
use this extension to convert results to real object
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for i in 0 ..< count {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
how to use in code
let orjObjs = realm.objects(UrObj.self).toArray(ofType: UrObj.self) as [UrObj]

Instatiate Realm Object from string in swift 3

I would like to know if it is possible to instantiate a realm object based on a string that is the class name of the realm object but without knowing what that string will be until it is provided.
For example:
for(_, object) in json["AllObjects"]{
let objectType = self.getRealmObjectBasedOnString(type: className, params: object.stringValue)
self.objectList.append(objectType)
}
Here I go through a json that I get and want to create a realm object from each json object in the array. The problem is that this method will be called several times and each time the only thing that will change is the className variable. So I would like to keep this logic in only one method instead of creating several methods with same logic or a huge and complicated if else that determines the realm object to be created.
Here is getRealmObjectBasedOnString
func getRealmObjectBasedOnString(type: String, params: String) -> Object{
switch type {
case "classA":
return ClassA(JSONString: params)!
case "classB":
return ClassB(JSONString: params)!
default:
return DefaultClass(JSONString: params)!
}
}
Can someone explain why this does not work and whether it is possible to accomplish what I want?
You can use NSClassFromString to get Realm object type from string, but keep in mind that Swift uses modules for nemespacing, so you'll need to add your app's module name before your class name.
guard let objectType = NSClassFromString("YourAppModuleName.\(json["className")") else {
// handle unexpected class here
}
let objectList = realm.objects(objectType)

Checking for null value (not nil or NSnull) in swift always return nil? [duplicate]

This question already has answers here:
How is optional binding used in swift?
(9 answers)
Closed 6 years ago.
I am working on a project which uses both swift an objective c. The team member before me have written this code in objective C ,which I am not familiar with. There is problem that most of the part involving storing and retrieving value from Sqlite is in obj C. This has been done in a common class to avoid Code redemption. However if i use swift to retrieve value through that obj C file a problem occur. If there is no value in that specified row it return "null".
Update: Checked for optional binding as said by Antony Raphel
Even if i check for nil directly before converting to 'as? String' the same error persist. I came to know that there is no equivalent of "null" in swift. Is there any hack to the value is empty (null) in swift?
Just replace your
var prevNotifCount = self.cmmn.getPreviousNotificationCount() as? String
and use
guard let prevNotifCount = self.cmmn.getPreviousNotificationCount() else{
print("No previous notification value")
return
}
no need to check for nil, if it will fail , else block will be executed
if let prevNotifCount = self.cmmn.getPreviousNotificationCount() as? String
{
self.cmmn.saveInDatabase("19", phoneNumber: "0", otp: "0")
print(self.cmmn.getPreviousNotificationCount())
}
else
{
print("No previous notification value")
}
This is standard Swift approach called optional binding. You safely unwrap an optional and if it is not nil assign it to a local variable
Try by adding if let to check nil condition like this:-
if let NotifCount = self.cmmn,getPreviousNotificationCount() as? String
{
prevNotifCount = NotifCount
}
Please try this, Hope it helps!
Use if let statement.
if let preNotifCount = self.cmmn.getPreviousNotofication {
//business logic
}
Now business logic would only be executed if preNotifCount is not nil.

Create PDF in Swift

I am following Apple's Docs to create a PDF file using Xcode6-Beta6 in Swift
var currentText:CFAttributedStringRef = CFAttributedStringCreate(nil, textView.text as NSString, nil)
if (currentText) { // <-- This is the line XCode is not happy
// More code here
}
Compiler throws Type 'CFAttributedStringRef' does not conform to protocol 'BooleanType' error
If I use if(currentText != nil) I get 'CFAttributedStringRef' is not convertible to 'UInt8'
From Apple's Docs for CFAttributedStringCreate
Return Value
An attributed string that contains the characters from str and the attributes specified by attributes. The result is NULL if there was a problem in creating the attributed string. Ownership follows the Create Rule.
Any idea how to resolve this? Thanks!
First you have to give it an explicit optional type (using the ?):
var currentText: CFAttributedStringRef? = ...
Then you can compare it to nil:
if currentText != nil {
// good to go
}
Your code compiles at the moment, because Apple hasn't yet "swiftified" CoreFoundation to return properly annotated types.
Be prepared that in the final release your code will not even compile, forcing you to use the optional type.

NSUserDefaults - How to tell if a key exists

I'm working on a small iPhone app, and I am using NSUserDefaults as my data persistence. It only has to keep track of a few things, such as some names and some numbers so I figure I might as well keep it simple.
I found this page for some reference, but I don't think it can answer my question. Basically, I want to be able to check if a value (or a key) already exists in the NSUserDefaults and then do something accordingly.
Some examples: The app starts up, if this is the first time it starts up it outputs an alert saying welcome. To tell if this is first time it has opened it reads the UserDefaults and checks.
Example 2: It says, "Hello [Name]", where Name is something you have entered. If you have opened the app and there is no name, it should say "Hello World." I need to check if you have entered a name already and act accordingly. The name would be stored in NSUserDefaults.
Some help here? I'd really appreciate it!
objectForKey: will return nil if it doesn't exist.
As mentioned above it wont work for primitive types where 0/NO could be a valid value. I am using this code.
NSUserDefaults *defaults= [NSUserDefaults standardUserDefaults];
if([[[defaults dictionaryRepresentation] allKeys] containsObject:#"mykey"]){
NSLog(#"mykey found");
}
The objectForKey: method will return nil if the value does not exist. Here's a simple IF / THEN test that will tell you if the value is nil:
if([[NSUserDefaults standardUserDefaults] objectForKey:#"YOUR_KEY"] != nil) {
...
}
Swift 3 / 4:
Here is a simple extension for Int/Double/Float/Bool key-value types that mimic the Optional-return behavior of the other types accessed through UserDefaults.
(Edit Aug 30 2018: Updated with more efficient syntax from Leo's suggestion.)
extension UserDefaults {
/// Convenience method to wrap the built-in .integer(forKey:) method in an optional returning nil if the key doesn't exist.
func integerOptional(forKey: String) -> Int? {
return self.object(forKey: forKey) as? Int
}
/// Convenience method to wrap the built-in .double(forKey:) method in an optional returning nil if the key doesn't exist.
func doubleOptional(forKey: String) -> Double? {
return self.object(forKey: forKey) as? Double
}
/// Convenience method to wrap the built-in .float(forKey:) method in an optional returning nil if the key doesn't exist.
func floatOptional(forKey: String) -> Float? {
return self.object(forKey: forKey) as? Float
}
/// Convenience method to wrap the built-in .bool(forKey:) method in an optional returning nil if the key doesn't exist.
func boolOptional(forKey: String) -> Bool? {
return self.object(forKey: forKey) as? Bool
}
}
They are now more consistent alongside the other built-in get methods (string, data, etc.). Just use the get methods in place of the old ones.
let AppDefaults = UserDefaults.standard
// assuming the key "Test" does not exist...
// old:
print(AppDefaults.integer(forKey: "Test")) // == 0
// new:
print(AppDefaults.integerOptional(forKey: "Test")) // == nil
Extend UserDefaults once to don't copy-paste this solution:
extension UserDefaults {
func hasValue(forKey key: String) -> Bool {
return nil != object(forKey: key)
}
}
// Example
UserDefaults.standard.hasValue(forKey: "username")
"objectForKey will return nil if it doesn't exist." It will also return nil if it does exist and it is either an integer or a boolean with a value of zero (i.e. FALSE or NO for the boolean).
I've tested this in the simulator for both 5.1 and 6.1. This means that you cannot really test for either integers or booleans having been set by asking for "the object". You can get away with this for integers if you don't mind treating "not set" as if it were "set to zero".
The people who already tested this appear to have been fooled by the false negative aspect, i.e. testing this by seeing if objectForKey returns nil when you know the key hasn't been set but failing to notice that it also returns nil if the key has been set but has been set to NO.
For my own problem, that sent me here, I just ended up changing the semantics of my boolean so that my desired default was in congruence with the value being set to NO. If that's not an option, you'll need to store as something other than a boolean and make sure that you can tell the difference between YES, NO, and "not set."
I just went through this, and all of your answers helped me toward a good solution, for me. I resisted going the route suggested by, just because I found it hard to read and comprehend.
Here's what I did. I had a BOOL being carried around in a variable called "_talkative".
When I set my default (NSUserDefaults) object, I set it as an object, as I could then test to see if it was nil:
//converting BOOL to an object so we can check on nil
[defaults setObject:#(_talkative) forKey:#"talkative"];
Then when I went to see if it existed, I used:
if ([defaults objectForKey:#"talkative"]!=nil )
{
Then I used the object as a BOOL:
if ([defaults boolForKey:#"talkative"]) {
...
This seems to work in my case. It just made more visual sense to me.
Try this little crumpet:
-(void)saveUserSettings{
NSNumber* value;
value = [NSNumber numberWithFloat:self.sensativity];
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"sensativity"];
}
-(void)loadUserSettings{
NSNumber* value;
value = [[NSUserDefaults standardUserDefaults] objectForKey:#"sensativity"];
if(value == nil){
self.sensativity = 4.0;
}else{
self.sensativity = [value floatValue];
}
}
Treat everything as an object. Seems to work for me.
Swift version to get Bool?
NSUserDefaults.standardUserDefaults().objectForKey(DefaultsIsGiver) as? Bool
In Swift3, I have used in this way
var hasAddedGeofencesAtleastOnce: Bool {
get {
return UserDefaults.standard.object(forKey: "hasAddedGeofencesAtleastOnce") != nil
}
}
The answer is great if you are to use that multiple times.
I hope it helps :)
Swift 3.0
if NSUserDefaults.standardUserDefaults().dictionaryRepresentation().contains({ $0.0 == "Your_Comparison_Key" }){
result = NSUserDefaults.standardUserDefaults().objectForKey(self.ticketDetail.ticket_id) as! String
}

Resources