I am trying to implement IAPs in one app but I'm still having difficulties with it. I followed various tutorials but all of them are out of date and full of errors. The only one that could work that I found is this one:
But I'm having a problem, the 3 products appear on my tableview but then when I click on one of them nothing happens... cell become blue and that's all... Am I missing something?
Or is that tutorial incomplete?
How do I run the purchase attempt?
Here is my code:
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
[productDetailsList addObjectsFromArray: response.products];
[productDisplayTableView reloadData];
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"Failed to connect with error: %#", [error localizedDescription]);
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [self.productDetailsList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *GenericTableIdentifier = #"GenericTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: GenericTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: GenericTableIdentifier];
}
NSUInteger row = [indexPath row];
SKProduct *thisProduct = [productDetailsList objectAtIndex:row];
[cell.textLabel setText:[NSString stringWithFormat:#"%# - %#", thisProduct.localizedTitle, thisProduct.price]];
return cell;
}
- (void)viewDidLoad
{
productDetailsList = [[NSMutableArray alloc] init];
productIdentifierList = [[NSMutableArray alloc] init];
for (short item_count=1; item_count <= 5; item_count++) {
[productIdentifierList addObject:[NSString stringWithFormat:#"com.mycompany.myapp.%d", item_count]];
}
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifierList]];
request.delegate = self;
[request start];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
You need to have something in the lines of:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([SKPaymentQueue canMakePayments])
{
SKProduct *selectedProduct = [self.productDetailsList objectAtIndex:indexPath.row];
SKPayment *payment = [SKPayment paymentWithProduct:selectedProduct];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
Apple provides a decent step by step guide for handling in app purchases.
The main way to run IAPs involves a few different methods, but there are a few different steps you need to follow when implementing IAPs.
The first of these requirements are protocols. Please include each of the following protocols in your header file.
SKProductsRequestDelegate
SKPaymentTransactionObserver
SKRequestDelegate
You need the request methods:
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if(response.products.count > 0)
{
SKProduct* product;
for(int i = 0; i<response.products.count; i++)
{
product = [response.products objectAtIndex:i];
if([product.productIdentifier isEqualToString:#"product identifier"])
{
self.currentProduct = product;
[self beginPaymentWithProduct:product];
}
}
}
}
I used the if statement to keep track of which product was being purchased. You will need an if-statement in that for-loop for each product identifier if you have multiple products. Use this later to unlock whatever on completion of the purchase.
You will also need the beginPayment method:
- (void)beginPaymentWithProduct:(SKProduct*)product
{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
You also need the payment processing methods. I will not post all of them here as this would take far too much space, but I will give you the prototypes.
-(void)requestDidFinish:(SKRequest *)request;
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error;
- (void)recordTransaction:(SKPaymentTransaction *)transaction;
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful;
- (void)completeTransaction:(SKPaymentTransaction *)transaction;
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
For each of your buttons in your table that are supposed to be purchasing, they will need to perform a method similar to this one on the didSelectRowAtIndex... method:
- (void)buyCoins:(id)sender
{
if([self canMakePurchases])
{
ualRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:[NSArray arrayWithObjects: #"product identifier", nil]]];
[ualRequest setDelegate:self];
[ualRequest start];
}
}
This method will run the product request successfully. If you have all of these components in, you should have no problems.
I have used this code successfully in several apps.
Related
I try using a custom kal datasource for my calendar. I succeeded in getting the data, and when I tried to run it I keep getting this error:
-[NSNull cc_componentsForMonthDayAndYear]: unrecognized selector sent to instance 0x2823fb8
My code
// KalParseDataSource.m
#import "KalParseDataSource.h"
#import <Parse/Parse.h>
#implementation KalParseDataSource
static BOOL IsDateBetweenInclusive(NSDate *date, NSDate *begin, NSDate *end)
{
return [date compare:begin] != NSOrderedAscending && [date compare:end] != NSOrderedDescending;
}
- (id)init
{
if ((self = [super init])) {
items = [[NSMutableArray alloc] init];
events= [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark UITableViewDataSource protocol conformance
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"MyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = #"Filler text";
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 0;
}
- (void)presentingDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate delegate:(id<KalDataSourceCallbacks>)delegate{
NSLog(#"getting data");
if ([events count] > 0) {
[delegate loadedDataSource:self];
return;
}
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:#"yyyy-MM-dd hh:mm:ss"];
PFUser *user = [PFUser currentUser];
PFQuery *query = [PFQuery queryWithClassName:#"CalendarEvents"];
[query whereKey:#"user" equalTo:user];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
[events removeAllObjects];
[events addObjectsFromArray:objects];
[delegate loadedDataSource:self];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (NSArray *)markedDatesFrom:(NSDate *)fromDate to:(NSDate *)toDate {
return [[self tagsFrom:fromDate to:toDate] valueForKeyPath:#"date"];
}
- (void)loadItemsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate {
[items addObjectsFromArray:[self tagsFrom:fromDate to:toDate]];
}
- (NSArray *)tagsFrom:(NSDate *)fromDate to:(NSDate *)toDate
{
NSMutableArray *matches = [NSMutableArray array];
for (PFObject *event in events){
if (IsDateBetweenInclusive([event objectForKey:#"event_date"], fromDate, toDate)){
[matches addObject:event];
}
}
return matches;
}
- (void)removeAllItems{
[items removeAllObjects];
}
#end
My view controller holding the calendar.
#import "MainMenuViewController.h"
#import "Kal.h"
#import "KalParseDataSource.h"
#interface MainMenuViewController ()
#end
#implementation MainMenuViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
id<KalDataSource> source = [[KalParseDataSource alloc] init];
KalViewController *calendar = [[KalViewController alloc] init];
calendar.dataSource = source;
[self addChildViewController:calendar];
[calendar didMoveToParentViewController:self];
[self.view addSubview:calendar.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm stuck with this for 3 days, Can anyone give simple example in using this 3rd party framework https://github.com/klazuka/Kal ? The one on the repo seems a bit complicated, I just want to use a custom data source.
it seems that the function cc_componentsForMonthDayAndYear you are calling with null just put a debug there from wherever the function is being called and check if the function being called is on NSDate and it is not released.
And you can always Enable Zombies to find the released object when crash happens. You can track if any variable gets released via setting NSZombieEnabled to YES. With zombies enabled, messages to deallocated objects will no longer behave strangely or crash in difficult-to-understand ways, but will instead log a message and die in a predictable and debugger-breakpointable way.
You can set NSZombieEnabled by the following steps.
Select Product from the menu bar above. Keep alt/option pressed and select "Test..." or "Run...".
1.
Go to the Arguments tab, and add NSZombieEnabled YES in the "Environment Variables" section.
OR
2.
Go to the Diagnostics tab, and check Enable Zombie Objects in the "Memory Management" section.
In my app I am getting the data from the web-service and I have to display it in UITableView.
But the condition here is I have to display only 10 records initially,then once the user scroll down I have to load more records.I tried searching it but didn't get any useful answer.
I agree that I will use -
(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
to display the value,but how will I fetch only 10 records from the service and then other record based on scroll.
Please provide some pointers or sample code.
Thanks
In case if some one need it,i was able to solve my problem this way.
First thing is you need the server configuration in such a way so that it should return 10 data at a time based on the row which is visible in TableView.
This is the tableView delegate which get called and returns the visible cells in tableView
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
int lastRow=[nameArray count]-1;
if(([indexPath row] == lastRow)&&(lastRow<[categoryArray count]))
{
if(tableView==m_pDetailsTableView) {
savedScrollPosition=lastRow;
startCellValue=[NSString stringWithFormat:#"%d",0];
endCellValue=[NSString stringWithFormat:#"%d",[nameArray count]+10];
[self connectToServer]; //Method to request to server to get more data
}
}
}
savedscrollPosition variable stores the variable as a point where you want to scroll the table view after load of data.
You should read about lazy loading. The code is available at Apple's website. Download it here
http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
check the code of
- (void)loadImagesForOnscreenRows
method.
It uses the same approach you need. It gets the current scroll position of the table view and on the basis of that, it will get the cells displayed on the screen and their indexPath. On the basis of that you will be able to show those cells which are shown in the screen.
For showing 10 rows, a simple calculation is required.
Just insert the new data into your datasource
see below
If you're using xml - check out XMLReader - turn XML into an NSDictionary
this sample code below uses AFNetworking (which is non blocking)
https://github.com/AFNetworking/AFNetworking/
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self fetchMoreData];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self fetchMoreData];
}
- (void)fetchMoreData
{
if ([resultArray count] > 0)
{
NSArray *visiblePaths = [myTableView indexPathsForVisibleRows];
NSIndexPath *lastRow = [visiblePaths lastObject];
// Check whether or not the very last row is visible.
NSInteger numberOfSections = [myTableView numberOfSections];
NSInteger lastRowSection = [lastRow section];
NSInteger lastRowRow = [lastRow row];
NSInteger numberOfRowsInSection = [myTableView numberOfRowsInSection:lastRowSection];
if (lastRowSection == numberOfSections - 1 &&
lastRowRow== numberOfRowsInSection - 1) {
DLog(#"it's the last row");
if ([resultArray count]%10 == 0) { // use a divider based on your pagination
[self fetchNextPage];
}
}
}
}
-(void)getFeeds{
ENTER_METHOD;
[resultArray removeAllObjects];
//reset this
NSString *url = [NSString stringWithFormat:#"/webserviceurl.xml?offset=0"];
[httpClient getPath:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self parseFeedsXMLString:operation.responseString];
// offset = offset + 10; // ONLY if there's results increment
} failure:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *detailError=nil;
}];
}
-(void)fetchNextPage{
NSString *url = [NSString stringWithFormat:#"/webserviceurl.xml?offset=%d",offset];
[httpClient getPath:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
DLog(#"operation.responseString:%#",operation.responseString);
[self parseNextFeedsXMLString:operation.responseString];
// offset = offset + 10; // ONLY increment if there's results
} failure:^(AFHTTPRequestOperation *operation, id responseObject){
}];
}
- (void)parseFeedsXMLString:(NSString *)xmlString
{
NSError *parseError = nil;
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLString:xmlString error:&parseError];
DLog(#"xmlDictionary:%#",xmlDictionary);
resultArray = [[NSMutableArray arrayWithArray:[[xmlDictionary objectForKey:#"feed"] objectForKey:#"entry"]]retain];
[myTableView reloadData];
}
-(void)parseNextFeedsXMLString:(NSString *)xmlString
{
NSError *parseError = nil;
NSDictionary *xmlDictionary = [XMLReader dictionaryForXMLString:xmlString error:&parseError];
DLog(#"xmlDictionary:%#",xmlDictionary);
//[resultArray insertObject:e atIndex:[resultArray count]];
NSMutableArray *results = [NSMutableArray arrayWithArray:[[xmlDictionary objectForKey:#"feed"] objectForKey:#"entry"]];
if ([results count]) {
page++;
for (NSDictionary *dict in results) {
[resultArray insertObject:dict atIndex:[results count]];
}
}
[myTableView reloadData];
}
If I correctly understand your question ,you can do the following.
1 ) implement scrollViewDidScroll
2 ) check for visible rows in that
3 ) if you found the last row just call the web service for loading
more data
4 ) on getting the data just reload the table
Try it .
You can adjust the return value of tableView:numberOfRowsInSection: method, every time you want to insert ten rows, you can plus 10 to the return value.
sorry for my poor english.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) {
i = i +10;
NSString *unescaped = mySearchBar.text;
NSString *escapedString = [unescaped stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
[PServiceAPI searchKeyWord:escapedString withOffSet:i Handler:^(NSArray *results, NSError *error) {
if (error == nil) {
[arBase addObjectsFromArray:results];
[myTableView2 reloadData];
}
}];
}
}
I want to enable an UIAlertView-Request before the user can delete an item from my tableview. But the indexPath seems to be "nil"...
That is my coding:
The header-file looks like that ..
#interface ReturnRootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
// ..
NSIndexPath *deleteSelectedRow;
}
// ..
#property (nonatomic, retain) NSIndexPath *deleteSelectedRow;
#end
.. the implemtation file looks like that ..
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSUserDefaults *setting = [NSUserDefaults standardUserDefaults];
[setting synchronize];
// for the UIAlert-View handling:
deleteSelectedRow = indexPath;
if ([setting boolForKey:#"delete_preference"]) {
// Sicherheitsabfrage vor dem Löschen
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Achtung", #" ")
message:NSLocalizedString(#"Sind Sie sicher, dass Sie den Eintrag löschen wollen?", #" ")
delegate:self
cancelButtonTitle:NSLocalizedString(#"Nein", #"Nein")
otherButtonTitles:NSLocalizedString(#"Ja", #"Ja"), nil];
[alert show];
[alert release];
} else {
// just the case you override the settings
[moc deleteObject:[fetchedResultsController objectAtIndexPath:deleteSelectedRow]];
NSError *error = nil;
if (![moc save:&error]) {
NSLog(#"Ungelöstes Problem %#, %#", error, [error userInfo]);
abort();
}
}
}
}
and last but not least the method for the uialertview-handling:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 1) {
// Delete request
[moc deleteObject:[fetchedResultsController objectAtIndexPath:deleteSelectedRow]];
// ..
and that is the place where my app dumps! deleteSelectedRow is nil.
by the way: deleteSelectedRow is not nil in the else-Statement from the tableview:commitEditingStyle-method! I'm fully helpless.
Before iOS 5 it works like that ...
Thanks for any hints.
ifeelhorst
As it is now, indexPath could be released by the time the alert view is clicked/returns. Change
deleteSelectedRow = indexPath;
to
self.deleteSelectedRow = indexPath; // to retain it
or (to make a copy of it)
self.deleteSelectedRow = [[indexPath copy] autorelease];
This does not explain why it is nil however, try putting a breakpoint on that line and on the alert view clickedButtonAtIndex callback to see what happens.
I think it's because you're assigning pointers rather than copying the object.
I've a simple applications that lets you create groups of people form persons in your AddressBook... So Groups and Persons are in a one-to-many relationships, since a Group can have multiple persons. That's not a many-to-many since I create my own model of Person.
Adding data works without problems.
Deleting data doesn't. If I create a new Person, I must restart the app to delete it or the to delete the Group that Person belongs. Otherwise I get a "EXC BAD ACCESS" in the console. With NSZombieEnabled in the enviroment I get -[CFString release]: message sent to deallocated instance 0x75140d0.
I start with the CoreData stuff automatically created by XCode, create the RootViewController (subclass of TableViewController), I pass it the context and put it in a NavigationController.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Creo il controller root e gli passo il context
RootViewController *rvc = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
rvc.context = [self managedObjectContext];
//Creo il navcon, gli associo il root e lo rendo visibile
navCon = [[UINavigationController alloc] initWithRootViewController:rvc];
[window addSubview:navCon.view];
[window makeKeyAndVisible];
[rvc release];
return YES;
}
The RootViewController shows the Groups, then clicking on a row lets you modify persons in that group, passing the "nuovogruppo" (the Group Model associated with that row)
- (void)showPersoneControllerWithGruppo:(Gruppo *)nuovogruppo {
PersoneController *pc = [[PersoneController alloc] initWithStyle:UITableViewStylePlain];
pc.gruppo = nuovogruppo;
pc.context = self.context;
pc.delegate = self;
//NSLog(#"%#",[gruppi objectAtIndex:indexPath.row]);
[self.navigationController pushViewController:pc animated:YES];
[pc release];
}
And this is how I delete the person (gruppo is the Group model these persons belong to, persone is an array filled with these persons on viewDidLoad, removeGPObject is an accessor method generated by XCode (Group to Persons relationship))
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
}
}
I hope someone can help me...
UPDATE
Since I was having errors about messages sent to already released instances I tried commenting out all the [... release] lines and finally find out what was causing the problem. The problem was in the creation method of the record and not in the deleting method. Here is the method I use to create it.
The line that was causing the roblem is [NomeCognome release]
I'd be very grateful if someone could explain me why this line crashes the app.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
if (property == kABPersonPhoneProperty) {
ABMultiValueRef phoneProperty = ABRecordCopyValue(person,property);
NSString *phone = (NSString *)ABMultiValueCopyValueAtIndex(phoneProperty,identifier);
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *surname = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *NomeCognome = [NSString stringWithFormat:#"%# %#", firstName, surname];
[firstName release];
[surname release];
Persona *persona = (Persona *)[NSEntityDescription insertNewObjectForEntityForName:#"Persona" inManagedObjectContext:context];
persona.ABRR = phone;
persona.NomeCognome = NomeCognome;
[phone release];
[NomeCognome release]; //This line makes the app crash!!! Why???
[gruppo addGPObject:persona];
NSError *error;
[context save:&error];
[self.delegate PersoneControllerDidSave:self];
[self loadContentAndReload:YES];
[self dismissModalViewControllerAnimated:YES];
}
Why that line crashes the app?
Use reloadData method at the end as the following.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[gruppo removeGPObject:[persone objectAtIndex:indexPath.row]];
[persone removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
NSError *error;
[context save:&error];
[tableView reloadData];
}
It may work now.
I've attempted to add the TopSongs parser and Core Data files into my application, and it now builds succesfully, with no errors or warning messages. However, as soon as the app loads, it crashes, giving the following reason:
UPDATE: I've got it all working, but my TableView doesn't show any data, and the app doesn't respond to the following breakpoints.
Thanks.
UPDATE: Here's the new code that doesn't respond to the breakpoints.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)table {
return [[fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.tableView = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *kCellIdentifier = #"SongCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
}
Incident *incident = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(#"#%d %#", #"#%d %#"), incident.title];
return cell;
}
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[table deselectRowAtIndexPath:indexPath animated:YES];
self.detailController.incident = [fetchedResultsController objectAtIndexPath:indexPath];
[self.navigationController pushViewController:self.detailController animated:YES];
}
UPDATE: Here's the code where all instances of fetch are found.
- (Category *)categoryWithName:(NSString *)name {
NSTimeInterval before = [NSDate timeIntervalSinceReferenceDate];
#ifdef USE_CACHING
// check cache
CacheNode *cacheNode = [cache objectForKey:name];
if (cacheNode != nil) {
// cache hit, update access counter
cacheNode.accessCounter = accessCounter++;
Category *category = (Category *)[managedObjectContext objectWithID:cacheNode.objectID];
totalCacheHitCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheHitCount++;
return category;
}
#endif
// cache missed, fetch from store - if not found in store there is no category object for the name and we must create one
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:self.categoryEntityDescription];
NSPredicate *predicate = [self.categoryNamePredicateTemplate predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:name forKey:kCategoryNameSubstitutionVariable]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
NSAssert1(fetchResults != nil, #"Unhandled error executing fetch request in import thread: %#", [error localizedDescription]);
Category *category = nil;
if ([fetchResults count] > 0) {
// get category from fetch
category = [fetchResults objectAtIndex:0];
} else if ([fetchResults count] == 0) {
// category not in store, must create a new category object
category = [[Category alloc] initWithEntity:self.categoryEntityDescription insertIntoManagedObjectContext:managedObjectContext];
category.name = name;
[category autorelease];
}
#ifdef USE_CACHING
// add to cache
// first check to see if cache is full
if ([cache count] >= cacheSize) {
// evict least recently used (LRU) item from cache
NSUInteger oldestAccessCount = UINT_MAX;
NSString *key = nil, *keyOfOldestCacheNode = nil;
for (key in cache) {
CacheNode *tmpNode = [cache objectForKey:key];
if (tmpNode.accessCounter < oldestAccessCount) {
oldestAccessCount = tmpNode.accessCounter;
[keyOfOldestCacheNode release];
keyOfOldestCacheNode = [key retain];
}
}
// retain the cache node for reuse
cacheNode = [[cache objectForKey:keyOfOldestCacheNode] retain];
// remove from the cache
[cache removeObjectForKey:keyOfOldestCacheNode];
} else {
// create a new cache node
cacheNode = [[CacheNode alloc] init];
}
cacheNode.objectID = [category objectID];
cacheNode.accessCounter = accessCounter++;
[cache setObject:cacheNode forKey:name];
[cacheNode release];
#endif
totalCacheMissCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheMissCount++;
return category;
}
And this one...
- (void)fetch {
NSError *error = nil;
BOOL success = [self.fetchedResultsController performFetch:&error];
NSAssert2(success, #"Unhandled error performing fetch at SongsViewController.m, line %d: %#", __LINE__, [error localizedDescription]);
[self.tableView reloadData];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Song" inManagedObjectContext:managedObjectContext]];
NSArray *sortDescriptors = nil;
NSString *sectionNameKeyPath = nil;
if ([fetchSectioningControl selectedSegmentIndex] == 1) {
sortDescriptors = [NSArray arrayWithObjects:[[[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES] autorelease], [[[NSSortDescriptor alloc] initWithKey:#"rank" ascending:YES] autorelease], nil];
sectionNameKeyPath = #"category.name";
} else {
sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"rank" ascending:YES] autorelease]];
}
[fetchRequest setSortDescriptors:sortDescriptors];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:sectionNameKeyPath cacheName:#"SongsCache"];
}
return fetchedResultsController;
}
your extra caching is probably a waste of cycles as Core Data performs its own caching internally. I am willing to bet you are slowing things down rather than speeding them up, not to mention the additional memory you are consuming.
Where are you setting categoryEntityDescription? That is now shown in the code you posted. It is probably nil.
Why are you retaining an NSEntityDescription?!? They are already in memory because of Core Data and retaining them is a waste which could lead to issues if Core Data wants to release it at some point.
update
Your caching is definitely not coming from Apple's code because they know that the cache is in Core Data.
As for the NSEntityDescription, again, do not retain the NSEntityDescription.
Are you 100% positive that the NSEntityDescription is not nil? Have you confirmed it in the debugger? Have you tested it with a freshly retrieved NSEntityDescription?
update
You need to learn to use the debugger as that will solve most of your coding issues. Put a breakpoint in this method and run your code in the debugger. Then when the execution stops on that break point you can inspect the values of the variables and learn what they are currently set to. That will confirm or deny your suspicions about what is and is not nil.
This error you are seeing happens when you fail to set the Entity in the NSFetchRequest which, based on your code, means that retained property is not being set before the code you have shown is being called.
Based on the code posted and the problem description, I suspect that the categoryEntityDescription property is returning nil.
I've seen this happen when the NSEntityDescription given to a fetch request is nil. The most likely cause of that is that you have a model entity that is named differently from the name you provided to entityForName. Barring that, it could be an error in configuration of your Core Data stack or a missing data model, but as a first step, I would recommend storing the result of entityForName in a local variable and breaking there to make sure it isn't nil.
Since you added the model file manually, is the .xcdatamodel file inside the Compile Sources step in your Target?
Go to the Targets entry in the Groups & Files pane in Xcode and click the disclosure triangle. Then click on the disclosure triangle for your app. Then check to see if it's in Compile Sources. If not, right click on Compile Sources and choose "Add -> Existing File..." and add it.
Edit based on update:
UPDATE: Here's the new code that
doesn't respond to the breakpoints.
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (void)tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Is your view controller set as the UITableViewDataSource/UITableViewDelegate for your UITableView? If not, these methods will not get called.