What is the recommended method of styling an iOS app? - objective-c

What is the recommended method of styling an iOS app? For example, if there are multiple labels or text views how can updating font style/color at one place update the style/color at all other places?
I know sub classing could be one way... is there any other way?

You could import a standard header file into all your controllers with several constants set for styling... example:
Styles.h
#define kFontSize 14
#define kFontFamily #"Helevetica"
Controller
#import "Styles.h" // at the top
myLabel.font = [UIFont fontWithName:kFontFamily size:kFontSize];
I personally think Interface Builder is the best way to style, however this answers your question directly.

Update: I would recommend starting by understanding UIAppearance APIs, and seeing how well they suit your needs. UIAppearance is a convenient way to provide custom default stylization of specific controls' attributes at multiple levels (e.g. globally or contextually).
My original answer, which predated UIAppearance's availability:
since we're working with an object based language...
for the implementation, it depends on how you want it to behave/execute. when the implementation becomes nontrivial, i will often create a protocol. you could use class methods or instance methods and significantly optimize these types for your usage because you create fewer intermediate colors, fonts, images, etc.
a basic interface could take the form:
#protocol MONLabelThemeProtocol
- (UIFont *)labelFont;
- (UIColor *)labelTextColor;
- (UITextAlignment)labelTextAlignment;
// ...
#end
#protocol MONTableViewCellThemeProtocol
- (UIFont *)tableViewCellFont;
- (UIColor *)tableViewCellTextColor;
- (UIImage *)tableViewCellImage;
- (NSInteger)tableViewCellIndentationLevel;
- (CGFloat)tableViewCellIndentationWidth;
// ...
#end
then a simple amalgamate theme could be declared like this:
#interface MONAmalgamateThemeBase : NSObject
< MONLabelThemeProtocol, MONTableViewCellThemeProtocol >
{
#protected
/* labels */
UIFont * labelFont;
UIColor * labelTextColor;
UITextAlignment labelTextAlignment;
// ...
/* table view cells */
UIFont * tableViewCellFont;
UIColor * tableViewCellTextColor;
UIImage * tableViewCellImage;
NSInteger tableViewCellIndentationLevel;
CGWidth tableViewCellIndentationWidth;
// ...
}
#end
in this example, the amalgamate defines the getters and dealloc and expects the subclasses to initialize the instance variables. you could also support lazy initialization if initialization times are high (e.g. uses many images).
then a specialization could take the form:
#interface MONDarkTheme : MONAmalgamateThemeBase
#end
#implementation MONDarkTheme
- (id)init
{
self = [super init];
if (nil != self) {
labelFont = [[UIFont boldSystemFontOfSize:15] retain];
labelTextColor = [[UIColor redColor] retain];
// and so on...
}
return self;
}
// ...
#end
/* declare another theme and set it up appropriately */
#interface MONLightTheme : MONAmalgamateThemeBase
#end
then just reuse the theme instances (e.g. MONDarkTheme) throughout the app to stylize the views. if you have a lot of themes or they are not trivial to construct, then you may want to create a collection for themes (theme manager). the amalgamate could also take a parameter, such as init with theme if your needs are simple. you can even configure objects to register for changes to themes, if you need support for dynamic changes.
finally, you can create a simple theme applier to make life easier - like so:
#interface UILabel (MONThemeAdditions)
- (void)mon_applyMONLabelTheme:(id<MONLabelTheme>)theme;
#end
#implementation UILabel (MONThemeAdditions)
- (void)mon_applyMONLabelTheme:(id<MONLabelTheme>)theme
{
assert(theme);
if (nil == theme) return;
self.font = [theme labelFont];
self.textColor = [theme labelTextColor];
self.textAlignment = [theme labelTextAlignment];
}
#end

Frankly, the best way to go about this is to use Interface Builder. While it might seem nice to change a single constant somewhere in the code and have the entire app change styles, it never quite works out that way. Here are my reasonings:
1) Developers don't write interface code as well as interface builder does.
Interface builder is a tool that has been refined, tested, and intreated over years. It offers fonts, text alignment, shadow, etc. It is backwards compatible for as far back as you'd ever want. It provides a very simple way for any number of developers and designers to jump in and work on something very straightforward.
2) There are always edge cases that you'll have to account for. Sure, a simple constant will do what you want most the time, but you'll eventually have to hack something in here and sneak something in there. The "simple" interface code you wrote to start off will grow and grow and grow. Other developers will have to maintain that code. You will have to maintain that code. You will have to file and fix bugs, tweak this, except that, etc. It will inevitably become a steaming pile of mess.
3) The more code you write, the more bugs you write. Interface builder is for building the 'look' of most iOS apps. Use it. Don't get too clever.
NOTE:
I understand that Interface builder cannot do everything for all apps. There are cases that coding an interface is the only solution. This answer is simply a general "best practice" I use in the bulk of my apps.

