UITableView Problem with loading in data and showing HUD - iphone

I have a UITableView with a Navigation Controller. The table view is made up of custom cells that each contain data that is downloaded from a server, obviously this downloading is time consuming and therefore I would like to show a HUD of some sort while this happens.
My problem is this, when I press the button to transition to the table view, the button that I press gets stuck in the "highlighted" state until all the data is downloaded, then the view transitions from the previous one to show the table view in all its glory. However, I would prefer to transfer to an empty table view, then show a loading HUD while the table is populated. (Or something similar, anything to show the user the program is actually doing something and hasn't crashed..)
Here is some of my code:
- (void) displayView:(int)intNewView{
NSLog(#"%i", intNewView);
[currentView.view removeFromSuperview];
[currentView release];
switch (intNewView) {
case 1:
currentView = [[View1 alloc] init];
break;
case 2:
currentView = [[View2 alloc] init];
break;
case 3:
currentView = [[View3 alloc] init];
break;
case 4:
currentView = [[View4 alloc] init];
break;
case 5:
vc = [[TableViewController alloc] init];
currentView = [[UINavigationController alloc] initWithRootViewController:vc];
[currentView setTitle:#"Events"];
break;
}
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
[self.view addSubview:currentView.view];
[UIView commitAnimations];
}
- (void)viewDidLoad {
[super viewDidLoad];
//Initialisation code..
[SVProgressHUD showInView:self.view status:#"Doing Stuff"];
[self viewDidAppear:YES];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"ViewDidAppear");
if ([stories count] == 0) {
NSString * path = #"http://myURL.com";
[self parseXMLFileAtURL:path];
}
}
I understand why the view doesn't transition and show the HUD before viewDidAppear is called, just not how to get around this? The bulk of the work is started by viewDidAppear.
Any help would be greatly appreciated,
Jack

I think you should make the whole thing asynchronous. I mean, once you get to your table, you render it as empty, assigning to it a data source with no data, make a HUD appear, then you launch a thread that will load data from the remote server.
Once the thread has completed its task, you can send a notification through your app, which you have previously bound your table to. So, when the table gets the notification that the thread has completed its tasks, you update the data source, dismiss the HUD, and reload the table.
If you need more details on this, please ask.

Related

Refresh tableView when app becomes active

I know there have been a couple questions asked similar to this, but I think my issues is a little different, so bear with me.
I have a tabbed view app with table views set in 2 of the 3 tabs. I want to be able to refresh just the table view of the selected tab when the app wakes back up. I have tried using the notification center to tell my tab to refresh, but it makes the other tab crash because it is stealing the view from my progress hud. I will put some code here, so hopefully I can find a solution that will work for me.
Tab 1 ViewController
- (void)viewDidLoad {
// becomeActive just calls viewDidAppear:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(becomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated {
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Updating";
HUD.detailsLabelText = #"Please Wait";
[HUD showWhileExecuting:#selector(refreshData) onTarget:self withObject:nil animated:YES];
}
Tab 2 ViewController
- (void)viewDidLoad
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.navigationItem.title = [defaults valueForKey:#"companyName"];
self.view.backgroundColor = background;
tableView.backgroundColor = [UIColor clearColor];
tableView.opaque = YES;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// becomeActive just calls viewDidAppear
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(becomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
if (_refreshHeaderView == nil) {
EGORefreshTableHeaderView *view = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.tableView.bounds.size.height, self.view.frame.size.width, self.tableView.bounds.size.height)];
view.delegate = self;
[self.tableView addSubview:view];
_refreshHeaderView = view;
[view release];
}
// update the last update date
[_refreshHeaderView refreshLastUpdatedDate];
}
-(void)viewDidAppear:(BOOL)animated {
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Updating";
HUD.detailsLabelText = #"Please Wait";
[HUD showWhileExecuting:#selector(updateBoard) onTarget:self withObject:nil animated:YES];
// update the last update date
[_refreshHeaderView refreshLastUpdatedDate];
}
With this setup, my application will refresh tab 2 just fine, assuming that tab 2 was the last tab open before you closed the application. If I switch to tab 1 when I close, upon restarting the app, the notification calls tab 2 first and steals the view out from under my progress hud, so when I try to call the viewDidAppear method, the view is null and the app crashes. I either need to figure out how to implement the notification center better so it doesn't crash the other tab, or a better way overall to just refresh when the app becomes active. Looking forward to a good answer. Thanks in advance.
EDIT
When I run with this setup, it aborts inside the MBProgressHUD
- (id)initWithView:(UIView *)view {
// Let's check if the view is nil (this is a common error when using the windw initializer above)
if (!view) {
[NSException raise:#"MBProgressHUDViewIsNillException"
format:#"The view used in the MBProgressHUD initializer is nil."]; // <-- Aborts on this line, so they know it can happen
}
id me = [self initWithFrame:view.bounds];
// We need to take care of rotation ourselfs if we're adding the HUD to a window
if ([view isKindOfClass:[UIWindow class]]) {
[self setTransformForCurrentOrientation:NO];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification object:nil];
return me;
}
Main problem here that notifications are received by all views, whether they will appear or not. Good practice would be to notify only those views which will appear in screen
you can use application delegate's (void)applicationWillEnterForeground:(UIApplication *)application method to handle change in application instead of notification.
if problem is all about refreshing tab that is on screen then keeping track of change in tab in AppDelegate and using that when application enters foreground would be better idea over notification.
Other option is to use viewWillAppear method in tabbarcontroller. When this method gets called, you can refresh current tab.
There really isn't much out there to help answer this question. #Learner gave me an idea that ultimately led to how I resolved this issue.
Since all of the logic I have works, the issue was to prevent the HUD from stealing the view from the other one since they both get called when they respond to the notification event. So in my -becomeActive event for both tabs, I added a check if the selectedIndex was a match for the current tab, if not, no refresh. Worked like a charm.
-(void)becomeActive:(NSNotification *)notification {
// only respond if the selected tab is our current tab
if (self.tabBarController.selectedIndex == 1) { // just set the number to your tab index
[self viewDidAppear:YES];
}
}

gesture recogniser on web view in an iphone app

I have created a webview to display the pdf, now using the gesture recognizer on single tap I have to call some method but single tap is not recognising
I have used this code
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 450,450)];
UITapGestureRecognizer *DoubleFingerDTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(screenTappedtwice:)];
DoubleFingerDTap.numberOfTapsRequired = 1;
[webView addGestureRecognizer:DoubleFingerDTap];
[DoubleFingerDTap release];
method called
- (void)screenTappedtwice:(UIGestureRecognizer *)sender {
CGPoint tapPoint = [sender locationInView:sender.view.superview];
[UIView beginAnimations:nil context:NULL];
sender.view.center = tapPoint;
//Check the current state of the navigation bar...
//BOOL navBarState = [self.navigationController isNavigationBarHidden];
// Set the navigationBarHidden to the opposite of the current state.
// [self.navigationController setNavigationBarHidden:TRUE animated:YES];
[self.navigationController setNavigationBarHidden:YES animated:YES];
[UIView commitAnimations];
}
Have you tried setting:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
to return YES? Also, ensure you set your tap gesture's delegate to self so that the message is properly received. I just tested this in a new project and it does work.
EDIT
Not quite sure what your animation begin & commit is for - the method setNavigationBarHidden:animated: animates itself. Additionally, the use of these animation definitions are discouraged in iOS 4 onwards - look into using block-based animations on UIView instead.
For your navigation controller, you are pretty much there - implement something like this:
- (void)screenTappedTwice:(UITapGestureRecognizer *)sender
{
BOOL shouldHideNavBar = [self.navigationController isNavigationBarHidden] ? NO : YES;
[self.navigationController setNavigationBarHidden:shouldHideNavBar animated:YES];
}

Make textfield appear from above in an animated way iphone sdk

Hi Friends I am using the code is
- (BOOL)textFieldShouldReturn:(UITextField *) sender
{
appDelegateObj.plogoImgView.hidden=NO;
[sender resignFirstResponder];
if (verticalOffset!=0)
{
[self moveView: -verticalOffset];
verticalOffset = 0;
}
return TRUE;
}
- (void)textFieldDidBeginEditing:(UITextField *)theTextField
{
appDelegateObj.plogoImgView.hidden=YES;
int wantedOffset;
if(theTextField.tag==10)
wantedOffset = theTextField.frame.origin.y - 50;
else
{
wantedOffset = theTextField.frame.origin.y - 150;
}
if ( wantedOffset < 0 )
{
wantedOffset = 0;
}
if ( wantedOffset != verticalOffset )
{
[self moveView: wantedOffset - verticalOffset];
verticalOffset = wantedOffset;
}
}
- (void)moveView:(int)offset
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect rect = self.view.frame;
rect.origin.y -= offset;
self.view.frame = rect;
[UIView commitAnimations];
}
My problem is the above code is working but in device whenever view will appear method called its not working how to write view will appear please tell me.
Thanks in advance..
I don't really get everything from your question, but i'll give it a go.
So you say:
problem is the above code is working but in device whenever view will appear method called its not working how to write view will appear please tell me.
Do you mean that the code is working in some tutorial you saw it in or is it working in your simulator and not on the device?
Seems odd to me that some animation code would work in simulator but not on the device, so that should not me the problem.
You're talking about viewWillAppear not being called, are you using a navigation controller? In a navigation controller based app the viewWillAppear method won't get called.
The methods you are using to show and hide the desired keyboard are also a little bit weird, you talk about animating an UITextField in, but you are moving the whole view?
Why don't you just create a property for the desired textfield or at least an instance variable, so you can address that textfield from any method to animate it in/out.
If you are using a nivation controller:
Create a method and call it something like "moveTextFieldIn", then in the previous viewController you will probably have something like this:
UIViewController * nextViewController = [[UIViewController alloc] init];
[[self navigationController] pushViewController:nextViewController animated:YES];
[nextViewController release];
Change that to this:
UIViewController * nextViewController = [[UIViewController alloc] init];
[nextViewController moveTextFieldIn];
[[self navigationController] pushViewController:nextViewController animated:YES];
[nextViewController release];
Or to this (if you're not creating moveTextFieldIn):
UIViewController * nextViewController = [[UIViewController alloc] init];
[nextViewController viewWillAppear];
[[self navigationController] pushViewController:nextViewController animated:YES];
[nextViewController release];
Well, lot's of guesses, but I hope this will be help you!
One more thing:
The standard animation duration is 0.3, so you can delete that line of code, keep it clean ;-)

UITableView reloadData causing all animations to stop working

Alright this is a really weird one so I am going to layout what is happening and then give some code after. For my example I am going to use a static amount of views, 2.
The Basics
I have a UIPageControl with X many Subviews added. On each subviews viewDidLoad is an NSXMLParse to grab an XML feed. Once the feed is obtained, it's parsed and the table is reloaded using the parsed array. There is also a Settings button on each view. When the Settings button is pressed, UIModalTransitionStyleCoverVertical:Animated:YES is run and a UINavigationController slides up into view with full animation. Dismiss also shows animation sliding out back to the previous view. If you are in Settings, you can PushViews two levels deep (Slide In Animation).
The Problem
A random amount of the time, when the app is built and run (Not Resumed) when you tap the Settings button, the Animation does not occur. Everything is functional except for all Core Animations are removed. DismissModal simply swaps back to the previous screen. PushView in the NavigationController no longer has any animation, the next view simply appears.
If you quit the app (Kill Process) and relaunch it, it may work fine for a period of time but at some point when you tap the Settings button, it will lose all animations.
The Details
I started with Apples PageControl Application for the groundwork. It creates a dynamic amount of views based on user settings.
- (void)awakeFromNib
{
kNumberOfPages = 2;
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < kNumberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// replace the placeholder if necessary
SecondViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[SecondViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
As each view is generated, it runs an NSXMLParse in its viewDidLoad. Everything works fine up to this point. Both views are generated and you can swipe between them.
If you push the Settings Button
- (IBAction)settingsButtonPressed:(id)sender;
{
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navigationController animated:YES];
[settingsViewController release];
[navigationController release];
}
At this point, SettingsViewController appears into view. However, sometimes it slides up with its proper animation. Other times it will simply appear and all further core animations are broken until the process is restarted.
I went through and checked all of NSXMLParse and have narrowed down the problem to one line. On each of my subviews, is a tableView, after the XML Parsing is done, I created an array with the results and ran [self.tableview reloadData]. If I comment out that line, the table obviously only loads blank but it doesn't have any issues with Animations.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSMutableArray *tableData = ARRAY_GENERATED_HERE;
[self.tableView reloadData];
}
My Testing
I will note from my tests, everything is fine if kNumberOfPages is set to 1 instead of 2. Only 1 view gets generated, the Animation glitch never occurs. Add a second view in, usually within opening Settings five times, it will glitch.
Still haven't come to a solution but it has to do with [tableView reloadData]. Any insight would be great.
Daniel pointed out something that makes sense.
My XML is fetched in the viewDidLoad using:
[NSThread detachNewThreadSelector:#selector(parseXMLFileAtURL:) toTarget:self withObject:path];
- (void)parseXMLFileAtURL:(NSString *)URL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
stories = [[NSMutableArray alloc] init];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
[pool release];
}
From your comment, you said you are running the parser in a background thread...UIKit is not thread safe and i suspect that is whats causing your problems...try making the reloadData call on the main thread, you can use NSObjects performSelectorInMainThread to do this...
[self performSelectorOnMainThread:#selector(operationComplete) withObject:nil waitUntilDone:false];

Hiding Back Button on Navigation Based iPhone App Fails

My issue is that the back button will not restore its visibility if my web request does not finish before or soon after ViewWillAppear has fired.
I have a navigation based iPhone 4.0 application used a simple Root and Detail view setup.
I am working with data that is returned from a webservice so when I push my detail view in its ViewDidLoad function I call my web service method in a separate thread and the Iphone lifecycle does its thing on the main thread. I must disable/hide the back button until the web request has finished (or failed) so I call self.navigationItem.hidesBackButton = YES; in ViewDidLoad and self.navigationItem.hidesBackButton = NO; in the delegate function which fires once my web request has finished or failed.
I already tried the following:
[self.navigationItem performSelectorOnMainThread:#selector(setHidesBackButton:) withObject:NO waitUntilDone:NO];
[self.navigationItem setHidesBackButton:NO];
[self.view setNeedsDisplay];
[self.navigationController.view setNeedsDisplay];
UINavigationItem *nav = self.navigationItem;
nav.hidesBackButton = NO;
Root View Controller Push Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ArticleViewController *articleViewController = [[ArticleViewController alloc] initWithNibName:#"ArticleViewController" bundle:nil];
NewsArticle *newsArticle = [newsItems objectAtIndex:indexPath.row];
articleViewController.articleID = newsArticle.newsID;
[self.navigationController pushViewController:articleViewController animated:YES];
[newsArticle release];
[articleViewController release];
}
Details View Controller Code:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
id scrollView = [[[self webContent] subviews] objectAtIndex:0];
if([scrollView respondsToSelector:#selector(setBackgroundColor:)] )
{
[scrollView performSelector:#selector(setBackgroundColor:)
withObject:[UIColor blackColor]];
}
[self getNewsArticle];
}
//Fires when the web request has finished
- (void) finish:(NewsArticle *)newsArticleFromSvc {
self.navigationItem.hidesBackButton = NO;
self.newsArticle = newsArticleFromSvc;
[self bindNewsArtice];
}
Any help is GREATLY appreciated I can hardly ##$&^ believe that hiding a button in a UI could cause me this much wasted time.
Try use this method of UINavigationItem :
- (void)setHidesBackButton:(BOOL)hidesBackButton animated:(BOOL)animated
I wasn't able to solve this problem. Instead I tweaked my App Logic to make hiding he back button not necessary.