I recently upgraded my iOS Project to Swift 3 and iOS 10. Since then I'm running into a weird problem with Realm.
Here is what I try to do: I have a set of Positions, which I want to update with Server Data. So for every existing Position, I want to update it if there exists a newer version. If there is a Server Position, which doesn't exist locally, then I add it.
Here is the Code for that:
let newPositions = serializePositions(jsonResponse)
for newPosition in newPositions {
if let existingPositon = uiRealm.object(ofType: Position.self, forPrimaryKey: newPosition.id as String) {
if (progress.learningVersion < serverLearningVersion) {
try! uiRealm.write {
existingPositon.rank = newPosition.rank
existingPositon.starred = newPosition.starred
}
}
} else {
try! uiRealm.write {
progress.positions.append(newPosition)
}
}
}
If I run this, the something weird happens:
For the firstItem in the loop (the first Position) it works correctly.
But then for the following Positions I get nil for existing Positions, even if it exists.
The Primary Key in the Position Model is a String Field and I use MongoDB Object Ids from the Server.
This is how the Positions are serialized from JSON:
func serializePositions(_ json: JSON) -> List<Position> {
let positions = List<Position>()
let serverPositions = json["positions"].arrayValue
for serverPosition in serverPosition {
let position = Position()
position.id = listItem["id"].stringValue
position.starred = listItem["starred"].boolValue
position.rank = listItem["version"].intValue
positions.append(position)
}
return positions
}
I'm pretty new to Realm and iOS and I hope, that I just make a stupid little mistake here. Thanks in advance for every idea..
Cheers,
Raffi
I discovered the issue. I simply closed a for loop at the wrong position. As the issue was not related to Realm or JSON in the end I don't post the changes. Really it dosnt't help nobody :)
Related
I have seen many SO question curious about this case but still I am posting this as many of developers out there may also want to know this another reason is that no solution is working for me .
I have used following code but it only works when My app is in background. but I am not notified when my app is killed and meanwhile user has updated the info of any contact. So in this case I am not sure how to do it.
What I am doing: here is a code snippet what I am trying to do
From iOS 9 you can register your class to observe CNContactStoreDidChangeNotification
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(addressBookDidChange),
name: NSNotification.Name.CNContactStoreDidChange,
object: nil)
And then:
#objc func addressBookDidChange(notification: NSNotification){
//Handle event here...
}
I found this solution over here:
Whats Happening: Through this way I am able to get my app notified once the user has updated his contact while app is in background.
What I want: I just want to know that if the user has updated any contact even though my app was killed then How to get my app notified with updated contacts?
Please let me know if you have solution of this issue in advance.
UPDATE: I have seen Whatsapp doing this. Is there anyone who can tell me how Whatsapp is doing this?
To check if a contact has changed you can use a custom hash function because the native one only checks for the identifier:
extension CNContact {
var customHash : Int {
var hasher = Hasher()
hasher.combine(identifier)
hasher.combine(contactType)
hasher.combine(namePrefix)
hasher.combine(givenName)
hasher.combine(middleName)
hasher.combine(familyName)
hasher.combine(previousFamilyName)
hasher.combine(nameSuffix)
hasher.combine(nickname)
hasher.combine(organizationName)
hasher.combine(departmentName)
hasher.combine(jobTitle)
hasher.combine(phoneticGivenName)
hasher.combine(phoneticMiddleName)
hasher.combine(phoneticFamilyName)
if #available(iOS 10.0, *) {
hasher.combine(phoneticOrganizationName)
}
hasher.combine(note)
hasher.combine(imageData)
hasher.combine(thumbnailImageData)
if #available(iOS 9.0, *) {
hasher.combine(imageDataAvailable)
}
hasher.combine(phoneNumbers)
hasher.combine(emailAddresses)
hasher.combine(postalAddresses)
hasher.combine(urlAddresses)
hasher.combine(contactRelations)
hasher.combine(socialProfiles)
hasher.combine(instantMessageAddresses)
hasher.combine(birthday)
hasher.combine(nonGregorianBirthday)
hasher.combine(dates)
return hasher.finalize()
}
}
(You can remove fields you don't care)
Then you have to keep a dictionary inside your app to store the hash values of all the contacts, to build it just do:
let hashedContacts = [String:Int]()
for contact in allContacts {
hashedContacts[contact.identifier] = contact.customHash
}
You have to store it on the file system.
Whenever a contact is updated, you update it:
hashedContacts[updatedContact.identifier] = updatedContact.customHash
Then at every launch, you load the saved dictionary, and you check for differences:
for contact in allContacts {
if contact.customHash != savedHashedValues[contact.identifier] {
// This contact has changed since last launch
...
}
}
And voilà!
EDIT:
How to save the hash map on disk...
var hashedContacts = ...
guard let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts")
else { return }
try? (hashedContacts as NSDictionary).write(to: name)
How to load the hash map from disk...
guard
let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts"),
let loadedContacts = (try? NSDictionary(contentsOf: name, error: ())) as? [String:Int]
else { return }
// Do whatever you want with loaded contacts...
Whenever you open your app you need to get all the contacts from the contact list and can compare to previous one which is saved inside of your app. After that you can push your contact list to server.
What you can do is send an update notification to your application on launch screen. This might have an illusion to your user that you have done the changes while in background.
I'm working with the Photos framework, specifically I'd like to keep track of the current camera roll status, thus updating it every time assets are added, deleted or modified (mainly when a picture is edited by the user - e.g a filter is added, image is cropped).
My first implementation would look something like the following:
private var lastAssetFetchResult : PHFetchResult<PHAsset>?
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let fetchResult = lastAssetFetchResult,
let details = changeInstance.changeDetails(for: fetchResult) else {return}
let modified = details.changedObjects
let removed = details.removedObjects
let added = details.insertedObjects
// update fetch result
lastAssetFetchResult = details.fetchResultAfterChanges
// do stuff with modified, removed, added
}
However, I soon found out that details.changedObjects would not contain only the assets that have been modified by the user, so I moved to the following implementation:
let modified = modifiedAssets(changeInstance: changeInstance)
with:
func modifiedAssets(changeInstance: PHChange) -> [PHAsset] {
var modified : [PHAsset] = []
lastAssetFetchResult?.enumerateObjects({ (obj, _, _) in
if let detail = changeInstance.changeDetails(for: obj) {
if detail.assetContentChanged {
if let updatedObj = detail.objectAfterChanges {
modified.append(updatedObj)
}
}
}
})
return modified
}
So, relying on the PHObjectChangeDetails.assetContentChanged
property, which, as documentation states indicates whether the asset’s photo or video content has changed.
This brought the results closer to the ones I was expecting, but still, I'm not entirely understanding its behavior.
On some devices (e.g. iPad Mini 3) I get the expected result (assetContentChanged = true) in all the cases that I tested, whereas on others (e.g. iPhone 6s Plus, iPhone 7) it's hardly ever matching my expectation (assetContentChanged is false even for assets that I cropped or added filters to).
All the devices share the latest iOS 11.2 version.
Am I getting anything wrong?
Do you think I could achieve my goal some other way?
Thank you in advance.
I am building an app that incorporates Firebase and need to be able to be able to add/and or set data at the top of the my database. Currently whenever I add data it places it randomly under the parent node. I've seen some Objective C answers to this but they haven't really made to much sense. Sorry for the lack of code, but I don't know where exactly where to start on this issue. Any help would be great!
When you say it is getting placed 'randomly' under the parent, I think this is the childByAutoId() method getting called. While the name of the nodes appears randomly (e.g. -K6tdghsbci7g8), it will actually ensure that data is added under a given parent node in order. This is very useful for adding new data to lists. For example:
let pointlessData:String = "some data"
ref.childByAutoId().setValue(pointlessData)
// creates a new ordered child node under the `ref` with "some data" as the value
There isn't really a way to order the nodes back to front in this fashion, although you could order negatively by timestamp by adding a negative timestamp to each node you add then order your results using ref.queryOrderedByChild("negativeTimestamp") such that the values you get are the newest first.
let path = FirebaseBaseUrl + "dialogs/" + self.receiverId + "/" + self.senderId
let chilRef = FIRDatabase.database().referenceFromURL(path)
let refChild = chilRef.childByAutoId()
let dic = NSMutableDictionary()
dic .setValue(text, forKey: "text")
dic .setValue(FIRServerValue.timestamp(), forKey: "timestamp")
dic .setValue(true, forKey: "your")
refChild.updateChildValues(dic as [NSObject : AnyObject]) { (error, ref) in
if(error != nil){
print("Error",error)
}else{
print("\n\n\n\n\nAdded successfully...")
}
}
I have the following Core Data model:
And I'm trying to update the many-to-many relationship between Speaker and TalkSlot from a JSON I receive from a REST API call.
I have tried dozens of ways, replacing my many-to-many by 2 one-to-many's, removing from one side or the other, but one way or the other I keep getting EXC_BAD_ACCESS or SIGABRT and I just don't understand the proper way to do it. Here is the last thing I tried:
for speaker in NSArray(array: slot!.speakers!.allObjects){
if let speaker = speaker as? Speaker {
speaker.mutableSetValueForKey("talks").removeObject(slot!)
}
}
slot!.mutableSetValueForKey("speakers").removeAllObjects()
if let speakersArray = talkSlotDict["speakers"] as? NSArray {
for speakerDict in speakersArray {
if let speakerDict = speakerDict as? NSDictionary {
if let linkDict = speakerDict["link"] as? NSDictionary {
if let href = linkDict["href"] as? String {
if let url = NSURL(string: href) {
if let uuid = url.lastPathComponent {
if let speaker = self.getSpeakerWithUuid(uuid) {
speaker.mutableSetValueForKey("talks").addObject(slot!)
slot!.mutableSetValueForKey("speakers").addObject(speaker)
}
}
}
}
}
}
}
}
If it helps, the API I'm using is documented here as I'm trying to cache the schedule of a conference into Core Data in an Apple Watch extension. Note that I managed to store all the rest of the schedule without any issue. But for this relationship, each time I try to update it after storing it the first time, I get an EXC_BAD_ACCESS (or sometimes a SIGABRT), at a random place in my code of course. Any idea what I'm doing wrong?
OK, after reading a few other questions associating Core Data EXC_BAD_ACCESS errors and multi-threading, I noticed that I was doing my caching on a NSURLSession callback. Once I called my caching function on the main thread using the code below, the EXC_BAD_ACCESS errors completely disappeared and now the data seems to be saved and updated properly:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.cacheSlotsForSchedule(schedule, data: data)
})
I'm new in swift , and I'd like to see running application in the device or extract different information from the device like how many times the application was used ...
This will probably work for you or get you close to what you want.
for runningApp in NSWorkspace.sharedWorkspace().runningApplications as! [NSRunningApplication] {
if let bundleIdentifier = runningApp.bundleIdentifier {
NSLog(bundleIdentifier)
}
if let launchDate = runningApp.launchDate {
NSLog(launchDate.description)
}
}