Nifty trick: the definitive way of iterating and working with large amount of views
If you've ever had to create an application that works with large amount of data and then displays them in a screen that simulates the look and feel of classic form in its paper version, you sure know the issue. If you wish to create the form in Interface Builder (and there's no reason why you shouldn't nowadays), there's one thing you worry more than the yearly visit of your grandmother and that's an absolutely obscene amount of IBOutlets you have to create to update the views with your data.
Or do you?
In this article, I'm going to show you an incredibly simple way of accomplishing old-school form look with the minimum amount of outlets. In fact, we are not going to create any at all.
How is that possible, do you ask? Well, let me tell you that our beloved friend IBOutlet has a little, not very well known brother-in-law that's called IBOutletCollection. And what exactly does IBOutletCollection do? Well, precisely what it sounds like, it's a collection of outlets. In the code, it can be defined like this:
Mind the required strong keyword, that's a rather big and at first glance unexpected change when compared to all of our IBOutlet property lines.
After you define your IBOutletCollection in code, you can fill the NSArray it's represented with in Interface Builder, much like you would do with regular outlets. So far it's all good and dandy, so where's the catch?
Well, truth to be told, there are two, but they're both solved by using the same solution. First problem is that because you now have your views (in my case, text fields – remember, we're creating a form) in an array, how do you actually know which text field are you working with when enumerating the collection? Furthermore, do you even know the order of the collection? The sad truth is that you don't, the order of elements in outlet collection is not guaranteed and despite the fact that right now in current version of iOS, the order is quite predictable, we still have to keep the unpredictability in mind and work our way around it to ensure there won't be any unexpected issues with our code in the future. So how do we solve this?
Well, the obvious solution is assigning all of our views a tag, but while that solution truly is obvious, it's also very short-handed: using magic numbers is rarely a good idea and limiting ourselves to integer identifiers is only marginally better than shooting ourselves in the foot. Furthermore, these artificial numbers probably won't correspond to identifiers used in our data source and even if they do, what if they happen to change in the future? Are we going to assign new tags to our views? Write a class that converts identifiers in our data to the tags we came up with when designing the form?
No, I have a better solution for you and it's one I already used in my previous article: it's adding a property to existing class, in my case UITextField.
For my needs, a string identifier was the best solution, therefore my UITextField category header file gained this line of code (warning, you may want to choose a smarter variable name than I did):
@property (nonatomic, assign) NSString *id;
And its implementation file gained following line of code:
@dynamic id;
After doing that (don't forget to import the DProperty pod – if you're unsure how to do that, feel free to consult my previous article on this topic), you're able to set the value of the id variable directly from the Identity Tab in Interface Builder:
Finally, you can enumerate through your text fields (or any other views, for that matter) like this and set their text values once you obtain/parse/get the required data:
for (UITextField *textField in [self textFields]) { [textField setText:[self objectDetails][[textField id]]]; }
Long live xib/storyboard files that contain as little outlets as possible!
I needed to display data from an Excel spreadsheet in one of my latest applications and I thought that the easiest way to do that would be to write an Excel to property list convertor in .NET Framework, then load the generated property list in my iOS app, skipping the tedious and more-than-likely incorrect custom spreadsheet parsing in Objective-C. I mean, what could go wrong?
At first, everything seemed to be just fine and dandy, but then I received new spreadsheet from the customer and upon generating the property list and importing it into my application, Xcode (well, xcodebuild to be precise) left me with an incredibly helpful message that solved precisely nothing:
Error: reading plist: The data couldn't be read because it isn't in the correct format.
You might be able to spot the error if your property list is a relatively short file but mine just reached eighteen thousand lines with the latest spreadsheet from our customer and the last thing in the world you want to do is to go through the entirety of it manually. Fortunately, you don't really have to.
If you fire up Terminal.app, navigate to the folder with your property list file and run the plutil command followed by your property list filename, you get a much more precise error message:
» plutil TrialData.plist TrialData.plist: Found non-key inside <dict> at line 15392
A line number! Well, that's an information I can work with! I have no idea why Xcode doesn't run your property list through the plutil utility automatically (although it might have something to do with Xcode sucking monkey balls), but now you're able to fix your property list file anyway, without the help of Xcode. Not like Xcode is much of a help most of the time anyway.
Nifty trick: the definitive way of implementing tab index to text fields
In the previous article, I told you how to add a property to a category – a task that's usually not easily done. We're going to use that knowledge now to implement a proper tab index and when I say proper, I mean without the use of tags, manually setting the firstResponder (because that's about as subtle as a gynecologist wearing a gas mask) or any subclassing nonsense.
I know what you're probably thinking right now: what do I even mean by “tab index”? Has [objectively:smart] suddenly turned into a blog about guitars? Well, fear not, a trivial explanation (that you can safely skip if you've ever created a simple HTML form) says that the tab index defines the order in which various user interface elements within a given form get activated whenever user presses the Next button on the iOS keyboard. Now that I think about it, a more fitting name would be something along the lines of “next index”, but that sounds… stupid. The term “tab index” itself comes probably from the ancient times when the birthday cake of HTML forms didn't look like a brush fire yet and a tabindex attribute of a HTML tag used to define which page element would get active whenever user pressed the Tab key.
Well, enough of the history lesson, let's do some programming!
First things first, you're going to need the DProperty library1 and the UITextField extension from previous blog post, so if you haven't done so already, now would be the perfect time to read it and implement these two things. When you're finished doing that, let's enhance the view controller containing all the sneaky little text fieldses in two simple ways: first, you want the view controller to conform to the UITextFieldDelegate protocol, so you edit the header file like this:
Open your storyboard file (or xib file, grandpa) in Interface Builder and set the view controller you just edited as the delegate of all instances of UITextField that are going to have a tab index by holding down the right mouse button and dragging, just like this:
Here comes the last step, this time drag a line from each UITextField that is going to be part of the sequence to the next UITextField in line and define natural and logical order of the text fields:
And… that's about it, actually! The most elegant solution to the problem that many iOS developers face!
Technically, you don't exactly need the DProperty library if you're willing to create the accessor methods by yourself – you'd just use the
id objc_getAssociatedObject(id object, void *key) and
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) functions. ↩︎
Nifty trick: the definitive way of adding properties to categories
This one might not seem all that useful at the first glance, I get that, but try to take it as a stepping stone to something far more awesome, something I'll describe in the next article in this series. You better look forward to it!
As you surely know, Objective-C allows you to extend existing classes without the need to subclass them by using so-called categories; however, categories can only bring you so far as they only allow you to add new methods – if you need to add new property, well, you're shit out of luck! Or… are you really?
The solution is called an associated object and if we don't want to bury ourselves in exactly one line of C code (which is one line more than I'd be comfortable with), add the very simple DProperty library into your project. If you're using CocoaPods (as you honestly should be), this process is as simple as adding the following line into your Podfile:
pod 'DProperty', '~> 0.0'
After that, there's nothing stopping you from creating a category with a similar interface…
Mind the use of the @dynamic keyword as opposed to the more-often used @sythesise one as @synthesise is just straight up not allowed in a category implementation section.
And the smarter ones already guess that the next article is going to describe the absolutely best and cleanest way of implementing a “go to next UITextField once I press the Next button on keyboard”. Oops.
Nifty trick: the definitive way to device detection
It sucks, but there's no avoiding it sometimes. Autolayout betrays you for quadrillionth time (on that given day), springs and struts are as much use as a one legged man at an arse kicking competition and you sigh – you know you have to fall back to the good ol' device detection macro.
But there's so many of them on the mighty Internet! Which one is the best one, which one is the fastest one? Well, fear not, I've got you covered. The following macro will tell you whether your beautiful app is running on a retina or a non-retina display (people still use these?) and, more importantly, if the device is iPhone 5 and newer or iPhone 4S and older.