title | author | category | excerpt | status | ||
---|---|---|---|---|---|---|
Reader Submissions -<br/> New Year's 2014 |
Mattt |
Reader Submissions |
As we prepare to increment our NSDateComponents -year by 1, it's time once again for NSHipster end-of-the-year Reader Submissions! |
|
As we prepare to increment our NSDateComponents -year
by 1
, it's time once again for NSHipster end-of-the-year Reader Submissions! Last year, we got some mind-blowing tips and tricks. With the release of iOS 7 & Mavericks, and a year's worth of new developments in the Objective-C ecosystem, there was a ton of new stuff to write about.
Thanks to Arnaud Coomans, Cédric Luthi, David Grandinetti, Ell Neal, Eric Allam, Erik Kerber, Jim Kubicek, Joachim Bengtsson, Johannes Lund, Josh Avant, João Prado Maia, Justin R. Miller, Kamil Pyć, Matthew Teece, Maximilian Tagher, Nigel Timothy Barber, Nolan O'Brien, Pitiphong Phongpattranont, Steve Moser, Thomas Visser, Vadim Shpakovski, & @jurre for contributing their great tips.
Let's make this official: NSHipster's Objective-C trend of 2013 is code block evaluation assignment. Recommended by both Jim Kubicek and Maximilian Tagher (citing this blog post by Dominik Wagner), this trick does wonders to make code cleaner, safer, and more concise.
Behind the magic is a GCC C extension, which causes a code block to return a value if enclosed within brackets and parentheses.
Watch, as it cuts through this view controller code like butter!
self.searchBar = ({
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:({
CGRect frame = self.tableView.frame;
frame.size.height = 50.0f;
frame;
})];
searchBar.delegate = self;
searchBar;
});
This not only segregates configuration details into initialization, but the additional scope allows generic variable names like frame
, button
, and view
to be reused in subsequent initializations. No more loginButtonFrame = ... / signupButtonFrame = ...
!
If code craftsmanship is important to you, strongly consider making this standard practice in your work. It may look a bit weird at first, but this will very likely become common convention by the end of 2014.
The ternary operator, ?
, is shorthand for if () {...} else {...}
. However, because of how difficult it can be to understand statements with ternary operators at a glance, they are generally dispreferred by veteran coders.
Nonetheless, Maximilian Tagher offers a lesser-known (yet much-loved by those in-the-know) use of the ternary operator: ?:
, which acts as a convenient way to specify a fallback value to return if the left-hand side is nil
.
NSLog(@"%@", @"a" ?: @"b"); // @"a"
NSLog(@"%@", nil ?: @"b"); // @"b"
This is especially convenient for providing default behavior when a property may not be set:
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
<#...#>
});
The main downside of this approach is that default Xcode project warning settings will raise a warning. You can get around this by wrapping the relevant code block in #pragma
declarations, but the added LOC nullifies much of the brevity that this approach provides:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
self.name = name ?: @"Unknown";
#pragma clang diagnostic pop
Vadim Shpakovski reminds us of one of the more obscure additions to LLVM 5.0, the @import
keyword. No more are the days of Xcode ▹ Project ▹ TARGETS ▹ General ▹ Linked Frameworks and Libraries ▹ +
. With @import
, the Xcode will automatically link MapKit
, CoreData
, or any other referenced framework as necessary. Even Prefix.pch
benefits, sporting a svelte new physique:
@import UIKit;
@import Foundation;
@import CoreGraphics;
Justin R. Miller, of MapBox fame, brings to our attention what is perhaps the most important feature in iOS 7 mapping: custom tile overlays:
Relatively little-known feature of iOS 7: you can turn off Apple's own maps now with
MKTileOverlay
,MKTileOverlayRenderer
, andcanReplaceMapContent
, unlike you could before with Apple or (pre-iOS 6) Google. And check out MBXMapKit if you'd like to do it in one line of code.
Speaking of iOS maps, João Prado Maia cites an amazing blog post by thoughtbot, "How To Efficiently Display Large Amounts of Data on iOS Maps" by Theodore Calmes. Consider it a must-read if you plan to do any significant iCartography in 2014.
Shifting gears a little bit, Eric Allam remarks that NSAttributedString
can do HTML now in iOS 7 with the new NSHTMLTextDocumentType
document type attribute. Combine with MMMarkdown for ridiculously easy Markdown rendering in a UITextView
.
Offering another tip, Vadim Shpakovski calls our attention to the relationship between launch arguments and NSUserDefaults
:
The command line argument -TestFeatureEnabled YES
can be checked in code with [[NSUserDefaults standardUserDefaults] boolForKey:@"TestFeatureEnabled"]
. This is useful for debugging development builds.
Thomas Visser bemoans his pick for least awesome Objective-C development in 2013:
In iOS7, the animation of the keyboard show/hide changed. Its duration and, most notably, its curve is different from previous iOS versions. As before, if you want to animate something with the keyboard, you can listen to
UIKeyboardWillShowNotification
/UIKeyboardWillHideNotification
and use the values from theuserInfo
dictionary to coordinate your animations with the keyboard. TheuserInfo
dictionary contains the keyboard's begin frame, end frame, animation curve and animation duration.
However, in iOS 7, the animation curve is an undefined value, meaning that it is not one of the 4 defined values of
UIViewAnimationCurve
. Instead, its value is7
. This is a problem if you want to use the same curve in your own animation, because such a curve is not defined. The work-around, as discussed on the Apple forums, is to manually translate theUIViewAnimationCurve
to aUIViewAnimationOptions
value. From the definition ofUIViewAnimationOptions
, we learn that this translation is done by shifting the curve value 16 times to the left:option = curve << 16
. This works great, but shouldn't be necessary. I hope Apple will add this mysterious 5th curve to the definitions in a future iOS update.
David Grandinetti has a tip for apps that want to track outgoing links from within an app: override AppDelegate -openURL:
:
-(BOOL)openURL:(NSURL *)url{
if ([[url scheme] hasPrefix:@"http"]) {
[[GAI sharedInstance].defaultTracker sendView:[url absoluteString]];
}
return [super openURL:url];
}
In this example, this information is being sent to Google Analytics, but one could easily adapt this approach for any analytics provider.
Still resisting the aesthetics of this year's iOS makeover? Kamil Pyć shows us how to act as if iOS 7 never happened:
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"UIUseLegacyUI"]
And, of course, following up from a previous tip, since
NSUserDefaults
is tied to launch arguments, this can also be specified on launch. Keep this tucked in the back of your mind—this could make for a simple yet effective April Fool's joke.
Categories are great, but suffer from that same original sin of Objective-C: lack of name-spacing. Duplicate method declarations in categories interact in undefined ways, and may lead to difficult-to-debug behavior.
Fortunately, Cédric Luthi shows us how to tell if any category methods are getting up in one another's business:
Set the
OBJC_PRINT_REPLACED_METHODS
environment variable toYES
in order to automatically log all methods that are smashed by categories.
In an encore submission, Cédric brings us a .plist file of Emoji grouped by category (mirrored from CloudApp to Gist in order to be more searchable). 😄👍
Here's a simple function from Nolan O'Brien that can be used to determine the type of image data based on the first couple bytes of the header:
static inline NSPUIImageType NSPUIImageTypeFromData(NSData *imageData) {
if (imageData.length > 4) {
const unsigned char * bytes = [imageData bytes];
if (bytes[0] == 0xff &&
bytes[1] == 0xd8 &&
bytes[2] == 0xff)
{
return NSPUIImageType_JPEG;
}
if (bytes[0] == 0x89 &&
bytes[1] == 0x50 &&
bytes[2] == 0x4e &&
bytes[3] == 0x47)
{
return NSPUIImageType_PNG;
}
}
return NSPUIImageType_Unknown;
}
Once again, showing off his unmatched knowledge of Objective-C internals, Cédric shares this extremely useful tip for debugging Key-Value Observing.
Print which context is passed to
observeValueForKeyPath:ofObject:change:context:
in lldb.
Say you have declared a context like this:
static const void *MyFooContext = &MyFooContext;
...and you want to to know what context it is when you are inside
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
You can do this:
(lldb) image lookup -a `context`
Address: MyApp[0x00026258] (MyApp.__DATA.__data + 4)
Summary: MyFooContext
On the subject of Key-Value Coding, Pitiphong Phongpattranont offers this useful function that builds a keypath from a variable list of selectors:
inline NSString * PTPKeyPathForSelectors(SEL selector, ...) {
if (!selector) {
return nil;
}
NSMutableArray *selectors = [NSMutableArray array];
va_list args;
va_start(args, selector);
SEL arg = selector;
do {
[selectors addObject:NSStringFromSelector(arg)];
} while((arg = va_arg(args, SEL)));
va_end(args);
return [selectors componentsJoinedByString:@"."];
}
NSString *keyPath = PTPKeyPathForSelectors(@selector(data), @selector(name), nil);
// => @"data.name"
And finally, Matthew Teece gives a shout-out to Nomad, a world-class collection of command-line utilities—specifically, Houston, which can send and manage push notifications from the command line, or within your Ruby application.
$ apn push "<token>" -c /path/to/cert.pem -m "Hello!"
Thus concludes this year's reader submissions. Thanks again to everyone for your submissions!
And thanks to you, dear reader, for sticking with NSHipster for another 52 weeks. Between the WWDC session and the book, 2013 has been a bellwether year for NSHipster. Thank you for your support and enthusiasm for the site—it really does mean the world to me.
We have a ton of great stuff planned for 2014, so keep your fixies primed and your artisanal espresso hot for another season of great iOS and OS X knowledge.