Как безопасно передать объект контекста в делегат UIAlertView?
В моем приложении я использую UIAlertView для отображения пользователю сообщения и некоторых параметров. В зависимости от нажатой кнопки я хочу, чтобы приложение выполняло что-то на объекте.
Код примера, который я использую,...
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
//baseAlert.context = ctx;
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = ...;//alertView.context;
[self performSelectorOnMainThread:@selector(xxx:) withObject: context waitUntilDone: NO];
}
}
Есть ли способ передать объект в делегат как объект контекста? или, может быть, каким-то другим способом?
Я мог бы добавить свойство в делегат, но тот же объект-делегат используется многими различными видами предупреждений. По этой причине я предпочел бы решение, в котором объект контекста привязан к экземпляру UIAlertView и переносится на делегат как часть объекта UIAlertView.
Ответы
Ответ 1
Я все еще считаю, что его локальное хранение - лучшее решение. Создайте локальную переменную класса NSMutableDictionary для хранения карты объектов контекста, сохраните контекст с помощью UIAlertView в качестве ключа и контекста в качестве значения.
Затем, когда метод alert вызывается, просто загляните в словарь, чтобы узнать, какой контекстный объект связан. Если вы не хотите использовать весь объект Alert в качестве ключа, вы можете использовать только адрес объекта UIAlertView:
NSString *alertKey = [NSString stringWithFormat:@"%x", baseAlert];
Адрес должен быть постоянным на телефоне. Или вы можете пометить каждое предупреждение, как предлагалось другим плакатом, и использовать тег для поиска контекста на карте.
Не забудьте очистить объект контекста, когда вы закончите!
Ответ 2
Полная реализация, которая позволяет передавать контекст:
@interface TDAlertView : UIAlertView
@property (nonatomic, strong) id context;
@end
@implementation TDAlertView
@end
И пример использования, обратите внимание на то, как мы предварительно создаем указатель:
@implementation SomeAlertViewDelegate
- (void)alertView:(TDAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog(@"%@", [alertView context]);
}
@end
Ответ 3
вы также можете использовать свойство тега (так как это подкласс UIView). Это просто int, но может быть достаточно для вас.
Ответ 4
Подклассификация UIAlertView не очень хорошая идея.
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIAlertView_Class/UIAlertView/UIAlertView.html
Замечания о подклассе
Класс UIAlertView предназначен для использования как есть и не поддерживает подклассы. Иерархия представления для этого класса является частной и не должна быть изменена.
UIAlertView с предоставленным пользователем контекстом и [self autorelease] отвечает на этот вопрос другим способом.
Ответ 5
Вместо того, чтобы обсуждать значение "не поддерживает подклассирование", я дам лучший ответ. Я создал общую категорию contextInfo для своей работы пару месяцев назад. Я просто положил его на github: JLTContextInfo.
#import "objc/runtime.h"
@interface NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo;
@end
@implementation NSObject (JLTContextInfo)
- (NSMutableDictionary *)JLT_contextInfo
{
static char key;
NSMutableDictionary *contextInfo = objc_getAssociatedObject(self, &key);
if (!contextInfo) {
contextInfo = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &key, contextInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return contextInfo;
}
@end
Это создает место для легкого хранения дополнительных данных для любого объекта, полученного из NSObject. Теперь ответ почти идентичен исходному вопросу.
-(void) showAlert: (id) ctx {
UIAlertView *baseAlert = [[UIAlertView alloc]
initWithTitle: title
message: msg
delegate:self
cancelButtonTitle: cancelButtonTitle
otherButtonTitles: buttonTitle1, buttonTitle2, nil];
[[baseAlert JLT_contextInfo] setObject:ctx forKey:@"ctx"];
[baseAlert show];
[baseAlert release];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
id context = [[alertView JLT_contextInfo] objectForKey:@"ctx"];
[self performSelectorOnMainThread:@selector(xxx:) withObject: context waitUntilDone: NO];
}
}
Ответ 6
Я сделал микс между ответом Kendall и использованием блоков в одном из классов контроллера базового представления. Теперь я могу использовать AlertView и ActionSheets с блоками, которые улучшают читаемость. Вот как я это сделал:
В .h моего ViewController объявляю тип блока (необязательно, но рекомендуется)
typedef void (^AlertViewBlock)(UIAlertView*,NSInteger);
Также я объявляю mutable dictionnary, который будет хранить блоки для каждого предупреждения:
NSMutableDictionary* m_AlertViewContext;
В файле реализации я добавляю метод для создания AlertView и сохранения блока:
-(void)displayAlertViewWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle withBlock:(AlertViewBlock)execBlock otherButtonTitles:(NSArray *)otherButtonTitles
{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles: nil];
for (NSString* otherButtonTitle in otherButtonTitles) {
[alert addButtonWithTitle:otherButtonTitle];
}
AlertViewBlock blockCopy = Block_copy(execBlock);
[m_AlertViewContext setObject:blockCopy forKey:[NSString stringWithFormat:@"%p",alert]];
Block_release(blockCopy);
[alert show];
[alert release];
}
Обратите внимание, что я получаю те же атрибуты, что и конструктор UIAlertView, но делегат (который будет сам). Также я получаю объект AlertViewBlock, который я сохраняю в переменной m_AlertViewContext.
Затем я показываю предупреждение, как обычно.
В обратных вызовах делегата я вызываю блок и даю ему параметры:
#pragma mark -
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* blockKey = [NSString stringWithFormat:@"%p",alertView];
AlertViewBlock block = [m_AlertViewContext objectForKey:blockKey];
block(alertView,buttonIndex);
[m_AlertViewContext removeObjectForKey:blockKey];
}
- (void)alertViewCancel:(UIAlertView *)alertView {
NSString* blockKey = [NSString stringWithFormat:@"%p",alertView];
[m_AlertViewContext removeObjectForKey:blockKey];
}
Теперь, когда мне нужно использовать AlertView, я могу вызвать его так:
[self displayAlertViewWithTitle:@"Title"
message:@"msg"
cancelButtonTitle:@"Cancel"
withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:@"DO ACTION"]){
[self doWhatYouHaveToDo];
}
} otherButtonTitles:[NSArray arrayWithObject:@"DO ACTION"]];
Я сделал то же самое для ActionSheet, и теперь очень просто использовать их.
Надеюсь, что это поможет.
Ответ 7
Из моего другого ответа, здесь быстрое и чистое решение, которое использует связанные объекты. В другом ответе я упоминаю, что вы могли бы заменить UIAlertView
на NSObject
и эффективно добавить свойство context
к любому объекту:
#import <objc/runtime.h>
@interface UIAlertView (Private)
@property (nonatomic, strong) id context;
@end
@implementation UIAlertView (Private)
@dynamic context;
-(void)setContext:(id)context {
objc_setAssociatedObject(self, @selector(context), context, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)context {
return objc_getAssociatedObject(self, @selector(context));
}
@end
И тогда вы сможете сделать что-то вроде:
NSObject *myObject = [NSObject new];
UIAlertView *alertView = ...
alertView.context = myObject;
ВАЖНО:
И не забудьте опустить контекст в dealloc
!!
Ответ 8
Вы можете подклассифицировать UIAlertView
и добавить там свойство.