A view's 'delete' button is pressed. The view belongs to a view controller, which handles the button press. However, that view controller is a child of a container view controller, so it sends its delegate a message that a deletion was requested, and includes the object that should be deleted.
The delegate (the parent view controller) receives the notification and presents a UIActionSheet
to confirm the deletion. It also makes itself the delegate of that action sheet.
The user confirms the deletion, and parent view controller is ready to delete the object. Except it has to do this in actionSheet:didDismissWithButtonIndex:
. By that point, it no longer knows which object was passed down from the child view controller.
Is there a way to attach an object to the alert sheet so that when it's dismiss action is fired, that object can be retrieved?
The Objective-C 2.0 runtime supports associated objects - using this API, you can, euh, associate object with each other using a key-value method. Example:
id someObject = // however you obtain it
objc_setAssociatedObject(theActionSheet, "AssociatedDelegateObject", someObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// later try to get the object:
id someObject = objc_getAssociatedObject(theActionSheet, "AssociatedDelegateObject");
// process the associated object, then release it:
objc_removeAssociatedObjects(theAlertSheet);
Edit: it seems that you don't really need to shoot a bird usign a cannon and use runtime functions, since the same class/object that manages the alert sheet cares about the delegation also, so you could just assign it temporarily to an instance variable. However, this approach may be easier to extend later when your object model gets more complicated.
You could use my ActionSheetDelegate class to create a Block that will act in the stead of the actionSheet:clickedButtonAtIndex:
method. Since the Block will be created in the same context as the creation of the action sheet, it can capture the object that you wish to delete:
ActionSheetDelegate * delegate;
delegate = [ActionSheetDelegate delegateWithHandler:
^( UIActionSheet * sheet, NSInteger idx ){
if( idx == [sheet destructiveButtonIndex] ){
[self destroyObject:obj];
}
// Cancel button "falls through" to no action
}];
You can also "associate" an object with another arbitrary object, using the Associated Objects runtime facility. In essence, this allows you to add an ivar to any instance at any time.
// Set:
objc_setAssociatedObject(sheet, &key, objectToDestroy, OBJC_ASSOCIATION_RETAIN);
// Retrieve:
id objectToDestroy = objc_getAssociatedObject(sheet, &key);
This just requires you to have a key
variable somewhere. The docs suggest a file-level static char
, whose address is used, as I've done here. Any value which will not change between setting and getting will work, however.
There are many ways to do this.
The simplest by far is just to give your parent view controller an instance variable that holds the object to be deleted. Since a UIActionSheet
blocks other user interactions, it shouldn't be possible for the user to request deletion of a second object while the first object's deletion is pending.
The best way is to not present an action sheet at all, but just do the deletion and give the user an “undo” button.
You can use an associated object (see H2CO3's answer).
You can use a wrapper that lets you set up a block as the alert view's button handler (see Josh Caswell's answer or my own BlockActionSheet
).
You can create a subclass of UIActionSheet and give it a property that holds the object pending deletion.