Loading xib from UIAlertView from a viewcontroller - iphone

In my app I have a xib that has several viewcontrollers and xibs (with more viewcontrollers)off of it.
I can go from xib to xib and load what ever view controllers I want.
My problem is when I go to a view in side one off my viewcontrollers (be it on the main xib or another) I have a navigation bar at the bottom with a single button marked "back". Now when this button is hit before it takes you back It asks if you want something to happen.
When you click yes a UIAlertView will pop up and tell you wants about to happen when you click "ok". I know my button is firing by NSLogging my tag.
However I need a certain xib file to load and it does not.
This is the code that I am trying to use -- now this works If I want to go from xib to xib from a button. So I am not sure why its not working in an alert view.
- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked one of the OK/Cancel buttons
if(alert.tag==1)
{
if (buttonIndex==0) {
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
}
}
}
Adding pic do to a response:

I have had similar problems before, not with a UIViewController, but with a UIActionSheet. When I tried to do this, I wanted to present a UIActionSheet depending on the selection of the UIAlertView. When I pressed something in the UIAlertView, the view was dismissed, but my action sheet was not shown. This was my solution; instead of:
if(alert.tag==1) {
if (buttonIndex==0) {
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
}
}
try something like:
if(alert.tag==1)
if (buttonIndex==0)
[self performSelector:#selector(presentController) withObject:nil afterDelay:.1];
with the method presentController being:
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
The problem seems to be that the UIAlertView takes some time to dismiss itself (very small amount of time) and in that time presenting another view doesn't work.
Also, as Alan Zeino said, if the name of the xib is not the same as the name of the view controller (for example if you have two different xibs- one for ipad and one for iphone), then you do need to specify which to use. If the name of the xib is the same as the view controller, you do not have to specify a name.
Edit:
If you want to use an action sheet and expect the delegate methods to work when their actions are triggered, you'd better set the delegate of the action sheet to self and in your interface file add the protocol.

You need to specify the xib you want to open:
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:#"XibNameHere" bundle:nil];

Since you have indicated that your log statement confirms correctly setting delegate and implementing the methods, the 'UIAlertView locks the screen' theory seems the only expectable one.
As Rickay has said performSelector:AfterDelay: would likely work, but as he has also said that requires a second method to be created. I would say that this is the perfect time for a block.
Here are a couple of options: Either should be copy and paste solutions.
1) Assuming the current main runloop simply has to finish it's current call stack before the new view controller can be pushed you could use:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag == 1) {
if (buttonIndex == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
});
}
}
}
This will add a block to the main queue that will create and show your ControllPanelViewController as soon as the main run loop becomes available.
If, however, that doesn't work and it is 'just a matter of time', a block will still work. Like So:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag == 1) {
if (buttonIndex == 0) {
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
});
}
}
}
This will create a block that will 'fire' in the number of seconds specified. Here set to 0.1 seconds.
Again the first one is a cleaner, arguably better, and much easier to read option.

Related

MBProgressHUD activity indicator doesn't show in viewDidAppear after segue is performed

