Archive for iOS

iOS Localization, some reflection and a hack

As an app developer, it can be quite effective to localize your apps (for this article, we focus on language), especially if you speak both English and your native tongue. At Japps, this is exactly what we do: localize at least to English and Dutch. Both iOS and Android provide means for this, although lozalizing the text on UI components is, frankly, a bit easier on Android. On iOS you are basically forced to duplicate your entire UI, which makes maintenance tricky (basically this violates the DRY principle).

Apple’s solution (prior to iOS 6)

Sadly, there are no real solutions available yet, although there are tools that speed up the process. The steps boil down to: duplicate Storyboard / nib files, extract strings from one language, translate strings into another and put them back (both using a command line tool). Any UI changes will require a repetition of these steps – however you can take a shortcut here by reusing most of the earlier translations. The great thing is that you can adjust your UI (sizes) to the different word lengths, but this is not always a big issue.

Alternative solution

Another solution that has been proposed is transferring the translation proces entirely to code by creating an outlet for each control that has text on it (see this tutorial). The downside is that that it requires quite some code for every new view. Furthermore, the texts in IB / storyboard will become totally meaningless, which may be confusing. To prevent this, they could be added just for clarity, but they’d still need to be defined in the strings file, which would again be non-DRY solution, and extra work.

Base Internationalization (iOS 6)

Luckily, Base Internationalization is coming, and it seems this will provide a KISS solution to localization on iOS. Auto layout could then ensure UI elements adapt their size and location to the length of the strings in the current language. This won’t work on iOS 5 though, so we’ll have to wait a bit before it is ‘acceptable’ to stop supporting the iOS 5 users.

Our solution (for the time being)

In the mean time, we crafted a bit of code that basically loops recursively over all views, checks the strings for square brackets. If anything is found like ‘[Blah]’ it is replaced by ‘BlahTranslated’, or ‘Blah’ if no translation was found. The latter means that for the ‘original’ language (ie the language that is used inside IB), no or only a few entries have to be added to Localizable.strings. For other (newly added) languages, everything can be added into one Localizable.strings file. Alongside, the UI in IB keeps being fairly understandable (since all texts are still relevant, albeit wrapped in square brackets).

This was implemented as ‘categories’ for NSString and UIView. In the latter case, subviews are checked recursively, and for different types of view (label, button, etc), slightly different steps are taken. The only thing that needs to be done is for every view that is loaded from IB  (in view controller’s viewDidLoad, or after manually loading the view from a nib) lngfkt.h needs to be included and [theTopView lngfk] needs to be called. In views that are instantiated from code NSLocalizedString can be used like ‘normal’.

The code

lngfkt.h

lngfkt.m

There’s a fair amount of edge cases that are not covered by this approach. Some just didn’t pop up yet (the above series of if statements could probably cover more UIView subclasses), others need some more trickery.

For navigation bars, the lngfk method has to be called separtely: [self.navigationController.navigationBar lngfk]. For static table headers and cell views, a bit of code is required in the class that implements the UITableViewDelegate class (the UITableViewController for instance):

The pitfalls

It is a hack. I doesn’t even really adhere to any naming conventions. We used it and found it a fair alternative compared to the other options. The code is posted just in case others like this idea as well. Please use it at your own risk. Also, this happens on runtime, which costs time. Not notably, in our case, but it could be.

 

iOS Core Plot (minimal example)

At our company, Japps, data plays an important role. We wanted to present graphs to the user inside our iOS apps. Luckily there is Core Plot (there’s other options, but we like this one). It works like a charm, it is extremely flexible, and it is open source (which is useful if you want even more flexibility or want to debug). There is also quite some reference code and Q&A available. However, I could not find a minimal example, so I created one.

Setting up shop

I created a new project from scratch, using the Single View template. I included Storyboard and ARC. Then I downloaded and installed Core Plot (I went for the static library approach).

In Storyboard, the class of the main view controller was changed to ‘CorePlotExampleViewController’, and this class was actually created (subclassing ViewController). The contents of this class is discussed next.

CorePlotExampleViewController.h

Now, CorePlotExampleViewController.h was slighty modified into:

What’s going on? Well, we import CorePlot-CocoaTouch.h, which gives us access to all Core Plot functionality. Also, we implement CPTPlotDataSource, why will become more clear later.

CorePlotExampleViewController.m

Next, we changed our view controller’s viewDidLoad:

What’s going on? First, CPTGraphHostingView* hostView is created. Alternatively you could (as described in other examples) do this via IB (Storyboard) and create an outlet and name it ‘hostView’.

Next, CPTGraph* graph is created and set as the hostViews ‘hosted graph’. Use the graph object to change axis and global layout. It can contain one or more plot spaces and actual plots (the lines, bars, dots, pies, etc). In this example, we take the graph’s default plot space and change its x and y ranges, this determines what part of the plot is shown to the user.

Finally, we create CPTScatterPlot* plot, the actual plot line itself. The X and Y values of the plot are not specified yet. Instead, we set its dataSource, that will provide the X and Y values upon the graphs’ request. To keep things simple, our CorePlotExampleViewController class will double as dataSource, but this could be a separate object.

To let our view controller double as datasource, we already let it implement the CPTPlotDataSource protocol (in the .h) file, and now only need to define two methods:

The method numberOfRecordsForPlot returns the number of plots. We simply set it to 9. The method numberForPlot is actually called twice (for an XYScatter plot at least, which is what we created) per data point (it took me a while to realize this). One time the X value should be returned, the other time the Y value. This is determined by fieldEnum.

The plot’s points are requested by their index, which in our case ranges from 0 to 8 (so 9 in total as we specified in numberOfRecordsForPlot). To get a nice quadratic plot without too much hassle we define x as ‘index – 4’ (but this could be something irregular as well, such as values returned from an array). The y value will then simply be x * x. The return value should be an NSNumber, so you’ll have to create this, for instance with numberFromInt, as in this case, or numberFromDouble (etc.).

Closing remarks

Well, that’s it really. You should now get a plot when you run your project. This is as simple as it gets, and from here you can make it as complicated as you want: change graph (theme, padding, border, background), change the axis (color, ticks, font, labels, location, multiple y axis), add more plots, change the plots line (thickness, color, symbols) add a legend (location, font), draw different plots (points, lines, bars, pies), etc. There’s is quite some information on this already, but perhaps I’ll do a followup post with the solutions we found for our graphing demands.