Itty Bitty Labs

Code and technical stuff from Itty Bitty Apps.

Subjective-C: Deconstructing iOS User Interfaces

Our iOS user interface prodigy Sam Page has been busy lately. He’s created a new resource for UIKit programmers called subjc.com.

He has already published a few detailed investigations & deconstructions of user interface elements from recent interesting iOS apps.

Check out these excellent articles and follow him on Twitter.


About Oliver

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

The UIView that wouldn’t be centered

Recently I was creating a banner for an informational page in one of our iOS apps. The designer had specified three centered lines of information about a property, as a static header above a map and some scrolling information below. As you would think, a perfect case for contained view controllers and constraint-based layout. But what appeared to be the easiest part turned to contain an interesting problem…. The base view controller (“self”) starts by apply some basic constraints to a UIView named bannerView at the top of the screen. (The iOS6 case is not shown here for clarity):

Adding constraints to the banner view
1
2
3
4
5
6
7
  if ([self respondsToSelector:@selector(topLayoutGuide)])
  {
    NSDictionary *viewsToConstrain = @{@"topLayoutGuide" : self.topLayoutGuide, @"bannerView" : self.bannerView};

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLayoutGuide][bannerView]" options:0 metrics:nil views:viewsToConstrain]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[bannerView]|" options:0 metrics:nil views:viewsToConstrain]];
  }

The banner view is anchored to the top of the view controller’s view and is constrained to its full width. At this stage bannerView has no frame set and won’t display anything, so it needs to supply its own height constraint. This is achieved by relying on the intrinsic height of its contents – each label is placed in the banner view and centered horizontally by constraint, then bannerView has a constraint applied that stacks each view and at the same time gives the view its intrinsic height, the total of the heights of those views. You could say it really ties the view together. (This is one of the tough concepts in auto layout: constraints don’t apply in one direction, they are all applied and solved simultaneously. Conflicts and ambiguities are the only problem, either the parent or the subview can supply the dimension.) An example:

Centering horizontally and stacking vertically
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
  self.addressLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  // font, color, text etc. are set 
  [self.addressLabel sizeToFit];
  self.addressLabel.translatesAutoresizingMaskIntoConstraints = NO;
  [self addSubview:self.addressLabel];

  // horizontal centering is repeated for the locality label and the features view (not shown)
  [self addConstraint:[NSLayoutConstraint constraintWithItem:self.addressLabel
                                                   attribute:NSLayoutAttributeCenterX
                                                   relatedBy:NSLayoutRelationEqual
                                                      toItem:self
                                                   attribute:NSLayoutAttributeCenterX
                                                  multiplier:1.0f
                                                    constant:0.f]];

  // create and apply the constraint to position everything vertically. All these views have an intrinsic height so bannerView (self) 
  // will have the total height of these views
  NSDictionary *viewsToConstrain = @{@"addressLabel" : self.addressLabel, @"localityLabel" : self.localityLabel, @"featureView" : self.featureView};

  NSString *vFormatString = [NSString stringWithFormat:@"V:|-%f-[addressLabel]-%f-[localityLabel]-%f-[featureView]-%f-|",
                             kAddressLabelTopMargin, kLocalityLabelTopMargin, kFeatureViewTopMargin, kViewBottomMargin];
  [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vFormatString
                                                               options:0
                                                               metrics:nil
                                                                 views:viewsToConstrain]];

The end result is the completed bannerView, anchored to the top of the containing view controller’s view. If the designer decides to alter the font size or order of the labels or the size of featureView then the banner view will be resized through its constraints automatically. In the view controller the next lower view is anchored to the base of the banner view so everything maintains its arrangement. Job done! Or so I thought. A look at the result shows not the three nicely centered elements expected, but two centered elements and one seemingly left aligned on the centre. As the bannerView has been selected, Reveal shows its bounds (in 2d mode) with the blue outline. That’s strange, since the view has the same NSLayoutAttributeCenterX constraint applied as the labels. The featureView is actually a view containing a single label. Once upon a time it was a view containing three icons and three labels, but to improve scrolling efficiency (remove transparency) a font was created containing glyphs for those icons, and the view created using the label’s attributedText property. For a quick sanity check, what if the view is a plain UIView?

