UIButton doesn't update - iphone

I'm trying to change the hidden property of a button and this is done in a method (View one):
-(void)changeSong:(NSString *)songName {
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:[[NSBundle mainBundle] pathForResource:songName ofType:#"mp3"]];
musicPlaying = YES;
playButton.hidden = YES;
pauseButton.hidden = NO;
}
This method is called from another view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MainMenuController *mainMenu = [[MainMenuController alloc] initWithNibName:#"MainMenu" bundle:nil];
[mainMenu changeSong:[songs objectAtIndex:indexPath.row]];
mainMenu = nil;
[mainMenu release];
[[self navigationController] popViewControllerAnimated:YES];
}
I know that the changeSong method is being called correctly because the music changes. However, the hidden property of the items don't change. I've tried calling [self.view setNeedsDisplay]; but this doesn't do anything.
Thanks

It looks like the MainMenuController hasn't finished initializing by the time you call -changeSong, which is why everything in MainMenuController is nil.
To solve this, either delay your call to -changeSong by using
[mainMenu performSelector:#selector(changeSong:) withObject:[songs objectAtIndex:indexPath.row] afterDelay:0.01];
or make your tableview the MainMenuController's delegate, and when MainMenuController is finished loading from the nib (using - (void)awakeFromNib in MainMenuController), call the delegate's method to change the song.
Since you're delaying the call in both cases, you'll have to be careful not to release the view controller before you do, so you'll have to change that code a little.

When initializing a view controller from a nib using -initWithNibName:bundle:, the actual view and its subviews aren't unarchived until the first time the view controller's view property is accessed, per the documentation:
The nib file you specify is not loaded right away. It is loaded the first time the view controller’s view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad method and perform your tasks there.
Try calling [mainMenu view] right after you initialize it from the nib. That will hydrate the view hierarchy from the nib.
However, I guess I don't understand why you're unarchiving a view controller from a nib and calling one of its methods that affects the UI (i.e., hiding or revealing buttons) without pushing that view controller to a navigation controller or presenting it modally. -changeSong: is a method on MainMenuController, so simply calling it right after you initialize MainMenuController won't have any effect on the buttons that it manages.
(Unrelated: You're setting mainMenu to nil before releasing it, which effectively means mainMenu can never be released. Call -release first, then, optionally, set it to nil.)

Related

viewDidAppear not getting called

In my main UIViewController I am adding a homescreen view controller as subviews:
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:vc];
controller.navigationBarHidden = YES;
controller.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:controller];
[self.view insertSubview:controller.view atIndex:0];
[controller didMoveToParentViewController:self];
The issue is that viewDidAppear and viewWillAppear is only called once, just like viewDidLoad. Why is this? How do I make this work?
Basically inside vc I am not getting viewDidAppear nor viewWillAppear.
I also just tried adding the UIViewController without the navigation controller and it still doesn't work:
vc.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
[self addChildViewController:vc];
[self.view insertSubview:vc.view atIndex:0];
[vc didMoveToParentViewController:self];
In my case, viewDidAppear was not called, because i have made unwanted mistake in viewWillAppear method.
-(void)viewWillAppear:(BOOL)animated {
[super viewdidAppear:animated]; // this prevented my viewDidAppear method to be called
}
The only way I can reproduce the problem of child controllers not receiving appearance methods is if the container view controller does the following (which I'm sure you're not doing):
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return NO;
}
Perhaps you can try explicitly enabling this:
- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
{
return YES;
}
But my child view controllers definitely are getting the viewWillAppear calls (either if I explicitly automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers or if I omit this altogether.
Update:
Ok, looking at the comments under your original question, it appears that the issue is that the child controller (B) in question is, itself, a container view controller (which is perfectly acceptable) presenting another child controller (C). And this controller B's own child controller C is being removed and you're wondering why you're not getting viewWillAppear or viewDidAppear for the container controller B. Container controllers do not get these appearance methods when their children are removed (or, more accurately, since containers should remove children, not children removing themselves, when the container removes a child, it does not receive the appearance methods).
If I've misunderstood the situation, let me know.
#Rob answer in Swift 4 (which helped me on my case which I was adding a childViewController to a UITabBarController)
override var shouldAutomaticallyForwardAppearanceMethods: Bool {
return true
}
Another case where this will not be called at launch time (yet may be called on when you return to the view) will be is if you have subclassed UINavigationController and your subclass overrides
-(void)viewDidAppear:(BOOL)animated
but fails to call [super viewDidAppear:animated];
Had a same problem
My container view controller did retain a child view controller via a property, but did not add a child view controller to its childViewControllers array.
My solution was to add this line of code in the container view controller
[self addChildViewController: childViewController];
After that UIKit started forwarding appearance methods to my child view controller just as expected
I also changed the property attribute from strong to weak just for beauty
When updating my code to 13.0, I lost my viewDidAppear calls.
In Objective-c, my solution was to add the following override all to the parent master view controller.
This allowed the ViewDidAppear call to get called once again...as it did in previous IOS (12 and earlier) version.
#implementation MasterViewController
//....some methods
(BOOL) shouldAutomaticallyForwardAppearanceMethods {
return YES;
}
// ...some methods
#end
My problem was that I was changing the tab in UITabBarController (selectedIndex = x) and then messing with the child view controllers in that tab. The problem is that it needs to be done the other way round: first mess with the child view controllers in other tab and then change the tab by setting the selectedIndex. After this change methods viewWillAppear/viewDidAppear begun to be called correctly.
Presenting view controllers using presentModalViewController or segues or pushViewController should fix it.
Alternatively, if for some reason you want to present your views without the built-in methods, in your own code you should be calling these methods manually. Something like this:
[self addChildViewController:controller];
BOOL animated = NO;
[controller viewWillAppear:animated];
[self.view insertSubview:controller.view atIndex:0];
[controller viewDidAppear:animated];
[controller didMoveToParentViewController:self];

How do you reload UIViewController's view from a XIB after a memory warning blows it away?

I have an iPhone view controller that's initialized with a XIB.
If a view controller's view is not visible when a memory warning comes through, it sets its view to nil (releasing it). But when an overlapping view is dismissed and the cleared-out view becomes visible again, Cocoa doesn't reload it from the XIB; it simply creates a blank one. This leaves a blank white screen, and a broken app.
The Apple doc for UIViewController's loadView method says, "If the view controller has an associated nib file, this method loads the view from the nib file. A view controller has an associated nib file if the nibName property returns a non-nil value." So I overrode loadView simply to check nibName after initializing the controller, and nibName is correct. So subsequent calls to loadView should be reloading from the XIB. I verified that loadView is called again after the memory warning.
UPDATE: With more testing and logging, I've determined that after the second viewDidLoad call, the view's IBOutlets are non-nil. Since I set them to nil in viewDidUnload, I conclude that the view was indeed reloaded from the XIB. So why is it showing up as an all-white screen?
Thanks for any insight.
Here's viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
_photoViewController = [[EGOSimplePhotoViewController alloc] initWithPhotoSource:_photoSource
scrollView:bigImageScrollView
enclosingView:photoSquare];
_photoViewController.delegate = self;
if(_progressHUD == nil)
{
if(self.navigationController.view != nil)
{
_progressHUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
}
else
{
_progressHUD = [[MBProgressHUD alloc] initWithView:self.view];
}
}
// Add HUD to screen.
if(self.navigationController.view != nil)
{
[self.navigationController.view addSubview:_progressHUD];
}
else
{
[self.view addSubview:_progressHUD];
}
[_progressHUD release];
_progressHUD.labelText = #"Loading...";
[_progressHUD show:YES];
}
And viewDidUnload:
- (void)viewDidUnload
{
[_photoViewController release];
[super viewDidUnload];
}
The viewControllers view loading mechanism after a memory warning is supposed to be transparent to the developer. you shouldn't have to do a thing to re-create the view.
For VCs loaded using nib, the system will re-create the view and viewDidLoad will be called.
For VCs loaded programatically loadView will be called again.
and the whole cycle repeats until viewDidAppear: and you see the view again.
USE CASE:
UITabBarController (2 tabs assumed)
User on Tab 0 >> Goes to Tab 1 >> Triggers memory warning >> All active VCs on UITabbarController receive memory warning >> UITabBarController will unload view of Tab0 >> Tab0 receives viewDidUnload:
When user switches back to Tab 0 its view will be created from scratch beginning from loadView OR viewDidLoad as I said.
If you are not receiving these events then your viewController hierarchy is messed up. Maybe you just added some VCs view as subView to something OR maybe your VC is not connected to window either directly OR through some container controller (UINavigation, UITabBarController etc) OR maybe you tried rolling up you own containerController and messed up.
Try understand the UIViewController lifecycle from loadView to viewDidUnload and dealloc. Its awesome. It will help a lot in writing good code and design of your apps.

