NSPredicate for latest item in a collection - objective-c

I have the following objects in my chat app (with one-to-many relationship)
#interface ChatEntry : NSManagedObject
#property (nonatomic, retain) NSString * text;
#property (nonatomic, retain) NSDate * timestamp;
#property (nonatomic, retain) ChatContext *context;
#end
#interface ChatContext : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * userId;
#property (nonatomic, retain) NSSet *entries;
#end
Each conversation (ChatContext) groups all messages send by this user (uniquely identified by userId field).
I'm trying to display a list of conversations which displays the last message of each conversation (similar to iMessage).
I'm using the following code:
NSFetchRequest* request=[[NSFetchRequest alloc] initWithEntityName:#"ChatEntry"];
request.predicate=[NSPredicate predicateWithFormat:#"timestamp=context.entries.#max.timestamp"];
request.sortDescriptors=[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"timestamp" ascending:NO]];
m_chat=[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:m_context sectionNameKeyPath:nil cacheName:#"chat"];
m_chat.delegate=self;
This works fine until I receive a new message (with NSFetchedResultsChangeInsert)
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
...
}
At that point, I get ANOTHER entry in my table view. The fetch controller keeps the previous entry (even though its timestamp is not the latest one anymore). I'm not sure how to solve this and force the controller to always evaluate the predicate.
Alternatively, I've tried to change my query to fetch ChatContext instances, but then I'm not sure how to actually read the latest entry in that context.

OK, I've managed to solve that. What I did is to add a "lastMessage" object to the context, which I keep updating on every new message. I then query the list of contexts, sorted by reversed lastMessage.timestamp. New messages update the lastMessage property in their context - which triggers an event and update the UI automatically.

Related

How to do deep copying Objective-C

There is a class Patient with properties:
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) Symptoms *symptoms;
#property (assign, nonatomic) Status status;
#property (weak, nonatomic) id <PatientDelegate> delegate;
There is a class Symptoms with properties:
#property (assign, nonatomic) CGFloat temperature;
#property (assign, nonatomic) BOOL headache;
#property (assign, nonatomic) BOOL stomach_ache;
Both classes implement protocol NSCopying:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Patient *newPatient = [[[self class] allocWithZone:zone] init];
[newPatient setName:self.name];
[newPatient setSymptoms:self.symptoms];
[newPatient setStatus:self.status];
[newPatient setDelegate:self.delegate];
return newPatient;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Symptoms *newSymptoms = [[[self class] allocWithZone:zone] init];
[newSymptoms setTemperature:self.temperature];
[newSymptoms setHeadache:self.headache];
[newSymptoms setStomach_ache:self.stomach_ache];
return newSymptoms;
}
Also there is a class Doctor:
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSMutableArray *history;
- (void)makeNoteIn:(Patient *)patient card:(NSMutableArray *)history;
- (void)report;
When patient gets well, doctor calls method makeNoteIn:
- (void)makeNoteIn:(Patient *)patient card:(NSMutableArray *)history {
Patient *newRecord = [patient copy];
[history addObject:newRecord];
}
After the record is made, all property of the patient return to the original values. While we are in the method makeNoteIn and the current patient is proceeded, in history there is link to this object which has correct property values. As soon as we exit the method or start to proceed another patient, all the property values reset to the initial value.
I tried to realise copying, but something still wrong.
When you want to deep-copy an object, you have to implement copy on all the substructures:
[newPatient setName:[self.name copy]];
[newPatient setSymptoms:[self.symptoms copy]];
Otherwise they will still reference the same object and changing one will affect all.
Note that you can do that automatically by declaring the properties as copy:
#property (copy, nonatomic) NSString *name;
#property (copy, nonatomic) Symptoms *symptoms;
It's common to use copy with NSString and NSArray to prevent assigning NSMutableString and NSMutableArray which could be changed externally by mistake. Make sure you implement NSCopying on Symptoms.

