I'm using a UIPageViewController to handle data entry where the last page is the active record and the previous pages are old records that can't be edited. So I need a way to verify that the user wants to leave the last page while allowing all of the other pages to navigate as usual.
Ideally I could really use a -(BOOL)pageShouldTurn method but that doesn't exist.
Does anyone know of a way to detect if a page is about to unload then stop the page turn based on some condition? I'm not having any luck with the gesture recognizer methods as they don't seem to be triggered even when the delegate is set.
Thanks to Michael, I've added this to my pageViewController which does exactly what I needed:
-(void)pageViewController:(UIPageViewController *)pvc willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
if ([pvc.viewControllers.lastObject pageIndex] == [self.pageDataSource.allObjects count]) {
UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc]
initWithTitle:#"Are You Done?"
message:#"Once you leave this page you can't edit this record again"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alertDialog show];
}
}
So the alert box stops the page from turning only once. When it is dismissed, the user can then change the page. My version checks to make sure this only happens on the last page, you could remove the 'if' statement and alert on every page turn, but that would be annoying.
Seems to me there are at least two options.
Number one, you have "- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers". You might be able to catch a transition there and deny / force the old page to be reset.
Or, you can now subclass "UIPageViewController" and in your subclassed controller, you can define a new delegate protocol (incorporating all the original UIPageViewControllerDelegate" methods) and you can add your own "-(BOOL) pageShouldTurn" protocol method.
Both of these possibilities require iOS 6.
Related
How can I create an UIAlertView with two stacked button? Instead of two buttons side by side? In my project I also use LMAlertView (GitHub repository) and I thought it would support them, but I can't find a way to obtain that very UIAlertView style.
Currently I create a UIAlertView with this code below:
LMAlertView *applicationUpdateAlert = [[LMAlertView alloc] initWithTitle:alertTitleFromServer
message:alertMessageFromServer
delegate:self
cancelButtonTitle:NC(#"Aggiorna ora")
otherButtonTitles:NC(#"Ricordamelo piĆ¹ tardi"), nil];
[applicationUpdateAlert show]
I don't know if this can be useful for you to better understand but LMAlertView has a property named buttonsShouldStack which I thought it would be useful for me but I can't properly use it, this is the code related to buttonsShouldStack in LMAlertView.m file (direct link):
- (void)setButtonsShouldStack:(BOOL)buttonsShouldStack
{
_buttonsShouldStack = buttonsShouldStack;
if (self.numberOfButtons == 2) {
[self.buttonTableView reloadData];
[self.otherTableView reloadData];
}
}
Thanks
Maybe you can give a try with this library. It has a demo project that cover your needs.
I fixed the problem by "rewriting" the LMAlertView component in order to treat UIAlertView buttons as UITableView's cells and by setting the rowNumber accordingly, if the alert has to show 1, 2 or more buttons. It's not an easy to show solution so I won't share the code here, also because I don't know if it's Apple's-approval safe.
Thanks to everyone!
When creating a UIActionSheet to prompt a user to delete an item from a list, I currently have to maintain the deleted item (or at least its index in the list) as an instance variable in my view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath row] == 4) {
// Delete button pressed
_deletingItemIndex = [indexPath section];
UIActionSheet actionSheet = ...
}
}
Then when the UIActionSheet closes, if the user decided to delete the item, I have to reference that _deletingItemIndex variable, then set it to -1 or some other nil value.
What I would like to do, is maintain either the deleting item, or its index, in the actual UIActionSheet without needing to subclass UIActionSheet.
I find it strange that the delegate method for UIActionSheet provides the sheet to the delegate, but you can't store any contextual information (not even a dictionary) in the sheet itself.
There are quite a few categories out there for adding block-based dismiss handlers to UIActionSheet and UIAlertView now. I personally use Mugunth Kumar's UIKitCategoryAdditions.
This would let you do the following...
[UIActionSheet actionSheetWithTitle:#"Hooray" message:#"Blocks Are Awesome!" buttons:#[...] showInView:self onDismiss:^(int buttonIndex) {
//Now you have access to all your local variables here!
} onCancel:^{
//And here!
}];
You should use objc_setAssociatedObject()
Have a look at http://darkdust.net/writings/objective-c/attaching-objects for some code.
UIActionSheet is a system object which should only be concerned with presenting a user with choices and getting a button index out.
Using an instance variable of view controller also doesn't make sense, because this isn't really a state of view controller, but rather of a current action which started existing the moment user started deleting.
The correct pattern, thus, is to create a new object, save information there, and use as a delegate:
MyDeletionAction *action = [MyDeletionAction actionWithIndex:[indexPath section]];
UIActionSheet actionSheet = ...
actionSheet.delegate = action;
...
and somewhere else, define MyDeletionAction as a class implementing the delegate protocol.
An added bonus of this approach is that you can take the code out of your view controller into separate classes, which is a good thing. Moreover, it's likely that your MyDeletionAction, MyInsertionAction etc. will share some common code.
Perhaps even the presence of action sheet should be an implementation detail of your action. For example, what if you provide an option to delete without confirmation?
Note also that in this approach someone should retain an action object, for example by object retaining itself until the action is fully completed, or by using a VC instance variable for this purpose:
self.lastAction = [MyDeletionAction actionWithIndex:[indexPath section]];
[self.lastAction start];
This also allows you to remember the state of last action for possible postprocessing.
start here appears because my actions usually have this kind of inheritance: MyDeletionAction <- MyAction <- NSOperation. Your mileage may vary.
I'm trying to write a simple message alert system, with a UIAlertView displaying when priority messages are collected from a server. The messages are sent as a Tab separated string in the following format:
Priority:TRUE\tTrackingID:MESSAGEID\tFrom:FROMUSERNAME\tFromID:FROMID\tSentTime:SENTTIME\tMessage:text
Messages are displayed as a list in a table view. Clicking on a cell segues to a detail view with the message content. If a message is marked as priority an alert should appear which, on dismissal, directs the user straight to the detail view for that message.
The code I have for dealing with each string is:
NSArray *msgArray = [messageString componentsSeparatedByString:#"\t"];
[self storeMessageData:msgArray];
Then:
- (void) storeMessageData: (NSArray *)messagesArray
{
if ([messagesArray[0] isEqualToString:#"Priority:True"])
{
[self alertWithMessage:#"priority"];
}
}
And:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0)
{
[self performSegueWithIdentifier:#"showPriority" sender:self];
}
}
This works OK if only one message is retrieved but if there are two or more, dismissing the alert still performs the segue but then the alert immediately pops up again, followed by another segue, for as many times as there are messages.
What I'd like to know is how I'd go about interrupting this process, so that the user is able to deal with the first message then, if there is more than one, another alert is shown on returning to previous view. Any ideas appreciated.
Instead of looping through all your messages and calling the method that displays an alert for each of them, which results in the multiple alerts being displayed to the user, while looping, add all the 'priority' messages in an array. Then, check the number of alerts in your array and you can show one alert that reflects this information: e.g. for one message you could display the title of the message and some other information as title and message of the alertView, while, when you have multiple messages, you could have a title stating something like "You have x new messages with high priority" where x is the number of messages and some other description.
I have a block completion being called from within a button press message and, depending on state, optionally a UIAlertView being displayed. However, when invoked the UIAlertView appears three (3) times...
With the full information but it disappears itself and shows
Just the title shows and when I click OK
Appears again with full information (as in #1) for which I have to dismiss again
Following is a snippet of the code:
[credential performDataOperation:[credential commandForCreateOnClass:self.className]
withArguments:edits
completionBlock:^(BOOL succeded, id before, id after, NSDictionary *arguments, NSError *error) {
if (succeded) {
self.object = after;
self.objectWasCreated = YES;
[self prepareEditsDictionary];
self.navigationItem.rightBarButtonItem.enabled=NO;
}
else {
errorRecieved = YES;
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Error Message" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
}];
You are probably seeing just two alerts. The first appears, but you also have code somewhere that summons the second, so it overrides the first. Then you dismiss the second and the first returns. You need to hunt for your code that presents the second alert, the one without the message, and figure out why that code is running. Just do a global search in your project for UIAlertView! It must be in there somewhere, because all alert views are created and presented in code.
You may have accidentally hooked up your button so that it has multiple action handlers. Of course I could be wrong, but this is a mistake I've sometimes made, and then I've been mystified why my method was being called twice or some unwanted extra thing was happening when I tapped the button. Check your nib/storyboard or code to make sure. The fact that a single button can have many actions for a single UIControlEvent is very surprising and is almost never used intentionally.
(If that's not the right answer, then perhaps the solution lies in your performDataOperation method, whose code you do not show. Maybe it calls the simpler UIAlertView, in addition to calling the block.)
In controller on different actions I need to show UIAlertView with "OK" and "CANCEL" buttons, and every "OK" click on those 5 UIAlertViews need to do different things. Is possible to declare something like lambda function to specify what "OK" button from each UIAlertView is going to do? (In code on 5 places I have with different questions and messages and actions on ok, some don't have text for input at all)
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Apply"
message:#"Are you sure you want to apply ?"
delegate:self
cancelButtonTitle:#"CANCEL"
otherButtonTitles:#"OK",
nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
(At the moment I remember action which cause UIAlertView to show and then based on action I do different things, but it is not clear code).
Use a category called UIKitCategoryAdditions that implements a block-based UIAlertView and UIActionSheet for the selection or cancel actions.
It makes it super simple to assign actions to many user prompts without having to implement the delegate methods and deal with handling more than one object's delegate response in the same controller.
It's possible, but by extending the existing controls. I would recommend using RIButtonItem, I'm using it myself in the latest couple of my projects.
Use an objective-c block in lieu of your delegate.
delegate: ^(UIAlertView * alertView, NSInteger buttonIndex) { doSomethingHere; };