Push query object to view controller - xcode

I have a query that grabs 10 objects, and I am trying to have an action that shows you a window with more information on the object selected, I am just new to Xcode and cannot figure out how to push the information to the new view controller. Here is the code I have that creates the query.
PFQuery *query = [PFQuery queryWithClassName:#"Arcade"];
CLLocation *currentLocation = locationManager.location;
PFGeoPoint *userLocation =
[PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude
longitude:currentLocation.coordinate.longitude];
query.limit = 10;
[query whereKey:kPAWParseLocationKey nearGeoPoint:userLocation withinMiles:kPAWWallPostMaximumSearchDistance];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
int i = 0;
for (PFObject *object in objects) {
if (i >= [self.EventTitles count]) break;//to make sure we only write up to the max number of UILabels available in EventTitles
[(UILabel *)self.EventTitles[i] setText:[object objectForKey:#"name"]];//I assume the "objectId" property of object is an NSString!
i++;
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
and this is the action to push to the new view controller:
-(IBAction)DetailEvent1:(id)sender{
TableDetailViewController *objDetail = [[TableDetailViewController alloc] initWithNibName:#"TableDetailViewController" bundle:nil];
[self addChildViewController:objDetail];
objDetail.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, self.view.bounds.size.height - 0.0f);
[self.view addSubview:objDetail.view];
}
If someone could help me write the one line of code that needs to be added to the DetailEvent1 action so that the chosen object is pushed I would greatly appreciate it.

TableDetailViewController *objDetail =
[[TableDetailViewController alloc]
initWithNibName:#"TableDetailViewController" bundle:nil];
objDetail.query = self.query
In other words, this is the moment you are creating the next view controller. The two view controllers are now in contact (self and objDetail). So this is the moment to pass data across from the one to the other.
Of course, there is no TableDetailViewController property query. Not yet! But you're going to make one, don't you see - so that you can make this exact move.
Oh, and perhaps there is no query property in self either. But you will need one, because you need a way to hold on to the query that you got in that first method so that it is still available to you in the second method. The way to share data between methods of the same object is very often thru a property.
See also this example from my book:
- (void)showItemsForRow: (NSIndexPath*) indexPath {
// create subtable of tracks and go there
TrackViewController *t =
[[TrackViewController alloc] initWithMediaItemCollection:
(self.albums)[indexPath.row]];
[self.navigationController pushViewController:t animated:YES];
}
In that example, I've gone even further: I've actually given TrackViewController a designated initializer so that I can create it and hand it the data all in one line.

Related

Core Data Exporting all tables

I have an app that creates individual events and stores them in core data. What I need to do it load one individually and then export it by email. The code below works except it exports every event where I need it to just export the index path selected one. The code does load the appropriate record because the NSLog (#"My record is: %#", currentItem); does display only the settings for that event but when the data is exported to email all events are sent. I need the selected event with the event name to export. Any thoughts?
NSInteger index = exportevent.tag;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
CDBaseItem *rawRecord = [self.fetchedResultsController objectAtIndexPath:indexPath];
CDSurveyItem *surveyItem = [CDSurveyItem castObject:rawRecord];
self.recordEditID = [rawRecord.objectID URIRepresentation];
NSManagedObjectID *objectId = [self.managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:self.recordEditID];
TSPItem *currentItem = [self.managedObjectContext objectWithID:objectId];
NSString *eventName = nil;
if (currentItem.eventname) {
eventName = currentItem.eventname;
}
else if (surveyItem.eventname) {
eventName = surveyItem.eventname;
}
[self setSelection:indexPath];
if (self.selection)
{
if (currentItem)
{
NSLog (#"My record is: %#", currentItem);
NSData *export = [CDJSONExporter exportContext:currentItem.managedObjectContext auxiliaryInfo:nil];
MFMailComposeViewController *composeVC1 = [[MFMailComposeViewController alloc] init];
composeVC1 = [[MFMailComposeViewController alloc] init];
composeVC1.mailComposeDelegate = self;
[composeVC1 setSubject:[NSString stringWithFormat:#"Settings From %# Event", eventName]];
[composeVC1 setMessageBody:[NSString stringWithFormat:#"Here is the event settings. Simply press on the attachment and then choose Open in iPIX"] isHTML:NO];
[composeVC1 addAttachmentData:export mimeType:#"application/octet-stream" fileName:[NSString stringWithFormat:#"%#.ipix", eventName]];
[self presentViewController:composeVC1 animated:NO completion:^(void){}];
}
[self setSelection:nil];
}
Your NSLog may be correct, but you're not exporting the thing that you're printing. In this line (which I assume is a reference to this project):
NSData *export = [CDJSONExporter exportContext:currentItem.managedObjectContext auxiliaryInfo:nil];
You're telling CDJSONExporter to export the context, not a single object. You get every object because that is what CDJSONExporter does. It gets everything it can find in the context and gives you a data object. It's not designed to do what you're asking it to do.
If you want to convert a single object to JSON, you could
Roll your own JSON conversion code. Since you know what the object looks like, this would be easy. Or...
Implement Encodable on your model object and then use JSONEncoder to convert to JSON. Or...
Find some other open source project that does what you want, instead of this one which does not.

OSX-NSTableview reloadData method do not load data from sqlite properly

I'm new to objective-c programming.
I'm doing an application to manage data from a sqlite database. It's used to check attendance of students.
As I'v implemented the update button which change registration time of a student, I used reloadData method to refresh the tableview.
However, it doesn't work properly. It did change the view, but not the right cell.
For example, I tried to change the registration time of student A, the method change that of student B.
I debugged it, the database was updated correctly, so I think it is the method wrong.
Can anyone give me a hint on the problem? Any suggestion will be appreciated :)
if ([[self.StartOrEndMatrix.selectedCell title] isEqualToString:#"上课"]) {
changedColumn = 3;
}else if([[self.StartOrEndMatrix.selectedCell title] isEqualToString:#"下课"]){
changedColumn = 4;
}
if ([[self.NameOrNumberMatrix.selectedCell title] isEqualToString:#"按姓名"]) {
Index = 1;
}else if ([[self.NameOrNumberMatrix.selectedCell title] isEqualToString:#"按学号"]){
Index = 0;
}
NSString* whereString = [[NSString alloc] initWithString:[self.RegistraitionTextField stringValue]];
NSString* data = [self presentTime];
[self updateStudentInSqlite:_database table:self.table atChangedColumn:changedColumn withOriginValue:whereString atIndex:Index andNewString:data];
[self updateColumnArrayNamed:self.attributeNameArray[changedColumn] atIndex:changedColumn withValue:data];
[self fullDataToStudentInTable];
[self.InfoTable reloadData];

UICollectionView reloadItemsAtIndexPaths

I've been trying to wrap my head around the reloadItemsAtIndexPaths method of UICollectionView.
I have an array of objects called objectsArray. When a user scrolls to the bottom of my collection view, I fetch the next batch of objects from the backend and append it to objectsArray by calling [objectsArray addObjectsFromArray:objects]; After doing so, I call [self.collectionView reloadData] which I know is expensive.
I'd like to optimize with the code below but I get an assertion failure when calling reloadItemsAtIndexPaths.
if (self.searchPage == 0) {
parseObjectsArray = [[NSMutableArray alloc] initWithArray:relevantDeals];
[self.collectionView reloadData];
} else {
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (int i = 0; i < [relevantDeals count]; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
[indexPaths addObject:indexPath];
}
[parseObjectsArray addObjectsFromArray:relevantDeals];
[self.collectionView reloadItemsAtIndexPaths:indexPaths];
//[self.collectionView reloadData];
}
Error:
* Assertion failure in -[UICollectionView _endItemAnimations], /SourceCache/UIKit/UIKit-2903.2/UICollectionView.m:3716
Any help/tips is greatly appreciated.
Thanks!
It looks like the items being added are new, in other words you are adding items which weren't there before. In that case, just replace reloadItemsAtIndexPaths:indexPaths with insertItemsAtIndexPaths:
[self.collectionView insertItemsAtIndexPaths:indexPaths]; // Use this for newly added rows
//[self.collectionView reloadItemsAtIndexPaths:indexPaths]; // Use this for existing rows which have changed
The assertion error was misleading. It wasn't until after I implemented
#try
{
[self.collectionView insertItemsAtIndexPaths:indexPaths];
}
#catch (NSException *except)
{
NSLog(#"DEBUG: failure to insertItemsAtIndexPaths. %#", except.description);
}
Did I realize was calculating my indexPaths wrong. My index offset for the new objects was off by one making my collectionview think I was adding more objects than I specified in numberOfItemsInSection.
Thanks for all those who attempted to help!
This error often arises when you increase/decrease the number of items in the array but it doesn't match the data source method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Are you updating it appropriately?

Add more UICollectionViewCell to an existing UICollectionView

I'm trying to add some more cells to an existing UICollectionView, which is already filled with some cells.
I tried to use the CollectionView reloadData but it seems to reload the entire collectionView and I just wanted to add more cells.
Can anybody help me?
The UICollectionView class has methods to add/remove items. E.g., to insert an item at some index (in section 0), modify your model accordingly and then do:
int indexPath = [NSIndexPath indexPathForItem:index];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath inSection:0];
[collectionView insertItemsAtIndexPaths:indexPaths];
The view will do the rest.
The easiest way to insert new cells to the UICollectionView without having to reload all its cell is by using the performBatchUpdates, which can be done easily by following the steps below.
// Lets assume you have some data coming from a NSURLConnection
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *erro)
{
// Parse the data to Json
NSMutableArray *newJson = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
// Variable used to say at which position you want to add the cells
int index;
// If you want to start adding before the previous content, like new Tweets on twitter
index = 0;
// If you want to start adding after the previous content, like reading older tweets on twitter
index = self.json.count;
// Create the indexes with a loop
NSMutableArray *indexes = [NSMutableArray array];
for (int i = index; i < json.count; i++)
{
[indexes addObject:[NSIndexPath indexPathForItem:i inSection:0]];
}
// Perform the updates
[self.collectionView performBatchUpdates:^{
//Insert the new data to your current data
[self.json addObjectsFromArray:newJson];
//Inser the new cells
[self.collectionView insertItemsAtIndexPaths:indexes];
} completion:nil];
}

Core Data, caching NSManagedObjects in NSMutableDictionary, Problems

I am writing a dictionary application, and i am trying to import raw data from strings, one string per word. Amongst other things, the raw input strings contain the names of the parts of speech the corresponding word belongs to. In my datamodel I have a separate entity for Words and PartOfSpeech, and i want to create one entity of the type PartOfSpeech for each unique part of speech there may be in the input strings, and establish the relationships from the Words to the relevant pars of speech. The PartOfSpeech entity has just one Atribute, name, and one-to-many relationship to the Word:
My first implementation of getting unique PartOfSpeech entities involved caching them in a mutable array and filtering it each time with a predicate. It worked, but it was slow. I decided to speed it up a bit by caching the PartsOfSpeech in an NSDictionary, and now when i try and save the datastore after the import, i get the error "Cannot save objects with references outside of their own stores.". It looks like the problem is in the dictionary, but how can i solve it?
Here is the code that worked:
(in both sniplets managedObjectContext is an ivar, and processStringsInBackground: method runs on a background thread using performSelectorInBackground:withObject: method)
- (void) processStringsInBackground:(NSFetchRequest *)wordStringsReq {
NSError *err = NULL;
NSFetchRequest *req = [[NSFetchRequest alloc] init];
[req setEntity:[NSEntityDescription entityForName:#"PartOfSpeech" inManagedObjectContext:managedObjectContext]];
err = NULL;
NSMutableArray *selectedPartsOfSpeech = [[managedObjectContext executeFetchRequest:req error:&err] mutableCopy];
NSPredicate *p = [NSPredicate predicateWithFormat:#"name like[c] $name"];
// NSPredicate *formNamePredicate = [NSPredicate predicateWithFormat:<#(NSString *)predicateFormat#>]
...
for (int i = 0; i < count; i++){
...
currentPos = [self uniqueEntityWithName:#"PartOfSpeech" usingMutableArray:selectedPartsOfSpeech predicate:p andDictionary:[NSDictionary dictionaryWithObject:partOfSpeech forKey:#"name"]];
...
}
}
- (NSManagedObject *) uniqueEntityWithName:(NSString *) entityName usingMutableArray:(NSMutableArray *)objects predicate:(NSPredicate *)aPredicate andDictionary:(NSDictionary *) params {
NSPredicate *p = [aPredicate predicateWithSubstitutionVariables:params];
NSArray *filteredArray = [objects filteredArrayUsingPredicate:p];
if ([filteredArray count] > 0) {
return [filteredArray objectAtIndex:0];
}
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:managedObjectContext];
NSArray *dicKeys = [params allKeys];
for (NSString *key in dicKeys) {
[newObject willChangeValueForKey:key];
[newObject setPrimitiveValue:[params valueForKey:key] forKey:key];
[newObject didChangeValueForKey:key];
}
[objects addObject:newObject];
return newObject;
}
And here is the same, but with caching using NSMutableDictionary, which fails to save afterwards:
- (void) processStringsInBackground:(NSFetchRequest *)wordStringsReq {
NSError *err = NULL;
[req setEntity:[NSEntityDescription entityForName:#"PartOfSpeech" inManagedObjectContext:managedObjectContext]];
NSArray *selectedPartsOfSpeech = [managedObjectContext executeFetchRequest:req error:&err];
NSMutableDictionary *partsOfSpeechChache = [[NSMutableDictionary alloc] init];
for (PartOfSpeech *pos in selectedPartsOfSpeech) {
[partsOfSpeechChache setObject:pos forKey:pos.name];
}
...
for (int i = 0; i < count; i++){
...
currentPos = [self uniqueEntity:#"PartOfSpeech" withName:partOfSpeech usingDictionary:partsOfSpeechChache];
...
}
}
- (NSManagedObject *)uniqueEntity:(NSString *) entityName withName:(NSString *) name usingDictionary:(NSMutableDictionary *) dic {
NSManagedObject *pos = [dic objectForKey:name];
if (pos != nil) {
return pos;
}
NSManagedObject *newPos = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:managedObjectContext];
[newPos willChangeValueForKey:#"name"];
[newPos setPrimitiveValue:name forKey:#"name"];
[newPos didChangeValueForKey:#"name"];
[dic setObject:newPos forKey:name];
return newPos;
}
Could you help me to find the problem?
Best regards,
Timofey.
The error is caused by forming a relationship between managedObjects that don't share the same persistent store. You can do that by:
Creating a managed object with initialization without inserting it into a context.
Deleting a managed object from a context while retaining it in another object e.g. array, and then forming a relationship with it.
Accidentally creating two Core Data stacks so that you have two context and two stores.
Confusing configurations in a multi-store context.
I don't see any part of the code you provided that would trigger the problem.
It turns out, that it is wrong to pass NSManagedContext to a thread different from the one it was created in. Instead, one should pass the NSPersistenceStroreCoordinator to another thread, and create a new managed object context there. In order to merge the changes into the "main" context, one should save the other thread's context, receive the notification on the completion of the save on the main thread and merge the changes (see apple docs regarding Core Data and concurrency, can't give you the link, because i read it in Xcode). So here are the changes i made to my code to make it work (only posting the changed lines):
— (void) processStringsInBackground:(NSDictionary *) params {
NSFetchRequest *wordStringsReq = [params objectForKey:#"wordStringsReq"];
NSPersistentStoreCoordinator *coordinator = [params objectForKey:#"coordinator"];
NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
[localContext setPersistentStoreCoordinator:coordinator];
(all the references to the managedObjectContext were replaced by localContext
And on the main thread, i call this method thusly:
.......
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:req, #"wordStringsReq", persistentStoreCoordinator, #"coordinator", nil]; //the params i pass to the background method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"NSManagingContextDidSaveChangesNotification" object:nil]; //register to receive the notification from the save
[self performSelectorInBackground:#selector(processStringsInBackground:) withObject:dict];
}
- (void) handleNotification:(NSNotification *) notific {
NSLog(#"got notification, %#", [notific name]);
[managedObjectContext mergeChangesFromContextDidSaveNotification:notific];
}
Good luck!
Good answers, though a bit dated. The fine documentation notes that the main NSManagedObjectContext should never be used in worker threads. Instead, create a separate NSManagedObjectContext private to the worker using the "main" MOC as a parent, and then that instead. Here's the relevant "Concurrency" page from the Core Data Programming Guide:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Conceptual/CoreData/Concurrency.html
Snippet (Swift)
let jsonArray = … //JSON data to be imported into Core Data
let moc = … //Our primary context on the main queue
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock {
for jsonObject in jsonArray {
let mo = … //Managed object that matches the incoming JSON structure
//update MO with data from the dictionary
}
do {
try privateMOC.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}

Resources