AlertView to Pause Code - ios

This seems to be a white elephant for the iPhone. I've tried all sorts, even loops and I can't make it work.
I have a view that loads a table. However I've moved into object archiving and for development purposes I want to have an initial AlertView that asks if a user wants to use a saved database or download a fresh copy
(void)showDBAlert {
UIActionSheet *alertDialogOpen;
alertDialogOpen = [[UIActionSheet alloc] initWithTitle:#"DownloadDB?"
delegate:self
cancelButtonTitle:#"Use Saved"
destructiveButtonTitle:#"Download DB"
otherButtonTitles:nil];
[alertDialogOpen showInView:self.view];
}
I'm using an ActionSheet in this instance. And I have implemented the protocol:
#interface ClubTableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate>
Based on this I run this to check what button was pressed:
(void)actionSheet:(UIActionSheet *) actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[actionSheet buttonTitleAtIndex:buttonIndex];
if ( [buttonTitle isEqualToString:#"Download DB"] ) {
[self loadDataFromDB];
}
if ([buttonTitle isEqualToString:#"Use Saved"] ) {
self.rows = [NSKeyedUnarchiver unarchiveObjectWithFile:[self archivePath]];
if (self.rows == nil) {
[self loadDataFromDB];
}
}
}
The problem is my code to build the table executes before the user has made there choice. This causes all sorts of havok. As a result, how can I pause the code until a user has made there choice?

If you are using UITableView, you will not be able to "pause" the code, however this wouldn't be the method of choice anyways. A possible solution would be to load an empty table (return 0 in (UITableView *)tableView:numberOfRowsInSection) until the user has selected something, then reload the tableView's data with [tableView reloadData];

In your tableView:numberOfRowsInSection: just return zero until the user has made a decision.

Add [self.tableView reloadData]; at the end of the actionSheet delegate method. This will trigger all UITableViewDataSource methods again.

Related

UI gets stuck on iOS 7 while dismissing an Alert displayed from didSelectRowAtIndexPath on iPad

I have a piece of code that, when a row in a table view is selected, will display an alert and wait until that alert is dismissed. While it works fine on iPads with iOS 5 and 6, on iOS 7 it gets stuck while trying to dismiss the alert.
To illustrate the issue I created a simple master-detail app and created a simple MyAlert class that extends UIAlertView and conforms to UIAlertViewDelegate:
#interface MyAlert : NSObject <UIAlertViewDelegate>
{
volatile BOOL completed;
UIAlertView * alert;
}
- (void) showAndWaitUntilDone:(NSString*)message;
#end
MyAlert.m:
#implementation MyAlert
- (void) showAndWaitUntilDone:(NSString*)message
{
alert = [[UIAlertView alloc] initWithTitle:#"Alert"
message:message
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Other", nil];
if (alert)
{
alert.delegate = self;
[self showAndWaitUnitlDone];
}
}
- (void) showAndWaitUnitlDone
{
completed = NO;
[alert show];
while (!completed)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
completed = YES;
}
#end
I then display the alert in my ViewController like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[MyAlert showAndWaitUntilDone:#"test msg"];
}
The effect:
Like mentioned before, this works OK on iOS 5 and 6.
If I don't block the main thread, everything seems to work OK, however, if I run this code e.g. from UIButton callback it works like a charm.
There seems to be a bug in iOS 7 which causes that UIAlertView delegate will never be called once the RunLoop is started. It acts exactly the same when the thread waiting for the UIAlertView is not the main thread. People suggest using UIAlertView with blocks or writing a custom Alert View.
For more details see Apple's developer forum thread: https://devforums.apple.com/message/887792
Maybe it's just me, but this seems like bad design in the first place. You're locking up the main thread for an unknown length of time.
Why not simply have the didSelectRowAtIndexPath display the alert, and have the alert callback (alertView:clickedButtonAtIndex:) do the remaining work you were waiting on (or call a function which will do it)? If need be, use a variable to store which entry was tapped... This would prevent the negative aspects of tying up the main thread while still giving what appears to be your desired effect.
It wouldn't surprise me if apple implemented something that prevents app designers from locking up the main thread (which is why your code will only work if waitUntilDone: is set to NO, essentially putting the code on an async call).

Does a UIAlertView's Delegate have to be a view controller?

I've got an issue where an object that's creating a UIAlertView is sending a message back to its delegate, but the delegate object is crashing.
I've got it working OK in a couple of other instances, but what I'm trying to do in this case (and what's different from other similar questions) is that the object that instantiates the alert, and which acts as its delegate is not itself a view, but rather an object that is instantiated within a view. To wit, in the parent view:
#implementation
*MyCustomObject customObject;
-(void)viewDidLoad {
customObject = [[MyCustomObject alloc] init];
}
#end
And in the custom object:
-(void)DoCoolThings {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Do You Want To Do Cool Things"
message:#"...description of cool things..."
delegate:self
cancelButtonTitle:#"No Thanks"
otherButtonTitles:#"HELLS YES", nil];
[message show];
}
and
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[self DoCoolThings];
} else {
[self GoAndCry];
}
}
When I do the alert view within a viewcontroller object, everything is fine. Doing it within this sub-object which itself has no view allows me to create the alert, but that object--which shouldn't be getting de-allocated based on the scoping--doesn't seem to want to continue to act as a delegate.
The error I'm getting is indeed a de-allocation message, but I feel strongly that this is not the problem because if I remove the alert, all the other stuff--specifically, it's a wrapper for a storekit purchase process--works fine, and all those delegate methods work happily.
I've got a solution which will allow me to move the Alert into the parent view's methods, but I was hoping not to have to. Is this limitation real, or is it my imagination? IE am I doing something else wrong?

`UIAlertView` appears multiple times

I display an alert view in the didConnect method for bluetooth. For some reason, it fires four times. I'm trying to bandaid it and it is not working too well. Basically, I put the alert view in a method of its own, and call that method in didConnect. That's when it fires four times. I'm trying to set it up to only fire once. What I tried to do was set the alert views method to return a TRUE value. Then I do this:
if ([successfulConnection self] == FALSE) {
[self successfullConnection];
}
This works great the first time, but then the method is set to TRUE for the remainder of the time. I have the feeling that if I set it back to equal FALSE at the end of the if statement, then it will fire four times and I'll be right back where I started. Does anyone know how to change the above code to have it only fire once when it tries to fire four times?
Also tried replacing the above code with this in my didConnect, but it never fired at all:
[successfulConnection self];
if (successfulConnection.visible == YES) {
[successfulConnection dismissWithClickedButtonIndex:0 animated:YES];
}
If you call successfulConnection from your didConnect method, I think this should work (myAlert is the property name for the alert view):
-(void)successfulConnection {
if (! self.myAlert) {
self.myAlert = [[UIAlertView alloc]initWithTitle:#"ttile" message:#"message" delegate:self cancelButtonTitle:#"cancel" otherButtonTitles: nil];
[self.myAlert show];
}
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.myAlert = nil;
//do whatever with the result
}
The simplest thing to do is to just have a boolean that gets set to true when the UIAlertView is displayed, and then false when the UIAlertView is dismissed. Then, whenever you are going to show the UIAlertView, first check if it is already displaying.
These are methods you can use according to your requirement:
EDIT : Perfect way... if you dont want to upload your app on App Store
To know that alertView is currently visible or not.
Usage : Display alertView only if necesary other its already present.
-(UIAlertView *)getLastAlertView
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
return topMostAlert;
}
Dissmiss any alertView present which you don't know.
Usage : dissmiss all alertView and then present new one
-(void)dissmissLastAlert
{
Class UIAlertManager = objc_getClass("_UIAlertManager");
UIAlertView *topMostAlert = [UIAlertManager performSelector:#selector(topMostAlert)];
if (topMostAlert) {
[topMostAlert dismissWithClickedButtonIndex:0 animated:YES];
}
}

iOS and xcode: how to create a modal segue to a special view only the first time the app is used

I want to make a "terms and conditions" screen that pops up the very first time that app is opened. On this view I would add a button that says "agree," and upon clicking it the code would execute:
[self dismissViewControllerAnimated:YES completion:nil];
...and would go to the first view of the app.
I am currently using a Tab Bar Controller that has 4 ViewControllers. So basically, I just need to have some method in viewWillAppear on my first ViewController that checks for an NSUserDefault key:value. The first time the app opens, it will be zero. After they click agree, I'll set it to 1 and the bit of code would never execute again.
Can you please offer some code to accomplish the task of routing the view from the firstViewController's view to this alternate view controller upon loading the app?
Thanks!
In the viewWillAppear method in your FirstViewController, check NSUserDefaults then present your TermsViewController. After user click agree in TermsViewController, set NSUserDefaults then call
[self.presentingViewController dismissModalViewControllerAnimated:YES]
The use of the popover window can get complicated. Try something like the following if you have little experience with Objective-C.
- (void)viewDidLoad {
if ([termsvalue == 0]) {
NSString *msg = [NSString stringWithFormat:#"Do you agree with the terms of use?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"- Confirmation -"
message:msg
delegate:self
cancelButtonTitle:#"Disagree"
otherButtonTitles:#"I agree", nil];
[alert setTag:100];
[alert show];
}
}
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView { // Validation
if ([alertView tag] == 100) {
return YES;
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { // Go
if ([alertView tag] == 100) {
if (buttonIndex == 1) {
// The user has agreed
}
}
}

iOS - Prompt When Navigating From View

I have a UINavigationController inside a UITabBarController. The navigationcontroller has a UITableView and a form for editing items. The problem is that if a tab is tapped during editing, the form is just cleared and the user is dumped back to the UITableView.
Is there a way I can add a prompt to confirm navigation away from the edit view?
First, declare a BOOL in your .h to store the editing state. Also declare a temporary variable we will use later for storing the selected row.
BOOL isEditing;
NSUInteger selectedRow;
In your viewDidLoad, initialize the boolean to NO
- (void)viewDidLoad {
// initialization
isEditing = NO;
[super viewDidLoad];
}
You can then conform your view controller to UITextFieldDelegate and UIAlertViewDelegate. The text field delegate allows the controller to receive callbacks when editing ends and begins for the text fields and the alert view delegate allow it to receive callbacks when an alert view is dismissed.
#interface MyController : UIViewController <UITextFieldDelegate, UIAlertViewDelegate>
You then also need to set all the text field's delegates to be assigned to the controller. So in your cellForRowAtIndexPath when you add the text fields, just add this:
textField.delegate = self;
Once you have this, you are all set up to receive callbacks from the text field - so now implement the following two methods like so:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
isEditing = YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
isEditing = NO;
}
Now the key here is to make a separate method for pushing the next view, so just do something like this (like you would normally when the table view row is selected):
- (void)showNextView {
// in this method create the child view controller and push it
// like you would normally when a cell is selected
// to get the selected row, use the `selectedRow` variable
// we declared earlier.
}
You now need to implement the table view callback when the user selects a row - in this method we test if they are editing and show them a prompt if they are. If they aren't, we go to the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
selectedRow = [indexPath row];
if (isEditing) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Continue Editing?"
message:#"Continue Editing or discard edits"
delegate:self
cancelButtonTitle:#"Discard"
otherButtonTitles:#"Continue"];
[alert show];
[alert release];
return;
}
[self showNextView];
}
Finally, we need to implement the alert view delegate callback for when the alert view is dismissed:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex]) return; // stay editing
[self showNextView];
}
Hope that all makes sense and is helpful to you!
Since you are using a UINavigationController, if you are pushing this "form" onto the stack you could set
#property(nonatomic) BOOL hidesBottomBarWhenPushed
That way the tab bar would be hidden until they are done with the form.
I solved this eventually by using a custom UIBarButtonItem which looks like a back arrow.

Resources