Subclassing UIAlertView

2019-02-20 01:31发布

问题:

Am attempting to subclass UIAlertView to better handle error states in my app. The trouble am having is with the otherButtonTitles nil terminated parameter, when I create my subclass it is only picking up the first string in the list rather than all the strings

+ (ErrorAlertView *)displayErrorAlertViewWithTitle:(NSString *)title 
                                           message:(NSString *)message 
                                          delegate:(id<UIAlertViewDelegate>)delegate 
                                     errorDelegate:(id<ErrorAlertViewDelegate>)errorDelegate
                                 cancelButtonTitle:(NSString *)cancelButtonTitle 
                                        displayNow:(BOOL)displayNow
                                               tag:(NSUInteger)tag
                                 otherButtonTitles:(NSString *)otherButtonTitles, ...;

{

  ErrorAlertView *errorAlertView = [[ErrorAlertView alloc]  initWithTitle:title 
                                                                  message:message 
                                                                 delegate:delegate 
                                                        cancelButtonTitle:cancelButtonTitle 
                                                        otherButtonTitles:otherButtonTitles, nil];


  errorAlertView.errorDelegate = errorDelegate;
  errorAlertView.tag = tag;

  if (displayNow) {

    [errorAlertView show];

  }

  return [errorAlertView autorelease];


}

If I make the following call the above method:

[ErrorAlertView displayErrorAlertViewWithTitle:[self noInternetConnectionAlertViewTitle] 
                                           message:[self noInternetConnectionAlertViewMessage] 
                                          delegate:self 
                                     errorDelegate:errorDelegate 
                                 cancelButtonTitle:@"OK" 
                                        displayNow:YES 
                                               tag:ErrorAlertTagInternetConnectionError 
                                 @"Try again",@"Setting", nil];

The UIAlertView is being shown with only the @"Try again" button being shown.

回答1:

You can't send a variable set of parameters like that.

When I subclassed UIAlertView I did this:

va_list args;
va_start(args, otherButtonTitles);
for (NSString *anOtherButtonTitle = otherButtonTitles; anOtherButtonTitle != nil; anOtherButtonTitle = va_arg(args, NSString*)) {
    [self addButtonWithTitle:anOtherButtonTitle];
}

Alternatively, you could create a variant of your function that accepts a va_list as a (single) parameter, and then runs the above code.

Generally, when writing a variadic function, you should include an alternative for handling this eventuality. Apple supply the addButtonWithTitle: method in this case.



回答2:

From the UIAlertView Class Reference

Subclassing Notes

The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.

There are, however, many other alert view implementations that you may find useful, posted here on CocoaControls.



回答3:

Instead of subclassing UIAlertView, I prefer to create simple classes that typically just have a show method that takes a delegate parameter. The delegate protocol then has expressive methods that correspond to the available options.

While this approach results in more delegate functions than the typical alert view delegate approach, I think it makes the code a lot more readable.

The code typically looks like this (Swift):

@objc protocol DeleteUniverseAlertViewDelegate {
    func deleteUniverseAlertViewDidConfirmDelete(view: DeleteUniverseAlertView)
}


class DeleteUniverseAlertView : NSObject, UIAlertViewDelegate {

    private weak var delegate: DeleteUniverseAlertViewDelegate? = nil

    class func showWithDelegate(delegate: DeleteUniverseAlertViewDelegate) -> DeleteUniverseAlertView {
        let view = DeleteUniverseAlertView()
        view.delegate = delegate
        UIAlertView(title: "Delete universe?", message: "Are you really, really sure about this?", delegate: view, cancelButtonTitle: "Cancel", otherButtonTitles: "Yes, delete!").show()
        return view
    }

    func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
        if (buttonIndex > 0) {
            delegate?.deleteUniverseAlertViewDidConfirmDelete(self)
        }
    }
}

When you then need to show this alert, just implement the protocol and show the custom alert like this (remember to keep a strong reference to the alert view):

deleteAlert = DeletePlaceAlertView.showWithDelegate(self)