Testing with a simple view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  self.featureView = [[UIView alloc] initWithFrame:CGRectZero];
  self.featureView.backgroundColor = [UIColorblueColor];
  [self.featureView addConstraint:[NSLayoutConstraint constraintWithItem:self.featureView
                                                               attribute:NSLayoutAttributeHeight
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:nil
                                                               attribute:NSLayoutAttributeNotAnAttribute
                                                              multiplier:1.0
                                                                constant:28.f]];
  [self.featureView addConstraint:[NSLayoutConstraint constraintWithItem:self.featureView
                                                               attribute:NSLayoutAttributeWidth
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:nil
                                                               attribute:NSLayoutAttributeNotAnAttribute
                                                              multiplier:1.0
                                                                constant:120.f]];

So everything is working with a plain view. Why doesn’t the features view work? The reason this view changed is because Instruments called it out as containing transparency. Image blending can really hurt scroll performance and you can find out which views have the feared transparency with Instruments – while running on a device with the Core Image instrument, check the “Color Blended Layers” box. You will find everything shaded green except for those views that use transparency, and they will be red. Red indicates SLOW. One way to get around this is to use opaque source images and arrange your layout so that no transparency is required. Another is to switch strategies, as in this case. A label using attributed strings created with a font designed with custom glyphs (replacing the original transparent UIImageViews) doesn’t trigger this warning. Examining the creation of the view reveals nothing to worry about. Strictly speaking this label could be added as a label rather than adding a label to a view and then using the view, but it remains the way it is to avoid touching too much other code that expects it to be a view. Experimenting with the internals of the view doesn’t change the way it left aligns on the centre either. Time to bring up the big gun, Reveal.

Looking at the UIView portion of Reveal’s inspector window for the feature view vs. the UILabel above it shows right away that there is no intrinsic content size set for the feature view, and it has a bounds of CGRectZero. This hasn’t mattered previously because the feature view has been used with conventional layout, explicit frame setting rather than constraints. What happens if an intrinsic content size is supplied for the custom class?

Adding intrinsic content size
1
2
3
4
5
6
7
8
  // after creation of the UILabel 
  [self invalidateIntrinsicContentSize];

// Later on in the class...
- (CGSize)intrinsicContentSize {

  return self.label.frame.size;
}

Running the app again shows that the view is just where it should be. A quick look in Reveal shows the intrinsic content size is as expected – clicking on the feature view shows its intrinsic content size is the same as for the label it contains, as we want. Adding a new view controller using constraint-based layout exposed an issue that didn’t cause problems in frame-based layout. Reveal uncovered the problem and verified the solution.


About Adam

Adam Eberbach is an iOS Developer at Itty Bitty Apps. He has worked on the iOS introspection tool Reveal and the Realestate.com.au iPhone/iPad App. You can follow him on twitter @aeberbach

We’re hiring!

Update April 2nd, 2014: We are no longer actively looking to fill these roles, but we are always on the lookout for great people. If you’re interested in working at Itty Bitty Apps please send an email to jobs@ittybittyapps.com and introduce yourself!


Itty Bitty Apps is looking for a number of talented iOS, Android and Mac developers to join our team.

We are a mobile consulting and product development company based in Melbourne Australia. If you’re an iOS developer, you might know us from our runtime introspection tool Reveal. If you’re in Australia and you’ve ever used the realestate.com.au or SEEK apps, you’ve seen some of our handiwork.

We are passionate about great software engineering, UX and design. You will need to be too, and more than that, you’ll need to be able to point to products you’ve worked on that reflect your own high standards.

We are intimately involved in the iOS and Mac development community in Australia, organising Melbourne Cocoaheads meetups which regularly attract 80+ attendees each month. We’ve also been instrumental in organising Swipe, Australia’s first iOS and Mac developer conference. You will need to have a similar passion for community and knowledge sharing.

Our work environment is relaxed but focussed on achieving great results for our clients and delivering amazing development tools to our customers. Most of our consulting engagements are on-site with clients, where we can help shape the design and implementation of their mobile products. You’ll need to be enthusiastic about working embedded with their teams and in their environments (largely agile).

