Activity indicator behavior not as expected - iphone

I have a table view that lists all states in the US. When a user clicks on a state, a list of river gauges for that state is generated from a web service call. The list is rendered in another table view controller. I am in the process of integrating an activity indicator to notify the user that there is network acitivity.
I have an object (GuageList) that holds the list of gauges as a mutable array. It is this object that makes the web service call and populates the array. The array is then used as the data source in the resulting table view controller. My first attempt at integrating an activity indicator was to place the initialization of this object in the destination table view controller that displays the gauges for a state. However, when I do this, I don't get expected results. When clicking on a state, there is a long pause, then the table view containing the list of gauges is displayed with the activity indicator briefly rendering in the destination controller.
Next, I tried placing the initialization of the GaugeList object in the initial table view controller (containing US states). My thoughts were to initialize the GaugeList object there, and pass it on the segue. This produced identical results.
Source segue code:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"sgShowStateRivers"]){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Loading Rivers\nPlease wait..." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alert setOpaque:NO];
[alert show];
UIActivityIndicatorView *activityStatus = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(120, 230, 50, 50)];
activityStatus.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[activityStatus startAnimating];
[alert addSubview:activityStatus];
RiversByStateTableViewController *riversVC = [segue destinationViewController];
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
NSArray *tempArray = (NSArray*)[groupedStates objectAtIndex:path.section];
NSString *key = [tempArray objectAtIndex:path.row];
NSString *stateID = [statesDict objectForKey:key];
[riversVC setStateIdentifier:stateID];
GaugeList *stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateID andType:nil];
[riversVC setStateGauges:stateGauges];
[activityStatus stopAnimating];
[alert dismissWithClickedButtonIndex:0 animated:YES];
}
}
In my destination, I simply use the stateGauges object to populate the table. My thoughts were that the stateGauges object would populate before the segue completed, but apparently I am incorrect.
What am I doing wrong?
Thanks! V

Normally you would use the prepareForSegue delegate method to populate the nextViewController with data you already have in runtime memory. This is just a initial step before the segue actually occurs and the next view is presented to the user. The actions should be relatively quick.
I suppose you could be holding up the main thread while you are downloading but that will essentially not allow you to update the views. I will assume you are making asynchronous web calls.
You can think of this like [view willDisplay] and [view didDisplay].
If you need to make a web call to populate your data, you might want to do this action when the user presses the State (cell, label, whatever). Present the view to show the download activity. Then after the web call has completed and the data has been verified, manually call the segue transition yourself using [self performSegueWithIdentifier:#"SegueName" sender:someobject];
Hope it helps.

Related

Need help presenting a view controller

I have a class that has an extension of UIButton that shows a UIAlertview under certain circumstance.
#interface CellButton : UIButton {}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"You Lose!"
message:#"Play Again?"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"cancel", nil];
[alert show];
This works fine, but I need to present a view controller when user presses ok.But as you may know you cannot present a view controller with an extension of UIButton.
So I was wondering if I can put the code below in another viewcontroller and allow it to work with the UIAlert in Cellbutton class.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) { // and they clicked OK.
GameController*myNewVC = [[GameController alloc] init];
[self presentModalViewController:myNewVC animated:NO];
}
}
You don't do it inside the UIButton.
The target of clicking the UIButton should be a UIViewController. After that, show an alert view FROM the view controller, and the view controller will be the delegate of the UIButton. From their everything will work fine.
This would work as long as you have set the alert view delegate to the view controller you want to handle the presentation.
I would suggest moving all of the functionality to the view controller though, i.e. present and handle the alert view from the same view controller, this can be triggered from an event from the button. I think this makes the code more readable and it doesn't really make sense for a button to know about alert views
You can declare a global variable (in appDelegate - and how to do it HERE) then ;
1 - set the variable 1 when user click button
2 - get the value from other viewcontroller's action
if the value is 1 then go ahead.

uialertview in login page in every launch

