I have this line of code that successfully runs on first got:
notificationCenter.postNotificationName(NotificationContactCreated, object: nil)
But later on when this same line in same function is called I get the fatal error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Yet if I set through the code both notificationCenter and NotificationContactCreated are both set.
I'm going round in circles and cannot find the cause of this...
Any help ...please???
Thanks
here is the notification center:
//
// notification center to be used across phone app
//
lazy var notificationCenter: NSNotificationCenter = {
return NSNotificationCenter.defaultCenter()
}()
here is the func where it gets called:
func updateAccount(firstName: String, lastName: String, emailAddress: String, companyName: String, isSelf: Bool) -> Void {
// we are going to update the Contacts entity
let fetchRequest = NSFetchRequest(entityName: entityNameContacts)
// filter on first name + last name + email address
fetchRequest.predicate = NSPredicate(format: "firstName == %# && lastName == %# && emailAddress == %#", firstName, lastName, emailAddress)
do {
// try to get an array of accounts based on filter, should only be one returned
let contactsArray = try context.executeFetchRequest(fetchRequest) as? [Contacts]
// if there is a record we just want to update it
if contactsArray?.count > 0 {
// grab the reading object
let contact = contactsArray![0]
// and update its data
contact.firstName = firstName
contact.lastName = lastName
contact.emailAddress = emailAddress
contact.companyName = companyName
}
else {
// otherwise we need to insert a new record
let newContact: Contacts = NSEntityDescription.insertNewObjectForEntityForName(entityNameContacts, inManagedObjectContext: context) as! Contacts
// and populate with new data
newContact.firstName = firstName
newContact.lastName = lastName
newContact.emailAddress = emailAddress
newContact.companyName = companyName
newContact.isSelf = isSelf
}
// write amends
saveContext()
self.notificationCenter.postNotificationName(NotificationContactCreated, object: nil)
} catch {
print("func updateAccount(firstName: String, lastName: String, emailAddress: String, companyName: String, isSelf: Bool) -> Void !!!ERROR")
}
Related
I've read up and down regarding this, and understand the basics here - I just can't understand why I get this error. I use the second init to instantiate a customer from Firebase, but even if I comment out everything inside it, I still get the error
Variable 'self.customerType' used before being initialized at the declaration of init?
class Customer {
enum customerTypes {
case consumer
case business
}
// MARK - properties
var id: String?
let customerType: customerTypes
var name1: String
var name2: String?
var address: Address?
var phone: String?
var email: String?
struct Address {
var street: String
var postalCode: String
var city: String
var country: String = "Norway"
}
init(type: customerTypes, name1: String, name2: String?, phone: String, email: String, address: Address? ) {
self.customerType = type
self.name1 = name1
self.name2 = name2
self.phone = phone
self.email = email
self.address = address
}
init?(data: [String: Any]) {
guard let type = data["customerType"] as? String else { return }
guard let name1 = data["name1"] as? String else { return }
self.customerType = type == "Consumer" ? .consumer : .business
self.name1 = name1
// if let name2 = data["name2"] as? String { self.name2 = name2 }
// if let phone = data["phone"] as? String { self.phone = phone }
// if let email = data["email"] as? String{ self.email = email }
// if let address = data["address"] as? [String: Any] {
// let street = address["street"] as? String
// let postCode = address["postCode"] as? String
// let city = address["city"] as? String
// if street != nil && postCode != nil && city != nil {
// self.address = Address(street: street!, postalCode: postCode!, city: city!)
// }
// }
}
What simple issue am I overlooking here?
You declare an initializer which promises to either return an initialized Customer or no Customer (because it is fallible). You alo declare let customerType: customerTypes as one of the properties of the class.
That means that if you successfully return from the initializer (that means, not returning nil), this property has to be initialized to some value.
The error is not very helpful in the location of the error, as the error is actually on the line below. By simply putting return in your guard, you are saying that your object is successfully initialized, which it is not, as you have not yet set customerType to a value.
So if you put a return nil in your guard clause, you will say that your initialization failed, and then you do not need to put a value in customerType.
The properties that don't have an initial value needs to set inside an init. You can fix the issue by either setting them as Optional or by setting a default value:
init?(data: [String: Any]) {
customerType = .consumer
name1 = ""
}
or:
var customerType: customerTypes?
var name1: String?
Note: By setting the properties Optional the compiler assumes that the initial value is nil.
I'm searching for a way to get added/deleted/modified contacts to send them to the server ..
I used Realm since it's faster than coreData to save the contacts
and on each refresh or when user re-enter the app .. I'm comparing the Realm Database (backup) with the sim contacts to detect if there is a change (insertion - modification - deletion)..
the code is working fine
but it isn't fast enough ..
I tried using the ABAddressBookRegisterExternalChangeCallback in an objective C file but it wasn't handy since it's being called only when a user is changed while the app is in background and it doesn't give me anything useful ...
also I tried CnContactStoredidchange notification but it's useless .
here's the databaseRealmModel im using the fullname+phonenumber as a primaryKey to quickly fetch it from the database
#objcMembers class RealmModel:Object
{
dynamic var fullName: String = ""
dynamic var phoneNumber: String = ""
dynamic var firstName: String = ""
dynamic var lastName: String = ""
dynamic var middleName: String = ""
dynamic var identifier: String = ""
dynamic var primaryKey :String = ""
#objc dynamic var contactImage: Data? = nil
convenience init(fullName: String, phoneNumber: String,firstName:String,lastName:String,middleName:String,contactImage:Data?,identifier:String)
{
self.init()
self.fullName = fullName
self.phoneNumber = phoneNumber
self.firstName = firstName
self.lastName = lastName
self.middleName = middleName
self.contactImage = contactImage
self.identifier = identifier
self.primaryKey = identifier + phoneNumber
}
override class func primaryKey() -> String? {
return "primaryKey"
}
}
2) here's where i'm calling the method
DispatchQueue.global(qos: .userInteractive).async
{
let start = CFAbsoluteTimeGetCurrent()
Utilities.getCotacts { (added,deleted,modified) in
let diff = CFAbsoluteTimeGetCurrent() - start
print("Realm Took \(diff) seconds")
print( "Realm Added Contacts" + String(added.count))
print("Realm deleted Contacts" + String(deleted.count))
print("Realm modified Contacts" + String(modified.count))
}
3) here's the implementation of the function
class func requestForAccess(completionHandler: #escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied, .notDetermined:
self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
if authorizationStatus == CNAuthorizationStatus.denied {
completionHandler(false)
}
}
})
default:
completionHandler(false)
}
}
class func getCotacts(completionHandler: #escaping (_ addedcontacts: ([RealmModel]),_ deletedContacts: ([RealmModel]),_ modifiedContacts: [RealmModel]) -> Void)
{
var addedContacts = [RealmModel]()
var deletedContacts = [RealmModel]()
var modifiedContacts = [RealmModel]()
self.requestForAccess { (approved) in
if approved == true
{ //getting All contacts in database
let realm = try! Realm()
let Arr = realm.objects(RealmModel.self)
var initialArr = Array.init(Arr)
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactImageDataKey,
CNContactPhoneNumbersKey,CNContactImageDataAvailableKey] as! [CNKeyDescriptor]
let contactFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
do {
try! realm.write {
try self.contactStore.enumerateContacts(with: contactFetchRequest) {
contact, stop in
//enumerating through contact list
for number in contact.phoneNumbers
{
let firstName = contact.givenName
let lastName = contact.familyName
let middleName = contact.middleName
let fullName = String(format: "%#%#%#%#%#", arguments: [Utilities.isStringNull(string: contact.givenName) ? "" : contact.givenName, Utilities.isStringNull(string: contact.middleName) ? "" : " ", Utilities.isStringNull(string: contact.middleName) ? "" : contact.middleName, Utilities.isStringNull(string: contact.familyName) ? "" : " ", Utilities.isStringNull(string: contact.familyName) ? "" : contact.familyName])
let phoneNumber = number.value.value(forKey: "digits") as? String
var contactsModel = RealmModel(fullName: fullName, phoneNumber: phoneNumber!, firstName: firstName, lastName: lastName, middleName: middleName,contactImage:contact.imageData,identifier:contact.identifier)
let databaseContact = realm.object(ofType: RealmModel.self, forPrimaryKey:contactsModel.primaryKey)
// contact doesnt exist in database so its a new contact
if(databaseContact == nil) { addedContacts.append(contactsModel)
realm.add(contactsModel,update: false)
//it's new contact no need to set update true
}
else
{ // User exists in Database
if(databaseContact?.fullName != contactsModel.fullName)
{
// if full name has been changed
let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey })
if indexesOfModifiedNumber.count > 0
{
modifiedContacts.append(initialArr[indexesOfModifiedNumber.first!])
initialArr.remove(at:indexesOfModifiedNumber.first!)
databaseContact?.fullName = contactsModel.fullName
}
}
else
{ // No Change and contact is found
let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey })
if (indexesOfModifiedNumber.count > 0)
{
initialArr.remove(at: indexesOfModifiedNumber.first!)
}
}
}
}
}
if initialArr.count > 0
{
// deleted Contacts
deletedContacts = initialArr
for element in deletedContacts
{
realm.delete(element)
}
}
completionHandler(addedContacts,deletedContacts,modifiedContacts)
}
}
}
else
{
print("access not approved")
}
}
}
since Realm is quicker at insertion and fetching
I didn't find that big difference between it and coreData
I'm testing them with a phone with 6000 contacts ..
Core data needs 90 seconds to finish while realm needs 70 seconds
I am attempting to simply read into the database that is structured as stated below. I am attempting to read the user's "userType" and use it in the following if statements below. Any help is appreciated!
Swift Code:
// Create firebase reference and link to database
var dataRef : DatabaseReference?
dataRef = Database.database().reference()
let userID = Auth.auth().currentUser!.uid // Get the User's ID
// Gather user's type (Customer or Company)
/*Use this space to gather the user's type into some variable named currUserType*/
if (currUserType == "Customer"){
self.performSegue(withIdentifier: "LoginToCustomer", sender: self)
print("User: " + userID + " has been signed in!")
}
else if (currUserType == "Company"){
self.performSegue(withIdentifier: "LoginToHost", sender: self)
}
else{
self.showMessage(alertTitle: "Error",
alertMessage: "Please report the following error with a description of what lead to to the error.",
actionTitle: "Dismiss")
}
Database Structure:
"Users" : {
"ZFH0lFe1fIb5bwSO2Q95ektD33L2" : {
"email" : "cust#test.com",
"userType" : "Customer"
}
First take the ref like i have took below:
let dbRef = Database.database().reference().child("Users")
Then create model like i have created below:
class Users {
var email: String?
var userType: String?
init(email: String, userType: String) {
self.email = email
self.userType = userType
}
}
Then create completion Handler like i have created below:
func getUsersData(handler: #escaping (_ usersArray: [Users]) -> ()) {
var usersArray = [Users]()
dbRef.observe(.value) { (datasnapshot) in
guard let usersnapshot = datasnapshot.children.allObjects as? [DataSnapshot] else { return }
for user in usersnapshot {
let email = user.childSnapshot(forPath: "email").value as! String
let userType = user.childSnapshot(forPath: "userType").value as! String
let userObj = Users(email: email, userType: userType)
usersArray.append(userObj)
}
handler(usersArray)
}
}
simply call this function which returns the whole array of users.
Refrence https://firebase.google.com/docs/database/ios/read-and-write#reading_and_writing_data
I am working on a Firebase Swift project using CocoaPods.
Every time after I log in the main ViewController, automatically I get EXC_BREAKPOINT error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Here are some of my code lines where I got errors:
All codes from Joke.swift:
import Foundation
import Firebase
class Joke {
private var _jokeRef: Firebase!
private var _jokeKey: String!
private var _jokeText: String!
private var _jokeVotes: Int!
private var _username: String!
var jokeKey: String {
return _jokeKey
}
var jokeText: String {
return _jokeText
}
var jokeVotes: Int {
return _jokeVotes //1
}
var username: String {
return _username
}
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
self._jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
self._jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
self._jokeText = joke
}
if let user = dictionary["author"] as? String {
self._username = user
} else {
self._username = ""
}
// The above properties are assigned to their key.
self._jokeRef = DataService.dataService.JOKE_REF.childByAppendingPath(self._jokeKey)
}
// Add or Subtract a Vote from the Joke.
func addSubtractVote(addVote: Bool) {
if addVote {
_jokeVotes = _jokeVotes + 1
} else {
_jokeVotes = _jokeVotes - 1
}
// Save the new vote total.
_jokeRef.childByAppendingPath("votes").setValue(_jokeVotes)
}
}
In JokeCellTableViewCell.swift:
var joke: Joke!
...............
func configureCell(joke: Joke) {
self.joke = joke
// Set the labels and textView.
self.jokeText.text = joke.jokeText
self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)" // 2
self.usernameLabel.text = joke.username
// Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean.
.........
}
And in the main ViewController, JokesFeedTableViewController.swift:
var jokes = [Joke]()
....................
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let joke = jokes[indexPath.row]
// We are using a custom cell.
if let cell = tableView.dequeueReusableCellWithIdentifier("JokeCellTableViewCell") as? JokeCellTableViewCell {
// Send the single joke to configureCell() in JokeCellTableViewCell.
cell.configureCell(joke) // 3
return cell
} else {
return JokeCellTableViewCell()
}
...........
// 1, // 2, // 3 are code lines where errors appear.
I hope you could help me to fix this!
Thank you in advance!
Your problem is that you have not clearly defined the expectations of the Joke class.
Your initializer suggests that the properties on Joke should be optional, however, you are using them as though they are not. You must decide on which way you want to take it.
If the properties can be optional, I would suggest something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
private(set) var jokeText: String?
private(set) var jokeVotes: Int?
let username: String
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
jokeText = joke
}
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}
However, if the properties should never be nil, you need something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
let jokeText: String
let jokeVotes: Int?
let username: String
// Initialize the new Joke
init?(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
guard let votes = dictionary["votes"] as? Int,
joke = dictionary["jokeText"] as? String else {
return nil
}
jokeText = joke
jokeVotes = votes
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}
I'm building a dummy iOS project in order to understand how to implement validation in Core Data with Swift. The Core Data model of the project has one entity called Person that contains two attributes: firstName and lastName. The project is based on Swift but, in order to start it, I'm using Objective-C to define the NSManagedObject subclass:
Person.h
#interface Person : NSManagedObject
#property (nonatomic, retain) NSString *firstName;
#property (nonatomic, retain) NSString *lastName;
#end
Person.m
#implementation Person
#dynamic firstName;
#dynamic lastName;
-(BOOL)validateFirstName:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil || [*ioValue isEqualToString: #""]) {
if (outError != NULL) {
NSString *errorStr = NSLocalizedStringFromTable(#"First name can't be empty", #"Person", #"validation: first name error");
NSDictionary *userInfoDict = #{ NSLocalizedDescriptionKey : errorStr };
NSError *error = [[NSError alloc] initWithDomain:#"Domain" code: 101 userInfo: userInfoDict];
*outError = error;
}
return NO;
}
return YES;
}
#end
Person-Bridging-Header.h
#import "Person.h"
In the Core Data Model Editor, I've set the entity class inside the Data Model Inspector as indicated:
class: Person
The first time I launch the project, I create an instance of Person in the AppDelegate application:didFinishLaunchingWithOptions: method with the following code:
if !NSUserDefaults.standardUserDefaults().boolForKey("isNotInitialLoad") {
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: managedObjectContext!) as Person
person.firstName = "John"
person.lastName = "Doe"
var error: NSError?
if !managedObjectContext!.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isNotInitialLoad")
NSUserDefaults.standardUserDefaults().synchronize()
}
The project has one UIViewController with the following code:
class ViewController: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var person: Person!
override func viewDidLoad() {
super.viewDidLoad()
//Fetch the Person object
var error: NSError?
let fetchRequest = NSFetchRequest(entityName: "Person")
let array = managedObjectContext.executeFetchRequest(fetchRequest, error:&error)
if array == nil {
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
person = array![0] as Person
}
#IBAction func changeFirstName(sender: AnyObject) {
//Generate a random firstName
let array = ["John", "Jimmy", "James", "Johnny", ""]
person.firstName = array[Int(arc4random_uniform(UInt32(5)))]
var error: NSError?
if !managedObjectContext.save(&error) {
println("Unresolved error \(error), \(error!.userInfo)")
return
}
//If success, display the new person's name
println("\(person.firstName)" + " " + "\(person.lastName)")
}
}
changeFirstName: is linked to a UIButton. Therefore, whenever I click on this button, a new String is randomly generated and assigned to person.firstName. If this new String is empty, validateFirstName:error: generates a NSError and the save operation fails.
This works great but, in order to have a pure Swift project, I've decided to delete Person.h, Person.m and Person-Bridging-Header.h and to replace them with a single Swift file:
class Person: NSManagedObject {
#NSManaged var firstName: String
#NSManaged var lastName: String
func validateFirstName(ioValue: AnyObject, error: NSErrorPointer) -> Bool {
if ioValue as? String == "" {
if error != nil {
let myBundle = NSBundle(forClass: self.dynamicType)
let errorString = myBundle.localizedStringForKey("First name can't be empty", value: "validation: first name error", table: "Person")
let userInfo = NSMutableDictionary()
userInfo[NSLocalizedFailureReasonErrorKey] = errorString
userInfo[NSValidationObjectErrorKey] = self
var validationError = NSError(domain: "Domain", code: NSManagedObjectValidationError, userInfo: userInfo)
error.memory = validationError
}
return false
}
return true
}
}
In the Core Data Model Editor, I've also changed the entity class inside the Data Model Inspector as indicated:
class: Person.Person //<Project name>.Person
The problem now is that the project crashes whenever I call changeFirstName:. The weirdest thing is that if I put a breakpoint inside validateFirstName:, I can see that this method is never called.
What am I doing wrong?
I am a little bit guessing here, but the (id *)ioValue parameter is mapped to Swift as
ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>
therefore the Swift variant should probably look like
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool {
if let firstName = ioValue.memory as? String {
if firstName == "" {
// firstName is empty string
// ...
}
} else {
// firstName is nil (or not a String)
// ...
}
return true
}
Update for Swift 2:
func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
guard let firstName = ioValue.memory as? String where firstName != "" else {
// firstName is nil, empty, or not a String
let errorString = "First name can't be empty"
let userDict = [ NSLocalizedDescriptionKey: errorString ]
throw NSError(domain: "domain", code: NSManagedObjectValidationError, userInfo: userDict)
}
// firstName is a non-empty string
}
As #SantaClaus correctly noticed, the validation function must now
throw an error if the validation fails.
Apple's Core Data Programming Guide is now updated for Swift 3. Here's the example code from the Managing Object Life Cycle > Object Validation page (memory has been renamed to pointee):
func validateAge(value: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
if value == nil {
return
}
let valueNumber = value!.pointee as! NSNumber
if valueNumber.floatValue > 0.0 {
return
}
let errorStr = NSLocalizedString("Age must be greater than zero", tableName: "Employee", comment: "validation: zero age error")
let userInfoDict = [NSLocalizedDescriptionKey: errorStr]
let error = NSError(domain: "EMPLOYEE_ERROR_DOMAIN", code: 1123, userInfo: userInfoDict)
throw error
}
EDIT: The example is not quite right. To get it to work, I've changed AutoreleasingUnsafeMutablePointer<AnyObject?> to an unwrapped optional and value?.pointee to value.pointee.