Reveal product development is done in-house, and whether you join us as an iOS or Android developer, you will have opportunities to work on this amazing product.

Our office is located in one of Melbourne’s best laneways for food and coffee in the heart of the city. You will not be left wanting for single origin or cold drip coffee, if that’s your thing.

We can sponsor international applicants via the 457 Temporary Work Visa and permanent residency applications. You will need to have an undergraduate degree to be considered for sponsorship. This could be the opportunity you’ve been looking for to move to one of the most liveable cities in the world.

Candidates with a track record of Open Source contributions and a mastery of multiple programming languages and platforms will be highly regarded.

If you’re interested in applying for any of the following roles, send an email to jobs@ittybittyapps.com telling us about your experience and links to examples of your work. Salary packages are commensurate with experience.

No recruiters please.



iOS Developer

Are you an iOS developer looking to challenge yourself and work with a team of A+ players? This is an opportunity to join one of the most respected iOS development companies in Australia and take your skills to a new level.

Responsibilities

  • Design, develop and maintain iOS applications for clients in-house.
  • Work with our clients on-site to help them deliver well designed and implemented mobile apps to their customers.
  • Contribute to UX and design discussions.
  • Post technical content to the Itty Bitty Apps blog.

Prerequisites

  • Excellent technical understanding of Objective-C, the iOS platform and development toolchain.
  • Good understanding and practical application of design patterns.
  • Good understanding and experience with testing frameworks.
  • Good understanding and experience developing apps that integrate with RESTful APIs and Web Services.
  • Good understanding and experience with revision control systems such as Git.
  • 2+ years commercial iOS development.