I have two UITableViewControllers A and B, and this is what I'm trying to do when I click on a table view cell in A:
Prepare to segue from A to B by setting some of B's variables from A.
Perform segue from A to B.
B appears.
Display a "Loading" activity indicator with [MBProgressHUD][1].
In a background task, retrieve data from a URL.
If an error occurs in the URL request (either no data received or non-200 status code), (a) hide activity indicator, then (b) display UIAlertView with an error message
Else, (a) Reload B's tableView with the retrieved data, then (b) Hide activity indicator
However, this is what's happening, and I don't know how to fix it:
After clicking a cell in A, B slides in from the right with an empty plain UITableView. The MBProgressHUD DOES NOT SHOW.
After a while, the tableView reloads with the retrieved data, with the MBProgressHUD appearing very briefly.
The MBProgressHUD immediately disappears.
There doesn't seem to be an error with the way the background task is performed. My problem is, how do I display the MBProgressHUD activity indicator as soon as my B view controller appears? (And actually, how come it's not showing?) Code is below.
A's prepareForSegue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
B *b = (B *)[segue destinationViewController];
// Set some of B's variables here...
}
Relevant methods in B
- (void)viewDidAppear:(BOOL)animated {
[self startOver];
}
- (void)startOver {
[self displayLoadingAndDisableTableViewInteractions];
[self retrieveListings];
[self.tableView reloadData];
[self hideLoadingAndEnableTableViewInteractions];
}
- (void)displayLoadingAndDisableTableViewInteractions {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Loading";
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.tableView.userInteractionEnabled = NO;
}
- (void)hideLoadingAndEnableTableViewInteractions {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.tableView.userInteractionEnabled = YES;
}
- (void)retrieveListings {
__block NSArray *newSearchResults;
// Perform synchronous URL request in another thread.
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
newSearchResults = [self fetchNewSearchResults];
});
// If nil was returned, there must have been some error--display a UIAlertView.
if (newSearchResults == nil) {
[[[UIAlertView alloc] initWithTitle:#"Oops!" message:#"An unknown error occurred. Try again later?" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
} else {
// Add the retrieved data to this UITableView's model. Then,
[self.tableView reloadData];
}
}
- (NSArray *)fetchNewSearchResults {
// Assemble NSMutableArray called newSearchResults from NSURLConnection data.
// Return nil if an error or a non-200 response code occurred.
return newSearchResults;
}
I think you have to call [self hideLoadingAndEnableTableViewInteractions]; after newSearchResults = [self fetchNewSearchResults]; You are retrieving data in another thread which means -startOver will continue executing after calling [self retrieveListings]; and will hide the HUD right away. Also because you are updating the display you have to make sure you are doing that on the main thread. See example
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
});
When B appears, it displays a plain and empty UITableView, but does not display the MBProgressHUD even if the task does begin in the background (and yet, the MBProgressHUD is called to show before that). Hence, my solution is to show the MBProgressHUD in viewDidLoad, which precedes viewWillAppear.
- (void)viewDidLoad {
// ...
[self displayLoadingAndDisableUI];
}
I set up two additional boolean properties to B--one in .h, called shouldStartOverUponAppearing, and one in a class extension in .m, called isLoadingAndDisabledUI. In startOver, I added the following lines:
- (void)startOver {
if (!self.isLoadingAndDisabledUI) {
[self displayLoadingAndDisabledUI];
}
}
The check is done so that startOver doesn't display another MBProgressHUD when it has already been displayed from viewDidLoad. That is because I have a third view controller, called C, that may call on B's startOver, but doesn't need to call viewDidLoad just to display the MBProgressHUD.
Also, this is how I defined viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
if (self.shouldStartOverUponAppearing) {
[self startOver];
self.shouldStartOverUponAppearing = NO;
}
}
This way, startOver will only be invoked IF B appeared from A. If B appears by pressing "Back" in C, it will do nothing and only display the old data that was there.
I think that this solution is FAR from elegant, but it works. I guess I'll just ask for a better approach in a separate SO question.
I have used a common method for MBProgressHUD.
#import "MBProgressHUD.h" in AppDelegate.h also following methods.
- (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title;
- (void)dismissGlobalHUD;
In AppDelegate.m add following methods.
- (MBProgressHUD *)showGlobalProgressHUDWithTitle:(NSString *)title {
[MBProgressHUD hideAllHUDsForView:self.window animated:YES];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.window animated:YES];
hud.labelText = title;
return hud;
}
- (void)dismissGlobalHUD {
[MBProgressHUD hideHUDForView:self.window animated:YES];
}
How to use?
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication]delegate];
//Show Indicator
[appDel showGlobalProgressHUDWithTitle:#"Loading..."];
//Hide Indicator
[appDel dismissGlobalHUD];
Hope this helps.

Modal View reappearing + crashing: "Attempting to transition while a transition is in progress"

Let me provide some context: I am building a tabbed application that allows a user to find and view some videos hosted on our server. Each tab has the videos grouped in a different manner with a segmented control in the navigation bar that a user can use to sort the list even more precisely (by title, date, etc...). Upon hitting "Sort" in the segmented control, a modal view controller is presented with the options available on a particular tab. An option is chosen and the choice is relayed back to parent view controller, which calls on the server for a sorted list.
Now here is the problem: On iOS 4.2, which we would like to support, the modal view either crashes after a sort option has been chosen, or it dismisses and then immediately reappears one more time. If it reappears, it only does so once and does NOT loop indefinitely. I know it has something to do with the transition and the view's life cycle but I can't seem to get this just right.
Code:
The parent view
-(void) segmentAction:(id)sender{
//create a sort view and pass it a value that indicates what the options should be
ModalSortViewController *sortView = [[ModalSortViewController alloc]
initWithNibName:nil bundle:nil sortByView:0];
[sortView setDelegate:self];
[sortView setModalTransitionStyle:UIModalTransitionStylePartialCurl];
[sortView setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentModalViewController:sortView animated:YES];
}
-(void) refresh:(id)sender{
[self fetchEntries];
}
//Delegate protocol for all tabbed table views
//Receives buttonIndex from the modal sort view
-(void)sortByButtonIndex:(int)buttonIndex{
if(buttonIndex==1){
//If sorting by title
fetchURL = #"fakeURL.com/?method=iGetCategories&sortBy=category&sortByOrder=ASC";
[self fetchEntries];
}
else if (buttonIndex==2){
//If sorting by number of items
fetchURL = #"fakeURL.com/?method=iGetCategories&sortBy=count&sortByOrder=DESC";
[self fetchEntries];
}
else if(buttonIndex==0){
//Resets sort selection to nothing
segmentedControl.selectedSegmentIndex = -1;
}
[self dismissModalViewControllerAnimated:YES];
}
The modal view
#synthesize delegate, option1, option2;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil sortByView:(int)_viewInt
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
sortChosen = 0;
viewInt = _viewInt;
}
return self;
}
//This method is called whenever a selection on the modal view has been made.
//The button tags have been set in IB and are sent to the parent table view controller
//where a switch statement is in place to sort its data by the selection.
-(IBAction)madeSelection:(id)sender{
sortChosen = [sender tag];
[self.delegate sortByButtonIndex:sortChosen];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];//Added after Felix pointed out that the super was not called
switch (viewInt) {
case CAT_FOLDERS:
[self.option1 setTitle:#"By Category Name" forState:UIControlStateNormal];
[self.option2 setTitle:#"By Number of Items" forState:UIControlStateNormal];
break;
case PRES_FOLDERS:
[self.option1 setTitle:#"By Presenter Name" forState:UIControlStateNormal];
[self.option2 setTitle:#"By Number of Items" forState:UIControlStateNormal];
break;
case MEDIA:
[self.option1 setTitle:#"By Media Title" forState:UIControlStateNormal];
[self.option2 setTitle:#"By Release Date" forState:UIControlStateNormal];
break;
default:
break;
}
}
Crash Results:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Attempting to begin a modal transition from <UINavigationController:
0x139160> to <ModalSortViewController: 0x172810> while a transition is already in
progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed'
Sorry about the length. I wanted to be as clear and thorough as possible. Thank you in advance!
EDIT: I should mention that the crashing/repeat appearing seems to be dependent on where sortByButtonIndex: is called and when the view is dismissed.
Figures I would solve this hours after I posted a bounty on it!
The problem was that the fetchEntries method, which I did not post because I didn't think it was the culprit, sets my segmented control's selected index to -1 when it completes its call to the server. It appears that newer versions of iOS ignore the the EventValueChanged if it is changing to -1. I simply set a condition to ignore a -1 index on the segmented control in segmentAction: method and it works.
-(void) segmentAction:(id)sender{
if(segmentedControl.selectedIndex != -1){
//create a sort view and pass it a value that indicates what the options should be
ModalSortViewController *sortView = [[ModalSortViewController alloc]
initWithNibName:nil bundle:nil sortByView:0];
[sortView setDelegate:self];
[sortView setModalTransitionStyle:UIModalTransitionStylePartialCurl];
[sortView setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentModalViewController:sortView animated:YES];
}
}
You are not calling super from within -(void)viewWillAppear:(BOOL)animated.
Try adding the following line at the top:
[super viewWillAppear:animated];
This could mean that the super implementation of your ViewController does not set its appear flags correctly.

Refresh UINavigationController?

I have a UINavigationController with two ViewControllers on the stack. At a certain point in the program execution, the second view controller is visible on the screen and at that moment, I would like to replace that ViewController with another. However, it's not working. Here is my code:
UINavigationController * thisNavController = self.waitingController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
NSLog (#"visible before: %#", [thisNavController.visibleViewController description]);
[thisNavController setViewControllers: [NSArray arrayWithArray: newControllers] animated: YES];
NSLog (#"visible after: %#", [thisNavController.visibleViewController description]);
[thisNavController.visibleViewController.view setNeedsDisplay];
The above code produces this output:
2011-05-05 13:30:22.201 myApp[3286:207] visible before: <DummyViewController: 0x4c8b4c0>
2011-05-05 13:30:22.209 myApp[3286:207] visible after: <RealViewController: 0x60173f0>
But what is shown on the screen does not change. It seems that everything works fine after I switch tabs, so it seems that it is a redrawing problem, but setNeedsDisplay does nothing and I couldn't find a method that tells the NavigationController that its viewControllers have changed.
Is there some refresh mechanism that I have to trigger to refresh the screen?
One solution would be to say add 2 (initial) view controllers when your app is started, and only allow navigation from the 2nd and 3rd ones, falling back to the 1st (root) view controller in your senario described. You never allow navigation back to this 1st view controller or from this 1st view controller to the 2nd; you see this sort of behaviour in some of Apple's apps, like iTunes and Remote - if there's no network connect the app shows a no-network connection view immediately.
So, when you want to show the 1st view controller above, you do something like:
NSArray *array = [navigationController popToRootViewControllerAnimated:NO];
Without more info about the navigation behaviour of your app I hope this helps.
Or show a modal view controller?
The problem turned out to be the fact that I was trying to replace the view controller stack before the initial transition animation for the Dummy controller has finished. This can be prevented in the following manner.
First, preserve the (eventual) delegate, set the current object as the delegate, set a flag that animation is in progress and push the new controller:
self.oldNavigationControllerDelegate = self.waitingController.navigationController.delegate;
self.waitingController.navigationController.delegate = self;
self.isAnimating = YES;
[viewController.navigationController pushViewController: [[DummyViewController alloc] init] animated: YES];
Then, implement the UIViewControllerDelegate protocol methods as follows:
#pragma mark -
#pragma mark UINavigationControllerDelegate methods
- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController)
self.isAnimating = YES;
}
- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController) {
self.isAnimating = NO;
if (self.readyPage != nil)
[self pageIsReady: self.readyPage]; // method to load the ready controller
}
}
After that, whenever your content/controller/download/whatever is ready, make sure that the navigation controller is no longer animating. If it is, set a flag that the page is ready. If it isn't, load the page:
if (self.isAnimating)
self.readyPage = controller;
else
[self pageIsReady: controller];
And, of course, implement the actual loading of the new stack (as usual):
- (void) pageIsReady: (UIViewController *) page {
// this method should replace the dummy that is spinning there
UINavigationController * thisNavController = self.waitingController.navigationController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
thisNavController.viewControllers = [NSArray arrayWithArray: newControllers];
thisNavController.delegate = self.oldNavigationControllerDelegate; // restore the original delegate
// clean up
self.isAnimating = NO;
self.readyPage = nil;
self.waitingController = nil;
self.oldNavigationControllerDelegate = nil;
}
This makes everybody happy :P

New UIView not loading

I have a TabController View setup and I am attempting to load a new view in the current tab. This view would be loaded depending on a selection from a UIAlertView.
As such I have this code attempting to do so:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
// Yes Button
if (buttonIndex == 0) {
NSLog(#"%# Pressed", [alertView buttonTitleAtIndex:0]);
user = [[UserInfo alloc] initWithNibName:#"UserInfo" bundle:nil];
[self.navigationController pushViewController:user animated:YES];
[user release];
}
// Don't show again Button
if (buttonIndex == 1) {
NSLog(#"%# Pressed", [alertView buttonTitleAtIndex:1]);
}
// No Button
if (buttonIndex == 2) {
NSLog(#"%# Pressed", [alertView buttonTitleAtIndex:2]);
}
}
The Yes block prints out to the console, however the new view does not load. User is an instance of UserInfo, which in turn is a subclass of UIViewController. The Nib UserInfo is created and is part of the project.
What am I missing?
These two lines are unnecessary:
[user.view setHidden:NO];
[self.navigationController setView:user.view];
But this one is:
[user release];
How are you setting up your tab bar controller? Have you placed this view inside of a UINavigationController then added that navigation controller to the TabBarController?
Please review the View Controller Programming Guide for iOS (specifically the Combined ViewController Interfaces section) and verify you are nesting these views correctly.
Is user a global variable? I usually see something like
UserInfo *user = [[UserInfo alloc] initWithNibName:#"UserInfo" bundle:nil];
as the pattern. Since the navigation controller is really going to be the main owner of the view

UIImagePickerController not loading in viewDidLoad for iPhone SDK

I'm trying to show a UIImagePickerController as soon as one of my view controller loads. I'd like to this without the user having to press a button so I overrode the viewDidLoad method as follows:
- (void)viewDidLoad {
[super viewDidLoad];
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagePickerController.allowsImageEditing = YES;
imagePickerController.delegate = self;
[self presentModalViewController:imagePickerController animated:YES];
[imagePickerController release];
}
This compiles and runs, however when the view controller is loaded the image picker is not displayed. This code works fine if I attach it to an event of a button for example. Any ideas?
Thanks.
I had the same problem but solved it. Try using
-(void) awakeFromNib {
}
It will load just after everything else loads.
Try putting the code in
-(void)viewDidAppear
That even runs every time the view appears on the screen though (including when it appears after you dismiss the UIImagePicker), so you might have to add a BOOL value to make it only happen the first time it shows, or when you want it (i.e. not after dismissing a modal view).
It seems that viewDidLoad is too early to use presentModalViewController:animated:. I'd sugget to fork off a one-shot timer to call the method from next run loop iteration:
[NSTimer
scheduledTimerWithTimeInterval:0
target:self
selector:#selector(onLoadTimer:)
userInfo:nil
repeats:NO];
add the following method:
- (void)onLoadTimer:(id)unused
{
[self presentModalViewController:imagePickerController animated:YES];
[imagePickerController release];
}