Correctly remove object (with code example) iOS - ios

Does the following code (removeViews) correctly remove the reference to the objects, i.e delete them, so I do not keep making more Views when the method createViews is called. createViews creates the views and removeViews sets them to nil. Note: this is a very simple example to enhance understanding, and serves no actual purpose.
-(void) createViews{
UITableView * tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,0,200,200)];
tableView.delegate=self;
tableView.datasource = self;
self.mainTableView = tableView;//self.mainTableView is a weak reference
[self.view.superView addSubview: self.mainTableView];
UIView * view = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,400)];
view.backgroundColor = [UIColor redColor];
self.mainView = view;//self.mainView is a strong reference
[self.view.superView addSubviews:self.mainView];
}
-(void) removeViews{
self.mainView = nil;
self.mainTableView=nil;
}

First remove them from their superView and then set them to nil
-(void) removeViews{
[self.mainView removeFromSuperview];
[self.mainTableView removeFromSuperview];
self.mainView = nil;
self.mainTableView=nil;
}

Related

Memory leak on Allocations

I'm a beginner and still trying to figure out how to read this.
There are 3 custom views and at the start I allocate the first one.
And then the second / deallocate the first and then the third / deallocate the second.
I do empty/nil all arrays right before deallocating each view so from what I see, all memory retained each time I allocated views, should be released whenever I deallocate/nil them but in the graph it keeps increasing, I don't see anything being released at all.
Is that supposed to look like that? I'm nil-ing delegates, arrays, dictionaries etc everything.
-(void)firstTOsecond {
[self.first removeFromSuperview];
self.first.delegate = nil;
self.first = nil;
self.second = [[Second alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view addSubview:self.second];
self.second.delegate = self;}
-(void)secondTOthird {
[self.second removeFromSuperview];
self.second.delegate = nil;
self.second = nil;
self.third = [[Third alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view addSubview:self.third];
self.third.delegate = self;}
EDIT
In First.m / Second.m / Third.m
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.buttonStartFrame = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"buttonStartFrame"]];
[self addSubview:self.buttonStartFrame];
self.buttonStartFrame.userInteractionEnabled = YES;
self.buttonStartButton = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"buttonStartDefault"]];
[self.buttonStartFrame addSubview:self.buttonStartButton];
self.buttonStartButton.userInteractionEnabled = YES;
self.labelStart = [[UILabel alloc]init];
[self.buttonStartButton addSubview:self.labelStart];
self.labelStart.textColor = [UIColor darkGrayColor];
self.labelStart.text = #"Start";
}
return self;}
the memory leak could be that you are continuously creating new view elements when they could be updated instead .. you can create helper methods to avoid allocating new views, and updating the one's on the main thread instead
- (void)limitedUpdateTextColorView:(NSDictionary *)somethingCache {
self.labelStart.textColor = [somethingCache valueForKey:#"bar"];
self.labelStart.text = [somethingCache valueForKey:#"foo"];
}

UITapGestureRecognizer EXC_BAD_ACCESS in self-contained UIView subclass

I've used UITapGestureRecognizer tons of times, but in this case I'm getting EXC_BAD_ACCESS when a tap occurs. I think it has to do with the fact that I'm adding it to an Alert-type overlay view. And for some reason the overlay view isn't getting retained even though it's on-screen.
I'm creating the view like this:
HelperView *testHelper = [HelperView helperWithBodyText:#"test text"];
[testHelper presentAtPoint:screenCenter];
The convenience method in HelperView.m looks like this:
+ (id)helperWithBodyText:(NSString*)text
{
return [[self alloc] initWithFrame:CGRectZero bodyText:text];
}
And the rest of the code looks like this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.container = [[AGWindowView alloc] initAndAddToKeyWindow];
self.container.supportedInterfaceOrientations = UIInterfaceOrientationMaskLandscapeRight;
self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
self.overlay.backgroundColor = [UIColor redColor];
self.overlay.alpha = 0.6;
[self.container addSubviewAndFillBounds:self.overlay]; //this fills the screen with a transparent red color, for testing
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissMe:)];
[self.overlay addGestureRecognizer:tap]; //I want to dismiss the whole view if the overlay is tapped
self.content = [[UIView alloc] initWithFrame:CGRectZero];
}
return self;
}
- (id)initWithFrame:(CGRect)frame bodyText:(NSString*)bodyText
{
self = [self initWithFrame:frame];
if (self) {
//TEST frame
self.content.frame = CGRectMake(0, 0, 217, 134);
// Initialization code
UIImage *bgImage = [[UIImage imageNamed:#"helper-bg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(30, 28, 20, 20)];
UIImageView *bgImgView = [[UIImageView alloc] initWithImage:bgImage];
bgImgView.bounds = self.content.frame;
[self.content addSubview:bgImgView];
}
return self;
}
- (void)presentAtPoint:(CGPoint)loc
{
CGPoint newPoint = [self.container convertPoint:loc toView:self.container];
self.content.center = newPoint;
[self.container addSubview:self.content];
}
- (void)dismissMe:(UITapGestureRecognizer*)recognizer
{
//this never happens - I get EXC_BAD_ACCESS when I tap the overlay
}
HelperView is not the view getting displayed and it's not getting retained, but you're using as the target for the gesture recognizer. The AGWindowView property "container" is getting displayed and retained by its superview. Your code needs refactoring since you have this view HelperView that doesn't ever display anything itself, but if you want it to work like this you need to retain HelperView so it doesn't automatically get released. You can do this by assigning it to a strong instance variable.

MasterViewController's TableViewController null in constructor?

I have the following MasterViewController:
- (id) init{
self = [super init];
if(self){
//self.title = #"Main Menu";
//self.clearsSelectionOnViewWillAppear = NO;
self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"view-1_0000s_0000_Muskoka-Logo1"]];
self.navigationItem.titleView = imageView;
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.opaque = NO;
self.tableView.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"view-1_0002s_0003s_0001_Bottom-panel"]];
self.tableView.scrollEnabled = NO;
}
return self;
}
It crashes when I try to set the background colour of the tableviewcontroller because the tableview inside the master is null. The image there is not null of course.
My only guess is that [super init] is not working properly?
A view controller's views and subviews do not exist until they have been loaded, which will occur during loadView (where the view hierarchy is built either in code or from a xib) All of those customisations should be in viewDidLoad. I'm surprised it's crashing, though, messages to nil don't cause crashes.