iPhone, need the IF for dismissModalViewControllerAnimated ELSE removeFromSuperview?

I need to add this to my dismiss button :-
[self dismissModalViewControllerAnimated:YES];
[self release];
else
[self.view removeFromSuperview];
I thought
if( self.navigationController.modalViewController ) {
would work be it nevers true
A couple of things:
1) You shouldn't ever release yourself in an object. If you're presenting a modal view controller, you should perform the release there since the view controller will now be retained by the view controller's .modalViewController property:
(In the parent):
UIViewController *someViewController = [[UIViewController alloc] init];
[self presentModalViewController:someViewController animated:YES];
[someViewController release];
2) The parent will store its child modal view controller in .modalViewController. The child will have its .parentViewController property set in this case. If the view has been added as a subview, its .superview property will be set. These are not mutually exclusive, however, so be careful. Generally speaking, UIViewControllers are intended to host full-screen views, and if you're adding the view as a subview, you should ask yourself if the view should just be a UIView subclass, and move the logic into the parent view controller.
That said, I suppose you could check your case (assuming you don't present modal view controller and add as a subview simultaneously):
if (self.parentViewController) {
[self dismissModalViewControllerAnimated:YES];
} else if (self.view.superview) {
[self.view removeFromSuperview]
}
In the latter superview case, the view controller will still be hanging around, so you'd need to let the other view controller know via delegate method or something to release you. In the first case, if you have released the presented view controller already as I described above, it will be released automatically when the parent view controller sets its .modalViewController property to nil.
Normally for a "dismiss" button I would call a method in the controller that presented the modal controller (use a delegate), not try to dismiss the modal view controller from within itself. I don't quite get what youre trying to do though, but that [self release] looks bad. I don't think you ever want to release self like that.
Try this in you modal viewcontroller:
- (IBAction)close:(id)sender {
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
Then just connect the button's action to that method.

after dismissing modal view, parent view seems deallocated?

I'm writing an iPhone app. Starting from a view controller in a navigation stack [called EditCreatorController], I am presenting a custom modal view controller [called BMSStringPickerController]. I have created a delegate protocol, etc. per the Apple guidelines for passing data back to the first view and using that view to dismiss the modal view. I even get the expected data back from the modal controller and am able to dismiss it just fine. The problem is, at that point, almost any action I take on the original view controller leads to debugger errors like
-[EditCreatorController performSelector:withObject:withObject:]: message sent to deallocated instance 0x3a647f0
or
-[EditCreatorController tableView:willSelectRowAtIndexPath:]: message sent to deallocated instance 0x3c12c40
In other words, it seems like the original view controller has evaporated while the modal view was showing. This is true no matter which of the two delegate callbacks is invoked.
Here is the code from the parent controller that invokes the modal view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 1) { // selection on creator type row
// create a string picker to choose new creator type from list
BMSStringPickerController *picker = [[BMSStringPickerController alloc] initWithNibName:#"BMSStringPickerController" bundle:nil];
picker.delegate = self;
picker.stringChoices = [NSArray arrayWithObjects:#"composer", #"lyricist", #"arranger", #"original artist", #"other", nil];
picker.currentChoice = creator.type;
picker.title = #"Creator Type";
// wrap it in a nav controller so we can get tile bar etc. (from VC prog guide p. 93)
UINavigationController *newNavigationController = [[UINavigationController alloc]
initWithRootViewController:picker];
[self.navigationController presentModalViewController:newNavigationController animated:YES];
[newNavigationController release];
[picker release];
}
}
And here are the delegate callbacks:
- (void)stringPickerController:(BMSStringPickerController *)picker didPickString:(NSString *)string {
NSLog(#"received string back: %#", string);
typeLabel.text = string; // only change the label for now; object only changes if done button pressed
[self.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
- (void)stringPickerControllerDidCancel:(BMSStringPickerController *)picker {
NSLog(#"picker cancelled");
[self dismissModalViewControllerAnimated:YES];
}
Another weird thing (perhaps a clue) is that although I get the "received string back" NSLog message, and assign it to typeLabel.text (typeLabel is an IBOutlet to a label in my table view), it never appears there, even with the table reload.
Anyone have some ideas?
Maybe you release the delegate in dealloc of BMSStringPickerController?
It may not solve your problem, but I suggest telling the picker to dismiss itself (in the delegate methods), allowing the responder chain to correctly handle the dismiss:
[picker dismissModalViewControllerAnimated:YES];
The default behavior when there is a memory warning is the release the view of all view controllers that are not visible. So if there was a memory warning while in your modal view controller, its parent view controller could have its view unloaded.
When this happens, viewDidUnload is called on the view controller so that you can release any references you hold into the view. If you have references that you didn't retain they will become invalid when the view is unloaded. Maybe this is happening in your case?
See the UIViewController reference Memory Management section for details. The UIViewController method didReceiveMemoryWarning: releases the view if the view is not currently visible and then calls viewDidUnload.

How to launch iPhone Camera on viewDidLoad?

I can't seem to launch the camera when loading my view. I end up making the user have to find and press a button on the screen just to load the camera (redundant). How can I do this? Code follows:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.allowsImageEditing = NO;
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
[super viewDidLoad];
[self presentModalViewController:self.imgPicker animated:YES];
}
UPDATE:
placing the above code into -viewDidAppear:(BOOL)animated allowed the camera to be launched, but the app crashed immediately afterward with the last routine being [UIWindowController transitionViewDidComplete:fromView:toView]; (as cited by Debugger)
You should do it in viewWillAppear:, or viewDidAppear: if the first doesn't work. trying to do it in viewDidLoad won't work because that is called after the view is first created, and the view isn't a subview of anything else at that point. As far as i understand it, in order to call presentModalViewController on self, the view must at some level be displayed in the UIWindow.
One other thing i just noticed; your code leaks memory depending on how you declare your imgPicker property. if it is declared with retain instead of assign, then unless you explicitly release it twice somewhere that picker will always exist in memory. You should autorelease the init'd object as you assign it to the property in that case.
Seems that it does not do it when you put the call to present modal view in view did load . You can try having a 2 second timer after the call to [super viewDidload] that pushes the picker view in or something like that.