Как безопасно передать объект контекста в делегат 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 и добавить там свойство.