Memory Management, ARC - what to nil? - iphone

Background -
I am using automatic reference counting on a project. The root view is a Table View (Master / Detail setup) showing a list of "slide shows". Click on a table cell and you are taken to the detail view which consists of a Scroll view with views (viewController.view) in it (this is the "slide show"). Each slide show has a front cover and back cover (same view controller formatted differently) that sandwich an variable number of pages. Here is the code to load the slide show:
- (void)loadScrollView
{
// The front and back cover are set in Interface Builder because they
// are reused for every slide show, just labels are changed.
[self.scrollView addSubview:self.frontCoverViewController.view];
[self.frontCoverViewController setCoverTitle:_data.name creationDate:_data.creationDate isFrontCover:YES];
[self.pagesArray addObject:self.frontCoverViewController];
for (int i = 0; i < [self getTotalNumberOfComps]; i++)
{
PageViewController *pageView = [[PageViewController alloc] init];
pageView.data = [_compsArray objectAtIndex:i];
[_scrollView addSubview:pageView.view];
pageView.data.imgView = pageView.imageView;
pageView.slideShowViewController = self;
[_pagesArray addObject:pageView];
}
[self.scrollView addSubview:self.backCoverViewController.view];
[self.backCoverViewController setCoverTitle:_data.name creationDate:_data.creationDate isFrontCover:NO];
[self.pagesArray addObject:self.backCoverViewController];
[self.scrollView bringSubviewToFront:_frontCoverViewController.view];
[self setCurrentPage:0];
}
Problem -
So Im trying to reuse this slide show view controller so I need to nil and recreate the pages in the middle because each slide show has a different number of slides. Note a slide [PageViewController] is just a view with an ImageView in it. It has more functionality so we need the controller however the main display of the V.C. is the ImageView. I have created the following method to "empty" the slide show before running loadScrollView again with new data. Here is the empty method:
- (void)saflyEmptyScrollView
{
for (int i = 0; i < [self.pagesArray count]; i++)
{
if (i == 0 && i == ([self.pagesArray count]-1)) {
CoverViewController *cover = (CoverViewController*)[self.pagesArray objectAtIndex:i];
[cover.view removeFromSuperview];
} else {
PageViewController *page = (PageViewController*)[self.pagesArray objectAtIndex:i];
[page.view removeFromSuperview];
page = nil;
}
}
self.pagesArray = nil;
self.pagesArray = [[NSMutableArray alloc] init];
}
Big Question -
My main question is do I need to set the ImageView of each of these pages to nil? Or does setting the page itself to nil also free up the memory used by the ImageView/Labels/etc that are used in that view controller?
I tried adding self.imageView = nil; to the PageViewController's viewDidUnload and viewWillUnload methods (one at a time not in both) and I realized that setting page = nil does not call the pages Unload methods. Am I freeing up memory correctly.
I've read a lot of articles but Im still not sure if Im managing memory in the best way possible. Thanks so much for the help!

Generally, you shouldn't have to set things to nil. And in this specific case, the setting things to nil is doing nothing.
The line page = nil; is redundant, because the variable page goes out of scope immediately afterwards anyway. ARC knows this and doesn't need you to set it to nil.
And self.pagesArray = nil; is redundant because you follow it with self.pagesArray = [[NSMutableArray alloc] init];. The second line on its own will suffice.

Related

Creating a custom view in Interfacebuilder and using that in code

I have made a view in the application that shows a UITableView. It will be full of results (obviously) almost all the time, however when it does not have any results I want to show another view that inform the user about how he/she could populate the table.
I want to design that view in the interfacebuilder. I will have to check in the code whether the datasource is empty or not to toggle between the two different nibs. How do I instantiate and configure a view made in Interfacebuilder?
The easies way to do this is by adding the view in xib normally and make it visible
Design your both views, the table view and the other view, give the tableView a tag of 111 for example and give the otherview another tag 222 for example
Now in viewDidLoad
Get both the views
UIView *noDataView = [self.view viewWithTag:222];
UITableView *tableView = [self.view viewWithTag:111];
//Hide both of them or only the noDataView until you know if you have data from the dataSource or not
Check for your data source
//hasElements do you have any element to show?
if(hasElements)
{
noDatView.hidden = YES;
tableView.hidden = NO;
}
else
{
noDatView.hidden = NO;
tableView.hidden = YES;
}
You can load nib file based on condition.You can write category as follows:
self.view = (UIView *)[self loadNib:#"SecondView" inPlaceholder:self.view];
- (UIView *)viewFromNib:(NSString *)nibName
{
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
for (id view in xib) { // have to iterate; index varies
if ([view isKindOfClass:[UIView class]]) return view;
}
return nil;
}
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
UIView *nibView = [self viewFromNib:nibName];
[nibView setFrame:placeholder.frame];
self.view = nibView;
//[self.view insertSubview:nibView aboveSubview:placeholder];
//[placeholder removeFromSuperview];
return nibView;
}
The other answers give you possible technical solutions but I would propose that if you are using the standard Apple design guidelines, you probably don't even need to worry about it. For instance, somewhere on your screen you should have a bar button item with the identifier "Add" (which shows the plus icon). Then rather than giving a long (often poorly localised) description of how to add items, just have a header for an empty section which says "No items" replacing items with whatever pluralised noun is appropriate for your table's items. For example, for an Archery related app I am working on:
Notice how the Edit button is currently disabled too, thus no explanation is needed as the only thing they can do at this point is tap the Add button (screenshots on the Appstore will have shown them what they can expect to see after this point).

