call presentModalViewController in block completion - iphone

in my code i'm using this class:
https://github.com/gpambrozio/BlockAlertsAnd-ActionSheets
and i want call a presentModalViewController when i press the button in the dialog view in this way:
BlockAlertView *alert = [BlockAlertView alertWithTitle:#"Example" message:#"Text"];
[alert setDestructiveButtonWithTitle:#"Ok" block:^{
FirstView *firstView = [[FirstView alloc] initWithNibName:#"FirstView" bundle:nil];
[self presentModalViewController:firstView animated:YES];
}];
[alert show];
but the app freeze, and after a minute open the view, so my question is how i can presentemodalviewcontroller in block completion?

Actually have some mistakes, first, when you instanciate some class / variable inside a block they responde and have life cycle just inside that block.
So your block you run in another thread and this is the cause, and with this way you probably will cause retain cycle that its very bad situation for you app, change for something like this:
BlockAlertView *alert = [BlockAlertView alertWithTitle:#"Example" message:#"Text"];
FirstView *firstView = [[FirstView alloc] initWithNibName:#"FirstView" bundle:nil];
__weak typeof(self) weakSelf = self;
[alert setDestructiveButtonWithTitle:#"Ok" block:^{
[weakSelf presentModalViewController: weakSelf.firstView animated:YES];
}];
[alert show];
Doing this, using __weak typeof we created a weak reference that prevent blocks from create strong reference cycle *(its good to read about) and now we make sure that will use our view that are instantiated for present new modal view.
Any doubt fill free for ask :)

Related

Objective-c:"Please wait" message when go to the next page

I am developing an app which has several viewControllers. The first one is "MainMenu" and the second one is "Page1".
I would like to show an alert says "Please wait..." when it goes to the next page. My code below is working but the alert comes up after the "page1" is loaded. I would like it to come up when a user press a button on "MainMenu" page.
Do you have any suggestions to accomplish this?
Thanks in advance.
AppDelegate.m
-(void)showAlert{
altpleasewait = [[UIAlertView alloc] initWithTitle:#"Please Wait..." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[altpleasewait show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.center = CGPointMake(altpleasewait.bounds.size.width / 2, altpleasewait.bounds.size.height - 50);
[indicator startAnimating];
[altpleasewait addSubview:indicator];
}
-(void)waitASecond{
[self performSelector:#selector(dismissAlert) withObject:self afterDelay:0.8];
}
-(void)dismissAlert{
[altpleasewait dismissWithClickedButtonIndex:0 animated:YES];
}
MainMenu.m
-(void)gotoNextPage{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
[appDelegate showAlert];
page1 = [self.storyboard instantiateViewControllerWithIdentifier:#"Page1"];
[self presentModalViewController:page1 animated:NO];
}
Page1.m
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
-------------some methods--------------
[appDelegate waitASecond];
This is just a suggestion. I would do the following.
In Page1.m in -viewDidAppear, I would call the alert view to pop up. This is happening on the main thread. So I am not preventing UI from being displayed, or responsive.
I would move all methods that are related to loading the Page1 content, e.g. texts that are to be read from a URL, into a block, and use GCD, so that they happen in the background, and not blocking the main thread.
Once the loading is done...
I would take them, update the UI, and dismiss the alert view.
Here is a simple tutorial that may give you an idea.
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
What you could do is put the alert in the IBAction. Check when they dismiss the alert, then change views. Or use an NSTimer, assign a selector to that timer and implement that selector to change views.

Using UIAlertView in an NSObject

I'm having a terrible time getting a UIAlertView to work within my custom NSObject class. In the research I've done it appears it should be possible but here's what I've run into.
First, here's my code:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"clickedButtonAtIndex: %d", buttonIndex);
}
-(void)testAlertView {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"List Contains Items"
message:#"List contains items. Remove all items & delete?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
}
If I set the delegate to self this code crashes as soon as I tap a button. If I set it to nil clickedButtonAtIndex is never called. I've tried with and without using the <UIAlertViewDelegate>.
I know someone will ask 'why are you doing this in NSObject instead of in your UIViewController?'. Primarily because I want to separate this code out so I can use it from multiple places in my app. But also because this is a small piece of a larger block of logic that makes sense to be on it's own.
Any ideas what I'm doing wrong?
Thanks,
Rich
I had the same problem using ARC. The root of the problem was the same. I solved it by putting my custom NSObject into a "strong" property to make sure the object exists as long as the calling object (an UIVIewCOntroller in my case) exists, so when the delegate of my alert view is called I still have my custom object around and the delegate method works fine.
Add the NSObject as strong property:
#import "Logout.h" // is NSObject
.
.
.
#property (nonatomic, strong) Logout *logout;
Then you will get the delegatemethods called in your NSObject.
Don´t forget to register the delegate for the UIAlertView:
#interface Logout () <UIAlertViewDelegate>
and in your method:
UIAlertView *a = [[UIAlertView alloc] initWithTitle:#"title"
message:#"message" delegate:self cancelButtonTitle:#"cancel"
otherButtonTitles:#"ok", nil];
[a show];
How To Present An Alert View Using UIAlertController When You Don't Have A View Controller. Detail description.
Yes, you can only use UIAlertController only in UIViewController classes. So how can we do it in NSObject classes. If you see the description link given above you will get to the answer. To summarise in a line for the above description: Create a new window above the the current window. This new window will be our viewController where we display alert. So using this viewController you can call the method [presentViewController: animated: completion:].
Answer:
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;
NSString *msg=#“Your mssg";
UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:#“Title" message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(#"Yes",#"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
// do your stuff
// very important to hide the window afterwards.
window.hidden = YES;
}]];
UIAlertAction *cancelAction= [UIAlertAction actionWithTitle:#"cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
window.hidden = YES;
}];
[alertCtrl addAction:cancelAction];
//http://stackoverflow.com/questions/25260290/makekeywindow-vs-makekeyandvisible
[window makeKeyAndVisible]; //The makeKeyAndVisible message makes a window key, and moves it to be in front of any other windows on its level
[window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];
});

