Itty Bitty Labs

Code and technical stuff from Itty Bitty Apps.

iOS 7 & UITextView's UITextInputTraits bugs

Today while working on Reveal I became aware of a bug in iOS 7’s UITextView’s handling of the UITextInputTraits protocol.

The UITextInputTraits protocol has methods for setting what sort of keyboard should be shown when a user taps on a UITextView and other such things.

iOS 7 also introduced a new selectable property on UITextView for controlling whether text selection is enabled. This is much better than the methods required in previous releases of iOS, where you had to subclass UITextView and return no for canBecomeFirstResponder or alternatively set an inputDelegate and handle the appropriate delegate callbacks to stop selection for occurring.

Unfortunately, setting both editable and selectable to NO in iOS 7 breaks all of the UITextInputTraits methods. If you attempt to call any of them your app will crash with an instance does not respond to selector exception. Which is weird because the UITextView instance will return YES if you call respondsToSelector: for any of the methods declared in the UITextInputTraits protocol.

This appears to me to be a bug in iOS 7. I’ve reported it to Apple as radar://15063164.

I’ve also noticed that for UITextView’s created via code (rather than in Storyboards) this bug doesn’t express. I’m not sure why yet. There may be additional properties at work or the fact that the in a Storyboard a UITextView is initialised via initWithCoder: rather than initWithFrame: and may be in a different state due to this.

Some code that shows the bug:

IBAViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

@interface IBAViewController ()

@property (strong, nonatomic) IBOutlet UITextView *editableAndSelectable;
@property (strong, nonatomic) IBOutlet UITextView *selectable;
@property (strong, nonatomic) IBOutlet UITextView *notEditableOrSelectable;

@end

@implementation IBAViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  /* 
  Remove the define below and we won't crash on the last NSAssert below.  Leave it in
  and we crash (assuming we've created and connected three UITextView's to the IBOutlets above
  in a storyboard for this view controller).
 */
  
#define USING_STORYBOARD
  
#ifndef USING_STORYBOARD
  self.editableAndSelectable = [[UITextView alloc] initWithFrame:self.view.bounds];
  self.selectable = [[UITextView alloc] initWithFrame:self.view.bounds];
  self.notEditableOrSelectable = [[UITextView alloc] initWithFrame:self.view.bounds];

  [self.view addSubview:self.editableAndSelectable];
  [self.view addSubview:self.selectable];
  [self.view addSubview:self.notEditableOrSelectable];
#endif

  self.editableAndSelectable.text = @"Text1";
  self.editableAndSelectable.editable = YES;
  self.editableAndSelectable.selectable = YES;
  
  self.selectable.text = @"Text2";
  self.selectable.editable = NO;
  self.selectable.selectable = YES;

  self.notEditableOrSelectable.text = @"Text3";
  self.notEditableOrSelectable.editable = NO;
  self.notEditableOrSelectable.selectable = NO;
  
  NSAssert(self.editableAndSelectable.editable == YES, @"Huh?");
  NSAssert(self.editableAndSelectable.selectable == YES, @"Huh?");
  
  NSAssert([self.editableAndSelectable respondsToSelector:@selector(isSecureTextEntry)], @"Huh?");
  NSAssert([self.editableAndSelectable isSecureTextEntry] == NO, @"Huh?");

  NSAssert(self.selectable.editable == NO, @"Huh?");
  NSAssert(self.selectable.selectable == YES, @"Huh?");
  
  NSAssert([self.editableAndSelectable respondsToSelector:@selector(isSecureTextEntry)], @"Huh?");
  NSAssert([self.editableAndSelectable isSecureTextEntry] == NO, @"Huh?");

  NSAssert(self.notEditableOrSelectable.editable == NO, @"Huh?");
  NSAssert(self.notEditableOrSelectable.selectable == NO, @"Huh?");
  
  NSAssert([self.notEditableOrSelectable respondsToSelector:@selector(isSecureTextEntry)], @"Huh?");
  NSAssert([self.notEditableOrSelectable isSecureTextEntry] == NO, @"Huh?"); // crashes here (on iOS 7)
}

@end

About Oliver

Oliver Jones is the Technical Director at Itty Bitty Apps. He spends his days building the iOS Introspection tool Reveal and playing Pinball. You can follow him on twitter @orj.