How to update a progressbar during instantiation of timeconsuming ui elements?

I would like to update a progressbar while i am instantiating some ui elements that takes some times. I first create my view during the viewLoad method and add my progress bar there. Once my view appeared in the viewDidAppear method I am making several uikit objects instantiation but i would like to update the progress bar in the mean time. I am not sure how to proceed since everything should happen in the main thread as it is ui elements.
Here is part of my code:
-(void) viewDidAppear:(BOOL)animated
{
// precompute the source and destination view screenshots for the custom segue
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self.progressBar setProgress:.3];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self.progressBar setProgress:.5];
}
In the above code I just need to create two screenshots of views to use them later on. The problem is that i only see the last update (.5) when setting the progress to the progress bar. What is the proper way to do this update?
You can use the performSelectorInBackground:withObject: method in order to instantiate your heavy views. That method (the one that instantiates your views) will have to set your progress bar progress in the main thread.
So your code would look something like this:
- (void)viewDidAppear:(BOOL)animated
{
[self performSelectorInBackground:#selector(instantiateHeavyViews) withObject:nil];
}
- (void)instantiateHeavyViews
{
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.3f] waitUntilDone:YES];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.5f] waitUntilDone:YES];
}
- (void)updateMyProgressView:(NSNumber *)progress
{
[self.progressBar setProgress:[progress floatValue]];
}
Edit: of course, it won't animate your progress bar (I don't know if that is what you wanted). If you want it to move on while your views are being created, you should use a delegate to be notified of the progress, and this can be a bit harder. This way you would be able to update the progress bar every time the delegate is notified.

How to remove everything fron the superView and not just the last item?

