Disabled touch when UIProgressBarHUD is shown - iphone

I've created an app where I'm creating a UIProgressBarHUD to show that something is loading. My question is, how can I disable the view so nothing can be pressed untill the loading is finished?
I've tried setting:
[self.view setUserInterationEnabled:NO];
However this doesn't work :/
Here is the code I'm using for adding the UIProgressHUD:
- (IBAction) showHUD:(id)sender
{
//[self.view setUserInteractionEnabled:NO];
UIProgressHUD *HUD = [[UIProgressHUD alloc]
initWithWindow:[[UIApplication sharedApplication] keyWindow]];
[HUD setText:#"Loading…"];
[HUD show:YES];
[HUD performSelector:#selector(done) withObject:nil afterDelay:1.5];
[HUD performSelector:#selector(setText:) withObject:#"Done!"
afterDelay:1.5];
[HUD performSelector:#selector(hide) withObject:nil afterDelay:4.0];
//[self.view setUserInteractionEnabled:YES];
[HUD release];
}
Any help would be muchly appreciated!!
- James

You can disable user interaction with the nifty property named userInteractionsEnabled, that is defined for UIView. It just so happens that UIWindow is a subclass of UIView, we we can easily disable user interactions for out whole app.
anyViewInYouApp.window.userInteractionsEnabled = NO;
Or keep a reference to the window if you like.

In MBProgressHUD.m
#define APPDELEGATE
(TMAppDelegate *)[[UIApplication sharedApplication]delegate]
- (void)show:(BOOL)animated
{
[[APPDELEGATE window] setUserInteractionEnabled:NO]; //use this line
}
- (void)hide:(BOOL)animated
{
[[APPDELEGATE window] setUserInteractionEnabled:YES]; //use this line
}

As pointed out here, UIProgressHUD is private. You should not use it.
There is a library that gives you what you are looking for though.
It allows you to keep the user from tapping anything while it is updating as you requested.

UIWindow *win = [UIApplication sharedApplication].keyWindow;
[win setUserInteractionEnabled:NO];

Related

doubletap on button with callback to segue causes crash

I have a button with this:
- (IBAction)themeBtnAction:(id)sender
{
NSString *language = [[OnlineStore sharedStore]getTheLanguage];
[[OnlineStore sharedStore]getTheThemeBaseGuides:language callback:^{
[self performSegueWithIdentifier:#"from main to themecategory" sender:self];
}]
}
But when user happens to doubletap the button it causes a crash. Probably because next viewcontroller is being loaded twice onto the heap (my guess) and the error message I get when I try to return with UINavigationController pop from that second UIViewControlleris: Unbalanced calls to begin/end appearance transitions for <MySecondViewController: 0xb257b80>.
How can I do to prevent this?
I have tried to put it inside callback within:
if ([NSStringFromClass([[viewControlles lastObject] class]) isEqualToString: #"MainViewController"]) {
and I have tried to do the callback within the selector goToNextView in the btn
[self performSelector:#selector(goToNextView) withObject:self afterDelay:1.0];
No luck.
Any suggestions. And please ask if this is unclear since I am a bit tired and just about to try to sleep now :)
You can ignore user interaction until the view controller disappears:
- (IBAction)themeBtnAction:(id)sender
{
NSString *language = [[OnlineStore sharedStore]getTheLanguage];
[[OnlineStore sharedStore]getTheThemeBaseGuides:language callback:^{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self performSegueWithIdentifier:#"from main to themecategory" sender:self];
}]
}
- (void)viewDidDisappear:(BOOL)animated
{
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
Or just disable user interaction on the button in a similar manner:
button.userInteractionEnabled = NO;
...
button.userInteractionEnabled = YES;

MBProgress view and pushing a view controller doesn't display

MPProgressView won't display when I try to push a viewcontroller until seconds before the pushed VC is displayed. Should the viewController be placed in the same function as the MBProgressView is displayed? I've made sure that my MBProgressView is on the main thread, I've tried many solutions on SO and can't see anyone with the same issue. I am simply trying to display the MBProgressHUD while the viewController is loading and being pushed. Thanks!
I am using MBProgressView as follows:
- (IBAction)pushButton:(id)sender
{
self.HUD =[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self.view addSubview:self.HUD];
self.HUD.labelText = #"Doing stuff...";
self.HUD.detailsLabelText = #"Just relax";
self.HUD.delegate=self;
[self.view addSubview:self.HUD];
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
}
- (void)loadCreate {
[self performSelectorOnMainThread:#selector(dataLoadMethodMail) withObject:nil waitUntilDone:YES];
}
-(void)dataLoadMethodMail
{NSLog(#"data load method is displaying");
SelectViewController *mvc = [[SelectViewController alloc] init];
[self.navigationController pushViewController:mvc animated:YES];
}
You don't need to add self.HUD to self.view, showHUDAddedTo: does it for you.
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
Shows the hud until loadCreate returns.
[self performSelectorOnMainThread:#selector(dataLoadMethodMail) withObject:nil waitUntilDone:YES];
dispatches something on main thread and returns right after (before the actual end of dataLoadMethodMail). The HUD is shown but disappears right away.
To solve the issue try hiding manually the HUD when dataLoadMethodMail finishes it's work.
Just replace
[self.HUD showWhileExecuting:#selector(loadCreate) onTarget:self withObject:nil animated:YES];
with
[self loadCreate];
and add
dispatch_async(dispatch_get_main_queue(), ^{
[self.HUD hide:YES];
});
at the end of dataLoadMethodMail
PS : Loading data should not be done on main thread.

MBProgressHUD view not hiding

In one stack I am displaying the MBProgressHUD and if by using the other stack when some calculation called I want MBProgressHUD to remove from the view but it is not been removed from the hud ..check what mistake I am doing..
first stack called LoginViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
}
-(void)myTask {
// Do something usefull in here instead of sleeping ...
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self.hud hide:YES];
self.hud=nil;
[self.hud removeFromSuperview];
//[self.hud showWhileExecuting:#selector(myTask1) onTarget:self withObject:nil animated:YES];
}
now theViewController get calls but view will be same Previous and
after some calculation and I want that in ViewController I want to remove theHUD from the view by calling the method in the LoginViewController..check code
- (void)didReceiveResponseFromServer:(NSString *)responseData
{
login=[[LoginViewController alloc]init];
[self.login myTask];
}
Set UP MBProgressHUD
- (void) setupHUD
{
//setup progress hud
self.HUD = [[MBProgressHUD alloc] initWithFrame:self.window.bounds];
[self.SpurView addSubview:self.HUD]; // add it as here.
self.HUD.dimBackground = YES;
self.HUD.minSize = CGSizeMake(150.f, 150.f);
self.HUD.delegate = self;
self.HUD.labelText = #"Loading...";
}
Then use for hide [self.HUD hide:YES]; as describe in your code .
like me ,
I tyr [MBProgressHUD hideHUDForView:bAnimatedView animated:YES]
but it will no work at times when I quick push in and back out .
So I add something to check the view of MBProgressHUD.
MBProgressHUD *HUD = [MBProgressHUD HUDForView:bAnimatedView];
if (HUD!= nil) {
[HUD removeFromSuperview];
HUD=nil;
}

QLPreviewController's view

I just trying to get to QLPreviewController.view. Indeed, I want to catch a tap event on its view to show/hide toolbar etc. I am trying:
QLPreviewController* qlpc = [QLPreviewController new];
qlpc.delegate = self;
qlpc.dataSource = self;
qlpc.currentPreviewItemIndex=qlIndex;
[navigator pushViewController:qlpc animated:YES];
qlpc.title = [path lastPathComponent];
[qlpc setToolbarItems:[NSArray arrayWithObjects:self.dirBrowserButton,self.space, self.editButton, self.btnSend, nil] animated:YES];
UITapGestureRecognizer* gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView=NO;
[qlpc.view addGestureRecognizer:[gestTap autorelease]];
[qlpc release];
And nothing happens
If I attach UITapRecognizer onto navigationController.view, it fires only if I touch toolbar/navbar. UISwipeGestureRecognizer works fine in that case.
I tried to attach a transparent overlay view and add gesture recognizers on it, but no luck.
Well, I saw some apps that implements such a feature so obviously it is possible, but how?
Sorry, I googled all day long and didn't find any solution. Please, help me.
With your solution, does the QLPreviewController's view still recieve touches? I've tried to do something similar (I'm stealing the view from QLPreviewController to use it) and it looks like my overlay view doesn't let anything pass trough to the view lying behind it.
I have been working on this problem today and the suggestion to override -(void)contentWasTappedInPreviewContentController:(id)item {} is close but when you do you mess with the preview controllers handling.
So I stopped overriding that method and instead created a RAC signal that fires whenever the method is called. This does not mess with the default behavior of QL. I am doing it in a subclass of the QLPreviewController but that shouldn't be necessary.
I have a property on my class:
#property RACSignal *contentTapped;
Then in my init method of my subclass of QLPreviewController:
_contentTapped = [self rac_signalForSelector:#selector(contentWasTappedInPreviewContentController:)];
Now in another class or even internally you can use the signal like this:
previewController.contentTapped subscribeNext:^(id x) {
// Put your handler here!
}];
Here is my solution (to use KVO), where I'm monitoring navigation bar status - and showing toolbar when needed (it seems that it hides toolbar by itself when tapped)
#define kNavigationBarKeyPath #"navigationBar.hidden"
static void * const NavigationBarKVOContext = (void*)&NavigationBarKVOContext;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setToolbarHidden:NO];
[self.navigationController addObserver:self forKeyPath:kNavigationBarKeyPath options:NSKeyValueObservingOptionPrior context:NavigationBarKVOContext];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController removeObserver:self forKeyPath:kNavigationBarKeyPath];
}
And
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == NavigationBarKVOContext ) {
BOOL prior = [change[NSKeyValueChangeNotificationIsPriorKey] boolValue];
if ( prior && self.navigationController.toolbarHidden ) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController setToolbarHidden:NO animated:YES];
});
}
}
}
I found none of the answers here to work, but the one that did for me was to subclass QLPreviewController and override viewDidAppear as so:
- (void)viewDidAppear:(BOOL)animated
{
UITapGestureRecognizer *gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:[gestTap autorelease]];
}
Ok, solution is very simple.
just added an overlay view onto keyWindow. Attached gesture recognizers onto overlay and it works.
QLPreviewController* qlpc = [QLPreviewController new];
qlpc.delegate = self;
qlpc.dataSource = self;
qlpc.currentPreviewItemIndex=qlIndex;
[navigator pushViewController:qlpc animated:YES];
qlpc.title = [path lastPathComponent];
UIView* overlay = [[[UIView alloc] initWithFrame:navigator.view.bounds] autorelease];
[[[UIApplication sharedApplication] keyWindow] addSubview:overlay];
[overlay setNeedsDisplay];
[qlpc setToolbarItems:[NSArray arrayWithObjects:self.dirBrowserButton,self.space, self.editButton, self.btnSend, nil] animated:YES];
UITapGestureRecognizer* gestTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showControls:)];
gestTap.cancelsTouchesInView=NO;
[overlay addGestureRecognizer:[gestTap autorelease]];
[qlpc release];
Subclass QLPreviewController and then override
-(void)contentWasTappedInPreviewContentController:(id)item
{}
Thats its !

