I have a very strange problem with UINavigationController on the iphone and I am banging my head against the wall on this.
Gist of it is I am executing a call to a server and when that call fails I swap out the current view with a view containing an error message. The code in question is called on the main thread by using performSelectorOnMainThread
What happens in practice is that on the device it shows a blank white screen about half the time. On the simulator it presents a blank screen every time leading me to think this is perhaps some kind of timing problem that is more prominent due to better processing speeds in a simulator. This works perfectly if I call the same function by clicking a button in the ui to display the page so I don't think its a problem with the code itself.
I have verified that the controller I am adding is in the navigation stack. Verified it is being called on the main thread, it is visible, the frame size and location are correct. I have tried explicitly setting the view to be visible, moved it to the front in its parent view and called setNeedsDisplay and even manually called drawRect. None of this works.
Any thoughts on what could be going on here? I am assuming it has something to do with the run loop but I can't figure it out. Help would be much appreciated. The relatively simple code in question is below
UINavigationController* navController = self.navigationController;
int count = [navController.viewControllers count];
NSMutableArray* controllers = [[NSMutableArray alloc] initWithCapacity:count];
for (int i=0; i<count; i++) {
if (self == [self.navigationController.viewControllers objectAtIndex:i]) {
[controllers addObject:newController];
}
else {
[controllers addObject:[self.navigationController.viewControllers objectAtIndex:i]];
}
}
[self.navigationController setViewControllers:controllers animated:YES];
[controllers release];
I really don't understand what you're doing there. Something like this won't work?
- (void)displayMyErrorVC {
MyErrorVC *errorVC = [[[MyErrorVC alloc] init] autorelease];
[self.navigationController pushViewController:errorVC animated:YES];
}
And then in your other thread, if you have an error:
[self performSelectorOnMainThread:#selector(displayMyErrorVC) withObject:nil waitUntilDone:NO];
Related
I have a ViewController, VC1a, that presents VC2:
VC1a -> presentViewController: VC2
Is it possible to change VC1a into VC1b so that when dissmissViewControllerAnimated is called, it animates to VC1b instead of VC1a?
The reason I ask is because I want to return to a different screen without it animating back to VC1a. This relates to portrait/landscape changes.
Warning. This is potentially a bad/confusing UI choice for your users. But if you must...
You may be able to put VC1a inside a UINavigationController and modify the navigation stack while VC2 is in the foreground. Something like:
// in VC1a.m
[self presentViewController:VC2 animated:YES completion:^{
NSMutableArray *navigationStack = [[NSMutableArray alloc] init];
for (UIViewController *viewController in self.navigationController.viewControllers)
{
if (viewController != self.navigationController.viewControllers.lastItem)
{
[navigationStack addObject:viewController];
}
else
{
VC1b *viewControllerToSwapIn = [[VC1b alloc] init];
// probably some more initialization here
[navigationStack addObject:viewControllerToSwapIn];
}
}
self.navigationController.viewControllers = navigationStack;
}];
or possibly a better idea:
// in VC1a.m
[self presentViewController:VC2 animated:YES completion:^{
VC1b *viewControllerToSwapIn = [[VC1b alloc] init];
// probably some more initialization here
[navigationStack addObject:viewControllerToSwapIn];
[self.navigationController popViewControllerAnimated:NO];
[self.navigationController pushViewController:viewControllerToSwapIn animated:NO];
}];
OK, I implemented a Container view controller with two children - one for portrait, one for landscape and this took care of my problem.
I have spent days trying to figure out how to handle rotation correctly within iOS6 and I didn't realise there was a bug in rotating UITableViewController when pushed from a UINavigationController.
Thanks for the response #paulrehkugler - I was getting pretty desperate when I asked this question. There just doesn't seem to be any good examples of how to handle orientation changes while UINav/TableViewControllers are presented from a main view. I certainly wasn't wanting to implement such a hack.
The app I'm making utilizes multiple views. such as a disclaimer view, a view to display answer so on and so forth.Up until now this is the code that I've been using to switch from one view to another
-(IBAction)swichtogain:(id)sender{
gainview *second = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:second animated:YES];
[second release];
}
I found this method in a tutorial, I was wondering, is this the best way to do it ? I use the same code to switch back n forth from one view to another for eg.
-(IBAction)swichtoview1:(id)sender{
view1 *view = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
}
and when in view1 if the user hits the back button the following code gets executed
-(IBAction)swichtomainview:(id)sender{
mainview *view = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
}
I haven't edited anything in the appdelegate files and this is a view based app. Does this method cause it to use more memory ? During the activity monitor test using the instruments , I noticed the memory usage gets higher every time I go from the main menu to another view and back to the main menu !. Is there a better way than this ?. Also one of the view is a calculator so when the user hits the calculate button it switches to the next view while changing the textfield to the answer, below is the code for that !
-(IBAction)calculate{
MyClass *setnum = [[MyClass alloc]init];
setnum.grade_num = grade;
setnum.stage_num = stage;
setnum.ex_lym = ex_ly;
setnum.pos_lym = pos_ly;
setnum.er_num = er;
setnum.noderatio = pos_ly/ex_ly;
if(text1.text.length <=0 ||text2.text.length <=0||text3.text.length<=0||text4.text.length<=0||text5.text.length <=0){
UIActionSheet *action = [[UIActionSheet alloc]initWithTitle:#"Incomplete Values" delegate:self cancelButtonTitle:#"Ok" destructiveButtonTitle:nil otherButtonTitles:nil];
[action showInView:self.view];
[action release];
}else{
answer *ans =[[answer alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:ans animated:YES];
float i = calc_gain(setnum.grade_num, setnum.noderatio, setnum.stage_num, setnum.er_num);
NSString *result = [NSString stringWithFormat:#"%f",i];
ans.answer1.text = result;
ans.bar.hidden = NO;
[ans release];
}
[setnum release];
}
You should consider using one of the provided container view controllers (UITabBarController, UINavigationBarController or UISplitViewController on the iPad and so on).
The way you use presentModalViewController is most likely the wrong way. For one, calling presentModalViewController will retain your views. Keeping allocating new controllers and displaying their views via presentModalView is therefore increasing your memory footprint with each navigation step.
In general, a viewcontroller which shows another modal viewcontroller is also responsible for dismissing it again. The way to dismiss a modal view controller is therefore to let the presented controller inform its parent through delegation and ask the parent to dismiss (often on tapping a 'done' button).
I'm not even sure whether stacking modalViewControllers is a supported scenario, but at least didn't find anything stated otherwise in the documentation.
Asked here yesterday:
Switching views for iphone application - is this the right way?
I think another good way to go about this is to do this and add a univanigationcontroller:
[self.navigationController pushViewController:second animated:YES];
I am trying to learn iPhone/iOS programming. I have an UIPickerView that should dispay its selected row as soon as it becomes visible (it is contained on a flippSideView).
Unfortunately, the flipSideViewController's awakeFromNib is not called. It is somewhat too late to do it in viewDidLoad.
So, how can I make the pickerView display the selected row as soon it becomes visible?
Update:
Here is how I show the flipside view
- (IBAction)showInfo:(id)sender {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.delegate = self;
controller.uData = userData;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
controller.pickerView.delegate = userData;
controller.pickerView.dataSource = userData;
[controller release];
}// showInfo
In the flipside controller there is a method mySelect (to help me trace trace the calls)
-(void) mySelect:(NSString*) strMethod{
int row = [uData getCurrentUserRow];
[pickerView selectRow:row inComponent:0 animated:NO];
NSLog(#"selectRow %d called from %# (pickerView=%d uData=%d)", row, strMethod, (int)pickerView, (int)uData); }
and when the program runs it generates the log
selectRow 3 called from viewDidLoad (pickerView=87412720 uData=89267696)
selectRow 3 called from viewWillAppear (pickerView=87412720 uData=89267696)
selectRow 3 called from viewDidAppear (pickerView=87412720 uData=89267696)
It seems you're having the problem i had a couple days ago, i really don't think it's a bug I think the problem is when everything is called (could be wrong). Correct me if I'm wrong but you want your PickerView to display a certain value when it's first shown onscreen correct? Are you creating the PickerView in code or from a nib?
In my app, the below code snippet is right at the bottom of my viewDidLoad method
[pickerView selectRow: 100 inComponent:0 animated:YES];
This works as intended. Please post a snippet of your code so we can better understand your problem.
PS: When developing for iOS i would stay away from awakeFromNib and just use ViewDidLoad:
There apparently is a bug in some versions of iOS that means your view is not loaded until you call [super viewWillAppear:animated] or force it to load by calling [self view].
I'm at a loss! It's one of those pesky bugs that happens only under specific conditions, yet I cannot link the conditions and the results directly.
My app has a paged UIScrollView where each page's view comes from a MyViewController, a subclass of UITableViewController. To minimize memory usage I unload those controllers that are not currently visible. Here is my "cleaning" method:
- (void) cleanViewControllers:(BOOL)all {
if (all) {
// called if some major changes occurred and ALL controllers need to be cleared
for (NSInteger i = 0; i < [viewControllers count]; i++)
[viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
else if ([viewControllers count] > 2) {
// called if only the nearest, no longer visible controller need to be cleared
NSInteger i = pageControl.currentPage - 2;
if (i > -1) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
i = pageControl.currentPage + 2;
if (i < [viewControllers count]) [viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
}
It's this line that crashes the app:
viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
viewControllers is an NSMutableArray containing objects of type MyViewController. MyViewController has no custom properties and its dealloc method contains nothing but a [super dealloc] call.
Here is what the debugger shows:
alt text http://a.imageshack.us/img831/3610/screenshot20100806at126.png
The thing is that this does not happen every time the controller is cleared, but only sometimes. Specifically, after certain changes trigger a complete cleaning and re-drawing of the ScrollView, it displays the current page (call it X) fine, but as soon as I scroll far enough to cause cleaning of X, this crash happens. It's driving me nuts!
Another thing, this does not happen in the 4.0 Simulator, nor on an iPad, but happens very consistently on a 1st gen iPod touch running 3.1.3.
Something is being released but a pointer is still dangling. Set NSZombieEnabled to YES and run it again. Here's how:
Product -> Edit Scheme
Select the "Arguments" tab
Add to "Variables to be set in the environment"
Name: NSZombieEnabled
Value: YES
In Xcode 4.1 and above:
Product -> Edit Scheme
Select the "Diagnostics" tab
You have an option to enable zombie objects.
Run the app again. At some point it'll tell you you're accessing an already released object. From that you'll need to figure out who's not retaining or who's over-releasing the object.
Happy Zombie Hunting.
Often this can be caused by allocing something then setting it to something within the ViewController then releasing it, for example:
UIBarButtonItem* timeLabel = [[UIBarButtonItem alloc] initWithTitle:#"time" style:UIBarButtonItemStylePlain target:nil action:nil];
NSArray *items = [NSArray arrayWithObjects: timeLabel, nil];
self.toolbarItems = items;
Now the natural thing to do after this is :
[timeLabel release];
But this will cause EXC_BAD_ACCESS on [super dealloc], presumably because the view controller releases the array AND all the items within it.
View controllers will automatically unload their views on a memory warning by default, so there's no reason to unload the controllers themselves unless they include significant overhead.
Is the view controller's view still in the view hierarchy? You can check with something like [viewController isViewLoaded] && viewController.view.superview. If so, it's probably not safe to remove the view controller.
(Note the isViewLoaded check, since UIViewController.view will load the view if it's not already loaded.)
Instead of replacing the elements in the array you can simply call removeObjectAtIndex: or removeAllObjects: to make sure there is nothing holding reference where it shouldn't.
I have a App that uses the In App feature.
-(void) completeTransaction: (skPaymenttransaction *)transaction{
}
When the above method gets called i want to remove all subviews and go back to my main menu window (the first view in my App).
Can anyone suggest to cleanest and best way to do this?
Cheers
EDIT:
Just to make things clear
Im not sure if this makes a difference but i have my main menu screen, then iam doing the following with an enter button.
UIViewController *controller = [[UIViewController alloc] initWithNibName:#"NibFile" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
Then i have a main screen with a button then when a user taps it, it then presents them with another modal view controller as above. On this view is a button that says BUY on it. They use clicks this and then the StoreKit does it business and once the payment is complete i want to get rid of the two modal controllers above and be left with the main menu screen.
Any Ideas.. ive tried as above
EDIT 2:
#Jordan Thanks,
But not sure if im doing this correctly. I understand the above code.
But when i start my app my app delegate loads a viewcontroller which is my main menu. Then i have a button that takes me to another view and on there is my features if the user clicks a feature that is not unlocked then it displays another view controller with the store on.
So with this in mind how do i get back to my main menu.
I have tried the following:
NSArray *subviews = [myAppdelegate.viewcontroller.view subviews];
for (int i=0; i<[subviews count]; i++)
{
[[subviews objectAtIndex:i] removeFromSuperview];
}
but i get and error along the lines of:
expected ':' before '.' ?
This should work.
// view is equal to your main view
NSArray *subviews = [view subviews];
for (int i=0; i<[subviews count]; i++)
{
[[subviews objectAtIndex:i] removeFromSuperview];
}
If you're talking about UIViewControllers and not subViews (they are different),then you can use:
[self.navigationController popToRootViewControllerAnimated:YES];
You're either adding UIViews to a UIViewController, in which case use my code above, or You're pushingViews (e.g. pushViewController) on top of a UIViewController, in which case use the code here.
We have to get the views to be removed in an array so that we can remove everything by means of enumeration
NSArray *ChildViews = [ParentView subviews];
for (UIView *childView in ChildViews) {
[childView removeFromSuperview];
}