In my app i had to draw certain checkboxes at a same time and i used a single function to add all of them. Now when a user clicks one of them all of those checkboxes should get removed from the superview and currently its just removing the last one. Also i have issue to recognize those checkboxes like which one is clicked. i know it should be done through Tag property but don't know how exactly it should be implemented.
Any suggestions.
Removing all subviews
int numberOfSubviews = [[yourView subviews] count];
for(int i=0;i<numberOfSubviews-1;i++
{
[[youView subviews]objectAtIndex:i]removeFromSuperView];
}
//this will leave check box that you added at last.... for first one to remain loop from 1 to numberOfSubviews....
Using tag property...
when you are creating checkbox objects use
checkBoxObject.tag = i;
//I am considering i as looop count which you are using in a loop
to add checkboxes.
then whenever you need a object of checkbox
[yourViewonwhichYouAddedCheckBox viewWithTag:<your tag >];
Thanks
For identifying a "checkbox" or better said any view within an action-method:
- (void)someActionHandler:(id)sender
{
UIView *actionOriginView = (UIView *)sender;
NSLog(#"this action came from view:%d", actionOriginView.tag);
}
For assigning the tag, you may use the IB or within your code, while instantiating;
UIView *myFunkyView = [[UIView alloc] initWithFrame:CGRectZero];
myFunkyView.tag = 1337;
For removing a bunch of views from your superview - lets assume their tag is set to 10 - 15;
for (int i=10;i <= 15;i++)
{
UIView *childView = [superview viewWithTag:i];
[childView removeFromSuperview];
}

UIImageView weird image property issue

I'm dealing with a super easy task that somehow introducing some difficulties...
All I'm trying to do is to create a view controller and set its UIImageView's image property to some image.
When I try to that, I get nil =\
GenericViewController *genericViewController = [[GenericViewController alloc] init];
UIImage *image = [UIImage imageNamed:#"Camera.png"];
genericViewController.genericImageView.image = image;
NSLog(#"%#", genericViewController.genericImageView.image);
Output: (null)
I imagine genericImageView is set up either in a nib or in the -loadView method. However, at the point in which you're trying to access the image view, the view for the VC hasn't been loaded yet. The quick fix is to call
(void)genericViewController.view;
before accessing genericImageView. This will force the view to load. The "better" approach would be to give genericViewController an image property that you assign to, then in its setter you can say
- (void)setGenericImage:(UIImage *)image {
if (_genericImage != image) {
[_genericImage release];
_genericImage = [image retain];
if ([self isViewLoaded]) {
self.genericImageView.image = image;
}
}
}
and in -viewDidLoad you can say
- (void)viewDidLoad {
[super viewDidLoad];
self.genericImageView.image = self.genericImage;
}
This method, besides being more modular and architecturally-sound, also has the advantage where if the view controller's view is unloaded (say, another view is pushed onto the nav stack and a memory warning comes along), when it gets re-loaded it will still have the image.

Is it possible to select a row in a **previous** UITableview

Is it possible to select a row in a previous UITableview.
I provided image samples to get a more clear picture of what is happening exactly.
http://img186.imageshack.us/img186/933/picture8a.png
http://img405.imageshack.us/img405/9327/picture9d.png
http://img200.imageshack.us/img200/5386/picture10thm.png
At the morning workout screen , one can select a row and behind it so it will play a Movie.
If the movie plays you can press the secondbutton and it will take you to the final table.
If you press the backbutton you will simply return to the previous screen.
Now here is where my problem lies.
If i'm in my final screen after pressing the secondbutton and I press on the backbutton it would be great if it could play the previous video( in other words the video connected to the previously selected cell)
So if it's possible for me to create a function or some sort of action that can actually select the previously selected row from the first field ( for instance Lunge Forward in this example ).
perhaps with some like
previousRow = [ self.tableView indexPathForSelectedRow];
[self tableView:[self tableView] didSelectRowAtIndexPath:previousRow];
just doing something there
Even if it's not possible I would appreciate it if someone would let me know.
edit:
Here is some code behind the cell when it's going to play a video
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
//check to see if video must be played
NSString *playVids = [dictionary objectForKey:#"playVids"];
finalscreen = [dictionary objectForKey:#"finalScreen"];
if(playVids == nil )
{
if(CurrentLevel != 0 )
{
if( finalscreen == nil )
{
NSString *movies = [dictionary objectForKey:#"movieID"];
NSURL *movie = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:movies ofType:#"m4v"]];
theMovie = [[MPMoviePlayerController alloc] initWithContentURL:movie];
[theMovie setOrientation:UIDeviceOrientationPortrait animated:NO];
[theMovie setScalingMode:MPMovieScalingModeAspectFit];
theMovie.backgroundColor = [ UIColor whiteColor];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMovieFinishedCallback:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:theMovie];
[theMovie play];
[self getToolBarStuff];
lblTitle.text = [dictionary objectForKey:#"Title"];
[[[UIApplication sharedApplication]keyWindow]addSubview:toolbar2];
[[[UIApplication sharedApplication]keyWindow]addSubview:imageView];
[[UIApplication sharedApplication]keyWindow]addSubview:lblTitle];
}
}
else
{
WebViewController *web = [[WebViewController alloc] initWithNibName:#"WebView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:web animated:YES];
[web release];
}
}
else {
//Prepare to tableview.
rvController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:[NSBundle mainBundle]];
//Increment the Current View
rvController.CurrentLevel += 1;
//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:#"Title"];
//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];
rvController.tableDataSource = Children;
[rvController release];
}
}
I get my info/feeds from a dictionaryfile and also different tags in my dictionary tell my tableview when to play the video and when not( playVids ).
so to be exact I have a start screen which brings you to a second screen and that one brings you to the morning workout screen and from there if you click a cell it will play a (different with every cell) video. I can't use the standard design from apple(with the backbutton above) since this assignment specifically asks to use their own design.
That's the reason why i created my own tabbar and backbutton.
My backbutton looks like this
-(void)back_clicked:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
CurrentLevel--;
switch( CurrentLevel ) {
case 0;
[toolbar removeFromSuperView];
default:
break;
}
}
So if I'm in the last screen I just pop that screen to return to my previous one( the morning workout). And what actually has to be done is pop that screen and play the last video. I thought I could do that by popping the last screen and then selecting the previously clicked cell in morning workout.
Any thoughts on that.
I am not sure I understand your problem exactly here. Can you show a bit more code? You have 3 view controllers, right? Are you using a standard navigation controller pattern with push/pop to get from one to the other? It looks like maybe you aren't since the buttons are in an odd layout.
If you are then pressing the back button can just pop view 3 and go back to view 2 naturally (it will still be there, so the video should still be playable) so I don't see why you need to mess with the table in view 1 in order to find that and make it show again. You should just be able to hook into viewWillAppear and do the same thing you did when coming from view 1.
And if you aren't using a standard navigation controller pattern then maybe this is the problem. From a UI perspective you probably should do that anyway - people will expect a standard back button in the title bar.
I am pretty sure your problem is soluble anyhow - you can always pass object references from one view to the other when creating them for example - but I don't think you need to resort to the method you have in mind.
edit: reading the additional detail I see you are indeed using push/pop. So you have a stack of views, and when one is popped it uncovers the one beneath it, right? It should therefore be possible to do what you want by implementing the viewWillAppear or viewDidAppear methods in your controller. When your final screen is popped, if I understand correctly it will uncover the view which is able to play the movie. Your viewcontroller will get the viewWillAppear / viewDidAppear messages in that case - by maintaining state and receiving those messages you can then implement logic to decide whether or not you need to play the movie. Rather than call the didSelectRow. method again you could perhaps put the movie play stuff in its own method so you can call from either viewDidAppear or didSelectRow...
I also am not sure that I fully understand your question either but it sounds similar to a problem I was working on recently. Check out this post on using up down arrows to move through the parent table while in a detail view. My app was based on the same sample code so you should be able to get this to work without much trouble.
Up/down arrows in nav bar of detail view to page through objects in parent table