Similar to Alex's idea, you could create a static class called ThemeManager:
typedef enum
{
defaultStyle,
redStyle,
} ThemeStyles;
#interface Level : NSObject
{
ThemeStyles currentTheme;
}
All classes which can be themed will import ThemeManager. Then, you can create methods like:
+ (UIColor*) fontColor;
Which other classes would call when they want a color for their font. Then, if you want to change themes, you could implement fontColor as:
+ (UIColor*) fontColor
{
switch (currentTheme)
{
case defaultStyle:
return [UIColor blackColor];
case redStyle:
return [UIColor redColor];
}
}
When you want to change the theme, you could have ThemeManager implement a method like:
+ (void) changeTheme:(ThemeStyles)newTheme
{
currentTheme = newTheme;
}

You can use a third-party abstraction of UIAppearance:
NUI: https://github.com/tombenner/nui
Pixate: http://www.pixate.com
Using a Storyboard has a lot of benefits, but many style options aren't available, not the least of which is custom fonts. If you want a deeply-customized UI, you will need some style code to make it happen.

I use plists. Just as I localize strings, I use the same procedure to change themes. I coded a singleton that loads a current theme plist and a fallback plist. Then I replace the names of resources with keys and macro functions that pull the real resource name from the singleton.
Cons: you have to set the resource for each element, not just set it in the NIB.
Pros: once you are done, most of the next theme involves photoshop and textmate, not IB or code.

You may need to look at this library. It supports multiple themes/skins on the fly. Supports images and colors currently. Font support will be added in future.
https://github.com/charithnidarsha/MultiThemeManager

Related

Which of the following will be efficient or faster in iOS?

