What is the recommended method of styling an iOS app?
Asked Answered
C

7

23

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?

Chorister answered 22/8, 2011 at 18:6 Comment(1)
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. github.com/charithnidarsha/MultiThemeManagerCableway
D
9

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.

Disario answered 22/8, 2011 at 18:12 Comment(4)
i won't downvote it but... if one chooses to go that route, external declarations such as: extern const CGFloat MONDarkThemeFontSize; and extern NSString* const MONDarkThemeFontName; are preferable over #define kShortName value for multiple reasons.Mafia
I would prefer as I think this is the easiest way and well suited for the app i m working on. Thanks Alex and @Justin.Chorister
@DexterW which one would you prefer?Chorister
It's best to use the tools you already have available... Interface builder.Footlights
M
9

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
Mafia answered 22/8, 2011 at 20:22 Comment(3)
I like this answer, although it's pretty old. How applicable is this now we have UIAppearance API?Mitchel
@Mitchel i have updated my answer. there are 105 occurrences of UI_APPEARANCE_SELECTOR in iOS7 -- there's enough control for most apps' needs. if you need more granularity or control of memory, attributes, styles, or applier functionalities (really, i was just getting started with the possibilities there), then the approach above can offer you all the control available. i also like it because it is neither necessarily global nor intrusive. i see the approaches as similar, but UIAppearance leans towards convenience, whereas mine leans towards control. also, i tend to write UIs…Mafia
@Mitchel …programmatically, so this tends to be a recurring issue with high locality (it's easy to apply and push themes around when all the details exist in the same place). it becomes more difficult when details exist in multiple places -- when you incorporate storyboards, XIBs, and some code. iow, it will be less convenient to introduce and apply these programmatic themes when things like initialization are moved from source to SB/XIB loading. in that case, you might override just to apply themes.Mafia
F
6

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.

Footlights answered 22/8, 2011 at 20:4 Comment(2)
+1. Just make separate nibs with different styles and reload views.Swords
Still I encounter apps you have to manage or design UI via Objective-C. It is good to know how to do it.Convoluted
R
3

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;
}
Rude answered 22/8, 2011 at 20:4 Comment(0)
R
3

You can use a third-party abstraction of UIAppearance:

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.

Rowell answered 3/6, 2013 at 5:49 Comment(0)
F
1

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.

Fi answered 22/8, 2011 at 21:20 Comment(0)
C
0

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

Cableway answered 31/5, 2014 at 10:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.