I am trying to create a setting page for an app that uses Table View Controllers to pass data back changing the detailed text label of the first view controller depending on the text of the selected row of the second Table View Controller. I put a NSLog in under my delegate method on the initial Table View Controller and it is not being called. I am very stuck, any help is greatly appreciated!
Initial VC .h:
// EditAlarmTVC.h (Initial table view controller .h file)
//
#import <UIKit/UIKit.h>
#import "AlarmSoundTVC.h"
#interface EditAlarmTVC : UITableViewController <AlarmSoundDelegate>
#property (weak, nonatomic) IBOutlet UIDatePicker *datePicker;
#property (weak, nonatomic) IBOutlet UITableViewCell *offMethodCell;
#property (weak, nonatomic) IBOutlet UITableViewCell *repeateCell;
#property (weak, nonatomic) IBOutlet UITableViewCell *alarmLabelCell;
#property (weak, nonatomic) IBOutlet UITableViewCell *alarmSoundCell;
#property (strong) NSManagedObjectModel *alarm;
- (IBAction)cancelSetAlarm:(id)sender;
- (IBAction)saveSetAlarm:(id)sender;
#end
Initial VC .m:
//EditAlarmTVC.m (Initial table view controller.m file)
#import "EditAlarmTVC.h"
#import <CoreData/CoreData.h>
#interface EditAlarmTVC ()
#property (weak, nonatomic) IBOutlet UITableView *myTableView;
#property (retain, nonatomic) NSMutableArray *detailedTextLabels;
#end
#implementation EditAlarmTVC
#synthesize alarm;
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"offMethod"];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"timePicker"];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"repeateLabelSoundCell"];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"deleteAlarmCell"];
self.detailedTextLabels =[NSMutableArray array];
[self.detailedTextLabels addObject:#"Off"];
[self.detailedTextLabels addObject:#"Never"];
[self.detailedTextLabels addObject:#"Wake up, get up!"];
[self.detailedTextLabels addObject:#"Default"];
}
-(void) viewWillAppear:(BOOL)animated{
[self.offMethodCell.detailTextLabel setText:[self.detailedTextLabels objectAtIndex:0]];
[self.repeateCell.detailTextLabel setText:[self.detailedTextLabels objectAtIndex:1]];
[self.alarmLabelCell.detailTextLabel setText:[self.detailedTextLabels objectAtIndex:2]];
[self.alarmSoundCell.detailTextLabel setText:[self.detailedTextLabels objectAtIndex:3]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)sendSelection:(NSString *)selectedAlarm{
NSLog(#"hello");
[self.detailedTextLabels replaceObjectAtIndex:3 withObject:selectedAlarm];
[self.myTableView reloadData];
}
-(NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context =nil;
id delegat = [[UIApplication sharedApplication] delegate];
if ([delegat respondsToSelector:#selector(managedObjectContext)]) {
context = [delegat managedObjectContext];
}
return context;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.section ==2 & indexPath.row==0){
[self performSegueWithIdentifier:#"repeateSegue" sender:indexPath];
} else if (indexPath.section ==2 & indexPath.row==1){
[self performSegueWithIdentifier:#"alarmLabelSegue" sender:indexPath];
} else if (indexPath.section ==0 & indexPath.row==0){
[self performSegueWithIdentifier:#"offMethodSegue" sender:indexPath];
} else if (indexPath.section ==2 & indexPath.row==2){
[self performSegueWithIdentifier:#"alarmSoundSegue" sender:indexPath];
} else if (indexPath.section ==3 & indexPath.row==0){
//DELETE ALARM
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Alarm" inManagedObjectContext:context];
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc]init];
[timeFormatter setDateFormat:#"h:mm"];
NSDate *timeNSDate = [self.datePicker date];
NSString *timeString = [timeFormatter stringFromDate:timeNSDate];
NSDateFormatter *amPmFormatter = [[NSDateFormatter alloc]init];
[amPmFormatter setDateFormat:#"a"];
NSDate *amPmNSDate = [self.datePicker date];
NSString *amPmString = [amPmFormatter stringFromDate:amPmNSDate];
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"time == %#", timeString];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"amPm == %#", amPmString];
NSPredicate *p3 = [NSPredicate predicateWithFormat:#"offMethod == %#", self.offMethodCell.detailTextLabel.text];
NSPredicate *p4 = [NSPredicate predicateWithFormat:#"repeate == %#", self.repeateCell.detailTextLabel.text];
NSPredicate *p5 = [NSPredicate predicateWithFormat:#"alarmSound == %#", self.alarmSoundCell.detailTextLabel.text];
NSPredicate *p6 = [NSPredicate predicateWithFormat:#"alarmLabel == %#", self.alarmLabelCell.detailTextLabel.text];
NSPredicate *alarmsPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[p1, p2, p3, p4, p5, p6]];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:alarmsPredicate];
NSError *error;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items)
{
[context deleteObject:managedObject];
}
EditAlarmTVC *goToEditAlarmTVC = [self.storyboard instantiateViewControllerWithIdentifier:#"setAlarmVC"];
[self.navigationController pushViewController:goToEditAlarmTVC animated:YES];
[self dismissViewControllerAnimated:NO completion:nil];
} else
return;
}
#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 {
if ([segue.identifier isEqualToString:#"alarmSoundSegue"])
{
AlarmSoundTVC *alarmSoundTVC = [[AlarmSoundTVC alloc]init];
[alarmSoundTVC setDelegate:self];
}
}
#end
Second VC .h:
//AlarmSoundTVC.h (Second table view controller .h file)
#import <UIKit/UIKit.h>
#protocol AlarmSoundDelegate <NSObject>
#required
-(void) sendSelection:(NSString *)selectedAlarm;
#end
#interface AlarmSoundTVC : UITableViewController
#property (strong, nonatomic) IBOutlet UITableView *myTableView;
#property (nonatomic, weak) id <AlarmSoundDelegate> delegate;
#end
Second VC .m:
// AlarmSoundTVC.m (Second table view controller .m file)
#import "AlarmSoundTVC.h"
#import "EditAlarmTVC.h"
#interface AlarmSoundTVC ()
#end
#implementation AlarmSoundTVC
- (void)viewDidLoad {
[super viewDidLoad];
self.myTableView.delegate = self;
}
-(void) viewWillDisappear:(BOOL)animated{
NSIndexPath *indexPath = [self.myTableView indexPathForSelectedRow];
UITableViewCell *selectedCell =[self.myTableView cellForRowAtIndexPath:(indexPath)];
NSString *selectedAlarm = selectedCell.textLabel.text;
[self.delegate sendSelection:selectedAlarm] ;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
#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 {
// }
#end
I am sorry if there is some extra unnecessary code in there. I am a bit new and wanted to be sure I provided plenty of information. Thanks again!
In your prepareForSegue you are setting the delegate on a new, local, instance of AlarmSoundTVC, that will be discarded as soon as the method exits. You need to use the instance that is going to be presented, which can be accessed via the destinationViewController property of the segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"alarmSoundSegue"])
{
AlarmSoundTVC *alarmSoundTVC = (AlarmSoundTVC *)segue.destinationViewController;
[alarmSoundTVC setDelegate:self];
}
}
Related
I have a view controller(A) with a label displaying the balance of some transactions. within the view controller I have a container view that holds a table view controller (B)
The balance updates when i change view controllers (with Tab bar) but I want to make it so that if a row gets deleted in controller B the label in controller A updates.
I am just learning objective C and i am not very comfortable with delegates. Here is the code that relates to what I am saying:
ControllerB header
#import <UIKit/UIKit.h>
#protocol KTTransactionsTableViewControllerDelegate
-(void) updateLabelWithString:(NSString*)string;
#end
#interface KTTransactionsTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
#property (strong, nonatomic)NSMutableArray *transactions;
#property (weak, nonatomic) id<KTTransactionsTableViewControllerDelegate>delegate;
#end
Deleting rows in controller B:
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
NSManagedObjectContext *context = [KTCoreDateHelper managedObjectContext];
[context deleteObject:(KTTransaction*)[self.transactions objectAtIndex:indexPath.row]];
[self.transactions removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// Save the context.
NSError *error;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//update the balance display
KTSummaryViewController *summaryVC = [[KTSummaryViewController alloc]init];
NSString* remainingBalance = [summaryVC calculateBalance];
[self.delegate updateLabelWithString:remainingBalance];
}
}
Controller A header
#import <UIKit/UIKit.h>
#import "KTTransactionsTableViewController.h"
#interface KTSummaryViewController : UIViewController <KTTransactionsTableViewControllerDelegate>
#property (strong, nonatomic) IBOutlet UILabel *remainingBalance;
#property (strong, nonatomic) IBOutlet UIView *dateView;
#property (strong, nonatomic) IBOutlet UIView *categoryView;
#property (strong, nonatomic) IBOutlet UISegmentedControl *segmentedControl;
- (IBAction)segmentValueChanged:(UISegmentedControl *)sender;
-(NSString*)calculateBalance;
#end
Controller A implementation:
#import "KTSummaryViewController.h"
#import "KTCategory.h"
#import "KTCoreDateHelper.h"
#import "KTTransaction.h"
#interface KTSummaryViewController ()
#end
#implementation KTSummaryViewController
#synthesize dateView,categoryView,remainingBalance;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
remainingBalance.textColor = [UIColor lightGrayColor];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self updateLabelWithString:[self calculateBalance]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSString*) calculateBalance
{
//Get all the Income transactions
NSManagedObjectContext *context = [KTCoreDateHelper managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"KTTransaction"
inManagedObjectContext:context];
[request setEntity:entity];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"amount"];
// Create an expression to represent the sum of marks
NSExpression *maxExpression = [NSExpression expressionForFunction:#"sum:"
arguments:#[keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"amountSum"];
[expressionDescription setExpression:maxExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error = nil;
NSArray *amountResult = [context executeFetchRequest:request error:&error];
NSLog(#"%#", amountResult);
NSNumber *sumOfAmounts = [[amountResult objectAtIndex:0] objectForKey:#"amountSum"];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc]init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSString *sumCurrency = [numberFormatter stringFromNumber:sumOfAmounts];
NSLog(#"%#",sumCurrency);
return sumCurrency;
}
-(void)updateLabelWithString:(NSString *)string{
remainingBalance.text = string;
}
Any help with understanding what I am doing wrong would be greatly appreciated!
It looks like you never set controller A as the delegate of controller B. Since B is in a container view of controller A, prepareForSegue will be called when these controllers are instantiated. You can set controller A as the delegate there (this code is in controller A),
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"Embed"]) {
KTTransactionsTableViewController *tvc = segue.destinationViewController;
tvc.delegate = self;
}
}
If you only have one segue from this controller, you can omit the if statement. If you use that clause, be sure you give the segue the same identifier in IB as you pass here.
My setup:
I have a UIViewController with a TableView inserted in it. Also in the viewController I have a textfield and a slider. I am trying to persist data from my text field, store it and then have fetch request populate the cells with whatever information was placed in the textField. However, when I hit the save button nothing populates the tableCell. The .m file below is of my UIVeiwController. Any help would be great as to why the cell isn't populating. Thanks!
#import "BottleViewController.h"
#import "LogEntry.h"
#import "CoreDataStack.h"
#interface BottleViewController () <NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) IBOutlet UILabel *ouncesLabel;
#property (strong, nonatomic) UISlider *slider;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) IBOutlet UILabel *entryAmount;
#property (strong, nonatomic) IBOutlet UITextField *textField;
#end
#implementation BottleViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.fetchedResultsController performFetch:nil];
}
-(void)dismissSelf{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
-(void)insertLogEntry {
CoreDataStack *coreDataStack = [CoreDataStack defaultStack];
LogEntry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"LogEntry" inManagedObjectContext:coreDataStack.managedObjectContext];
entry.amount = self.slider.value;
entry.date = [[NSDate date] timeIntervalSince1970];
entry.testString = self.textField.text;
[coreDataStack saveContext];
}
- (IBAction)backWasPressed:(id)sender {
[self dismissSelf];
}
- (IBAction)mySlider:(id)sender {
self.slider = (UISlider*)sender;
NSString *newText = [[NSString alloc] initWithFormat:#"%d", (int)self.slider.value];
self.ouncesLabel.text = newText;
}
- (IBAction)saveWasPressed:(id)sender {
[self.view endEditing:YES];
[self insertLogEntry];
}
-(NSFetchRequest *)entryListFetchRequest{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"LogEntry"];
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO]];
return fetchRequest;
}
-(NSFetchedResultsController *)fetchedResultsController{
if(_fetchedResultsController != nil){
return _fetchedResultsController;
}
CoreDataStack *coreDataStack =[CoreDataStack defaultStack];
NSFetchRequest *fetchRequest = [self entryListFetchRequest];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:coreDataStack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
#pragma mark UITableViewDelegates
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return self.fetchedResultsController.sections.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
id<NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier =#"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
LogEntry *entry = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = entry.testString;
return cell;
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self.tableView reloadData];
}
#end
Nevermind figured it out. I forgot to connect the datasource and delegates.
I have a problem with the bookstableview.m segue. if you look bellow there where it says PFObject *object = [self.objects objectatindex.indexPath.row] there is an error under "objects" saying "Property objects not found on object of type 'BooksTableViewController'."
here is the rest of the code: Bookstableview.m
#import "BooksTableViewController.h"
#import "BookDetailViewController.m"
#interface BooksTableViewController ()
#end
#implementation BooksTableViewController
#synthesize bookstableview;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(RetrieveDatafromParse)];
}
-(void) RetrieveDatafromParse {
PFQuery * getbooks = [PFQuery queryWithClassName:#"BooksTableView"];
[getbooks findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
Booksarray =[[NSArray alloc] initWithArray: objects];
}
[bookstableview reloadData];
NSLog(#"%#",objects);
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
return Booksarray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
UITableViewCell * cell = [bookstableview dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell ==nil) {
cell = [[ UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
PFObject * tempObject = [Booksarray objectAtIndex:indexPath.row];
cell.textLabel.text = [tempObject objectForKey:#"Books"];
cell.detailTextLabel.text= [tempObject objectForKey:#"Code"];
return cell;
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"booksseg" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowDetails"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
PFObject * object = [self.objects objectAtIndex:indexPath.row];
PFFile *file = [object objectForKey:#"BooksTableView"];
[[segue destinationViewController] setFile:file];
}
}
#end
bookstableview.h
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface BooksTableViewController : UITableViewController <UITableViewDelegate >
{
NSArray * Booksarray;
}
#property (strong, nonatomic) IBOutlet UITableView *bookstableview;
#end
Booksdetailview.h
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import "BooksTableViewController.h"
#interface BookDetailViewController : UIViewController {
}
#property (weak, nonatomic) IBOutlet UIImageView *BookImage;
#property (weak, nonatomic) IBOutlet UILabel *bookTitle;
#property (weak, nonatomic) IBOutlet UILabel *bookDesc;
#property (weak,nonatomic) PFObject *file;
#end
Replace self.objects with Booksarray:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowDetails"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
PFObject * object = [Booksarray objectAtIndex:indexPath.row];
PFFile *file = [object objectForKey:#"BooksTableView"];
[[segue destinationViewController] setFile:file];
}
}
Also: normally, only class names are capitalized, while variables and methods begin with a lower-case letter, so _booksArray would be a more appropriate name for the variable. Helps avoid confusion and prevents overlap between class/variable names.
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;
}
I am currently developing an app the needs to have an initial setting screen (name, date, amount of deposit, and percentage). I have created a view controller that will add and then save to core data. I can also retrieve the information.
What I need next is to be able to pop into another view controller and enter continuing number for the same person. Whenever i enter data into the second view controller it comes back as an addition as if it were a new person. I need some help to figure it out as i am still new and learning.
Below is the .h and .m files of the two view controllers.
ForexDetailViewController.h
#import <UIKit/UIKit.h>
#interface ForexDetailViewController : UIViewController
#property (weak, nonatomic) IBOutlet UITextField *nameTextField;
#property (weak, nonatomic) IBOutlet UITextField *startamountTextField;
#property (weak, nonatomic) IBOutlet UITextField *startmonthTextField;
#property (strong, nonatomic) IBOutlet UITextField *startpercentTextField;
#property (weak, nonatomic) IBOutlet UITextField *endmonth1year1TextField;
#property (weak, nonatomic) IBOutlet UITextField *endpercentmonth1year1TextField;
#property (strong) NSManagedObject *device;
- (IBAction)cancel:(id)sender;
- (IBAction)save:(id)sender;
- (IBAction)textFieldReturn:(id)sender;
- (IBAction)calcultae;
#end
ForexDetailViewController.m
#import "ForexDetailViewController.h"
#interface ForexDetailViewController ()
#end
#implementation ForexDetailViewController
- (IBAction)textFieldReturn:(id)sender
{
[sender resignFirstResponder];
}
#synthesize device;
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (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.
if (self.device) {
[self.nameTextField setText:[self.device valueForKey:#"name"]];
[self.startamountTextField setText:[self.device valueForKey:#"startamount"]];
[self.startmonthTextField setText:[self.device valueForKey:#"startmonth"]];
[self.startpercentTextField setText:[self.device valueForKey:#"startpercent"]];
[self.endmonth1year1TextField setText:[self.device valueForKey:#"endmonth1year1"]];
[self.endpercentmonth1year1TextField setText:[self.device valueForKey:#"endpercentmonth1year1"]];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
if (self.device) {
// Update existing device
[self.device setValue:self.nameTextField.text forKey:#"name"];
[self.device setValue:self.startamountTextField.text forKey:#"startamount"];
[self.device setValue:self.startmonthTextField.text forKey:#"startmonth"];
[self.device setValue:self.startpercentTextField.text forKey:#"startpercent"];
[self.device setValue:self.endmonth1year1TextField.text forKey:#"startpercent"];
[self.device setValue:self.endpercentmonth1year1TextField.text forKey:#"endpercentmonth1year1"];
} else {
// Create a new device
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Calculations" inManagedObjectContext:context];
[newDevice setValue:self.nameTextField.text forKey:#"name"];
[newDevice setValue:self.startamountTextField.text forKey:#"startamount"];
[newDevice setValue:self.startmonthTextField.text forKey:#"startmonth"];
[newDevice setValue:self.startpercentTextField.text forKey:#"startpercent"];
[newDevice setValue:self.endmonth1year1TextField.text forKey:#"endmonth1year1"];
[newDevice setValue:self.endpercentmonth1year1TextField.text forKey:#"endpercentmonth1year1"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
TimeLineViewController.h
#import <UIKit/UIKit.h>
#interface TimeLineViewController : UIViewController
#property (weak, nonatomic) IBOutlet UITextField *startamountTextField;
#property (weak, nonatomic) IBOutlet UITextField *endmonth1year1TextField;
#property (weak, nonatomic) IBOutlet UITextField *endpercentmonth1year1TextField;
#property (strong) NSManagedObject *device;
- (IBAction)cancel:(id)sender;
- (IBAction)save:(id)sender;
- (IBAction)textFieldReturn:(id)sender;
#end
TimeLineViewController.m
#import "TimeLineViewController.h"
#interface TimeLineViewController ()
#end
#implementation TimeLineViewController
- (IBAction)textFieldReturn:(id)sender
{
[sender resignFirstResponder];
}
#synthesize device;
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (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.
if (self.device) {
[self.startamountTextField setText:[self.device valueForKey:#"startamount"]];
[self.endmonth1year1TextField setText:[self.device valueForKey:#"endmonthe1year1"]];
[self.endpercentmonth1year1TextField setText:[self.device valueForKey:#"endpercentmonth1year1"]];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
if (self.device) {
// Update existing device
[self.device setValue:self.startamountTextField.text forKey:#"startamount"];
[self.device setValue:self.endmonth1year1TextField.text forKey:#"endmonth1year1"];
[self.device setValue:self.endpercentmonth1year1TextField.text forKey:#"endpercentmonth1year1"];
} else {
// Create a new device
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Calculations" inManagedObjectContext:context];
[newDevice setValue:self.startamountTextField.text forKey:#"startamount"];
[newDevice setValue:self.endmonth1year1TextField.text forKey:#"endmonth1year1"];
[newDevice setValue:self.endpercentmonth1year1TextField.text forKey:#"endpercentmonth1year1"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
ForexViewController.m
#import "ForexViewController.h"
#import "ForexDetailViewController.h"
#interface ForexViewController ()
#property (strong) NSMutableArray *devices;
#end
#implementation ForexViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Calculations"];
self.devices = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#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 the number of rows in the section.
return self.devices.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSManagedObject *device = [self.devices objectAtIndex:indexPath.row];
[cell.textLabel setText:[NSString stringWithFormat:#"%#", [device valueForKey:#"name"]]];
[cell.detailTextLabel setText:[device valueForKey:#"startamount"]];
[cell.detailTextLabel setText:[device valueForKey:#"startmonth"]];
[cell.detailTextLabel setText:[device valueForKey:#"startpercent"]];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSManagedObjectContext *context = [self managedObjectContext];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete object from database
[context deleteObject:[self.devices objectAtIndex:indexPath.row]];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
// Remove device from table view
[self.devices removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
}
#pragma mark - Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdateDevice"]) {
NSManagedObject *selectedDevice = [self.devices objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
ForexDetailViewController *destViewController = segue.destinationViewController;
destViewController.device = selectedDevice;
}
}
#end
Where is your code that shows how you show each view?
If you're doing this via Storyboard segue's, you should be setting the device property on the view controllers when you're preparing for segue.
Currently, I don't see how self.device is getting set for either of these view controllers so each time the view loads, it think's there isn't a device and therefore creates a new record for one.
A quick example with a storyboard segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"changeDeviceSettings"]) {
SomeViewController *vc = segue.destinationViewController;
vc.device = selectedDeviceGoesHere;
}
}