App crashes at [UIWebView webView:didReceiveTitle:forFrame:]

I am implementing a simple in-app browser. In my home view (UITableViewController), I have something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WebViewController *webViewController = [[WebViewController alloc] init];
switch (indexPath.row) {
case 0:
webViewController.stringURL = #"http://www.google.com";
break;
case 1:
webViewController.stringURL = #"http://www.bing.com";
break;
default:
webViewController.stringURL = #"http://stackoverflow.com";
break;
}
[self.navigationController pushViewController:webViewController animated:YES];
[webViewController release];
}
The app crashed after I repetitively navigated back and forth between my home view and webViewControllera few times.
Inside WebViewController class, I have nothing but a [UIWebView *webView] and a [UIActivityIndicatorView *activityIndicator]. Both are with attributes nonatomic, retain. Here is the implementation.
#import "WebViewController.h"
#implementation WebViewController
#synthesize webView, activityIndicator, stringURL;
- (void)dealloc
{
[self.webView release];
self.webView.delegate = nil;
[self.activityIndicator release];
[super dealloc];
}
-(void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view = contentView;
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.origin.y = 0.0f;
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.backgroundColor = [UIColor blueColor];
self.webView.scalesPageToFit = YES;
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.webView.delegate = self;
[self.view addSubview: self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.stringURL]]];
self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
self.activityIndicator.center = self.view.center;
[self.view addSubview: self.activityIndicator];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadView];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
}
#end
I just ran my app in Instruments using the Zombies template, which shows -[UIWebView webView:didReceiveTitle:forFrame:] is the Zombie call. But I still can’t figure out what is actually the problem.
(Please download trace if needed)
Any help is greatly appreciated!
[Update]:
As #7KV7 and #David pointed out, there is an obvious bug in my dealloc function. I should call self.webView.delegate=nil; first before I release self.webView. Sorry about that. Unfortunately, after I fix it, the app still crashes in the same way.
If I delete [webViewController release]; from the first code block, the crash actually is gone. But obviously, there will be memory leak.
First of all, remove that call to loadView in viewDidLoad. The framework will the call the method when it doesn't find a view provided in XIB file. Second, your loadView is filled with memory leaks. You are allocating, initializing and retaining an object every time the method is called. So you are taking ownership twice and releasing it only once in the dealloc.
The objects are not being properly deallocated. You should do something like alloc-init-autorelease to solve this. Next thing is the that every time the controller gets loaded, because of your call to loadView, you end up creating two web view objects and two requests. You lose reference to one of them as you reassign. Herein, lies the problem mentioned in the title. You aren't able to reset the delegate of a web view object that has your controller as a delegate. Imagine a request being completed soon after you leave. Here the message will go to a zombie object. This is a pretty good example for why you need to nil out your delegates.
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[self.activityIndicator release];
[super dealloc];
}
Try this dealloc. You were releasing the webview and then setting the delegate as nil. You should first set the delegate as nil and then release it. Hope this solves the issue.
I think what's happening is that you are going back while the page is still loading so the controller gets deallocated and then the webview finishes loading.
Try calling [webView stopLoading] in your viewDidUnload method to make sure this isn't happening.
Don't know if it's the cause of your problem, but this is definitely wrong:
[self.webView release];
self.webView.delegate = nil;
You cannot (safely) refer to self.webView after you release it!
Instead of pushing webViewController,add its view to self.view .
Dont call [self loadView]; in viewDidLoad.