My first screen that contains a uitableview, I used to call webservice method in viewdidload:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title =#"Brand List";
getBrands *obj = [[getBrands alloc]init];
[obj getBrandsList];
getBrandsList, but before the webservice finished, it loads the screen, so the screen contains an empty list.
So I need to wait (ie. loading dialog) until the method completes and then show the screen with data.
How do I do this?
Create an new View with an UIActivityIndicatorView and an UILabel. Then add it on top of your window with some CATransition. When you finish download remove this screen.
-(IBAction) buttonClick: (id) sender{
//show loading dialog here(activity indicator)
[self performSelectorInBackground:#selector(callWebService) withObject:nil];
}
-(void) callWebService
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
//resend request and parse response.
// create new view controller object and push screen
// hide loading indicator
[pool release];
}
Related
In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad
I am working on a project that has a TableView which loads the contents of a JSON file on my server. Everything works correctly, but I have two problems.
1) When I change the View and load a different View, when I came back to this TableView ... the TableView tries to re-load the contents, there is no errors, but the progress bar appears briefly. How to avoid this from happening?
2) My second problem is that, once loaded, if I lose the internet connection and change the View, the content gets lost. Even I already downloaded.
How would I do the cache of this information?
Here is the code:
#interface ProgramacaoTableViewController ()
{
// Object thats hold the content
MProgramacao *_programacao;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// TS MESSAGE
[TSMessage setDefaultViewController:self];
[self.navigationController.navigationBar setTranslucent:YES];
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
////
// Check Connection and Load Data
if ([self IsConnected]) {
// YES INTERNET
// show loader view
[ProgressHUD show:#"Loading.."];
// fetch the feed
_programacao = [[MProgramacao alloc] initFromURLWithString:#"http://myurl..."
completion:^(JSONModel *model, JSONModelError *err) {
//hide the loader view
[ProgressHUD dismiss];
//json fetched
[self.tableView reloadData];
}];
}
else {
// NO INTERNET
[TSMessage showNotificationWithTitle:NSLocalizedString(#"Error Message", nil)
subtitle:NSLocalizedString(#"try again", nil)
type:TSMessageNotificationTypeError];
}
}
I edit the code.
You should download the data just in the viewDidLoad, and then when the user WANT, he can pull the tableView to refresh. This is the correct way.
In this way, your tableView will remains loaded also when you push a viewController and then come back, and your "temporary cache" is your array _programacao.
If you want store the data also in case you close the app, you could use for example CoreData, but this is another thing that is not necessary for your purpose.
i implemented a UIRefreshControl for UICollectionView so that user can pull to refresh the content in UICollectionView. I am testing on the iPad simulator.
On the first attempt, I'm able to pull and refresh the content. However, I notice that the loading icon is still loading and doesn't stop. On my second attempt with the loading icon still showing, I pulled to refresh but it fails to call my selector(refreshCollectionAction).
Here is what I did:
-(void)viewDidLoad
{
// Do any additional setup after loading the view.
[super viewDidLoad];
// Register collectionView pull down to refresh
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refreshCollectionAction)
forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:refreshControl];
self.collectionView.alwaysBounceVertical = YES;
.....
}
-(void)refreshCollectionAction
{
NSLog(#"refresh collection action");
// Empty product Items Array
[[ProductStore getInstance] emptyProductInStore];
NSInteger numOfProductInStore = [[[ProductStore getInstance] allProductItems] count];
if (numOfProductInStore <= 0) {
// Fetch data from webservice and reload collectionView
[self fetchFeed:#""];
}
}
Am I missing some configurations? fetchFeed will request the data from web services. I have verified that the webservice still works.
[self.refreshControl endRefreshing];
Call this method at the end of any refresh operation (whether it was initiated programmatically or by the user) to return the refresh control to its default state. If the refresh control is at least partially visible, calling this method also hides it. If animations are also enabled, the control is hidden using an animation.
UIRefreshControl Class Reference
#interface ProductSearchViewController ()
#property(nonatomic)UIRefreshControl *refreshControl;
#end
- (void)viewDidLoad
{
// Do any additional setup after loading the view.
[super viewDidLoad];
// Register collectionView pull down to refresh
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(refreshCollectionAction)
forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:self.refreshControl];
self.collectionView.alwaysBounceVertical = YES;
...
}
-(void)refreshCollectionAction
{
NSLog(#"refresh collection action");
// Empty product Items Array
[[posProductStore getInstance] emptyProductInStore];
NSInteger numOfProductInStore = [[[posProductStore getInstance] allProductItems] count];
if (numOfProductInStore <= 0) {
[self fetchFeed:#""];
}
[self.refreshControl endRefreshing];
}
so basically i declare refreshControl as a class variable. as Neil mentioned, i added [self.refreshControl endRefreshing] at the end of method -(void)refreshCollectionAction.
I've read and read on SO about this, and I just can't seem to find anything that matches my situation.
I've got MBProgressHUD loading when the view appears, as my app immediately goes to grab some webservice data. My problem is the back button on my navigationcontroller is unresponsive while the HUD is displayed (and therefore while the app gets its data). I want the user to be able to tap to dismiss (or to be able to hit the back button in the worst case) to get the heck out, if it's an endless wait. Here's my code that runs as soon as the view appears:
#ifdef __BLOCKS__
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.labelText = #"Loading";
hud.dimBackground = NO;
hud.userInteractionEnabled = YES;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do a task in the background
NSString *strURL = #"http://WEBSERVICE_URL_HERE";
//All the usual stuff to get the data from the service in here
NSDictionary* responseDict = [json objectForKey:#"data"]; // Get the dictionary
NSArray* resultsArray = [responseDict objectForKey:#"key"];
// Hide the HUD in the main tread
dispatch_async(dispatch_get_main_queue(), ^{
for (NSDictionary* internalDict in resultsArray)
{
for (NSString *key in [internalDict allKeys])
{//Parse everything and display the results
}
}
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
});
});
#endif
Leaving out all the gibberish about parsing the JSON. This all works fine, and the HUD dismisses after the data shows up and gets displayed. How in the world can I enable a way to stop all this on a tap and get back to the (blank) interface? GestureRecognizer? Would I set that up in the MBProgressHUD class? So frustrated...
Kindest thanks for any help. My apologies for the long post. And for my ugly code...
No need to extend MBProgressHUD. Simply add an UITapGestureRecognizer to it.
ViewDidLoad
:
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:NO];
HUD.mode = MBProgressHUDModeAnnularDeterminate;
UITapGestureRecognizer *HUDSingleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(singleTap:)];
[HUD addGestureRecognizer:HUDSingleTap];
And then:
-(void)singleTap:(UITapGestureRecognizer*)sender
{
//do what you need.
}
The MBProgressHUD is just a view with a custom drawing to indicate the current progress, which means it is not responsible for any of your app's logic. If you have a long running operation which needs to be canceled at some point, you have to implement this yourself.
The most elegant solution is to extend the MBProgressHUD. You can either draw a custom area which plays the role of a button, add a button programmatically or just wait for a tap event on the whole view. Then you can call a delegate method whenever that button or the view is tapped.
It can look like this:
// MBProgressHUD.h
#protocol MBProgressHUDDelegate <NSObject>
- (void)hudViewWasTapped; // or any other name
#end
// MBProgressHUD.m
// Either this, or some selector you set up for a gesture recognizer
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:#selector(hudViewWasTapped)]) {
[self.delegate performSelector:#selector(hudViewWasTapped)];
}
}
you have to set your view controller as the delegate for theMBProgressHUD and act accordingly.
Let me know if you need more clarification on this :)
To have extra information:
You could create contentView in your view
And simply show the hud in your contentView (not in your self.view or self.navigationController.view)
in this way your navigationBar's view will not be responsible for your hudView. So, you can go back from your navigationController's view to previous page.
All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.