Search code examples
iosautomatic-ref-countinguitextinput

inputAssistantItem and inputAccessoryView stopped working with ARC


I'm using this code to add some buttons above the on-screen keyboard in my iOS app. Note that I'm using the older inputAccessoryView method on phones and pre-iOS 9 devices, and the newer inputAssistantItem on iOS 9 tablets:

UITextView *textInputMultiline = [[UITextView alloc] initWithFrame:frame];
TextInputToolbar *textInputToolbar = [TextInputToolbar alloc]; // custom class
(void)[textInputToolbar initWithNibName:@"TextInputToolbar" bundle:nil];
textInputToolbar.textView = textInputMultiline;
if ((self.appDelegate.isTablet)&&([[[UIDevice currentDevice] systemVersion] compare:@"9.0"] != NSOrderedAscending)) {
    NSMutableArray *barButtonItems = [NSMutableArray array];
    [barButtonItems addObject:[[UIBarButtonItem alloc] initWithTitle:@"button 1" style:UIBarButtonItemStylePlain target:textInputToolbar action:@selector(button1)]];
    [barButtonItems addObject:[[UIBarButtonItem alloc] initWithTitle:@"button 2" style:UIBarButtonItemStylePlain target:textInputToolbar action:@selector(button2)]];
    [barButtonItems addObject:[[UIBarButtonItem alloc] initWithTitle:@"button 3" style:UIBarButtonItemStylePlain target:textInputToolbar action:@selector(button3)]];
    UIBarButtonItem *representativeItem = nil;
    UIBarButtonItemGroup *group = [[UIBarButtonItemGroup alloc] initWithBarButtonItems:barButtonItems representativeItem:representativeItem];
    textInputMultiline.inputAssistantItem.trailingBarButtonGroups = [NSArray arrayWithObject:group];
} else {
    textInputMultiline.inputAccessoryView = textInputToolbar.view;
}

My custom toolbar class looks like this:

@interface TextInputToolbar : UIViewController {
    UITextView *textView;

    IBOutlet UIButton *button1;
    IBOutlet UIButton *button2;
    IBOutlet UIButton *button3;
}

@property (nonatomic, strong) UITextView *textView;

- (void)insertText:(NSString *)text;

- (IBAction)button1;
- (IBAction)button2;
- (IBAction)button3;

@end

And...

#import "TextInputToolbar.h"

@implementation TextInputToolbar

@synthesize textView;

- (void)viewDidLoad {
    NSLog(@"viewDidLoad");
    [super viewDidLoad];
}

- (void)insertText:(NSString *)text {
    [self.textView insertText:text];
}

- (IBAction)button1 {
    NSLog(@"button1");
    [self insertText:@"1"];
}

- (IBAction)button2 {
    NSLog(@"button2");
    [self insertText:@"2"];
}

- (IBAction)button3 {
    NSLog(@"button3");
    [self insertText:@"3"];
}

@end

This worked as expected when my app was not using ARC. I recently updated to ARC, which only required minimal changes to the code above (I previously had autoreleases on the UIBarButtonItems, and didn't have the (void) cast before the initWithNibName), and now the buttons still appear as expected, but don't work. On iOS 8, I get a crash when tapping one of the buttons ([CALayer button1]: unrecognized selector sent to instance, which I think indicates an invalid memory pointer), and on iOS 9, nothing happens when I tap a button, and the logging in the button methods is not called.

I have a copy of my project before updating to ARC, and when I go back and run that on my iOS 8 or iOS 9 devices, the toolbar buttons work again. So it seems ARC is either the source of the problem or the trigger of a different problem.

If I point a barButtonItem to self, like this...

[barButtonItems addObject:[[UIBarButtonItem alloc] initWithTitle:@"button 3" style:UIBarButtonItemStylePlain target:self action:@selector(test)]];

...the method call is received as expected. If I change a barButtonItem selector to an invalid method, like this...

[barButtonItems addObject:[[UIBarButtonItem alloc] initWithTitle:@"button 3" style:UIBarButtonItemStylePlain target:textInputToolbar action:@selector(flkjfd)]];

...nothing happens. That suggests to me that textInputToolbar has somehow become nil by the time the button selector is called, because if it weren't nil, this would generate an unrecognized selector crash.

But I know that the TextInputToolbar class and its view are loading, because the logging in viewDidLoad occurs, and because the view appears as the inputAccessoryView an phones and iOS 8 tablets.

Any idea what's happening, or what else I can do to troubleshoot?


Solution

  • Your code was never correct and only worked because of a leak. You're basically losing / not retaining the view controller. It used to just continue to exist and work, but under ARC it's released so there's nothing to respond to the buttons. ARC has fixed your memory issue and made you aware that there's a problem, though not in an idea way.

    To fix, retain the view controller while its view is being used.

    Also, I'm not sure where you learnt to do:

    TextInputToolbar *textInputToolbar = [TextInputToolbar alloc]; // custom class
    (void)[textInputToolbar initWithNibName:@"TextInputToolbar" bundle:nil];
    

    but you shouldn't. Do that all on one line. Don't ignore the object returned from init calls - it may be different to the one you originally called it on.