Now that Apple has revealed iOS 7, there's no doubt that iOS developers around the world have a huge work ahead, to support the new user interface.
Every iOS major version has many changes in the SDK.
New concepts, new classes/frameworks to deal with, obsolete features, etc.
So there's usually always some work to do in order to fully support a new iOS major version.
Till now, if the app was made correctly, with compatibility in mind, it was usually not a big deal.
But with iOS7, things are completely different.
The iOS UI haven't changed (or only very slightly) from the first iOS version to iOS6.
So the modifications required to support a new iOS versions were mostly in the code logic.
Now we've got a completely different UI, and supporting this in our apps will require much more work.
When thinking about the UI, existing iOS apps can usually be categorized in three main categories.
The first category should be quite OK with iOS7. When you deal only with SDK features, without any kind of modification (custom drawing code, custom layout, etc), you're not really concerned about a change in the UI, as your app will immediately reflect all changes in the SDK.
But I think most of the AppStore apps don't fit in this category.
The second category of applications will obviously need modifications.
While some parts may adapt well to the new UI, all custom parts will need to be adapted.
Depending on the amount of custom parts, this may actually be a huge work.
The third category may be safe, as long as it's really completely custom, and unless the changes in the SDK breaks the custom parts.
But then, there's something else.
The problem with most applications with a custom UI is that the UI, even if custom, was somehow related to the original iPhone UI.
So even if the custom components will work with iOS7, there's a strong risk that the app will quickly look outdated.
I think users will get used of the new flat UI. So if an application does not use a flat interface, even if it's a killer app, it may finally get less users, only because it will feel less integrated with the system.
I guess that's how users will react, and I doubt we can change this mentality.
So to resume, most of the existing applications will need to be modified to support the flat UI of iOS7.
Now what's the best way to achieve this, in a developer's perspective?
Of course, we could just drop support to iOS versions lesser than 7. But I don't think this is an option.
While some new apps may just do that, and even if the average iPhone user usually updates his device quickly, I think iOS6 (and also iOS5) needs to be supported.
So most of the existing applications will have to deal with two completely different kind of user interfaces. Headache ahead…
In Objective-C, we can of course query the system for the running iOS version.
This is done through the UIDevice
class.
I won't fall in the debate of version checking versus feature checking.
While the second is obviously better, in such a specific and general case, version checking is perfectly OK.
So the iOS version number can be retrieved with:
NSString * version = [ [ UIDevice currentDevice ] systemVersion ];
This is not very usefull, as it returns a string with the full version number.
In our case, we're only interested by the major version number. And an integer value would be much more practical than a string.
So let's add a method for this in UIDevice, using a category:
/* UIDevice+VersionCheck.h */ @interface UIDevice( VersionCheck ) - ( NSUInteger )systemMajorVersion; @end /* UIDevice+VersionCheck.m */ @implementation UIDevice( VersionCheck ) - ( NSUInteger )systemMajorVersion { NSString * versionString; versionString = [ self systemVersion ]; return ( NSUInteger )[ versionString doubleValue ]; } @end
It simply asks for the version string, and converts it to an integer.
As we cast a double to an integer, we'll get only the major system version.
This way, as we've augmented the UIDevice
class, we can use:
NSUInteger version = [ [ UIDevice currentDevice ] systemMajorVersion ];
And of course, as we now got an integer, we can use it quite simply in conditional statements:
if( [ [ UIDevice currentDevice ] systemMajorVersion ] < 7 ) { /* iOS 6 and previous versions */ } else { /* iOS 7 and above */ }
But obviously, this will lead to very crappy code, because you'll have to put this kind of statement at many places.
Your code will then become hardly maintanable.
So how can we improve this?
We can solve this using the class cluster approach.
First of all, what's a class cluster?
You may have noticed when using instances of NSArray
, for instance, that the resulting object is not really a NSArray
.
For instance, you may expect the following code to print NSArray
to the console:
NSLog( @"%@", NSStringFromClass( [ [ [ NSArray alloc ] init ] class ] ) );
But in fact, it will print a different class name.
This is because NSArray
is a class cluster.
Unlike other object-oriented languages, Objective-C does not have constructors.
So creating an instance of a particular class is usually done by sending the alloc
message to a class.
The difference is that alloc
is a standard static method. So unlike classic constructors, it has the ability to return a value.
In that case, a new instance of the requested class.
But as it's a standard method, it can actually return something else, like an instance of another class.
This is, for short, the main concept behind a class cluster.
The basic idea is to have a public base class, which exposes all the required and common methods.
Then, depending on the software's needs, we can have many other private classes, extending that base class and overriding behaviours.
This way, we can achieve separate specialization, while keeping a standard and unique interface.
For instance, think again about the NSArray
class.
It obvisouly has to deal with many specializations, like mutable or immutable instances, toll-free bridging (for communicating with the CoreFoundation API), etc.
Dealing with all these cases in one unique class would mean lots of code inside a class, leading to maintanability issues.
So this problem is solved by having many specialized classes extending NSArray
.
All of those classes aren't exposed in the public API.
The only public one is still NSArray
, but it will just act as a frontend to the other ones.
This can be done by overriding the alloc
method, and returning an instance of another class, depending on the context.
To give a practical example, let's imagine the following scenario: in your app, you have a subclass of UIView in which, amongs other things, you override the drawRect:
method.
This is usually the case for custom UI components.
If you want to support both iOS6 and iOS7, you may need to code different drawing code, to adapt to the general system UI.
We dont want if/else statements in the drawRect:
method. This is crappy.
So we'll simply make a base class, containing all the common code, and we'll override the alloc
method in order to have a class cluster:
/* TestView.h */ @interface TestView: UIView /* Common method */ - ( void )test; @end /* TestView.m */ @implementation TestView + ( id )alloc { if( [ self class ] == [ TestView class ] ) { if( [ [ UIDevice currentDevice ] systemMajorVersion ] < 7 ) { return [ TestViewIOS6 alloc ]; } else { return [ TestViewIOS7 alloc ]; } } else { return [ super alloc ]; } } - ( void )test {} @end
Look at the alloc
method implementation.
When called, it detects the iOS version and actually instanciates a specialized subclass, depending on the iOS version.
So when calling:
[ TestView alloc ]
You'll actually get an instance of the TestViewIOS6
prior to iOS 7, and an instance of TestViewIOS7
on iOS7 and later.
This way, you can have your common and version independant code in the base class, and specialized code in the the different subclasses, while keeping a unique interface when using your custom view.
For instance:
/* TestViewIOS6.m */ @implementation TestViewIOS6: TestView - ( void )drawRect: ( CGRect )rect { /* Custom iOS6 drawing code */ } @end /* TestViewIOS7.m */ @implementation TestViewIOS7 - ( void )drawRect: ( CGRect )rect { /* Custom iOS7 drawing code */ } @end
This way, all of your specific code will be in specialized subclasses.
It means your code will be cleaner and more maintanable.
Also, when you'll decide to drop support for iOS6, the only thing you'll have to do is to remove the specialized iOS6 subclass.
Much simpler than removing if/else statements…
Enjoy, and have fun with iOS7 : )