Adding the scrollview created by photoscroller to a subview

I'm trying to modify Apple's PhotoScroller example to make the scrollview that is created into a subview instead of it being a view that takes up the entire screen. Any ideas on how this can be accomplished?
- (void)loadView
{
// Step 1: make the outer paging scroll view
CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
pagingScrollView.pagingEnabled = YES;
pagingScrollView.backgroundColor = [UIColor blackColor];
pagingScrollView.showsVerticalScrollIndicator = NO;
pagingScrollView.showsHorizontalScrollIndicator = NO;
pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
pagingScrollView.delegate = self;
// When I do this it fails
[self.view addSubview:pagingScrollView];
// Step 2: prepare to tile content
recycledPages = [[NSMutableSet alloc] init];
visiblePages = [[NSMutableSet alloc] init];
[self tilePages];
}
You just need to modify the frame of the scrollview to be positioned and sized how you want:
This is the line in the view controller that sets it up in the example
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
As an example here is a sample frame with some hardcoded values:
CGRect scrollFrame = CGRectMake(100,100,100,100);
pagingScrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
So, I found out that I was able to add the scrollView as a subview by changing the method from loadView to viewDidLoad.
I have no clue why that works, but it does. I'd love to know why that's the case however...

UILabel only shows up when placed in viewDidAppear of viewcontroller

I can't figure out why a UILabel only shows up when I create it from the viewDidAppear within my viewController. Here is my code so far:
Within AppDelegate:
CGRect viewBounds;
viewBounds.origin.x = 0;
viewBounds.origin.y = 0;
viewBounds.size.width = screenBounds.size.height;
viewBounds.size.height = screenBounds.size.width;
view = [[EAGLView alloc] initWithFrame: viewBounds];
overlayView = [[OverlayView alloc] initWithFrame: screenBounds];
overlayViewController = [[OverlayViewController alloc] init];
[overlayViewController setView:overlayView];
[window addSubview:view];
[window addSubview: overlayViewController.view];
[window makeKeyAndVisible];
[view start];
Within overlayViewController: (This function is successfully called, but the UILabel doesn't show up)
-(void)showText
{
NSLog(#"showText()");
textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 300.0f)];
textLabel.textColor = [UIColor whiteColor];
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.font = [UIFont fontWithName:#"Arial" size:30];
textLabel.text = [NSString stringWithFormat:#"TESTING!"];
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
}
Within overlayViewController: (Placing the above code into the viewDidAppear makes it show up from the beginning)
-(void)viewDidAppear:(BOOL)animated
{
textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 300.0f)];
textLabel.textColor = [UIColor whiteColor];
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.font = [UIFont fontWithName:#"Arial" size:30];
textLabel.text = [NSString stringWithFormat:#"TESTING!"];
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
[super viewDidAppear:animated];
}
Why would the UILabel not show up from within showText() function when it's called? I verified that the NSLog outputs to the console, yet the UILabel is not on the screen.
To give a little more context, this is an AR application. There is an EAGLView showing the feed of the camera on the screen. As I said, the UILabel, when placed in the viewDidLoad of overlayViewController, shows up the moment the app launches above the camera video feed. When placed inside the showText function, the UILabel doesn't show.
Any help would be appreciated! Thank you!
PS. To give more information, I have tried calling showText() in two ways:
Within my EAGLView.mm (which is where most of the AR functions are handled), I setup a notification as such:
[[NSNotificationCenter defaultCenter] postNotificationName:#"showTextOverlay" object:nil];
Then, within OverlayViewController.m, I placed an observer within ViewDidAppear (since ViewDidLoad doesn't seem to get called, but ViewDidAppear does...)
-(void)viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(showText)
name:#"showTextOverlay"
object:nil];
[super viewDidAppear:animated];
}
The selector of this observer calls showText(), which is also inside of OverlayViewController.
I next tried a second way:
Within EAGLView.mm, I got the application delegate and controllers directly as such:
ImageTargetsAppDelegate *delegate = (ImageTargetsAppDelegate *)[UIApplication sharedApplication].delegate;
OverlayViewController *controller = delegate.overlayViewController;
[controller showText];
But both ways still did not show any UILabel...
ANSWERED:
I figured this out. It turns out that the way the sample application is written, the updates to UIKit were not being called on the main thread. Therefore, I used the performSelectorOnMainThread when calling my showText...
Thank you everyone for your help!
One thing to think about is that showText ends with these lines:
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
and then the next line is:
[overlayViewController setView:overlayView];
So, what would the value of self.view be in showText if the viewController does even set its view until the next line? You are likely adding a subview to a nil object.
Generally speaking, you should be doing things a little bit differently. The designated initializer for a view controller is initWithNibName:bundle rather than init so it is recommended that you use that.
More importantly, the OverlayViewController should either load its view from a .xib file or implement the loadView method to create its view. If you add the label inside of that method or in one of the methods called after it, like viewDidLoad, you will see the label.

Resources