Which of the following will be efficient or faster in iOS?
/* Public Static Variable */
static UICollectionView *mDocsCollection;
#interface ClassA ()
{
}
#end
#implementation ClassA
- (UICollectionView *)documentsCollection
{
if (!mDocsCollection) {
mDocsCollection = (UICollectionView *)[self.view viewWithTag:VIEW_TAG_DOCS_COLLECTION];
}
return mDocsCollection;
}
#end
/* Interface Variable */
#interface ClassA ()
{
UICollectionView *mDocsCollection;
}
#end
#implementation ClassA
- (UICollectionView *)documentsCollection
{
if (!mDocsCollection) {
mDocsCollection = (UICollectionView *)[self.view viewWithTag:VIEW_TAG_DOCS_COLLECTION];
}
return mDocsCollection;
}
#end
/* Private Static Variable */
- (UICollectionView *)documentsCollection
{
static UICollectionView *docsCollection;
if (!docsCollection) {
docsCollection = (UICollectionView *)[self.view viewWithTag:VIEW_TAG_DOCS_COLLECTION];
}
return docsCollection;
}
/* Typecasting is done every time (No Variables used) */
- (UICollectionView *)documentsCollection
{
return (UICollectionView *)[self.view viewWithTag:VIEW_TAG_DOCS_COLLECTION];
}
With the speed of todays devices I doubt there would be any measurable difference. However there are some other observations:
As per other comments, it's not a good idea to use static variables in these situations. In fact, I recommend only using statics where absolutely necessary.
Again unless necessary, I recommend to people to avoid using viewWithTag:. It may seem like an easy solution to finding controls at first. But once you project becomes larger or other programmers join, it's a likely cause of on going bugs. I recommend using IBOutlets and/or UIView extends so that you have direct access to correctly typed references to the controls that always point to the control you want.
And by using an IBOutlet or property, you simply would not need any of these methods :)
The last is slowest. The others are equally fast, probably only a few machine instructions apart.
The file-scope static and the method-scope static do the same thing. The ivar does something very different. This matters if you have multiple instances of ClassA or multiple threads, in which case the statics are probably simply wrong.
I doubt the difference will be observable in this case, but you could benchmark it and see. A simple-minded benchmark would do something like:
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
// put the code to benchmark here
NSLog(#"elapsed = %f seconds", CFAbsoluteTimeGetCurrent() - startTime);
You might want to repeat your tests many times so you establish a confidence level on the numbers you generate. Also note that the order in which you test them can affect the results, so you might want to delay the benchmarking code until the app has really finished loading and quieted down.
Theoretically, if you call this documentsCollection method a lot, storing the value in a variable might offer some performance improvement, but you'd only bother with this sort of implementation when using some very expensive method or if you're calling it a lot.
FYI, the static implementations can result in very different behavior if its possible that you might reinstantiate ClassA more than once (i.e. dismiss the view controller and re-present it later). The static variables are not unique to just this instance of ClassA, but are shared across instances. Given that you're dealing with views, I doubt this behavior is desirable.

Objective-c add support for new classes to old devices

The problem: every new iOS adds a lot of new useful classes. For example, UIRefreshControl. I want to add support for this class in iOS5 build.
Not cool solution: In all classes that must use UIRefreshControl, I can check for current iOS version and use inline replacement for that classes, for example:
pseudocode
...
- (void)viewDidLoad
{
...
if([[UIDevice currentDevice].systemVersion floatValue] < 6.0)
{
self.refreshControl = [[MyCustonRefreshControl_for_iOS5 alloc] init];
}
else
{
self.refreshControl = [[UIRefreshControl alloc] init];
}
...
}
This solution is't cool, because I must add same code in all classes where I want to use latest iOS features.
Possible cool solution:
1) get or create your own 100% compatible class, for example for UIRefreshControl you can use CKRefreshControl (https://github.com/instructure/CKRefreshControl);
2) use Objective-C runtime to define replacement class as main class when App starts.
pseudocode
...
// ios 5 compatibility
#include <objc/runtime.h>
#import "CKRefreshControl.h"
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// pre-ios 6 compatibility
if([[UIDevice currentDevice].systemVersion floatValue] < 6.0)
{
// register refresh control
Class clazz = objc_allocateClassPair([CKRefreshControl class], "UIRefreshControl", 0);
objc_registerClassPair(clazz);
}
...
}
I think that this way is really cool, but this code won't work.
You may want to make one consistent interface with different ways of doing the job. You need a strategy design pattern to solve your problem. Then you need to check for version only once - at initialisation of object which is doing iOS version specific job.
If all method calls are identical (and no class/"static" methods to speak of), simply use a "factory" method to create the objects and then use them "normally".
Otherwise, I'd probably use a "wrapper" class that reroutes calls to either the builtin support or your "replacement", based on one of the tests Filip's post.
The second soultion should work fine
There's can be some issues when ARC is enabled, so, you should move allocate_pair code to some class, that is compiled with -fno-objc-arc flag
For more information, look here (in comments):
http://www.mikeash.com/pyblog/friday-qa-2010-11-6-creating-classes-at-runtime-in-objective-c.html
#abuharsky, [CKRefreshControl]https://github.com/instructure/CKRefreshControl has been updated and now does exactly what you are asking for. All you need to do is:
self.refreshControl = [[UIRefreshControl alloc] init];
Let us know if that doesn't work for you.

Objective C: How to support deprecated methods