I have an alertview to show the update of new version in the login page (1st page). But Actually i want this alert to come only in the login page every time it launches .
i have seen so many question related to this but none helped some of them are Displaying an alert message only ONCE but to reappear again on app launch
UIAlertView, once the user opens the app. Shown once
.......
Now my code is
[self performSelector:#selector(getUpdate) withObject:nil afterDelay:0.1];
in DidBecomeActive method. We are calling Webservice to get the update alertmsg.
and my problem is
1) By using the code in DidBecomeActive,For the 1st time it works gud after closing without logout and whenever we reopen the app, the alert comes in current page no matters 1st or last.
What I would suggest is that you call a method in your view controller from your app delegate.
In your appdelegate.m file you will have a method called applicationDidFinishLaunchingWithOptions... in here will be some code which instantiates the view and calls the first view controller.
For instance I have just started a new project (Master Detail Application - but yours will be similar).
There is some code in my AppDelegate.m file that looks like this:
PMXMasterViewController *masterViewController = [[PMXMasterViewController alloc] initWithNibName:#"PMXMasterViewController_iPhone" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
self.window.rootViewController = self.navigationController;
masterViewController.managedObjectContext = self.managedObjectContext;
What this does is init the view controller, add it to the navigation controller and set the managedObjectContext variable.
After this you can call any methods on your view controller:
[masterViewController showAlertDialog:#"My Message Here"]
In masterViewController you need to make a method called showAlertDialog:
- (void)showAlertDialog:(NSString *)message {
// show alert dialog here
}
As the app delegate's applicationDidFinishLauching... method will only get called when the app starts that means that your alert dialog will only show when the app starts!
Hope that helps.
Let me guess that u need to display the alert only the first time when the application launches... if that is so... try this method
To determine whether its the first run in the appdidfinishlaunching method
store a bool in nsuserdefaults like firstRun
if([defaults objectForKey:#"firstRun"]==nil)
{
[defaults setBool:YES forKey:#"firstRun"];
}
else
{
[defaults setBool:NO forKey:#"firstRun"];
}
and then later when u display the alert display it only when this value is YES
Write following code in the viewDidLoad method of your concerned view controller:
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"UNLOCK PIN!"
message:#"Enter your valid PIN."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
message.alertViewStyle = UIAlertViewStyleSecureTextInput;
[message show];

UIAlertView with a UIActivityIndicator shows up too late/Threading issue

In my app I have a couple places where I'm calling a web service and retrieving JSON, which is then parsed into arrays of objects. The time-consuming part is definitely the web service call. The behavior I desire is as follows:
User selects row of interest
On current view, AlertView with ActivtiyIndicator subview added
Data loads in new VC
New view appears
AV indicator gone
My VC's are equipped with doLoadData methods that basically load an array of data that is displayed by each VC. For example:
AnimalViewController has a button "View Dogs" that when pressed does:
DogViewController *vc = [[DogViewController alloc] init];[self.navigationController pushViewController:vc animated:YES];
[vc release];
DogViewController has property NSArray *dogs and method doLoadDogs, which takes a while to happen.
Currently I have in the "View Dogs" method:
Create an AlertView with an ActivityIndicator
Show it
Create the VC
Push it
Hide the AlertView
Release and nil it
This seems like it would not be difficult but I've yet to find a simple implementation of this with threading.
If your network call/array creation takes a long time and is on the main thread, then all of this will essentially happen at the same time. When you do things like show an alert view, push a navigation controller, etc., what you're really doing is scheduling these things to happen at the next turn of the run loop. Since your "View Dogs" method essentially blocks until everything is done, the run loop isn't executed again until after everything is needed.
The best solution is to factor out your network loading code and not block your main thread/main event loop. As a quick fix, you could factor out the pieces of your current "View Dogs" method using GCD. As an example (obviously this would need to be updated to your actual requirements/code):
- (void)viewDogs:(id)sender
{
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// create and schedule the alert view
UIAlertView* alert = [[UIAlertView alloc] initWithTitle...]; // your real alert view
dispatch_async(mainQueue, ^{ [alert show]; });
dispatch_async(mainQueue, ^{
DogViewController* vc = [[DogViewController alloc] init];
[[self navigationController] pushViewController:vc animated:YES];
[vc release];
dispatch_async(mainQueue, ^{ [alert dismissWith...]; [alert release]; });
});
}

efficient way of making a multiview app?

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];

taking string from a text field and passing it to the previous viewController

im my app that im tring to build, i want to have a table view, where you select a row e.g email address. the view pushes to a simple page with a uitextField, when you hit the save button it pops the view controller back to the initial page, where the user can select the next field.
the issue that i am having is passing the information entered in the textfile back to the first view controller. this should be really simple, but anything i try just does not work
what is the best way to go around this.
thanks
You are probably thinking about the problem backwards. In an MVC system like Cocoa, the job of View Controllers is to manage views, not data. Create a model object to hold the data you're updating. When you create a view controller, pass the model object to it. It may update the model with changes the user makes. It should not worry about who called it, or who it returns to. It should just update the model object, and other interested parties should read the model object. As an example:
SettingsViewController would have a model object called Settings
When you dive into a detail view controller like EmailViewController, you pass the settings to it like emailViewController.settings = self.settings before presenting it.
When the user makes changes, just update the object like self.settings.emailAddress = ...
This separates your view logic from your model logic, which is a key features of Cocoa patterns. If you fight this pattern, you're going to often find yourself thinking "it sure is hard to get there from here."
You can either use a delegate method or, even simpler, just define an instance variable NSString *textEntry in the first view controller that can be set (property/synthesize) and then access that view controller from the stack.
For example, in the pushed view, do something like:
FirstViewController *firstViewController = [[[self navigationController] viewControllers] objectAtIndex:0];
[firstViewController setTextEntry:[textfield text]];
The easiest way I found to do this is using NSNotificationCenter.
In the ViewController with the TableView:
- (void)updateRowValue:(NSNotification *)notification{
NSDictionary *valuesDictionary = [[NSDictionary alloc] initWithDictionary: [notification userInfo] copyItems:NO];
NSString *newString = [valuesDictionary objectForKey:#"StringVal"]
}
This is the method called when row is selected
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TextFieldViewController *tfvc = [[TextFieldViewController alloc] init];
[tfvc setPostNotificationString:#"updateRowValue"];
[self.navigationController pushViewController:tfvc animated:YES];
}
Now in the viewController with the textField, when you press the button to return to the previous viewController call this:
- (IBAction)saveButtonPressed{
NSArray *valuesArray = [[NSArray alloc] initWithObjects:textField.text,nil];
NSArray *keyArray = [[NSArray alloc] initWithObjects:#"StringVal",nil];
NSDictionary *dictionary = [[[NSDictionary alloc] initWithObjects:valuesArray forKeys:keyArray] autorelease];
[[NSNotificationCenter defaultCenter] postNotificationName:[self postNotificationString] object:self userInfo:dictionary];
[[self navigationController] popViewControllerAnimated:YES];
}