How to release a viewcontroller while using addSubview? - iphone

I've the code as below:
+(void) addHeader:(UIViewController*) hostViewController requiresBackBtn:(BOOL)BooleanValue
{
ApplicationHeader *appHeader = [[ApplicationHeader alloc] initWithNibName:#"ApplicationHeader" bundle:nil];
appHeader.hostViewController = hostViewController;
[appHeader.view setFrame:CGRectZero];
[hostViewController.view addSubview:appHeader.view];
if (BooleanValue) {
[appHeader.view setFrame:CGRectMake(0, 0, 320, 97)];
}
else {
[appHeader.backBtn setHidden:TRUE];
[appHeader.view setFrame:CGRectMake(0, 0, 320, 74)];
}
// [appHeader release]; // This call tends to app Crash!!!!!
}
If I call release to appHeader then the app crashes I press the button which found in appHeader!!
And if I doesn't this is a memory leak.
What to do now?
:(

I don't know what an ApplicationHeader is, but I know that yours has a retained view from
[hostViewController.view addSubview:appHeader.view];
Would ApplicationHeader be better as a subclass of UIView? Without more information, I'm not sure what to suggest as a solution but I think you need to rethink what you are trying to accomplish.

Adding other controller's view in your viewController is against Apple's "one controller per screen" policy. It will surely give level 1 and 2 memory warnings and app will crash intempestively.

Related

vuforia and uinavigationcontroller

I have a xCode project generated by Unity3D (which includes a animation) and I've integrated vuforia SDK for displaying an animation (Augmented reality)
The issue that I got by now is to add the navigation between the camera screen and the dashboard of my app.
In int OpenEAGL_UnityCallback(UIWindow** window, int* screenWidth, int* screenHeight, int* openglesVersion) method I'm adding the back button pragmatically like:
if (wrapperObj == nil)
{
wrapperObj = [[WrapperClass alloc] init];
}
[backButton addTarget:wrapperObj action:#selector(goToDashBoard:) forControlEvents:UIControlEventTouchUpInside];
Pass it to a wrapper class
#interface WrapperClass : NSObject
#property (nonatomic, retain) UINavigationController *navigation;
-(void)goToDashBoard:(UIButton*)sender;
#end
void goToPreviousScreen()
{
}
#implementation WrapperClass
#synthesize navigation;
-(void) goToDashBoard:(UIButton*)sender
{
[[self navigation] popViewControllerAnimated:YES];
goToPreviousScreen();
}
#end
Can anyone point me to a solution how to make the navigation up and running?
UPDATE
When trying to pop the view in this way:
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate.navigationController popToRootViewControllerAnimated:YES];
I get this log:
2012-12-05 16:20:45.502 arapp[5993:907] <HomeViewController: 0x246db00>
StopQCAR
(Filename: /Applications/buildAgent/work/14194e8ce88cdf47/Runtime/ExportGenerated/iPhonePlayer-armv7/UnityEngineDebug.cpp Line: 43)
Could not deactivate dataset.
(Filename: /Applications/buildAgent/work/14194e8ce88cdf47/Runtime/ExportGenerated/iPhonePlayer-armv7/UnityEngineDebug.cpp Line: 43)
Could not destroy dataset.
(Filename: /Applications/buildAgent/work/14194e8ce88cdf47/Runtime/ExportGenerated/iPhonePlayer-armv7/UnityEngineDebug.cpp Line: 43)
Could not deinitialize the tracker.
(Filename: /Applications/buildAgent/work/14194e8ce88cdf47/Runtime/ExportGenerated/iPhonePlayer-armv7/UnityEngineDebug.cpp Line: 43)
Could not deinitialize the tracker.
(Filename: /Applications/buildAgent/work/14194e8ce88cdf47/Runtime/ExportGenerated/iPhonePlayer-armv7/UnityEngineDebug.cpp Line: 43)
ghashtable.c:294: assertion 'hash != NULL' failed
If I understand your question correctly you are trying to modify the Unity built Xcode project by adding files, viewcontrollers, code, etc. to it. It's been my experience that this is not a good idea and you may be much better off building your app in Unity as much as possible. The moderators on Vuforia's forum strongly discourage it also. I built a Unity/Vuforia app this way, mostly to avoid spending the time learning how to do it in Unity with C#. I ended up with a memory leak I couldn't track down and had to bite the bullet and go back to Unity and learn Unity scripting. Of course there are many iOS things you can't do in Unity, and I have no idea what your app does, but there are many plugins that can help you, and I ended learning how to write my own plugins to get some iOS functionality I needed.
However, if you want to navigate between the AR view and other view controllers the way I did it was by making another viewController that was basically an overlay and contained just the button for navigation. Then I initialized it and added it as a subview to the UnityViewController. Then you add the code to go to whatever view you want, like your dashboard.
// Init Vuforia VC, then add its view as subview to "view" AR view, line 524
VuforiaViewController *vuforia = [[VuforiaViewController alloc] init];
// Create a full-screen window
_window = [[UIWindow alloc] initWithFrame:rect];
EAGLView* view = [[EAGLView alloc] initWithFrame:rect];
UnityViewController *controller = [[UnityViewController alloc] init];
sGLViewController = controller;
sGLView = view;
controller.view = view;
_splashView = [ [UIImageView alloc] initWithFrame: [[UIScreen mainScreen] bounds] ];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
_splashView.image = [UIImage imageNamed:SplashViewImage(UIInterfaceOrientationPortrait)];
}
else
{
_splashView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_splashView.autoresizesSubviews = YES;
}
[view addSubview:_splashView];
[view addSubview:vuforia.view]; // Overlay view added as subview of AR view
As you may know you can comment out startUnity() in the AppController to prevent the AR view from coming up when the app starts, and call it in another VC to get back to the AR view. This approach let me navigate between the AR view and several other VC's, but ultimately the app was unstable and had to be replaced with one built entirely in Unity. Maybe you can find a better way, but you may be better off diving into Unity and plugins, depending on what you want your app to do. Hope this helps.

subview not showing on already displayed view

I'm new on iPhone development and I'm stuck with a subview not showing after the controller is notified to refresh these items.
So, it's my main page showing, showing the 3-last items of a newsfeed at the bottom.
The view controler handles the loading of the newsfeed (in a NSArray).
When loading the page, the newsfeed is correctly displayed as a subview of the main view. However, when the view already exists (for example just after login for the first time), the newsfeed is not displayed, or refresehed. The avatar images that are fetched from internet asynchronously are displayed correctly. But not the labels.
The debug shows that the code is correctly executed...but nothing happens...
Here is the code handling the notification and refresh.
I'm sure I miss something stupid but cannot put my finger on it.
Does an UILabel or UIImage of the subview can avoid the element to be refreshed?
Thanks for the help!
The controler code
-(void) handleRefreshNewsfeed:(NSNotification*)notification{
if (self.newsfeedMostRecentDate == nil){
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd HH:mm"];
self.newsfeedMostRecentDate = [dateFormatter dateFromString:#"20100101 00:00"];
[dateFormatter release];
}
NSArray * newsfeed = [cdw getNewsfeedForUser:self.currentUser andLimit:self.newsfeedLimit];
if ([newsfeed count] > 0){
self.newsfeedMostRecentDate = [(Newsfeed *)[newsfeed objectAtIndex:0] posted];
if ([self.view isKindOfClass:[MenuView class]]){
[(MenuView *)self.view refreshNewsfeed:newsfeed];
}
}
}
The main view declaration:
#interface MenuView : UIView {
MenuNewsfeed * newsFeedView;
}
#property(nonatomic, retain) IBOutlet MenuNewsfeed *newsFeedView;
-(void)displayNewsfeed:(NSArray *)newsfeed;
-(void)refreshNewsfeed:(NSArray *)newsfeed;
The main view code :
-(void)displayNewsfeed:(NSArray *)newsfeed{
[self.newsFeedView setCurrentUser:currentUser];
[self.newsFeedView displayNewsfeed:newsfeed];
[UIView animateWithDuration:0.75f
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
self.newsFeedView.alpha = 1.0;
}
completion:^(BOOL finished){
//nothing
}
];
}
-(void)refreshNewsfeed:(NSArray *)newsfeed{
if ([self.newsFeedView.newsfeed count] > 0){
[UIView animateWithDuration:0.50f
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
self.newsFeedView.alpha = 0.0;
}
completion:^(BOOL finished){
// [self displayNewsfeed:newsfeed];
}
];
}else {
[self displayNewsfeed:newsfeed];
}
}
Michael
This may not answer your problem.
However, there is a small glitch that I can see. The CGRectMake (50, 100, 55, 12) will create a rect of 12px wight but 55px height.
Well, at least "te" or "tes" of your text should be visible as I do not see any insects. With some insect value (10 or so) it could happen that noting of your text string is visible.
Or no. When a text is clipped, depending on the default line break mode, it could be truncated to a certain extend and some "..." added at the end.
Could it be that you only see some points instead of "test test test"?
Otherwise check wether the tmp label is hidden by some other view, although it should be most on top of the view's stack.
But give it some realistic rect of maybe 20, 100, just to be save, before you do any further testing.
I've found out what the problem was : the code handling the notification (and modifying the UI) was not processed on the main thread.
[self performSelectorOnMainThread:#selector(refreshNewsfeed) withObject:notification.object waitUntilDone:NO];
Calling the refresh method via performSelectorOnMainThread solved the issue.

How do I release/delete a UIImageView class?

I'm new to iPhone programming, so I might not even be using the correct methods... anyway, I'm trying to make a game, and when I want to create an enemy I make a new UIImageView, like this:
enemyBird *asdf = [[enemyBird alloc] initWithFrame:CGRectMake(30, -20, 45, 30)];
[self.view addSubview:asdf];
When enemyBird initializes, an NSTimer is made so the enemyBird can fly around and do its own thing. Now, I want to get rid of the bird after it leaves the screen, in this code here:
if (self.center.y > 500)
{ //[self dealloc]; //doesn't work
//[self release]; //doesn't work
//[self removeFromSuperview]; //this makes it disappear, but the NSTimer is still running
}
But I don't know how to do that. Am I doing this properly? Or is there an entirely different way I should be doing this? Thanks in advance.
for game development better use cocos2d framework: It´s quite easy
As view retains all its subviews you can release your imageView right after you add it to controller's view:
enemyBird *asdf = [[enemyBird alloc] initWithFrame:CGRectMake(30, -20, 45, 30)];
[self.view addSubview:asdf];
[enemyBird release];
In your timer handler when bird leaves the screen remove it from superview, set reference to nil (to make sure you won't access deallocated instance) and invalidate timer so it won't fire again:
if (self.center.y > 500)
{
[enemyBird removeFromSuperview];
enemyBird = nil;
[timer invalidate];
}
You have to send removeFromSuperview to your image view, to put it out of the main view.
In the same time, you invalidate the timer, and you might be fine.
Also just after alloc/init the image view and add it to the main view, you should release it.

Problem with removing a subview and then adding it again

I have a problem with removing a subview, or more precisely with checking if it is still there after having deleted it.
My app first adds a subview to self.view.
[self.view addSubview:tabsClippedView];
Then it adds another subview to this subview (to which it adds several buttons as subviews, but I guess this is unimportant in this context):
[tabsClippedView addSubview:tabsView];
Finally, I have a method which allows the tabsView to be deleted and then created again. I need to do this so as to update the number of buttons in that tabsView (as the user can delete buttons). The method looks basically like this:
[self.tabsView removeFromSuperview];
After that I call a method called showTabs (which I already called in the very beginning of the app in order to add the subViews). This is where it all becomes problematic and where my app crashes (I get no error in the debug console, so I don't really know what the issue is...):
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
This is where the app crashes: when trying to assess if tabsView isDescendantOfView (I don't get any of the following logs):
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
I'd be grateful for any suggestions where the problem could be.
EDIT:
These are the methods to set up my views:
-(void) initTabsClippedView { // sets up tabsClippedView
NSLog(#"initTabsClippedView method started...");
CGRect tabsClippedFrame = CGRectMake(258,30,70,81*6);
tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
tabsClippedView.clipsToBounds = true;
[self.view addSubview:tabsClippedView];
NSLog(#"initTabsClippedView method ended.");
}
-(void) initTabs {
NSLog(#"initTabs started. Adding buttons to tabsClippedView...");
CGRect tabsFrame = CGRectMake(-30,0,50,480);
tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];
[tabsClippedView addSubview:tabsView];
// sets up buttons in tabsClippedView
And this is where I delete the tabsClippedView (triggered by a button found in tabsClippedView):
-(void)tabDelete:(id)sender
{
UIButton *button = (UIButton *)sender;
[UIView animateWithDuration:0.75
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
button.transform = CGAffineTransformMakeTranslation(-30, 0);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[self.tabsView removeFromSuperview];
//...
}
completion:^(BOOL finished){
NSLog(#"tabsView removed from Superview. Objects Deleted.");
[self showTabs];
NSLog(#"TabDelete finished. Button removed and tabsView updated accordingly.");
}
];
}];
And this is the showTabs method which was already called when I started the app:
-(void)showTabs {
NSLog(#"showTabs started...");
currentView = #"Tabs";
if ([tabsClippedView isDescendantOfView:self.view]) {
NSLog(#"There is already a tabsClippedView.");
} else {
NSLog(#"There is no tabsClippedView. I'll add one...");
[self initTabsClippedView];
}
if ([tabsView isDescendantOfView:tabsClippedView]) {
NSLog(#"There is already a tabsView");
} else {
NSLog(#"There is no tabsView for the buttons. I'll add one including buttons.");
[self initTabs];
}
Is it possible that you are getting EXC_BAD_ACCESS? Is it possible that the app is crashing because tabsView is deallocated when you send isDescendantOfView: to it. If you run with breakpoints enabled it should tell you the reason for the crash. If it is an EXC_BAD_ACCESS problem you should try NSZombie.
To activate NSZombie do the following:
Get info of the executable.
Go to the arguments tab.
In the "Variables to be set in the environment:" section add:
Name: NSZombieEnabled
Value: YES
Then run your app as usual and when it crashes it should tell you which deallocated object received what message.
EDIT: Just saw your edit. I think I nailed it. You're autoreleasing the views when you create them, so when they are removed from their superviews they are no longer retained and thus deallocated. You're app crashes because you're trying to run methods on deallocated views.
EDIT 2: Thought I should tell you that there is a better solution than the one posted by Praveen S.
Change your code as follows:
[tabsClippedView release];
tabsClippedView = [[UIView alloc] initWithFrame:tabsClippedFrame];
and
[tabsView release];
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
The above code does the same thing as the code posted by Praveen S, but without the autorelease. An autorelease is more expensive than a regular release and should only be used when needed and in this case it isn't.
Rather than releasing before you allocate a new view you probably want to release the view when you're done with it:
[tabsView removeFromSuperview];
[tabsView release];
tabsView = nil;
or simply
[tabsView removeFromSuperview];
self.tabsView = nil;
and then instead of:
if ([tabsView isDescendantOfView:tabsClippedView]) ...
you can use:
if (tabsView) ...
As you might have noticed, there really is no need for you to retain the view. You could just as well do the following:
tabsView = [[UIView alloc] initWithFrame:tabsFrame];
[tabsClippedView addSubview:tabsView]; // This retains tabsView
[tabsView release];
and then to remove the view you would use:
[tabsView removeFromSuperview]; // This will release the tabsView
tabsView = nil;
Also remember to set the views to nil in viewDidUnload.
EDIT 3: Why self made such a difference:
What you need to understand is how properties and reference counting works. There are books and such you could read about it. I'm sure Google can provide you with some good references as well.
The difference between
self.tabsView = [[UIView alloc] initWithFrame:frame];
and
tabsView = [[UIView alloc] initWithFrame:frame];
is that self.tabsView is accessing the properties setter, while tabsView is accessing the instance variable directly.
A nonatomic, retain property's implementation looks something like the following:
- (void)setTabsView:(UIView *)view
{
if (view != tabsView) {
[tabsView release];
tabsView = [view retain];
}
}
So the property is taking care of the memory management for you. In my solution I take care of the memory management myself and thus I don't need the property to do it for me, so I don't use it.
I hope this explains why self made such a difference.
Change your code as follows:
self.tabsClippedView = [[[UIView alloc] initWithFrame:tabsClippedFrame] autorelease];
and
self.tabsView = [[[UIView alloc] initWithFrame:tabsFrame] autorelease];

iPhone - Splash Screen with progress bar

I tried to create a SplashView which display the Default.png in the background and a UIProgressBar in front. But the splash screen is not being updated...
Inside my view controller I load first the splash view with a parameter how many steps my initialisation has and then I start a second thread via NSTimer and after each initialisation step I tell the SplashView to display the new progress value.
All looks good in theory, but when running this app the progress bar is not being updated (the method of the splash screen receives the values, I can see it in the logs). I also tried to add usleep(10000); in between to give the view updates a bit time and also instead of using the progress bar I drew directly on the view and called [self setNeedsDisplay]; but all didn't work :/
What am I doing wrong?
Thanks for your help!
Tom
Here is some code:
SPLASHSCREEN:
- (id)initWithFrame:(CGRect)frame withStepCount:(int)stepCount {
if (self = [super initWithFrame:frame]) {
// Initialization code
background = [[UIImageView alloc] initWithFrame: [self bounds]];
[background setImage: [UIImage imageWithContentsOfFile: [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] resourcePath], #"Default.png"]]];
[self addSubview: background];
progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
[progressView setFrame:CGRectMake(60.0f, 222.0f, 200.0f, 20.0f)];
[progressView setProgress: 0.0f];
stepValue = 1.0f / (float)stepCount;
[self addSubview:progressView];
}
return self;
}
- (void)tick {
value += stepValue;
[progressView setProgress: value];
}
VIEWCONTROLLER:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
splashView = [[SplashView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 480.0f) withStepCount:9];
[self setView: splashView];
NSTimer* delayTimer;
delayTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(finishInitialization) userInfo:nil repeats:NO];
}
return self;
}
- (void)finishInitialization {
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// do some stuff, like allocation, opening a db, creating views, heavy stuff...
[splashView tick]; // this should update the progress bar...
// init done... set the right view and release the SplashView
}
As mentioned in another answer, for some finite amount of time, as your app is being launched, Default.png is displayed and you have no control over it. However, if in your AppDelegate, you create a new view that displays the same Default.png, you can create a seamless transition from the original Default.png to a view that you can add a progress bar to.
Now, presumably, you have created a view or similar and you are updating a progress bar every so often in order to give the user some feedback. The challenge here is that your view is only drawn when it gets called to do a drawRect. If, however, you go from AppDelegate to some initialization code to a viewcontroller's viewDidLoad, without the run loop getting a chance to figure out which views need to have drawRect called on, then your view will never display its status bar.
Therefore in order to accomplish what you want, you have to either make sure that drawRect gets called, such as by pushing off a lot of your initialization code into other threads or timer tasks, or you can force the drawing by calling drawRect yourself, after setting up contexts and such.
If you go with the background tasks, then make sure your initialization code is thread-safe.
Default.png is just a graphic, a static image shown while the application is launching. If you want to show further progress, you'll have to show everything at the applicationDidLaunch phase. Show your modal "Splash Screen" there first (Create a view controller, add its view as a subview of your main window) and dismiss it when you are done whatever additional loading you needed to do.
Also, you need to do update your progress bar in a seperate thread. Updating your GUI in the same thread where a lot of business is going on is (in my opinion, but I could be wrong) a bad idea.
The main thread is, as far as I know, the only one that can safely do GUI things, and its event loop (that is, the main application thread's) is the one that does the actual displaying after you've called -setNeedsDisplay. Spawn a new thread to do your loading, and update the progress on the main thread.