So with each new OS a bunch of methods are declared deprecated. The strange thing for me is that if I want to still support iOS5 AND iOS6 I have to use BOTH the deprecated and the replacement method, like with UILabel's minimumScaleFactor over minimumFontSize.
If I replace myLabel.minimumFontSize to myLabel.minimumScaleFactor than my app will come crashing down in iOS5.
So I use an if with -respondsToSelector: to find out whether the OS is 5 or 6 and use minimumScaleFactor or minimumFontSize accordingly.
The problem is that I might have to write a bunch of ifs and respondsToSelectors in my code and that feels dumb.
Is there a better way to deal with deprecations?
I create function which i can use in any ios:
void AbsSdk30_upg60_UILabel_setMinimumScaleFactor(UILabel * label, CGFloat minimumScaleFactor) {
if ([label respondsToSelector: #selector(setMinimumScaleFactor:)]) {
[label setMinimumScaleFactor: minimumScaleFactor];
}
else {
const CGFloat curFontSize = label.font.pointSize;
const CGFloat fontSize = (0.0 == curFontSize) ? [UIFont labelFontSize] : curFontSize;
[label setMinimumFontSize: fontSize * minimumScaleFactor];
}
}
Language independend solution: get the OS at the start of your app and set a global variable. Then, when neede query the variable for the OS version. You could do it in a case/switch statement to allow for easy extensability if changes in future versions occur.
Pseudo code:
switch iOSversion
case < 6
dothis
break
case <7
dothat
break
case >7
OS not supported ;)
Technically it is the same thing as with the IFs, but your source would be shorter and more structured plus you don't have to query the OS version everytime, but once at the start of your app.
For that you should put the deployment target minimum. So that if you run your application.At that time it will not give you deprecated errors.And also you don't need to write the if s and respondsToSelectors in your code. you can put your deployment target to ios 5.0 or 5.1
I think it will work for you.
And if you want your deployment target 6.0 then there will be a another method which can replace that deprecated method.
I treat 'deprecated' as a warning that a method may go away at some point in the future, not that it has to be replaced now. Rather than complicating the code for current builds I leave myself comments about what to change when I drop support for certain older versions.
Except for things that stop working in a new release, I don't #ifdef by versions.
Add a category to UILabel like this in a .h file or in .h and .m files where the .h file will be imported by the code that uses it:
#interface UILabel (UILabelCategory)
+ (CGFloat)minimumLabelFontSize;
- (void)adjustsFontSizeToFitWidthWithMinimumFontSize:(CGFloat)fontSize;
#end
#implementation UILabel (UILabelCategory)
+ (CGFloat)minimumLabelFontSize // class method that returns a default minimum font size
{
return 11;
}
- (void)adjustsFontSizeToFitWidthWithMinimumFontSize:(CGFloat)fontSize
{
if ([self respondsToSelector: #selector(setMinimumScaleFactor:)])
{
CGFloat currentFontSize = self.font.pointSize == 0 ? [UIFont labelFontSize] : self.font.pointSize;
[self setMinimumScaleFactor:fontSize / currentFontSize];
}
else
{
[self setMinimumFontSize:fontSize]; // deprecated, only use on iOS's that don't support setMinimumScaleFactor
}
[self setAdjustsFontSizeToFitWidth:YES];
}
#end
And then call the UILabel extension like this from multiple places in your code:
(assuming that you have a UILabel object called _instructions and that you import the file that implements the UILabelCategory extension)
[_instructions adjustsFontSizeToFitWidthWithMinimumFontSize:[UILabel minimumLabelFontSize]];
or like this:
[_instructions adjustsFontSizeToFitWidthWithMinimumFontSize:14];
Note: remember that on iOS 6 and prior the setMinimumFontSize only works if you also set the number of lines to 1 like this:
[_instructions setNumberOfLines:1]; // on iOS6 and earlier the AdjustsFontSizeToFitWidth property is only effective if the numberOfLines is 1

Help with Wikibooks WikiDraw Obj-C application

I decided to start learning some Obj-C. And I thought that Wikibooks wikidraw application would be a good place to start (after some very basic "Hello World" programs). I've followed the chapters and now I'm at the end of "WikiDraws view class". So now I'm supposed to be able to compile and run. Of course it dosen't work. I got a lot of errors at first but i have fixed most of them, only 6 remaining. This is one of them:
- (void) mouseDragged:(NSPoint) pt
{
NSPoint np;
np.x = pt.x - _anchor.x;
np.y = pt.y - _anchor.y;
if ( _dragState == 0) {
// dragging of object
[self offsetLocationByX:np.x byY:np.y];
}
else if ( _dragState >= 1 && _dragState < 9 )
{
// dragging a handle
NSRect nb = [self newBoundsFromBounds:[self bounds] forHandle:_dragState withDelta:np];
[self setBounds:nb];
}
}
- (NSRect) newBoundsFromBounds:(NSRect) old forHandle:(int) whichOne withDelta:(NSPoint) p
{
// figure out the desired bounds from the old one, the handle being dragged and the new point.
NSRect nb = old;
switch( whichOne )
{ ..........
So at
NSRect nb = [self newBoundsFromBounds:...
I get an error message, "Invailid initializer" and "WKDShape may not respond to '-newBoundsFromBounds:forHandle:withDelta:"- . What should I do? I'm new to coding but eager to learn.
/Carl-Philip
Assuming you've pasted that code in the order written in your source code and newBoundsFromBounds:forHandle:withDelta: isn't declared (as distinct from being defined) at some earlier point, I think the problem is just that at nb = [self newBoundsFromBounds:... the compiler doesn't yet know what the return type will be. An NSRect is a C-style struct rather than an Objective-C class, so the compiler really does need to know.
As a solution, either put the definition of newBoundsFromBounds:... before mouseDragged:, add it to the #interface in your header file if you want it to be exposed to everyone or declare it internally to the implementation file as a category method. To do the final one, add the following to the top of your .m, assuming your class is called WikiDrawsView:
#interface WikiDrawsView (private)
- (NSRect)newBoundsFromBounds:(NSRect) old
forHandle:(int) whichOne
withDelta:(NSPoint) p;
#end
The 'private' is just a name you get to pick, it has no special meaning. Something like 'private' is often used to signify that you're using a category in a similar way that you might use private class member functions in C++ or a language like that.
The quick way to describe categories is that they patch additional methods onto existing classes at runtime, and they use the #interface [classname] ([category name]) syntax, with no member variable section. I'm sure your tutorial will get to them, Apple's documentation on them is here. This is a common use of categories but not the primary use.
To address the "WKDShape may not respond" warning, make sure you declare -newBoundsFromBounds:forHandle:withDelta: before -mouseDragged:. You can add it to the public interface in "WKDShape.h", or in an anonymous category in "WKDShape.m".

How to base class behaviors on typedef enums, the way Apple does in Objective-C?

Kind of a weird newbie question...I want to use typedef enum declarations in one of my classes . This particular class gets used by other classes, where a client class might say something like "set your style to Style1 (enumerated type)". Then the target class's behavior would change accordingly. This is analogous to the way that UITableViewCellStyles are used in the iPhone SDK.
So, I read through a few UIKit framework headers to get a better idea of how Apple was handling the enumerated types. I see that they declare a bunch of enums everywhere, like so...
typedef enum {
UIBarButtonSystemItemDone,
UIBarButtonSystemItemCancel,
UIBarButtonSystemItemEdit,
UIBarButtonSystemItemSave,
UIBarButtonSystemItemAdd,
...
UIBarButtonSystemItemUndo, // available in iPhone 3.0
UIBarButtonSystemItemRedo, // available in iPhone 3.0
} UIBarButtonSystemItem;
...but I don't see any clues in the header on how they actually handle these types (I'm basically trying to see an example of their implementation, so this isn't a suprise). My instinctive thought as a fairly newb programmer would be to match the int values of each type to some behavior/variable stored in an array, plist etc. But also as a newb programmer I expect that everything that I think will be wrong. So I have two questions:
Anybody have a guess as to how Apple itself handles enum type values to change behaviors?
In general for this type of setup, is there a best design practice that everyone knows about or is this just an open-ended scenario?
Simple enums are usually handled in a switch statement:
typedef enum {
eRedStyle,
eGreenStyle,
eBlueStyle
} MyStyle;
#implementation MyClass
- (void)setStyle:(MyStyle)style {
switch (style) {
case eRedStyle :
self.backgroundColor = [UIColor redColor];
break;
case eGreenStyle :
self.backgroundColor = [UIColor greenColor];
break;
case eBlueStyle :
self.backgroundColor = [UIColor blueColor];
break;
default :
NSLog(#"Bad Style: %d", style);
break;
}
}
An enumeration is (almost but not really) an integer, but made easy to read for the programmer that's using it. Given that, you can have routines that check the value of an enum the same way you would an integer:
void DoMyEnumeratedThing(UIBarButtonSystemItem item)
{
if (item == UIBarButtonSystemItemDone)
DoMyItemDoneThing();
else if (item == UIBarButtonSystemItemCancel)
DoMyItemCancelThing();
// ...and so forth and so on.
}
While I don't know the gory details of Apple's OS internals, every enumeration check essentially boils down to something like the above. As for your "best design practice" question, the answer really depends on what you're trying to accomplish with the enumeration. While every use-case is switch-like, sometimes the enumeration is a set of bits in a bitfield that allow the client to toggle one or more of them at the same time (which is not the case in the example you provide).