How can I cast my NSURLSessionDownloadTask to my custom NSURLSessionDownloadTask (inheritance)?

I have created a custom NSURLSessionDownloadTask named VJSessionTask and I have just added some custom things like a type (enum) and a custom object (id):
#interface VJSessionTask : NSURLSessionDownloadTask
typedef enum types
{
LS, LSH, DL, UL, RM, TH
} type;
#property enum types type;
#property (strong, nonatomic) id customObject;
#property (strong, nonatomic) NSString *progressNotif;
#property (strong, nonatomic) NSString *doneNotif;
#property (strong, nonatomic) NSURL *tmpFile;
#end
And when I do this:
VJSessionTask *taskSession = (VJSessionTask *)[self.prioritySession downloadTaskWithRequest:listFileRequest];
// init taskSession with its type
taskSession.type = LS;
I get this error:
-[__NSCFLocalDownloadTask setType:]: unrecognized selector sent to instance 0x1556198f0
Then I come to you as I don't understand or I don't know how to do that...
Thank you in advance ;)
NSURLSessionTasks are not strictly speaking subclass-able unfortunately. This is evident in that the system can queue a data task and return a NSCFLocalDownloadTask (presumably meaning that the task will return its content from the cache).
The best way to go about doing this is to borrow on from the architectural decision of AFNetworking and have individual taskDelegates that monitor all the responses an individual task works on. Then when you want to find the data relating to a task you can query your dictionary of taskDelegates. Each task has a unique identifier that you can use to key your dictionary with.
In AFNetworking you can see the taskDelegate is defined as follows:
#interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
#property (nonatomic, weak) AFURLSessionManager *manager;
#property (nonatomic, strong) NSMutableData *mutableData;
#property (nonatomic, strong) NSProgress *progress;
#property (nonatomic, copy) NSURL *downloadFileURL;
#property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
#property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
#end
#implementation AFURLSessionManagerTaskDelegate
and subsequently retrieved as follows:
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[#(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
See this post for more info

One to Many Core Data binding issues

I have 2 Core Data objects. Items and TimeLog. The items object has a one to many relation with TimeLog and I am using the IB and Array Controller to automatically populate the 2 NSTableView's
The top table view is for the items. When you select an item the bottom table should populate with the time logs.
However when I add an item, the application crashes with an error
<_NSFaultingMutableSet 0x102e0e790> addObserver:forKeyPath:options:context:] is not supported. Key path: date
I am using an Array Controller to populate all the information automatically. When I create and add and item I am not setting anything for the timeLog relationship because there is no time to add when they first add the item. The object is saving as I have logging that is triggered after the core data save event.
Items.h
#class TimeLog;
#interface Items : NSManagedObject
#property (nonatomic, retain) NSString * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#property (nonatomic, retain) NSSet *timeLog;
#end
#interface Items (CoreDataGeneratedAccessors)
- (void)addTimeLogObject:(TimeLog *)value;
- (void)removeTimeLogObject:(TimeLog *)value;
- (void)addTimeLog:(NSSet *)values;
- (void)removeTimeLog:(NSSet *)values;
#end
TimeLog.h
#class Items;
#interface TimeLog : NSManagedObject
#property (nonatomic, retain) NSString * time;
#property (nonatomic, retain) NSDate * date;
#property (nonatomic, retain) Items *item;
#end
What is causing this error and how do I get rid of it?
I resolved this by creating another NSArrayController for my TimeLog and setting up the table like so.
TimeLog Arra Controller
Set Controller Content -> Content Set -> Bind to Items array controller. Model Key path to timeLog
Then each column of the table.

Unable to refresh UITableView with modified data