load a different nib if phone is not connected to internet

I use Apple's Reachability class and it's working fine using an alert to tell the user that the connection is not available or the connection is lost. However, I want to change the alert to something more visual. I want to load a nib that tells the user no active connection is present but the nib is not loading. I also tried loading my other nibs but it also doesn't load the nib.
- (BOOL) checkNetworkStatus:(NSNotification *)notice
{
// called after network status changes
NetworkStatus internetStatus = [internetReachable currentReachabilityStatus];
switch (internetStatus)
{
case NotReachable:
{
NSLog(#"The internet is down.");
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"No Internet Connection" message:#"You are currently not connected to a WI-FI or cellular Data.\nPlease make sure you are connected." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
[alert release];
//NoConnection *noConn = [[NoConnection alloc] initWithNibName:#"NoConnecton" bundle:nil];
//[self presentModalViewController:noConn animated:NO];
//[NoConnection release];
self.isConnected = NO;
return NO;
break;
}
//more cases.........
the alert part is working just fine but the part for loading the nib is not. can you tell me whats wrong here? I'm calling this function in viewWillAppear. Thanks!
You can do the following:
if ( ! isConnected )
{
NoConnection *noConn = [[NoConnection alloc] initWithNibName:#"NoConnecton" bundle:nil];
[self presentModalViewController:noConn animated:NO];
[NoConnection release];
}
The code you have presented should work, sow the problem must be somewhere else probably in the nib - linking, you might have forgot to link something to the nib file.
try this
[self.navigationController presentModalViewController:noConn animated:YES];
Does your nib has NoConnection as a File's Owner (I guess NoConnection is a subclass of UIViewController, check it. I'll call this NoConnectionViewController bellow because you should name it like that for no mistake) ?
Is the file's owner view property linked with the graphical view ? Check it.
Are you working without status bar at top of the window ? That could be a problem.
Are your here inside a modalViewController ? If yes, your code won't work, you must use instead :
NoConnectionViewController* nextWindow = [[NoConnectionViewController alloc] initWithNibName:#"NoConnecton" bundle:nil]; // Check your nib name here, seems to be a mistake
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:nextWindow];
[self presentModalViewController:navController animated:YES];
[navController release];
[nextWindow release];
You need to use the delegate method of alert view
#pragma mark - AlertView Delegates
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(alertView.tag == 1)
{
NoConnection *noConn = [[NoConnection alloc] initWithNibName:#"NoConnecton" bundle:nil];
[self presentModalViewController:noConn animated:NO];
[NoConnection release];
}
}
don't forget to assign tag value of alertView to 1.
and also dont forget to conforms to the UIAlertViewDelegate protocol
Happy Coding :)

Update text in alert message

Still trying to update the message in an UIAlertview while the alert is active. I removed most of the first part of the question and publish more code in the update below.
UPDATE 3: Adding more code!
In the .h file I declare the following (among others):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
UIAlertView *alert;
}
I #property and #synthesize the UIAlertview to.
Next I create the alert in an IBAction which is run by a button click:
-(IBAction)convert {
convertButton.enabled = NO;
mailButton.enabled = NO;
backButton.enabled = NO;
//show the alert window
alert = [[UIAlertView alloc] initWithTitle:#"Converting in progress\nPlease Wait..." message:#"\n\n\n" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[indicator startAnimating];
[alert addSubview:indicator];
[indicator release];
[alert setMessage:#"getting roster"];
}
It then jumps to the following function:
- (void)didPresentAlertView:(UIAlertView *)progressAlert {
//A lot of code
[alert setMessage:#"Checking history"];
//more code
[alert setMessage:#"writing history"];
//even more code
[alert setMessage:#"converting roster"];
}
The didPresentAlertView method ends with an ASIHTTPRequest to post data to a webpage and when this request is finished the code finally jumps to the last method to exit the UIAlertView and closes everything up:
- (void)requestFinished:(ASIHTTPRequest *)request {
[timer invalidate];
[alert dismissWithClickedButtonIndex:0 animated:YES];
backButton.enabled = YES;
[alert release];
}
I also removed the autorelease from my UIAlertView init to make sure it exists during the rest of the process.
As it is now, the code only fires the very first setMessage -> 'getting roster' and the very last -> 'converting roster'. The setMessage requests in the middle do not get fired..
Really hope someone can help me here!
Thanks all!
Now I see the problem.
When you update the message property, it doesn't fire redrawing the view right away. The view is marked as 'needed to be drawn', and the actual drawing happens later, typically at the end of the current or next runloop in the main thread.
Therefore, while your didPresentAlertView method is running on the main thread, the alert view is not redrawn until the method is finished. This is why a computation-intensive job needs to run on a separate thread to increase the responsiveness of the UI, as the UI-related job is done on the main thread.
What you should do is run your //A lot of code //more code and //even more code on a separate thread, and update the message property on the main thread.
For example, your code may look similar to :
// this is inside didPresentAlertView method
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock: ^{
// A lot of code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"Checking history" waitUntilDone:NO];
// more code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"writing history" waitUntilDone:NO];
// it goes on
}];
If you are working with iOS 4.0 and later, and want to use the GDC, be careful because it may detect independency of your computation and message updates and let them happen concurrently (which is not desired here).

UIAlertView shows up twice

My alertview appears twice and requires 2 user clicks to dismiss.
- (void) showAlert: (NSString *) message
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"You chose"
message: message
delegate: self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"ok",nil];
av.tag = SLIDER_ALERT;
[av show];
}
I am then releasing the alertview in the delegate method "alertView: (UIAlertView *) alertView clickedButtonAtIndex: (int) index"
I suspect the problem is that I have built my own view hierarchy programmaticaly. I have one parent view for the viewcontroller. Under that i have 2 other views (parentView -> subview1 and subview2). I've tried to call [self.view addSubview: av] but that does not work. Neither does bringToFrontSubView:
Any help would be much appreciated
Peyman
The Alert code is fine (other than the release, mentioned in the comments).
[av show] is all that's required to show a view. You don't add UIAlertViews as subviews.
Call it after a delay of 0.1 sec [self performSelector:#selector(showAlert:) withObject:nil afterDelay:0.10];
Hope this will solve your problem.
With using autorelease the Alert View seems to be twice or 3 times. And for iOS4 it needs to be autoreleased otherwise it will crash.