UITableView dissapears

Discussion in 'iOS Programming' started by larswik, Oct 18, 2012.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    I am having an intermittent problem with a TableView. When testing the app on my iPad it can work fine for days. But every now and then when I return to my root viewController the entire table view is gone. Not just the text from the array but the whole tableView it's self is missing. It happens so intermittently and it isn't crashing the app so there is no crash report that can give me clues.

    I am using ARC so I don't need to release the objects myself.

    My header file
    Code:
    #import <UIKit/UIKit.h>
    #import "BoilerPlateCode.h"
    #import "MainVC.h"
    #import <AVFoundation/AVFoundation.h>
    
    @interface WelcomeVC : UIViewController <UIAlertViewDelegate, UITableViewDataSource, UITableViewDelegate>{
        int pickerSelected;
        
        NSMutableArray *characterArray;
        IBOutlet UIButton *muteMusic;
        IBOutlet UIImageView *tableViewWhiteWash;
    
        
        AVAudioPlayer *audioPlayer;
        UITextField *nameField;
        NSMutableDictionary *mainCharacterDict;
        [COLOR="Red"]UITableView *characterListTableView;[/COLOR]
        UIAlertView* dialog;
        UIAlertView *alertDelete;
    }
    
    - (IBAction)continueButton:(id)sender;
    - (IBAction)deleteButton:(id)sender;
    - (IBAction)muteMusicButton:(id)sender;
    
    @end
    
    In my implementation I instantiate the the object within an if statement that checks to see if it already exists in the first place.

    Code:
    #import "WelcomeVC.h"
    
    @implementation WelcomeVC
    
    -(void)viewWillAppear:(BOOL)animated{
        self.navigationController.navigationBar.hidden = YES;
        pickerSelected = -1;
        
        if (!characterListTableView) {
            characterListTableView = [[UITableView alloc] initWithFrame:CGRectMake(625, 171, 272, 388) style:UITableViewStylePlain];
            characterListTableView.dataSource = self;
            characterListTableView.delegate = self;
            characterListTableView.backgroundColor = [UIColor clearColor];
            characterListTableView.separatorColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7];
            characterListTableView.opaque = NO;
            characterListTableView.backgroundView = nil;
            [characterListTableView.layer setBorderColor: [[UIColor blackColor] CGColor]];//set black boarder around notefield
            [characterListTableView.layer setBorderWidth: 2.0];//set black boarder thikness
            CALayer * btv = [characterListTableView layer]; // rounds the edges of the noteField
            [btv setMasksToBounds:YES];
            [btv setCornerRadius:10.0];
            
            [self.view addSubview:characterListTableView];
            
        }
        [tableViewWhiteWash setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.3]];
        CALayer * bvbg = [tableViewWhiteWash layer]; // rounds the edges of the noteField
        [bvbg setMasksToBounds:YES];
        [bvbg setCornerRadius:10.0];
        
        [self loadCharecters]; // sets up the tableView array
        [characterListTableView reloadData];
        [audioPlayer play];
    }
    
    
    I can't think of any other place in the code that it would be causing. There is no other method in this Class that references this tableView, it is all within the viewDidLoad. Can anyone see anything wrong with this code?

    Thanks!
     
  2. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #2
    Here is an image to help show my problem. It happened again this morning so I took a screen shot on my iPad.

    The tableView is allocated memory and initiated. Could it be something with ARC that is managing the removal of objects?

    My 'if' statement should catch if the object exists or not, if it is removed it will create a new one. I'm assuming the if statemented is returning a false so it sees the object in memory and won't create a new one. But then it is not being drawn to the screen.

    So odd.
     

    Attached Files:

  3. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #3
    First, you are adding the table in viewWillAppear:animated:, not in viewDidLoad.

    Is this happening on iOS 5?

    If yes, then it must be happening because your view is getting unloaded (due to a memory warning, as it doesn't always disappear), and all its subviews are removed. The buttons will be recreated as they load from a nib I assume?

    This should not happen in iOS 6 as views are never unloaded in iOS 6.

    In your if statement, you are checking if the table view exists, not if it has been added as a subview. If you are using ARC, then you should declare the table view as a weak property instead of an ivar. So if your view along with your table disappears, your weak reference to it will be nil. And your if condition will work.

    The other (dirty) way will be to replace the if condition like this:

    Code:
    if (characterListTableView && [self.view.subviews containsObject:characterListTableView]) {
        // Do nothing
    } else {
        if (!characterListTableView) {
            // Create a new table and point your ivar to it
        }
    
        [self.view addSubview:characterListTableView];
    }
    
     
  4. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #4
    You are correct. It is ios 5. I didn't even think of the low memory issue and how that could unload. There is a lot that is happening in my app so that might be the problem then. Correct again the buttons are nibs. Only the tableView is created programaticlly.

    The viewWillAppear will always be called when the view appears again. Instead of having the code to create the tableView in the viewDidLoad it made more sense to have it in the viewWillAppear and then check to see if it is there or not with an if statement. This created less code. Should it be in the viewDidLoad instead?

    Using the "weak property instead of an ivar." I never really understood even though I have read documentation about it, it hasn't sunk in. For the most part if I need an object just in a Method I will declare it locally so it gets destroyed when done. If I need an iVar to to be accessed in many Methods I will declare it globally and then alloc / init it so it stays around to be used.

    Retain I would use instead of Alloc / Init So in I would do

    Code:
    @property (retain, nonatomatic) NSDictionary *mydict;
    myDict = [[NSDictionary dictionaryWithDictionary: someDict];
    
    or the Alloc version
    Code:
    NSDictionary *mydict;
    myDict = [[NSDictionary alloc] initWithDictionary:someDict......];
    
    My understanding is both of these will produce the same results, to retain the object in memory? "Weak" baffles me, even though I read docs on it before. It is either retained, or not retained?

    The other think I did to test it this morning was to add this.

    Code:
    -(void)viewWillDisappear:(BOOL)animated{
        [characterListTableView removeFromSuperview]; // I could not solve the problem with the tableView dissapearing. So I remove it.
        characterListTableView = nil;
    
    }
    
    Now when it leaves this view it is removed from the view and when it returns it is created again. But that seemed like a waste. I'll read up on "weak" properties again. Maybe I will understand it now.

    Thanks for your help.
     
  5. CodeBreaker, Oct 19, 2012
    Last edited: Oct 19, 2012

    CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #5
    Yes. It should ideally be in viewDidLoad. Because unlike viewWillAppear:animated:, viewDidLoad is called only once after your view loads (viewWillAppear:animated: is called every time your view is about to be displayed on the screen). And you should release (set nil) the table in viewDidUnload. So to summarise, for iOS 5, you create your subviews in viewDidLoad and release (set nil) all references to your subviews in viewDidUnload to free up memory. So both these methods are balanced.

    But do keep in mind that the OS calls didReceiveMemoryWarning on your view controller for you to safely release data/variables which can be recreated later. If you do not free up enough memory there, your views will be force unloaded in iOS 5 to save memory, which in turn calls viewDidUnload. If it still cannot meet the memory demand, your app will be killed.

    In iOS 6, views are never unloaded and hence your subviews are never removed (and hence viewDidUnload is never called/depreciated). So, if you do
    not free enough memory in didReceiveMemoryWarning, your app will be killed.

    A weak reference is a reference to an object which does not stop it from being deallocated. It is loosely equivalent to an assign property in NARC. So in other terms, your reference to an object is valid as long as some other object has a strong reference to it. In this case, your view has a strong reference to your table view. And you keep a weak reference to it. Your reference will be valid as long as the view is still holding the table view. It will be nil when the view gets unloaded as it releases all its superviews, and the table view gets deallocated.

    But in your code, you have a strong reference to your table view (an ivar). When the view unloads, it releases the table view. But as you have a strong reference to it, it is not deallocated. Hence your if condition fails.

    You ideally should be reversing in viewWillDisappear:animated whatever you did in viewWillAppear:animated. But things like adding subviews and other view related instantiation should not be done in viewWillAppear:animated, as these methods are frequently called.
     
  6. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #6
    Yep, that is why I put it into an if statement so that it would be be allocated twice. But I have never used the "didReceiveMemoryWarning" and always just ignored it. This is yet another learning opportunity an do can put in an NSLog to see if it is called while using the app.

    Thanks for the incite on "weak", I kinda get it. I will be curious to see if the memory is the issue under ios5. But it makes me wonder because it is the root viewController. That would me that when I have other views on the stack that are taking up memory it would delete what it needs to to free up the space. But this is the only place I have seen the problem occur so far. Perhaps Instruments has a tool (will look when I get home) that allows me to see what is happening with the memory.

    Thanks again
     
  7. CodeBreaker macrumors 6502

    Joined:
    Nov 5, 2010
    Location:
    Sea of Tranquility
    #7
    Visible views are never unloaded. Your table will disappear when you push another view controller on your root controller, and your app then receives a memory warning level 2 (level 1 is when you just receive the warning, and if you do not free up some memory, it will receive a level 2 warning where it will force unload the views). When you pop your detail controller and come back to the root view, the nib for it will be again loaded, viewDidLoad will be called and you will need to create and add a table there. So your table instantiation should be in viewDidLoad, and you should add the table view without any if condition.

    The best way to debug memory warnings is the simulator. You can trigger memory warnings using Hardware -> Simulate Memory Warning... The way I do this is by assigning a keyboard shortcut to it, and triggering many warnings at once for every screen in my app.

    Instruments memory tools (allocations, leaks and activity monitor) will tell you the real time memory usage of your app.
     
  8. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #8
    Thanks again, you pointed me in a good direction to read up on. So I can see what I need to fix and begin to implement memory warnings.

    Thanks.
     

Share This Page