Highly Regarded

  • Experience developing Android applications.
  • Experience with other languages and platforms (e.g. Java, C, C#, Ruby, Rails, NodeJS, JavaScript, HTML5 and CSS).


Android Developer

Have you been doing Android development for a few years? Looking for your next challenge? This is an opportunity to join us as a lead Android developer, share your knowledge with the team and develop some amazing products.

Responsibilities

  • Design, develop and maintain Android applications for clients in-house.
  • Work with our clients on-site to help them deliver well designed and implemented mobile apps to their customers.
  • Develop frameworks and libraries for use across applications.
  • Contribute to UX and design discussions.
  • Mentor other developers on the team.
  • Post technical content to the Itty Bitty Apps blog.

Prerequisites

  • Excellent technical understanding of Java, the Android platform and development toolchain.
  • Good understanding and practical application of design patterns.
  • Good understanding and experience with testing frameworks.
  • Good understanding and experience developing apps that integrate with RESTful APIs and Web Services.
  • Good understanding and experience with revision control systems such as Git.
  • 2+ years commercial Android development.
  • 4+ years commercial development on other platforms.

Highly Regarded

  • Experience developing iOS applications.
  • Experience with other languages and platforms (e.g. Java, C, C#, Ruby, Rails, NodeJS, JavaScript, HTML5 and CSS).


Mac Developer

Are you an old hand at AppKit? Been cutting Objective-C since the NeXT days? Are you constantly telling those UIKit whippersnappers to get off your lawn? Have you been instrumental in delivering complex Mac apps to market? If so, you could be the one to help take Reveal to the next level. This is a unique opportunity to become a core member of the Reveal development team.

Responsibilities

  • Design and implement new features of the Reveal client and server applications.
  • Fix bugs and refactor existing code.
  • Development of custom AppKit UI components.
  • Help triage and prioritise features on the product backlog.
  • Reply to technical support questions.
  • Mentor other developers on the team.
  • Post technical content to the Itty Bitty Apps blog.

Prerequisites

  • 4+ years commercial AppKit development.
  • Experience developing client-server and/or peer-to-peer applications.
  • Experience developing custom Appkit UI components.
  • Solid understanding and experience with Core Graphics.
  • Good understanding and experience with revision control systems such as Git.
  • You must be able to reference commercially released Mac software you have developed (or been substantially responsible for developing).

Highly Regarded

  • Commercial experience developing iOS applications.
  • Experience with Scene Kit.
  • Experience developing IDE’s (even on other platforms).
  • Experience with other languages and platforms (e.g. Java, C, C#, Ruby, Rails, NodeJS, JavaScript, HTML5 and CSS).

About Sean

Sean Woodhouse is the director of Itty Bitty Apps, creators of the iOS runtime inspection tool Reveal. You can follow him on twitter @seanwoodhouse


Working with iOS 6 and 7

If business reasons require you to continue supporting iOS 6, this means you may need to work with both iOS 6 and 7 for quite a while. Developers always hate this, as it may increase the code base cyclomatic complexity (e.g. if iOS 6 do this, else if iOS 7 do that…) . I love it, since my wife, mum, dad and manager are still using iOS 6.

Layout issue on iOS 7

Here is a very simple app running on iOS 6.

After switch the simulator to iOS 7, the label is missing

Why? Let’s reveal it.

The label is actually behind the NavigationBar. In iOS 7, apple introduced a new property called [UIViewController setEdgesForExtendedLayout:] and the default value is UIRectEdgeAll. When your container is navigation controller, the default layout will start from the top of navigation bar. This is why all of the UI elements had been shift up 44pt.

A quick way to fix this issue is add the following snippet to method - (void)viewDidLoad.

Fix Layout issue
1
self.edgesForExtendedLayout = UIRectEdgeNone;

Now, it’s been fixed.

iOS 6 runtime exception

Let’s run the app on iOS 6 then, we found the following runtime exception.

iOS 6 runtime exception
1
[LAViewController setEdgesForExtendedLayout:]: unrecognized selector sent to instance 0x778a210

All iOS 7 only API invokes need to be wrapped with proper guarder.

1
2
3
4
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
{
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

Xcode 4 compile error

Some machines might still running with a Xcode 4.6. When they pull the latest code, they will fail to compile then.

1
2
Property 'edgesForExtendedLayout' not found on object of type 'LAViewController *'
Use of undeclared identifier 'UIRectEdgeNone'

To avoid the compile stage error, we need to create the following macro.

1
2
3
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
#define IOS7_SDK_AVAILABLE 1
#endif

Then wrap the iOS 7 code path if needed.

1
2
3
#ifdef IOS7_SDK_AVAILABLE
...
#endif

UILabel background inconsistency

As a UILabel, iOS 7 default background color is clearColor (which make sense most of the time), while the iOS 6 default value is white. So we’d better to explicit set the label background color.

1
view.backgroundColor = [UIColor clearColor];

Hide status bar while in full screen

In iOS 6, when we call presentViewController, the default modal screen will be full screen. (UIModalPresentationFullScreen). In order to achieve a consistent experience on iOS 7, we add the following code to modal controller.

1
2
3
4
- (BOOL)prefersStatusBarHidden
{
  return YES;
}

[UIToolbar barStyle]

We used UIToolbar with the system keyboard (Actually, it’s IBAForms and EZForm using them). As the iOS 6 keyboard is in dark skin, people style the toolbar like this.

1
self.barStyle = UIBarStyleBlack;// or UIBarStyleBlackTranslucents

Coming to iOS 7, the keyboard skin becomes lighter, so we need different barStyle for various iOS version.

1
2
3
4
5
6
7
8
if ([[[UIDevice currentDevice] systemVersion] compare:@"7.0" options:NSNumericSearch] != NSOrderedAscending)
{
    self.barStyle = UIBarStyleDefault;
}
else
{
    self.barStyle = UIBarStyleBlack;//or UIBarStyleBlackTranslucent
}

More than that…

It is definitely more than that. The above tips are just some of the most common issues I have seen when working on iOS 6 & 7 codebases.


About Long

Long Sun is an iOS Developer at Itty Bitty Apps. He has worked on the iOS introspection tool Reveal and Realcommercial iPhone/iPad App. You can follow him on twitter @spritesun

Integrating Reveal without modifying your Xcode project

So you use Reveal and you love it. But you’ve just fired up a new app in Xcode’s debugger and you want to have a quick look at the internals of the app’s view hierarchy, but you haven’t integrated Reveal via the static or dynamic library yet. What to do?

No problem! LLDB to the rescue.

LLDB can execute arbitrary code inside your running application. So you can just load Reveal into any iOS process you are currently debugging.

Just hit ^⌘Y to pause the iOS app in the Xcode debugger and then at the (lldb) prompt type:

LLDB Commands
1
2
3
expr (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2);
expr [(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStart" object:nil];
continue

Simple, right?

OK, so maybe not so simple and easy to remember. Wouldn’t it be better if we could just type reveal_load at the LLDB prompt or something like that?

Well, we can! LLDB supports command aliases. Just like with bash, you can create an alias in LLDB for a more complex command. The syntax is a bit different but the concept is the same. Also, just like bash, LLDB has a ‘dot file’ that it loads every time it starts which is the ideal place to put your LLDB command alises. That dot file is ~/.lldbinit.

Open up your favourite text editor, create the file .lldbinit file in your home directory and chuck the following LLDB command aliases into it:

~/.lldbinit
1
2
3
4
command alias reveal_load_sim expr (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2);
command alias reveal_load_dev expr (void*)dlopen([(NSString*)[(NSBundle*)[NSBundle mainBundle] pathForResource:@"libReveal" ofType:@"dylib"] cStringUsingEncoding:0x4], 0x2);
command alias reveal_start expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStart" object:nil];
command alias reveal_stop expr (void)[(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStop" object:nil];

The above snippet creates four command aliases: reveal_load_sim, reveal_load_dev, reveal_start and reveal_stop.

  • reveal_load_sim – This alias only works when running your app on the iOS Simulator. It loads the libReveal.dylib from the Reveal application bundle (assuming you put Reveal in your system’s Applications folder). If you have Reveal elsewhere change the alias to reflect this.
  • reveal_load_dev – This alias works when running your app on device or in the iOS Simulator. It however assumes you have added the libReveal.dylib bundled with Reveal to your application’s Copy Resources build phase. (Make sure it is not in your Link Binary with Libraries build phase. Yes, I was fibbing a little about not having to modify your Xcode project. Sorry.
  • reveal_start – This alias posts a notification via NSNotificationCenter to start the Reveal server.
  • reveal_stop – This alias posts a notification via NSNotificationCenter to stop the Reveal server.

Note: *Invoking the reveal_start command is only required if you invoke one of the reveal_load commands after iOS has posted the UIApplicationDidFinishLaunchingNotification notification. i.e. After your application delegate has handled application::didFinishLaunchingWithOptions:.

With these aliases defined you can now issue these quick commands at the LLDB prompt in Xcode. LLDB even auto-completes them for you!

Ok so now you have some quick LLDB command aliases and can trigger them manually, how do you make it more automatic?

Breakpoints!

Xcode (LLDB really) can execute commands in response to breakpoints. Using this knowledge we can add breakpoints to our application that load and start Reveal automatically.

The image above shows how you can add the Reveal LLDB commands to a break point in Xcode.

Some things to note about this breakpoint:

  • The break point is set to continue automatically so that the debugger doesn’t stop your app from executing when it loads Reveal.
  • Both the load and start commands are being executed. If you place your breakpoint in (or before) application:didFinishLaunchingWithOptions: you don’t have to actually issue the reveal_start command. Reveal automatically listens for UIApplicationDidFinishLaunchingNotification and starts automatically when this notification is fired by an app. There is no harm in trying to start Reveal twice. You might just see duplicate log messages.

When the breakpoint commands are successfully executed by LLDB you will see something like the following in the debugger console:

1
2
(void *) $0 = 0x0000000109022350
2013-11-07 15:18:20.687 Test[18049:70b]  INFO: Reveal server started.

Now you’re armed with LLDB and Reveal super powers!


About Oliver

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

Xcode and the case of the disappearing quick help

Recently I was coding in Xcode 5 (not something I tend to do much as an AppCode user), and I noticed a bug when trying to view the quick help for a symbol. This is usually done by option clicking on a symbol in the Xcode editor pane. The quick help popup window would show up and then rapidly shrink down to nothing. Most mysterious.

You can see this effect in the embedded video below.

This behaviour is not good, so I filed it as a bug with Apple (rdar://15285710). Apple’s bug report team helpfully got back to me today and I was informed that if I removed an Xcode preference the problem would go away.

I made sure Xcode wasn’t running and then issued the following defaults command at the terminal command line:

1
defaults delete com.apple.dt.Xcode WebKitJavaScriptEnabled

On relaunching Xcode the quick help popup was back to behaving as it should.
Great success!


About Oliver

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

Reveal tips: Navigation

Some of the awesome features of Reveal have been going unoticed, so I have written a quick rundown of how to improve your Reveal workflow so you can navigate your app like a pro.

And here is the tl;dr video:

Navigating the hierarchy

The outline view gives you a detailed one-for-one representation of the application’s view hieararchy. For a lot of apps, their view hierarchy will be massive, and sometimes scrolling and clicking just doesn’t cut it. My most used shortcut in Reveal has to be selecting the current view’s superview for those times I clicked the wrong view in the canvas.
These are the shortcuts that help you pop around the tree with ease:

  • Select current views’s superview [
  • Select current views’s first subview ]
  • Select current views’s next sibling
  • Select current views’s previous sibling ;

Because the outline uses native OS X controls, system defaults for expanding and collapsing nodes are the same as Finder

  • Collapse all nodes from the current selection
  • Expand all nodes from the current selection

Canvas viewing options

Now, one of the most eye-zappingly-awesome parts of Reveal is the 3d Canvas, which has quite a few features that people are yet to discover. There is a collection of mouse/trackpad combos to pan, rotate and increase the z-depth of the 3d representation.

  • Pan Up/Down two finger up/down or mouse-wheel
  • Pan Left/Right two finger left/right or mouse-wheel
  • Rotate two finger left/right or mouse-wheel
  • Z-Depth two finger pinch or mouse-wheel

Easy management of Zoom levels:

  • Zoom in + or two finger pinch
  • Zoom out or two finger pinch
  • Zoom to actual size 0
  • Zoom to fit 9
  • Zoom to current selection 8

Gaining some focus

Reveal lists every* UIView in the view hierarchy from the UIScreen down. Because that easily becomes an excessive amount of views to scroll through, Reveal allows you to drill-down and isolate your focus on a particular subset by simply double clicking a view:

I personally use this focussing a lot, especially when iterating with designers or any time I have to manipulate a UITableViewCell.
Once ‘focussed’ on a subset of the view hierarchy, you can navigate your way back up the tree using the path-bar at the top of the canvas. This is really helpful for those times you accidently double-clicked a view that was in front of the view you wanted to focus on.
You can also go back and forth in your focus history with the left/back buttons or the key-board shortcuts:

  • Back in Focus History ^
  • Forward in Focus History ^

* Reveal cannot see into a UIRemoteViewControllers’ views such as MFMailComposeViewController


About Pete

Peter Goldsmith is an iOS Developer for Itty Bitty Apps and has worked on their amazing iOS introspection tool Reveal. He thinks you should follow him on twitter @peterngoldsmith

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 a Senior iOS/MacOS Developer at Itty Bitty Apps. He spends his days building the iOS Introspection tool Reveal and playing Pinball. You can follow him on twitter @orj.

Refactoring in Xcode 5

I have a confession to make. I’m an AppCode user. I hardly use Xcode at all.

Ok, so if you know me or have seen my Cocoaheads talk/ramble regarding AppCode you probably already know this.

If you’ve never used AppCode before I suggest you take a look at it, and in particular play with its support for refactoring. Some people can’t get past AppCode’s slightly ugly Java based UI, but if you do get to know AppCode’s keyboard shortcuts and power features I think you will agree it is hard to beat as a Cocoa development tool. It makes me a much more productive developer. I feel like some of my fingers have been removed when using Xcode.

But I digress, this post isn’t about AppCode, it is about Xcode. How to get it to act more like AppCode. Err…. I mean, expose Xcode’s (somewhat limited) refactoring abilities in a better way than is the default, and also to learn about the small in number but still useful code navigation shortcuts Xcode has.

Refactoring Keyboard Shortcuts

Xcode has some limited support for global code refactoring. Tucked away in the Edit menu is the Refactor sub-menu. None of the items in this menu have keyboard shortcuts by default which makes them about as useful as a poke in the eye.

We are going to correct this considerable oversight.

You can edit any of Xcode’s keybindings in its Preferences on the Key Bindings tab. Here you can search the list for all the Refactoring items by typing refactor in the search field. Then double click in the Key column to enable the shortcut editor and then press the key combo you want to use. Xcode will tell you if you’ve created a conflict with an existing key binding. Finding a key combo you like that doesn’t have an existing binding, and thus conflict, can involve a bit of trial and error.

What do these refactoring things do?

Apple has some documentation on the refactoring workflows here. It is always a good idea to consult the official documentation. Below I provide a quick synopsis of the main refactoring actions.

  • Rename – Rename should be obvious. It lets you rename methods, classes, etc and have those changes applied across your entire codebase. Think of it as a smart, semantic, find and replace.
  • Extract – Extract gives you the ability to extract a piece of selected code into a new method or function. It will try and smartly determine what parameters and return values to give the new method based on surrounding code.
  • Move Up – These actions allow you to move a method, property or instance variable up to its super class.
  • Move Down – You would think that Move Down would do the inverse of Move Up, and it does, kinda. Unfortunately it only works with instance variables. Which makes it not as useful as Move Up which can also move methods.
  • Encapsulate – Encapsulate is useful for when you want to convert all uses of an instance variable to into accessor methods, thereby encapsulating that instance variable behind those methods.

Once you’ve added key bindings for these refactoring actions I encourage you to experiment with what they do. Try them out on parts of your code. You may discover that you quickly come to rely on some of them in your day-to-day coding activities.

A note about Snapshots

When you first use a refactoring action in Xcode it prompts you to enable Snapshots. I don’t know if anyone has actually clicked the Enable button in the entire history of Xcode. But if you’re worried about Xcode accidentally obliterating your code, it is there if you want to use it. But you use source control right? So that shouldn’t be a problem, right?

Important: One thing to be aware of however if you do not enable snapshots is that Xcode’s support for undoing changes made by a refactoring is a bit limited without a snapshot. Even when you do save a snapshot restoring the snapshot doesn’t always restore your project perfectly. It might leave you with changes you will need to un-stage in your git repo. This is probably most important to remember when performing a rename refactoring on a class or method used widely in your project.

If you do want to enable/disable snapshots for mass edits like refactoring actions you can do so in File -> Project Settings (or File -> Workspace Settings if you’re using a workspace).

Note also that you can create a snapshot at anytime with ^⌘S. Restoring a snapshot doesn’t have a default key binding. It is in the Xcode File menu. You can manage your project’s snapshots in the Xcode Organizer on the Projects tab.

Other Essential Xcode Keyboard Shortcuts

Edit All in Scope

Although Xcode provides a Rename refactoring action. This action, like all the Xcode refactoring actions, has quite a heavy UI. A quicker more constrained way of renaming things like variables is to use Edit All in Scope ^⌘E which is in the Editor menu. This gives you a quick in-line rename action for renaming variables, parameters, methods and the like. It only changes items in the current scope (and file) so it won’t rename methods in the header or usages in other files like the Rename refactoring action does but it is good for quick local scope renames.

Jump to Definition

Another essential keyboard (and mouse) shortcut to know is Jump to Definition ^⌘J When your cursor is on a symbol (method, variable, whatever) you can press this key combo to quickly jump to the definition (or if you are on the definition to the declaration) of that symbol. Very handy.

You can do this with the mouse too, just hold when you click on a symbol in your code.

Open Quickly

Probably the most essential keyboard shortcut in your Xcode arsenal is Open Quickly ⇧⌘O. You should be using this keyboard combo all day to quickly jump around your code. You can search for symbols and filenames in this dialog. Also note that you can use abbreviations and partial matching in the open quick search field. So if you have a View Controller subclass named AwesomeViewController in your project you can search for AweVC in Open Quickly and it will be found.

Hopefully you’ve found these Xcode tips and hints from an AppCode user to be useful.


About Oliver

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

Lifting the lid on the iOS 7 UIPicker

One of the significant visual changes in iOS 7 is the ‘flattening’ of the UIPicker, and by association the UIDatePicker. On the surface, the new picker looks much cleaner. I’ve never been a fan of the heavy-handed visual treatment given to the iOS 6 picker, and the pseudo 3D rotational effect using gradient overlays probably grated on most anal engineering and designer types. ‘They’re faking it!’, I thought. So when the new iOS 7 picker arrived on the scene there was a piece of me that thought, ‘cool, they’ve cleaned it up and done the 3D effect properly’. That was until I got to use the thing.

I found the new picker quite visually pleasing initially but having used it, I’m not so sure. For example, the UIDatePicker has simplified the colour palette to black and grey, which makes it harder to distinguish the day from the month. The colour highlight on ‘Today’ has been lost too. They’ve also squeezed in an extra two rows above and below the selection, which from an information density perspective is great, but it has reduced the hit areas of each row. Worse than that, the hit areas on the UIDatePicker are inconsistent. Marc Edwards summarised some of these issues a few weeks ago in this tweet.

As you can see from Marc’s diagram, you can no longer drag above and below the AM/PM column to move it. This is a huge pain because now you have to place your finger over the content in order to make your selection. In iOS 6 I’d often push or pull underneath the selected AM/PM value to change it. You’re also not able to tap the AM/PM or minute items to select them, which is completely inconsistent because you can tap the day and hour items! WAT

To add insult to injury, the hit area in the iOS 7 UIDatePicker does not extend to the boundaries of the control. The actual hit area is highlighted in blue below. So now you can’t fat-finger the edges of the picker either.

The inset tap area causes real problems when the picker is placed inline within a UITableView. It’s the same old ‘scroll views within scroll views’ conundrum developers have been struggling with since the dawn of graphical user interfaces, but made worse because you can easily miss the tap area within the control’s bounds and end up inadvertently scrolling the whole UITableView.

Don’t get me wrong, I think inline pickers are the way to go. It puts them in context with what you’re doing and avoids the issues of placement consistency across the iPhone and iPad. I just wish the tap targets were easier to hit and extended out to the bounds of the control.

Another issue I have with the picker is that Apple have stripped back so much of the ornamentation and deferred so much to the content that pickers with only a small amount of content can look downright weird. Without fully populated rows the transformed text looks out of place and at a glance it’s hard to tell why. It’s not until you move it that it makes sense.

Under the bonnet

From a usability point of view, the new UIPicker has taken one step forward and a few steps back. But the fun really starts when you take a look under the covers.

Here’s the iOS 6 UIDateTime picker view hierarchy displayed in Reveal.

…and here’s the iOS 7 UIDateTime picker.

As you can see, they’ve gone to town with the new implementation. It’s pretty obvious why performance would suck on lower-end devices. Impressive as the 3D transforms on the cells are, it’s hard not to feel like this is overkill for the magnified tumbler effect. The UIDatePicker contains no less than 12 UITableViews with each cell having its own unique 3D transform. The hierarchy is further bloated by the fact that UITableViewCell in iOS 7 introduces a UITableViewCellScrollView that contains the UITableViewCellContentView, neither of which appear to be utilised in the UIDatePicker.

Whether the UIPicker was implemented this way on purpose or not, I don’t know, but it’s certainly not optimised. Maybe Apple really did want the latest version of iOS to push the performance boundaries, but unlike the navigation bar blur effects which were ramped down on poorer performing devices, the implementation of the UIPicker doesn’t support this kind of fallback.

So that’s the new iOS 7 UIPicker. I was left wondering whether the 3D rotation effect was really worth it, both in terms of performance and usability. I’m not even sure the effect has a place in a predominantly flat UI. I can’t see it changing any time soon but I wouldn’t be surprised to see a few fixes and optimisations from Apple in subsequent releases to address some of the issues mentioned above. Watch this space.

If you want to have a peek under the covers yourself, get on over to http://revealapp.com and give Reveal a spin.


About Sean

Sean Woodhouse is the director of Itty Bitty Apps, creators of the iOS runtime inspection tool Reveal. You can follow him on twitter @seanwoodhouse