I have a SurroundViewController (CollectionView) which shows images loaded from a webserver. If you click on the image you will be navigated to a DetailViewController (TableView) which shows additional information to the image. Both Emebded in a NavigationController (see storyboard image).
My problem start when I do a refresh in the SurroundViewController, when coming back from the DetailViewController. Then it crashes with EXC_BAD_ACCESS on the performSelector line
WebApi.m
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:#"%#/%#/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:#selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
I checked in the Debug Console:
2014-03-11 14:22:51.989 Foo[6923:60b] -[SurroundViewController refresh:] [Line 352] refreshing
2014-03-11 14:22:51.998 Foo[6923:60b] -[WebApi getSurroundImages] [Line 393] do surround composition
(lldb) po self.delegate
[no Objective-C description available]
Seems that the object which is not available. What I do not understand is. I am in the SurroundViewController and do activly a refresh by pull-to-refresh. So I am on the Surround View and the object should be available...
How do I fix this issue, that the App does not crash with EXC_BAD_ACCESS at the performSelector line?
Here's the code which is involved with the issue (necessary parts):
SurroundViewController.h
#import <UIKit/UIKit.h>
#import "WebApi.h"
#import "DetailViewController.h"
#import "SingletonClass.h"
#interface SurroundViewController : UICollectionViewController <WebApiDelegate>
#property (nonatomic, strong) WebApi *swebapi;
#property (nonatomic, strong) SingletonClass *sshare;
#end
SurroundViewController.m
#import "SurroundViewController.h"
#interface SurroundViewController ()
#property (nonatomic, strong) UIRefreshControl *refresh;
#end
#implementation SurroundViewController
-(void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[super viewDidLoad];
[self addRefresh];
[self.swebapi getSurroundImages]; // will call delegate didComposition
}
- (void)viewDidAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// cell configuration
}
-(void)addRefresh {
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
self.refresh = refreshControl;
[self.collectionView addSubview:self.refresh];
}
-(void)refresh:(UIRefreshControl*)refresh {
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refreshing..."];
[self.swebapi getSurroundImages];
}
-(void)didLoadFoo {
[self.swebapi doComposition];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"toDetailView" sender:indexPath];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"toDetailView"]) {
DetailViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = sender;
dvc.idx = [self getItemOfSection:indexPath];
dvc.detailData = [[self.sshare coItem:dvc.idx] mutableCopy];
}
}
- (int)getItemOfSection:(NSIndexPath *)indexPath {
return (int)indexPath.item + ((int)indexPath.section * 4);
}
#end
WebApi.h
#import "AFHTTPRequestOperationManager.h"
#import "Errors.h"
#class WebApi;
#protocol WebApiDelegate <NSObject>
#optional
-(void)didLoadFoo;
#end
#interface WebApi : AFHTTPRequestOperationManager <SingletonDelegate>
#property (assign, nonatomic)id<WebApiDelegate> delegate;
#property (nonatomic, strong) Errors *error;
+(WebApi*)sharedInstance;
-(void)getSurroundStream;
-(void)getSurroundImages;
#end
WebApi.m
#import "WebApi.h"
#define kApiHost #"http://sample.com"
#define kApiPath #"sample"
#implementation WebApi
-(WebApi*)initWithBaseURL:url {
self = [super init];
if (self != nil) {
self.sshare = [SingletonClass sharedInstance];
self.error = [[Errors alloc] init];
}
return self;
}
+(WebApi*)sharedInstance
{
static WebApi *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kApiHost]];
});
return sharedInstance;
}
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:#"%#/%#/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:#selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
-(void)getSurroundImages {
[self getSurroundStream];
}
#end
SingletonClass.h
#import <Foundation/Foundation.h>
#class Singleton;
#protocol SingletonDelegate <NSObject>
-(void)didRefreshToken;
#end
#interface SingletonClass : NSObject
#property (assign, nonatomic) id<SingletonDelegate> delegate;
#property (nonatomic, strong) NSMutableArray *viData;
#property (nonatomic, strong) NSMutableArray *coData;
#end
SingletonClasss.m
#import "SingletonClass.h"
#implementation SingletonClass
static SingletonClass *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (SingletonClass *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
self.coData = [[NSMutableArray alloc] init];
self.viData = [[NSMutableArray alloc] init];
}
return self;
}
// We don't want to allocate a new instance, so return the current one.
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Equally, we don't want to generate multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
-(NSMutableDictionary *)coItem:(int)position {
NSAssert(self.coData.count > position, #"Position does not exists: coData.count: %lu > position: %d", (unsigned long)self.coData.count, position);
return self.coData[position];
}
#end
DetailViewController.h
#import <UIKit/UIKit.h>
#import "SingletonClass.h"
#import "WebApi.h"
#interface DetailViewController : UITableViewController <WebApiDelegate>
#property (nonatomic) int idx;
#property (nonatomic, strong) SingletonClass *sshare;
#property (nonatomic, strong) WebApi *swebapi;
#property (nonatomic, strong) NSMutableDictionary *detailData;
#end
DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
#property (nonatomic, strong) NSArray *cellRows;
#end
#implementation DetailViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
NSAssert(self.detailData, #"detailData is not available");
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:NO animated:NO];
[super viewDidLoad];
self.cellRows = #[#"cellLocation:", #"cellIntention:"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.cellRows.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"detailCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
SEL functionCall = NSSelectorFromString(self.cellRows[indexPath.row]);
[self performSelector:functionCall withObject:cell];
return cell;
}
- (void)cellLocation:(UITableViewCell*)cell {
// configuration of table cell
}
- (void)cellIntention:(UITableViewCell*)cell {
// configuration of table cell
}
#end
You are setting DetailViewController as delegate. Of course you will get EXC_BAD_ACCESS after it's deallocated.
You should use notifications instead delegates for shared instances.
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender and - (void)removeObserver:(id)notificationObserver are yours friends.
In your protocol, you set didLoadDoo as optional,
#protocol WebApiDelegate <NSObject>
#optional
-(void)didLoadFoo;
#end
so you need to secure the call to that method in your delegate
if ([self.delegate respondsToSelector:#selector(didLoadFoo)]) {
[self.delegate performSelector:#selector(didLoadFoo)];
}
As you are working with a singleton
+(WebApi*)sharedInstance
if your singleton.delegate is change somewhere else in your code (i.e. in your detailVC), it is change everyWhere !
edit :
After some more check, now we know that WebApi.delegate is change in detailVC, and the bug appear when we are back from detailVC because at this step detailVC is becoming nil and of course WebApi.delegate also.
So, the solution is to reset WebApi.delegate when we are back in SurroundViewController, and we can do that in :
SurroundViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.swebapi.delegate = self;
}
Related
I have project with normal cell and working success but I want to change it with custom cell I added Cell.h and Cell.m files into my project and I need to integrate custom cell to my working cell. And last I want to show detail view title description and image (I added title codes working) My codes under
Cell.h
#import <UIKit/UIKit.h>
#interface Cell : UITableViewCell
#property (nonatomic, weak) IBOutlet UIImageView *imaj;
#property (nonatomic, weak) IBOutlet UILabel *descriptionLabel;
#property (nonatomic, weak) IBOutlet UILabel *titleLabel;
#end
Cell.m
#import "Cell.h"
#implementation Cell
#synthesize imaj = _imaj;
#synthesize descriptionLabel = _descriptionLabel;
#synthesize titleLabel = _titleLabel;
#end
ViewController.m
#import "MasterViewController.h"
#import "DetailViewController.h"
#import "SDWebImage/UIImageView+WebCache.h"
#import "MBProgressHUD.h"
#import "Cell.h"
static NSString *const kConsumerKey = #"a1SNULSPtp4eLQTsTXKKSgXkYB5H4CMFXmleFvqE";
#interface MasterViewController () <UISearchBarDelegate, UISearchDisplayDelegate,MBProgressHUDDelegate>{
MBProgressHUD *HUD;
}
#property (nonatomic, assign) NSInteger currentPage;
#property (nonatomic, assign) NSInteger totalPages;
#property (nonatomic, assign) NSInteger totalItems;
#property (nonatomic, assign) NSInteger maxPages;
#property (nonatomic, strong) NSMutableArray *activePhotos;
#property (strong, nonatomic) NSMutableArray *staticDataSource;
#property (nonatomic, strong) NSMutableArray *searchResults;
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.activePhotos = [[NSMutableArray alloc] init];
self.searchResults = [[NSMutableArray alloc] init];
self.staticDataSource = [[NSMutableArray alloc] init];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadPhotos:self.currentPage];
}
#pragma mark - Table View
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// if (self.currentPage == self.maxPages
// || self.currentPage == self.totalPages
// || self.currentPage == self.totalPages
// || self.totalItems == self.photos.count) {
// return self.photos.count;
// } else if (self.tableView == self.searchDisplayController.searchResultsTableView){
// return [self.searchResults count];
//
// }
// return self.photos.count + 1;
return self.activePhotos.count + 1;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.currentPage != self.maxPages && indexPath.row == [self.staticDataSource count] - 1 ) {
[self loadPhotos:++self.currentPage];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (indexPath.row == [self.activePhotos count]) {
cell = [self.tableView dequeueReusableCellWithIdentifier:#"LoadingCell" forIndexPath:indexPath];
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD showWhileExecuting:#selector(myTask) onTarget:self withObject:nil animated:YES];
} else {
NSDictionary *photoItem = self.activePhotos[indexPath.row];
cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
// cell.textLabel.text = [self.searchResults[indexPath.row] valueForKey:#"name"];
// } else {
// NSDictionary *photoItem = self.photos[indexPath.row];
cell.textLabel.text = [photoItem objectForKey:#"name"];
if (![[photoItem objectForKey:#"description"] isEqual:[NSNull null]]) {
cell.detailTextLabel.text = [photoItem objectForKey:#"description"];
}
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:[photoItem objectForKey:#"image_url"] ] placeholderImage:[UIImage imageNamed:#"placeholder.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (error) {
NSLog(#"Error occured : %#", [error description]);
}
}];
}
// NSLog(#"%#",self.searchResults);
return cell;
}
- (void)myTask {
// Do something usefull in here instead of sleeping ...
sleep(1.5);
}
#pragma mark UISearchDisplay delegate
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
// [self.searchResults removeAllObjects];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#", searchText];
self.activePhotos = [NSMutableArray arrayWithArray:[self.staticDataSource filteredArrayUsingPredicate:resultPredicate]];
//[self.tableData filteredArrayUsingPredicate:resultPredicate];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles]
objectAtIndex:[self.searchDisplayController.searchBar
selectedScopeButtonIndex]]];
return YES;
}
- (void)loadPhotos:(NSInteger)page
{
NSString *apiURL = [NSString stringWithFormat:#"https://api.500px.com/v1/photos?feature=editors&page=%ld&consumer_key=%#",(long)page,kConsumerKey];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:apiURL]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (!error) {
NSError *jsonError = nil;
NSMutableDictionary *jsonObject = (NSMutableDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
NSLog(#"%#",jsonObject);
[self.staticDataSource addObjectsFromArray:[jsonObject objectForKey:#"photos"]];
self.currentPage = [[jsonObject objectForKey:#"current_page"] integerValue];
self.totalPages = [[jsonObject objectForKey:#"total_pages"] integerValue];
self.totalItems = [[jsonObject objectForKey:#"total_items"] integerValue];
self.activePhotos = self.staticDataSource;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}] resume];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
DetailViewController *vc = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
vc.StoreList = [self.activePhotos objectAtIndex:indexPath.row];
}
#end
Also I uploaded working project here
http://www.filedropper.com/needcustomcell
In your
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell;
// rest of your code
}
that would do it. You have used UITableViewCell you need to replace ot with your custom cell class.
I have a table view controller and a view controller.
StackTableViewController - list of strings
HomeViewController - empty view controller with a label
The HomeViewController label should present always the first sting of the StackTableViewController.
I need to make sure if the first string is deleted to present the new first string.
And this is where I have the problem...if I delete the first string and going back to the HomeViewController, the label is still the string I just deleted....And if I terminate the app and open it again, the correct string shown in the label.
This is How I did it so far:
this is the relevant methods in my StackTableViewController.h + .m:
#protocol StackTableViewControllerDelegate <NSObject>
#optional
-(void)didDeleteObject;
#end
#interface StackTableViewController : UITableViewController <UITableViewDataSource,UITableViewDelegate>
#property (strong,nonatomic) id<StackTableViewControllerDelegate> delegate;
#property (strong, nonatomic) NSString *currentTarget;
#end
#import "StackTableViewController.h"
#import "Target.h"
#import "StackTableViewCell.h"
#import "HomeViewController.h"
#import "CoreDataStack.h"
#interface StackTableViewController () <NSFetchedResultsControllerDelegate>
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultController;
#end
#implementation StackTableViewController
- (id)init {
self = [super initWithNibName:#"StackTableViewController" bundle:nil];
if (self) {
// Do something
[self.fetchedResultController performFetch:nil];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
Target *current = [self.fetchedResultController objectAtIndexPath:indexPath];
self.currentTarget = current.body;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
Target *current = [self.fetchedResultController objectAtIndexPath:indexPath];
self.currentTarget = current.body;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
Target *target = [self.fetchedResultController objectAtIndexPath:indexPath];
CoreDataStack *stack = [CoreDataStack defaultStack];
[[stack managedObjectContext] deleteObject:target];
[stack saveContext];
if ([_delegate respondsToSelector:#selector(didDeleteObject)]) {
[_delegate didDeleteObject];
}
}
And this is the relevant methods in the HomeViewController.h + .m:
#import <UIKit/UIKit.h>
#import "StackTableViewController.h"
#interface HomeViewController : UIViewController {
StackTableViewController *stackTableViewController;
}
#property (strong, nonatomic) IBOutlet UILabel *homeLabel;
- (IBAction)goToStack:(id)sender;
#import "StackTableViewController.h"
#interface HomeViewController () <StackTableViewControllerDelegate>
#end
#implementation HomeViewController
- (id)init {
self = [super initWithNibName:#"HomeViewController" bundle:nil];
if (self) {
// Do something
stackTableViewController = [[StackTableViewController alloc] init];
stackTableViewController.delegate = self;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self.navigationController setNavigationBarHidden:YES];
self.homeLabel.font = [UIFont fontWithName:#"Candara-Bold" size:40];
self.homeLabel.text = stackTableViewController.currentTarget;
}
- (void)didDeleteObject {
self.homeLabel.text = stackTableViewController.currentTarget;
}
- (IBAction)goToStack:(id)sender {
StackTableViewController *vc = [[StackTableViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
CoreDataStack.h +.m:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface CoreDataStack : NSObject
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+ (instancetype)defaultStack;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#end
#import "CoreDataStack.h"
#implementation CoreDataStack
#pragma mark - Core Data stack
#synthesize managedObjectContext = _managedObjectContext;
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
+ (instancetype)defaultStack {
static CoreDataStack *defaultStack;
static dispatch_once_t onceTocken;
dispatch_once (&onceTocken, ^{
defaultStack = [[self alloc] init];
});
return defaultStack;
}
- (NSURL *)applicationDocumentsDirectory {
// The directory the application uses to store the Core Data store file. This code uses a directory named "digitalCrown.Treats" in the application's documents directory.
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Treats" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Treats.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
#pragma mark - Core Data Saving support
- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
#end
Please help me to solve this, I was tying allot of ways but probably i'm missing something that got to do with view controller lifecycle or something.
(the CoreDataStack is a singleton)
tnx!!
I have noticed that you haven't exposed where and how do you go back as you said here:
And this is where I have the problem...if I delete the first string
and going back to the HomeViewController, the label is still the
string I just deleted
You have two choices to fix it:
1) Use viewWillAppear function and update the required text in it.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// get the most recent updates and assign to the UI
self.homeLabel.text = stackTableViewController.currentTarget;
}
So either you have just loaded this UI or coming back from the next UI, this will be going to help to get the latest updates.
2) Before calling popViewControllerAnimated make sure you have updated text.
if (_delegate && [_delegate respondsToSelector:#selector(didDeleteObject)])
{
// updated the UI through delegate
[_delegate didDeleteObject];
}
// This should be call when your work is done, since
// It will start ending the session of this UI so it will obviously
// miss the track of the `_delegate` variable.
[self.navigationController popViewControllerAnimated:YES];
Hope it helps!
I think you just need to reset your currentTarget property after you delete the object:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
Target *target = [self.fetchedResultController objectAtIndexPath:indexPath];
CoreDataStack *stack = [CoreDataStack defaultStack];
[[stack managedObjectContext] deleteObject:target];
[stack saveContext];
// The FRC should recognise that the deletion has happened, and consequently have updated its
// indexes, so the following will access the NEW first item, even if it has just changed:
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
Target *current = [self.fetchedResultController objectAtIndexPath:indexPath];
self.currentTarget = current.body;
if ([_delegate respondsToSelector:#selector(didDeleteObject)]) {
[_delegate didDeleteObject];
}
}
I think you'd be better off setting up your variables differently. Specifically, it seems like you would be better off to have a singleton object that holds and manages the stack for you, but here's a way to do what you have set up. I've written a basic app that includes all of its UI in code. Here are the pertinent files:
// AppDelegate.h
// StackTableTesting
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
// AppDelegate.m
// StackTableTesting
#import "AppDelegate.h"
#import "HomeViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
HomeViewController *vc = [[HomeViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
nav.navigationBar.translucent = NO;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
#end
// Created by Michael McEvoy on 12/23/14.
// Copyright (c) 2014 Mustard Seed Software LLC. All rights reserved.
#import <UIKit/UIKit.h>
#import "StackTableViewController.h"
#interface HomeViewController : UIViewController
#end
// HomeViewController.m
// StackTableTesting
#import "HomeViewController.h"
#import "StackTableViewController.h"
#interface HomeViewController () <StackTableViewControllerDelegate> {
}
#pragma mark -
#pragma mark - Private Properties
#property (strong, nonatomic) StackTableViewController *stackTableViewController;
#property (strong, nonatomic) UILabel *homeLabel;
#end
#pragma mark -
#pragma mark - Implementation
#implementation HomeViewController
#pragma mark -
#pragma mark - Initialization
- (instancetype)init {
self = [super init];
if (self != nil) {
self.stackTableViewController = [[StackTableViewController alloc] init];
self.stackTableViewController.delegate = self;
}
return self;
}
#pragma mark -
#pragma mark - View Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUserInterface];
}
#pragma mark -
#pragma mark - StackTableViewControllerDelegate Protocol Methods
- (void)didDeleteObject {
self.homeLabel.text = self.stackTableViewController.currentTarget;
}
#pragma mark -
#pragma mark - Button Presses
- (void)goToStack {
[self.navigationController pushViewController:self.stackTableViewController animated:YES];
}
#pragma mark -
#pragma mark - UI Setup
// This is because there's no Storyboard
- (void)setupUserInterface {
self.homeLabel = [[UILabel alloc] init];
self.homeLabel.font = [UIFont fontWithName:#"Candara-Bold" size:40];
self.homeLabel.frame = CGRectMake(20, 20, 200, 50);
self.homeLabel.text = self.stackTableViewController.currentTarget;
self.homeLabel.textColor = [UIColor blackColor];
[self.view addSubview:self.homeLabel];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(20, 80, 200, 50);
[button addTarget:self action:#selector(goToStack) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"Go To Stack" forState:UIControlStateNormal];
[self.view addSubview:button];
self.view.backgroundColor = [UIColor whiteColor];
}
#end
// StackTableViewController.h
// StackTableTesting
#import <UIKit/UIKit.h>
#protocol StackTableViewControllerDelegate <NSObject>
- (void)didDeleteObject;
#end
#interface StackTableViewController : UITableViewController {
}
#pragma mark -
#pragma mark - Properties
#property (weak, nonatomic) id <StackTableViewControllerDelegate> delegate;
#property (copy, nonatomic) NSString *currentTarget;
#end
// StackTableViewController.m
// StackTableTesting
#import "StackTableViewController.h"
#interface StackTableViewController () {
}
#pragma mark -
#pragma mark - Private Properties
#property (strong, nonatomic) NSMutableArray *stack;
#end
#pragma mark -
#pragma mark - Implementation
#implementation StackTableViewController
#pragma mark -
#pragma mark - Initialization
- (instancetype)init {
self = [super init];
if (self != nil) {
self.stack = [NSMutableArray array];
for (int i = 0; i < 10; i = i + 1) {
[self.stack addObject:[NSString stringWithFormat:#"Item %d", i + 1]];
self.currentTarget = self.stack[0];
}
}
return self;
}
#pragma mark -
#pragma mark - View Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"cell"];
}
#pragma mark -
#pragma mark - UITableViewDataSource Protocol Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.stack.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
cell.textLabel.text = self.stack[indexPath.row];
return cell;
}
#pragma mark -
#pragma mark - UITableViewDelegate Protocol Methods
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[self.stack removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
self.currentTarget = self.stack[0];
if ([_delegate respondsToSelector:#selector(didDeleteObject)]) {
[_delegate didDeleteObject];
}
}
#end
I think this setup does what you're trying to do, based on your question.
Just set the label value again when HomeViewController appear again. That should work since you said that re-launching the app. solves the problem. This should work too.
- (void)viewWillAppear:(BOOL)animated {
self.homeLabel.text = stackTableViewController.currentTarget;
}
- (id)init {
self = [super initWithNibName:#"HomeViewController" bundle:nil];
if (self) {
// Do something
stackTableViewController = [[StackTableViewController alloc] init];
stackTableViewController.delegate = self;
}
return self;
}
Here, you create a StackTableViewController and set self as the delegate. This is fine.
- (IBAction)goToStack:(id)sender {
StackTableViewController *vc = [[StackTableViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
Here, you create a brand new StackTableViewController and push it, without setting the delegate object. Any changes you do in the pushed view controller won't be fed back to the home view controller because the delegate is not set.
Either push your existing stackViewController, or set the delegate on your newly created one.
As an additional point, this:
#property (strong,nonatomic) id<StackTableViewControllerDelegate> delegate;
Coupled with this:
#interface HomeViewController : UIViewController {
StackTableViewController *stackTableViewController;
}
And the fact you're setting the delegate of the stack table view controller to the home view controller (which owns the stack table view controller) means you're creating a strong reference cycle. Delegates are normally weak references for this purpose.
I've programmatically created a UITableView within my MatchCenterViewController, however it doesn't seem to populate with the JSON data being returned by my cloud code function. It simply shows a blank View Controller. MatchCenterViewController is a ViewController embedded within a Navigation View Controller.
MatchCenterViewController.h:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import "AsyncImageView.h"
#import "SearchViewController.h"
#interface MatchCenterViewController : UIViewController <UITableViewDataSource>
#property (nonatomic) IBOutlet NSString *itemSearch;
#property (nonatomic, strong) NSArray *imageURLs;
#property (strong, nonatomic) NSString *matchingCategoryCondition;
#property (strong, nonatomic) NSString *matchingCategoryLocation;
#property (strong, nonatomic) NSNumber *matchingCategoryMaxPrice;
#property (strong, nonatomic) NSNumber *matchingCategoryMinPrice;
#property (strong, nonatomic) NSArray *matchCenterArray;
#end
MatchCenterViewController.m:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [_matchCenter dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *matchCenterDictionary= [self.matchCenterArray objectAtIndex:indexPath.row];
cell.textLabel.text = [matchCenterDictionary objectForKey:#"Title"];// title of the first object
// if([matchCenterDictionary objectForKey:#"Price"] != NULL)
// {
// cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#",[matchCenterDictionary objectForKey:#"Price"]];
// }
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.matchCenterArray = [[NSArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenterTest"
withParameters:#{
#"test": #"Hi",
}
block:^(NSDictionary *result, NSError *error) {
if (!error) {
self.matchCenterArray = [result objectForKey:#"Top 3"];
dispatch_async(dispatch_get_main_queue(), ^{
[_matchCenter reloadData];
});
NSLog(#"Test Result: '%#'", result);
}
}];
}
#end
How do you create this viewController? If you don't use [[MatchCenterViewController alloc] initWithNibName:... bundle:...] the table creation won't be called. This might happen because you have the viewController in a storyBoard, in this case initWithCoder: would be the method to overwrite.
I would recommend to move this code to viewDidLoad, which will be called regardless how the viewController was created . E.g.:
- (void)viewDidLoad {
[super viewDidLoad];
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
self.matchCenterArray = [[NSArray alloc] init];
}
So I've been trying this for a while now and it is completely frustrating me but here is a brief summary of what I am trying to do. I have a LocationTableViewController with a plus button on the top right to add new locations to the table view. When that happens, I enter the LocationEditViewController where I can enter the name of the location I want to add. After adding my text and hitting the save location button I want the code to bring me back to the LocationTableViewController and there in my table I see my newly added location. Posted below is the code of the two view controllers. Hopefully you guys can help me thanks a ton!!
#import <UIKit/UIKit.h>
#import "Location.h"
#import "User.h"
#interface LocationEditViewController : UIViewController <UITextFieldDelegate>
#property (strong, nonatomic) Location *location;
#property (strong, nonatomic) User *user;
#property (strong, nonatomic) UITextField *locationNameField;
- (void)saveLocation:(id) sender;
#end
#import "LocationEditViewController.h"
#interface LocationEditViewController ()
#end
#implementation LocationEditViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = #"Edit";
self.location = [[Location alloc] init];
self.user = [[User alloc] init];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UILabel *locationLabel = [[UILabel alloc] init];
locationLabel.frame = CGRectMake(20,15,50,30);
locationLabel.text = #"Name:";
[self.view addSubview:locationLabel];
self.locationNameField = [[UITextField alloc] init];
self.locationNameField.frame = CGRectMake(15,50,290,30);
self.locationNameField.borderStyle = UITextBorderStyleBezel;
self.locationNameField.keyboardType = UIKeyboardTypeDefault;
self.locationNameField.delegate = self;
[self.view addSubview:self.locationNameField];
UIButton *saveLocationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
saveLocationButton.frame = CGRectMake(15,400,290,50);
[saveLocationButton setTitle:#"Save Location" forState:UIControlStateNormal];
[saveLocationButton addTarget:self action:#selector(saveLocation:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:saveLocationButton];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)saveLocation:(id)sender {
self.location.name = self.locationNameField.text;
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:self.user.createdLocations];
[tempArray addObject:self.location];
self.user.createdLocations = [[NSArray alloc] initWithArray:tempArray];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Location Added"
message:#"This location is now accessable in the locations tab"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[self.tabBarController.tabBar.items[1] setBadgeValue:[NSString stringWithFormat:#"%i",self.user.createdLocations.count]];
[self dismissViewControllerAnimated:YES completion:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"somethingAddedNotification" object:nil];
}];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Here is the LocationTableViewController code:
#import <UIKit/UIKit.h>
#import "User.h"
#interface LocationTableViewController : UITableViewController
#property (strong, nonatomic) NSArray *locations;
#property (strong, nonatomic) User *user;
#property (strong, nonatomic) id _observer;
- (void) addLocationPressed;
#end
#import "LocationTableViewController.h"
#import "LocationEditViewController.h"
#import "LocationViewController.h"
#interface LocationTableViewController ()
#end
#implementation LocationTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
self.title = #"Locations";
self.user = [[User alloc] init];
UIBarButtonItem *addLocationButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addLocationPressed)];
self.navigationItem.rightBarButtonItem = addLocationButton;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//Location *loc = [[Location alloc] init];
//self.user.createdLocations = #[loc];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void) addLocationPressed
{
LocationEditViewController *locationEditVC = [[LocationEditViewController alloc] init];
[self presentViewController:locationEditVC animated:YES completion:nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.user.createdLocations.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
cell.textLabel.text = [self.user.createdLocations[indexPath.row] name];
NSLog(#"%#", [self.user.createdLocations[indexPath.row] name]);
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
LocationViewController *locationVC = [[LocationViewController alloc] init];
locationVC.location = self.user.createdLocations[indexPath.row];
[self.navigationController pushViewController:locationVC animated:YES];
}
- (void)viewWillAppear:(BOOL)animated {
__observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"somethingAddedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *notification)
{
[self.tableView reloadData];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:__observer];
}
For additional information here is the user class where I am getting the array
#import <Foundation/Foundation.h>
#import "Location.h"
#interface User : NSObject
#property (strong, nonatomic) Location *profilePhoto;
#property (strong, nonatomic) NSString *location;
#property (strong, nonatomic) NSArray *createdLocations;
-(id) initWithTitle: (Location *) aLoc
detail: (NSString *) aDet
filename: (NSArray *) aLocList;
//-(id)initWithJSON;
//+(NSString *)getPathToArchive;
//+(User *)getUser;
//+(void)saveUser:(User *)aUser;
#end
#import "User.h"
#import "Location.h"
#implementation User
- (id)init;
{
self = [self initWithTitle: [[Location alloc] init]
detail: #"Temp"
filename: [[NSArray alloc] init]];
return self;
}
-(id) initWithTitle: (Location *) aLoc
detail: (NSString *) aDet
filename: (NSArray *) aLocList
{
self = [super init];
if (self) {
self.profilePhoto = aLoc;
self.location = aDet;
self.createdLocations = aLocList;
}
return self;
}
#end
and the Locations class if necessary
#import <Foundation/Foundation.h>
#interface Location : NSObject
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *detail;
#property (strong, nonatomic) NSString *filename;
#property (strong, nonatomic) NSString *thumbnail;
-(id) initWithTitle: (NSString *) aTitle
detail: (NSString *) aDetail
filename: (NSString *) aFilename
thumbnail: (NSString *) aThumbnail;
#end
#import "Location.h"
#implementation Location
-(id)init
{
self = [self initWithTitle:#"Title"
detail:#"Detail"
filename:#"placeholder.jpg"
thumbnail:#"placeholder.jpg"];
return self;
}
-(id)initWithTitle:(NSString *)aTitle
detail:(NSString *)aDetail
filename:(NSString *)aFilename
thumbnail:(NSString *)aThumbnail
{
self = [super init];
if (self) {
self.name = aTitle;
self.detail = aDetail;
self.filename = aFilename;
self.thumbnail = aThumbnail;
}
return self;
}
#end
Hopefully you guys can help me! Thanks again!!!
Have you set a breakpoint on...
[self.tableView reloadData];
...in your observer block? Does it get hit? Separately, Drew Crawford is wary of that API you are using (http://sealedabstract.com/code/nsnotificationcenter-with-blocks-considered-harmful/).
Like the comment said, this is definitely a problem, and may in fact be the root cause. You must use the __weak reference inside the block or else NSNotificationCenter owns a strong reference to self and self owns a strong reference to NSNotificationCenter, and you have a retain cycle.
- (void)viewWillAppear:(BOOL)animated {
__weak LocationTableViewController *weakSelf = self;
__observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"somethingAddedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *notification)
{
[weakSelf.tableView reloadData];
}];
}
Hi I am working off of a Ray Wenderlich tutorial that displays a list of purchased IAP in a tableview. The table view is not sorted and I would like them to be sorted by name.
Either the tableview loads with unsorted content, no content, or with the code below I get the error:
[HMContentController unlockedCycleClass]: unrecognized selector sent to instance 0x1e56ee80
2013-06-08 11:15:47.582 GCC7.1[6659:907] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HMContentController unlockedCycleClass]: unrecognized selector sent to instance 0x1e56ee80'*
Here is the code that I have tried.
HMContenController.h //the model
#import <Foundation/Foundation.h>
UIKIT_EXTERN NSString *const HMContentControllerCurrentCycleClassDidChangeNotification;
UIKIT_EXTERN NSString *const HMContentControllerUnlockedCycleClassDidChangeNotification;
#class CycleClass;
#interface HMContentController : NSObject
+ (HMContentController *)sharedInstance;
- (NSArray *) unlockedCycleClass;
- (NSArray *) sortedArray;
#property (nonatomic, strong) CycleClass * currentCycleClass;
(void)unlockCycleClassWithDirURL:(NSURL *)dirURL;
#end
HMContentController.m
#import "HMContentController.h"
#import "CycleClass.h"
NSString *const HMContentControllerCurrentCycleClassDidChangeNotification = #"HMContentControllerCurrentThemeDidChangeNotification";
NSString *const HMContentControllerUnlockedCycleClassDidChangeNotification = #"HMContentControllerUnlockedThemesDidChangeNotification";
#implementation HMContentController {
NSMutableArray * _unlockedCycleClass;
NSArray * _sortedArray; //my added code
}
+ (HMContentController *)sharedInstance {
static dispatch_once_t once;
static HMContentController * sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
if ((self = [super init])) {
_unlockedCycleClass = [NSMutableArray array];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];//my added sorting code
_sortedArray=[_unlockedCycleClass sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]]; //my added sorting code
BOOL hasRunBefore = [[NSUserDefaults standardUserDefaults] boolForKey:#"hasRunBefore"];
if (!hasRunBefore) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"hasRunBefore"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self setHints:20];
}
}
return self;
}
- (void)setCurrentCycleClass:(CycleClass *)currentCycleClass {
_currentCycleClass = currentCycleClass;
[[NSNotificationCenter defaultCenter] postNotificationName:HMContentControllerCurrentCycleClassDidChangeNotification object:nil];
}
- (NSArray *)unlockedCycleClass {
return _unlockedCycleClass;
}
- (void)unlockCycleClassWithDirURL:(NSURL *)dirURL {
CycleClass * cycleClass = [[CycleClass alloc] initWithDirURL:dirURL];
// Make sure we don't already have class
BOOL found = FALSE;
for (int i = 0; i < _sortedArray.count; ++i) {
CycleClass * curCycleClass = _unlockedCycleClass[i];
if ([cycleClass.name isEqualToString:curCycleClass.name]) {
NSLog(#"Cycle Class already unlocked, replacing...");
if (self.currentCycleClass == curCycleClass) {
self.currentCycleClass = cycleClass;
}
_unlockedCycleClass[i] = cycleClass;
found = TRUE;
break;
}
}
if (!found) {
// Unlock new theme
[_unlockedCycleClass addObject:cycleClass];
}
if (!self.currentTheme) {
self.currentCycleClass = cycleClass;
}
// Notify observers
[[NSNotificationCenter defaultCenter] postNotificationName:HMContentControllerUnlockedCycleClassDidChangeNotification object:self];
}
- (void)unlockContentWithDirURL:(NSURL *)dirURL {
if ([CycleClass classAtURL:dirURL]) {
[self unlockCycleClassWithDirURL:dirURL];
}
else if ([HMTheme themeAtURL:dirURL]) {
[self unlockThemeWithDirURL:dirURL];
} else if ([HMWords wordsAtURL:dirURL]) {
[self unlockWordsWithDirURL:dirURL];
} else {
NSLog(#"Unexpected content!");
}
}
#end
MyCycleClass //The tableview
#import "MyCycleClassesViewController.h"
#import "HMContentController.h"
#import "CycleClass.h"
#interface MyCycleClassesViewController () <UIActionSheetDelegate>
#property (weak, nonatomic) IBOutlet UILabel *classLabel;
#end
#implementation MyCycleClassesViewController {
NSIndexPath * _selectedIndexPath;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self tableView] reloadData]; //refresh table
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(unlockedCycleClassChanged:) name:HMContentControllerUnlockedCycleClassDidChangeNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)unlockedCycleClassChanged:(NSNotification *)notification {
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [HMContentController sharedInstance].unlockedCycleClass.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
CycleClass * class = [HMContentController sharedInstance].sortedArray[indexPath.row]; //originally sortedArray was .unlockedCycleClass which loaded unordered classes
if ([HMContentController sharedInstance].currentCycleClass == class) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
_selectedIndexPath = indexPath;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
cell.textLabel.text = class.name;
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSMutableArray * indexPathsToReload = [NSMutableArray array];
if (_selectedIndexPath) {
[indexPathsToReload addObject:_selectedIndexPath];
}
if (![indexPath isEqual:_selectedIndexPath]) {
[indexPathsToReload addObject:indexPath];
}
CycleClass * class = [HMContentController sharedInstance].sortedArray[indexPath.row];
[[HMContentController sharedInstance] setCurrentCycleClass:class];
[self.tableView reloadRowsAtIndexPaths:indexPathsToReload withRowAnimation:UITableViewRowAnimationNone];
}
#end
CycleClass.h/m (Just incase it is helpful, the IAP info)
#import <Foundation/Foundation.h>
#interface CycleClass : NSObject
#property (nonatomic, readonly, strong) NSURL * dirURL;
#property (nonatomic, readonly, strong) NSString * name;
#property (nonatomic, readonly, strong) NSURL * noMusicURL;
#property (nonatomic, readonly, strong) NSURL * withMusicURL;
#property (nonatomic, readonly, strong) NSURL * imageURL;
+ (BOOL)classAtURL:(NSURL *)url;
- (id)initWithDirURL:(NSURL *)url;
#end
import "CycleClass.h"
#implementation CycleClass
+ (BOOL)classAtURL:(NSURL *)url {
NSURL * plistURL = [url URLByAppendingPathComponent:#"cycleClass.plist"];
return [[NSFileManager defaultManager] fileExistsAtPath:plistURL.path];
}
- (id)initWithDirURL:(NSURL *)url {
if ((self = [super init])) {
_dirURL = url;
NSURL * plistURL = [url URLByAppendingPathComponent:#"cycleClass.plist"];
NSDictionary * dict = [NSDictionary dictionaryWithContentsOfURL:plistURL];
if (dict == nil) return nil;
_name = dict[#"name"];
NSString * noMusicString = dict[#"noMusic"];
if (noMusicString) {
_noMusicURL = [url URLByAppendingPathComponent:noMusicString];
}
NSString * withMusicString = dict[#"withMusic"];
if (withMusicString) {
_withMusicURL = [url URLByAppendingPathComponent:withMusicString];
}
NSString * imageString = dict[#"image"];
if (imageString) {
_imageURL = [url URLByAppendingPathComponent:imageString];
}
}
return self;
}
#end
And the IAP plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>2. Simon Beginner Hills</string>
<key>noMusic</key>
<string>simon2NoMusic.mp3</string>
<key>withMusic</key>
<string>simon2Music.mp3</string>
<key>image</key>
<string>simongraph2.png</string>
</dict>
</plist>
I'm completely stumped on this but it seems like there should be a simple obvious fix... please help!!
As rdelmar said, you need to sort after you fill the array.
You want to make sure sortedArray is updated whenever unlockedCycleClass (which by the way is an Array, not a Class) is updated. So eg in HMContentController.m, you could repeat your lines:
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];//my added sorting code
_sortedArray=[_unlockedCycleClass sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]]; //my added sorting code
inside the unlockCycleClassWithDirURL function, after you do
[_unlockedCycleClass addObject:cycleClass];
This is not the most efficient way to do things, but I think it should work.