I have a iPad app, using XCode 4.5, Storyboards, Core Data and iOS 6. I select a row, make a change to the contents of the record (which is successful), but the row doesn't change. I have tried to refresh the UITableView, but cellForRowAtIndexPath is never called. I have searched SO and Google to no avail; I don't see what's wrong. Can someone please tell me how to fix this? (with an explanation of what I'm doing wrong for the next time?)
Here is the pertinent code:
- (IBAction)btnModify:(UIButton *)sender {
//NSLog(#"btnModify clicked");
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
// find client by primary telephone number
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aClientPrimaryPhone ==[c] %#", cvPrimaryPhone.text];
ClientInfo *clientDataFound = [ClientInfo MR_findFirstWithPredicate:predicate inContext:localContext];
if(clientDataFound) {
clientDataFound.aClientName = cvCustName.text; // now start moving the data
clientDataFound.aClientAddr1 = cvAddress1.text;
clientDataFound.aClientAddr2 = cvAddress2.text;
clientDataFound.aClientCity = cvContactCity.text;
clientDataFound.aClientPostalCode = cvPostalCode.text;
clientDataFound.aClientCellPhone = cvCellPhone.text;
clientDataFound.aClientPrimaryPhone = cvPrimaryPhone.text;
clientDataFound.aClientEMail = cvPersonalEmail.text;
clientDataFound.aClientNotes = cvNotes.text;
[localContext MR_saveNestedContexts];
[self reloadClientList];
}
}
-(void) reloadClientList {
//Init Array to hold TableView Data
tableDataArray = [NSMutableArray new];
[tableDataArray addObjectsFromArray:[ClientInfo findAll]]; // Load
[self.clientList reloadData];
}
and this is ClientInfo.m
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface ClientInfo : NSManagedObject
#property (nonatomic, retain) NSString * aClientAddr1;
#property (nonatomic, retain) NSString * aClientAddr2;
#property (nonatomic, retain) NSString * aClientCellPhone;
#property (nonatomic, retain) NSString * aClientCity;
#property (nonatomic, retain) NSString * aClientEMail;
#property (nonatomic, retain) NSData * aClientImage;
#property (nonatomic, retain) NSString * aClientName;
#property (nonatomic, retain) NSString * aClientNotes;
#property (nonatomic, retain) NSString * aClientPostalCode;
#property (nonatomic, retain) NSString * aClientPrimaryPhone;
#end
I found it... my "clientList" was NOT connected to the object... don't know how I missed that one!
There are a few reasons I can think of:
Your table view reference clientList is nil. (not connected)
Your table view's DataSource & Delegate is not set (Actually I'm not sure if it compiles when DataSource is not set)
Your table view is a subclass of UITableView and in that subclass reloadData method is overridden.

Objective C - NSFetchRequest with category unique

I'm using Core Data for my iPhone app.
Initially I have a UITableViewController that just lists all the "Stores".
However, now we realized that list is getting too long, and would like to break it down into 2 layers of UITableViewController. The first one being "States" and the 2nd being the stores in the selected State.
I figured out how to grab all stores in a certain State
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"state.abbreviation == 'AA'"]];
How can I using NSFetchRequest, just to grab the list of States? Something like this sql statement maybe? Is it possible?
SELECT DISTINCT abbreviation FROM Stores;
Below is my Model (simplified)
Store.h
#class State;
#interface Store : ManagedAppObject {
#private
}
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) State * state;
#end
State.h
#interface State : ManagedAppObject {
#private
}
#property (nonatomic, retain) NSString * abbreviation;
#property (nonatomic, retain) NSString * name;
#end
Thank you,
Tee
To get unique results you'd usually use
fetchRequest.propertiesToFetch = [NSArray arrayWithObject:#"<#propertyName#>"];
fetchRequest.returnsDistinctResults = YES;
fetchRequest.resultType = NSDictionaryResultType;
But unless I'm missing something here, that's probably not what you should do. Why don't you just set your fetchRequest to fetch State entities in the first place? Then use the state.stores relation (which hopefully you have modeled as the inverse of store.state) to get the data for your second 'layer' tableView.