0

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!!

JohnBigs
  • 2,691
  • 3
  • 31
  • 61
  • So, you accepted a solution that answered after me and provided same solution? – hasan Dec 29 '14 at 19:27
  • #2 in the solution i accepted was my problem, i forgot to pop the view controller, i didn't use #1 which offered the same solution as you did buddy. I would accept your solution otherwise @hasan83 – JohnBigs Dec 29 '14 at 20:05
  • actually solution number one works too. which submitted first. which is simpler. you don't need the delegate. The funny thing that someone down voted it :) anyway thanks for your polite response. – hasan Dec 29 '14 at 20:13
  • 1
    You right, down vote for something that works is kinda stupid, anyway i up voted it for the balance :) – JohnBigs Dec 29 '14 at 20:15

5 Answers5

1

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!

NeverHopeless
  • 11,077
  • 4
  • 35
  • 56
  • `if (_delegate && [_delegate respondsToSelector:@selector(didDeleteObject)])` you cont have to check for `_delegate` existence. either it exists or is nil. As you can send any message to nil and it will return nil, the code will not crash and behave correct. you claim `// someone else may mistakenly, miss the _delegate assignment and code will crash.` is plain wrong. – vikingosegundo Dec 26 '14 at 22:54
  • @vikingosegundo, Thank you for highlighting, I just explored that it will not crash but it is expected to work faster: http://stackoverflow.com/questions/6479267/objective-c-why-check-nil-before-respondstoselector – NeverHopeless Dec 26 '14 at 23:08
  • 1st: it would only speed up things if the delegate is not set in most of the cases. if it is set, it is a extra operation that will cost time. 2.: what is the slowest? a) with extra call? b) without the check or c) the user operating the app? – vikingosegundo Dec 26 '14 at 23:34
  • hey man, now that I looked at it I saw that i have never did [self.navigationController popViewControllerAnimated:YES]; in my stacktableviewcontroller, and that might be it, I added it to viewWillDisappear, also updated the currentTarget between the delegate methods as you said but used my original delegate methods...now its working :) so thanks :) – JohnBigs Dec 27 '14 at 00:40
  • now this brings another issue that when there are no objects in the table view and the label is empty, now when O add a new object to the table view the label is not updated.. – JohnBigs Dec 27 '14 at 01:01
0

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];
    }
}
pbasdf
  • 21,386
  • 4
  • 43
  • 75
0

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.

mbm29414
  • 11,558
  • 6
  • 56
  • 87
  • I have a singleton class...the CoreDataStack is a singelton – JohnBigs Dec 23 '14 at 18:22
  • still not working, the your Haddon for to my code is what pbasdf suggested...but its not working :/ – JohnBigs Dec 23 '14 at 18:35
  • @JohnBigs The `CoreDataStack` might be a singleton, but you're referencing your persistent instance of `StackTableViewController` to determine what to display on `HomeViewController`, so you're not really **USING** it as a singleton. Also, if you copy my code into a new project, it should work. It's working for me. I can delete items, and `HomeViewController` shows the appropriate text when I return to it. – mbm29414 Dec 23 '14 at 18:41
  • thanks allot for the code, I appreciate it, but I really want someone to look at my code and telling me what I did wrong :/, I feel this is the best way for me to understand...will you do that? I can update my question and add the rest of the CoreDataStack class. my delegate is set the same as yours @mbm29414 – JohnBigs Dec 23 '14 at 19:02
  • i added the CoreDataStack class anyway so if you have the time and energy for this that will be awesome :) – JohnBigs Dec 23 '14 at 19:08
  • @JohnBigs I answered the way I did because: 1) You're doing things in a fairly non-standard way (calling `initWithNibName:` from `init`, keeping and referencing a child view controller's data source, etc.) and, 2) I didn't have your UI for testing, so I wrote my own. If you just want a quick and dirty "fix", I think you need to update `self.currentTarget` in `StackTableViewController` in `tableView:commitEditingStyle:...`. Overall, though, I think the **REAL** answer is to change the architecture so that you don't have iOS anti-patterns in your code (like referencing a child's data source). – mbm29414 Dec 23 '14 at 21:37
  • what am I doing that is called "referencing a child's data source"? I'm a newbie, sorry if I asked basic questions, I just started few weeks ago.. – JohnBigs Dec 23 '14 at 21:41
  • this was my brothers account so this is why its not a new account :) – JohnBigs Dec 23 '14 at 21:42
  • and I didn't mean to critic your answer, I understand now what you did and it's great. but since I shared my core data class can you maybe give me an answer based on that? and How should I change my structure to be the right way? – JohnBigs Dec 23 '14 at 21:44
  • @JohnBigs Your "parent" view controller is `HomeViewController`. Your "child" view controller is `StackTableViewController`. Your data source is being managed in `StackTableViewController`, which you create non-lazily in `HomeViewController`'s `init` method. That's not the normal way to do things, and is probably wrong. If you're just using the `CoreDataStack` to "pass through" that data, then just reference `CoreDataStack` from `HomeViewController` and don't create `StackTableViewController` until it is needed. – mbm29414 Dec 23 '14 at 21:46
  • Huh. Well, someone gave me a down vote, and I'm not quite sure why. – mbm29414 Dec 24 '14 at 03:51
0

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;
}
hasan
  • 23,815
  • 10
  • 63
  • 101
0
- (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.

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • hey bro, i tried now your suggestion and pushed the stackviewcontroller i already created ,still not working :/ when I delete the first object in the table view and go back to the home view controller the string in the label is the deleted one.. – JohnBigs Dec 27 '14 at 00:26