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]; });
});
}
Related
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.
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];
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];
Views that contain UIWebViews take forever to load and for a few seconds it almost seems as though the app has stopped working. I tried inserting a HUD view into the IBAction, but it does not show up. Does anyone have any suggestions for a good way to implement this (btw, the HUD view IS working, it just doesn't show up since it is in the same action as presentModalViewController).
-(IBAction)charities {
[SVProgressHUD showInView:self.view status:#"Loading"];
Charities *variable = [[Charities alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:variable animated:NO];
}
Create a new delegate protocol in Charities, and set whatever view from which you are executing this code as the delegate. Then, from the UIWebViewDelegate webViewDidFinishLoad:, call a delegate method that will present the modal view. This way, you see the loading HUD until the webview is actually ready to be viewed, at which point the view will be presented.
Got it. I used a NSTimer in the same action as the HUD view and called a function that would bring up the new view. It works perfectly now.
-(IBAction)charities {
[NSTimer scheduledTimerWithTimeInterval:0.0000000000000000000001 target:self selector:#selector(loadCharities) userInfo:nil repeats:NO];
[SVProgressHUD showInView:self.view status:#"One sec"];
}
-(void)loadCharities {
Charities *variable = [[Charities alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:variable animated:NO];
}
my app is nav based. in which i have a main tableView which shows feed items in cells. when a cell is clicked, a detailview is created which shows details of that feed item. i am working with push notifications now. when action button in notification is clicked,
(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:
is called. how can i implement that method that if action button in notification is clicked. it should parse the feed again, reload the tableview, creates the latest feed items detailview and push it in navigational stack. i tried some code but it didn't work. here is the code i wrote.
in AppDelegate:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
RootViewController *controller = [[RootViewController alloc] init];
[controller newFeedItem];
}
in RootViewController:
- (void)newFeedItem
{
spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame=CGRectMake(130, 170, 50, 50);
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelector:#selector(doStuff) withObject:nil afterDelay:0.01];
}
-(void)doStuff
{
[[self stories] removeAllObjects];
[self startParsing];
[self.tableView reloadData];
// create detailview and push it in navigational stack
[spinner stopAnimating];
[spinner release];
}
but activity indicator is not appearing and tableView is also not reloading. Why is it happening so? Thanx in advance
Not quite sure about your program flow but I assume you show the rootViewController at program start and at a later time you receive a remote notification.
In your code (in didReceiveRemoteNotification) you are instantiating a new rootViewController and that new one will be different from the one already on the screen. Following your approach you might want alloc the controller once and keep it around for later when the remote notification arrives.
Personally I would suggest to use local notifications and to fire a local notification in didReceiveRemoteNotification and catch it in the rootViewController. That would ensure the currently active instance of the controller responds.
Also not sure why the spinner not shows, for a try call it from the viewDidAppear, just it see it it works at all and if the problem is with the reaction to the remote notification. And use some breakpoints.
Edit with respect to your comment:
in the interface define
RootViewController *controller
in the implementation you alloc the controller (for example in appDidFininshLaunching)
if (controller == nil) controller = [[RootViewController alloc] init]
in didReceiveRemoteNotification you can then do
[controller newFeedItem];
without alloc'ing it again and you can refer to the same controller. Don't forget to release it in -(void)dealloc