Personal Project Preferences for IOS

It’s Personal

How developers setup their projects is a very personal subject and can invoke extreme emotion if ever critized. While I’m pretty set in my ways as to how I like to setup my iOS projects, I know that I’ve gotten to my preferred setup because of others so I would like to share mine.

Workspaces and Projects

When starting a new iOS project I always create a new workspace. Most of my projects are not just a singular project and workspaces provide the best relationship management, whether it is a static library, framework or related.

Filesystem

Since I edit the classes in the Xcode and do not access them via the file system I don’t create any additional folders or hierarchy inside the project(s). It doesn’t make seems to me. Any class organization I handle with groups.

With the addition of asset folders in Xcode, image assets are now kept within the project as well. The file system project folders are split between the various “source” projects, configurations, external libraries and misc dependencies.

Configurations

It is a huge pet peeve when I see code like this:

NSString *serverURL = nil;
#if DEBUG
serverURL = @"https://path.to.dev/";
#else
serverURL = @"https://path.to.prod/";
#endif

These type of environment key/values shouldn’t be determined by preprocessor macros inside of your app. Even logging can/should be handled by a dedicated class. Since my projects usually have three build configurations, Debug, Adhoc and Release, I’ll create three plists: ConfigDevelopment, ConfigAdhoc and ConfigRelease.

You can create as many or few as you want depending on your setup

I don’t need all three of these plists in the app’s bundle, only the appropriate one which corresponds to my current build configuration. This is handled by a build script

#!/bin/sh

PATH_TO_CONFIGURATIONS="$SRCROOT/../../Configurations"
ConfigDevelopment="$PATH_TO_CONFIGURATIONS/ConfigDevelopment.plist"
ConfigRelease="$PATH_TO_CONFIGURATIONS/ConfigRelease.plist"
ConfigAdhoc="$PATH_TO_CONFIGURATIONS/ConfigAdhoc.plist"

echo "Checking for file $ConfigDevelopment, $ConfigRelease, $ConfigAdhoc..."

if [ $CONFIGURATION == "Debug" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigDevelopment" ]; then
cp "$ConfigDevelopment" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigDevelopment not found"
exit 1
fi
fi

if [ $CONFIGURATION == "Adhoc" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigAdhoc" ]; then
cp "$ConfigAdhoc" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigAdhoc not found"
exit 1
fi
fi

if [ $CONFIGURATION == "Release" ]; then
echo "Processing $CONFIGURATION"
if [ -f "$ConfigRelease" ]; then
cp "$ConfigRelease" "$PATH_TO_CONFIGURATIONS/Config.plist"
else
echo "$ConfigRelease not found"
exit 1
fi
fi

if [ -f "$PATH_TO_CONFIGURATIONS/Config.plist" ]; then
echo "Processing $CONFIGURATION"
echo "Found Config.plist, and copying to $TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH"
cp "$PATH_TO_CONFIGURATIONS/Config.plist" "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/"
else
echo "Did not find Config.plist"
exit 1
fi

In my projects I create a simple NSObject subclass, AppConfig which loads the Config.plist from the bundle and sets the values from the plist’s dictionary. Though I haven’t done it yet, you could “automate” this process further by using some Objective-C runtime to dynamically create and set the properties.

WIth this setup whatever setting, URL, path, etc. I can grab by calling [AppConfig defaultConfig] without having to maintain or deal with unnecessary runtime settings.

Subclassing

I spend a lot of time and energy evaluating DRY in my code. This isn’t only applied to abstraction, but inheritenance. View controllers and views (standard UIView subclasses, UILabels, UIButtons, etc) will share some basic default attributes that I only want to set once, and by design, modify once to be applied to all. You can accomplish a lot on the UI side of things by using UIAppearance, which I do utilize, but anything outside of that supported by the protocol I have a base class.

For example, this is what my base UIViewController would look like.

- (instancetype)init {
    
    self = [super init];
    
    if (self) {

        self.edgesForExtendedLayout           = UIRectEdgeNone;
        self.extendedLayoutIncludesOpaqueBars = YES;
    }
    
    return self;
}

// MARK: View Life Cycle

- (void)loadView {
    
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.backgroundColor = [UIColor whiteColor];
}

There isn’t much to look at, but in a fairly large application having type/copy/paste that becomes tedious and unnecessary. Now I just have to subclass this base controller.

In the case of UIView and UIControl subclasses, I almost exclusively use AutoLayout and I don’t use Storyboards or XIBs. I create a base AutoLayout view and button class and set the AutoLayout “flag”. Labels are given some extra love by adding an additional property so that I can handle multiline and edgeInsets.

// MARK: Initializers

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    
    if (self) {
        
        _edgeInsets = UIEdgeInsetsZero;
        
        self.translatesAutoresizingMaskIntoConstraints = NO;
        self.textColor       = [UIColor whiteColor];
        self.backgroundColor = [UIColor clearColor];
        self.font            = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    }
    
    return self;
}

- (instancetype)init {
    return [self initWithFrame:CGRectZero];
}

/**
 * @discussion
 * In order for autolayout (or sizeThatFits) to correctly calculate the size of the
 * label when using edgeinsets then we must adjust the rect accordingly.
 *
 * If not then you can run into the situation where multiline text is cut off.
 * This happens when calculating dynamic size height cells in UICollectionView's.
 */

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {

    UIEdgeInsets insets = self.edgeInsets;
    CGRect rect = [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, insets)
                    limitedToNumberOfLines:numberOfLines];
    
    rect.origin.x    -= insets.left;
    rect.origin.y    -= insets.top;
    rect.size.width  += (insets.left + insets.right);
    rect.size.height += (insets.top + insets.bottom);
    
    return rect;
}

- (void)drawTextInRect:(CGRect)rect
{
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}

MVCUC,e (Models, Views, Controllers, Utilies, Categories, etc)

I like to take the abstraction of responsibility a bit further using my own Foundation framework project added to the workspace that holds all the models, UIView parent classes, utilies and categories. The app project itself is mainly view controllers, static assets and config files. This type of setup wasn’t as necessary early on in iOS development, but as devices and functionality (Apple Watch, separate iPad and/or Mac apps and extensions) are added it has been extremely beneficial to keep these separate.

Daniel Kennet’s article talks about the benefits of taking a similar approach.

Basically, if it doesn’t involve UI or application state, it goes in a separate framework. My iPod-scanning app Music Rescue contained a complete framework for reading an iPod’s database. My pet record-keeping app Clarus contained one to work with the application’s document format.

Dependencies

This is probably the most debatible preference I have. I do not like CocoaPods. I prefer to use git submodules. I admire and respect the work that has gone into CocoaPods and there are certainly benefits to using it, but it has caused more headaches and is one more external dependency that I have to work with. Ruby (I know it is installed by default), messing with pod files, the XCode project modifications to name a few. Git submodules aren’t without their issues, but you have complete visbility into what is going on. I don’t feel that way about CocoaPods. I’ve used it on a few projects at different points over the past year and it just annoys me.

Networking

My network stack is homegrown solution, which consists of a Client, Request and Session class. The client has paginator property.


------------------------------------------------- =====================
Client                                              paginator
------------------------------------------------- =====================
------------------------------------------------
Request
------------------------------------------------
------------------------------------------------
Session
------------------------------------------------

The only class and methods that are accessed directly by the app is the Client instance which calls down the Request class (handles get, put, post and delete verbs) which in turns calls down the Session (wrapper around NSURLSessions).

The client has a property called paginator which allows the Client instance to call nextPage or previousPage for request pagination.

@property (nonatomic, weak) RTVPaginator *paginator;

XIB’s, Storyboards and Views

Unless it is a requirement I DO NOT use XIB’s or Storyboards. Over the course my iOS career, when I did use them, it became yet another asset to manage and I found myself, more times than not, doing all of my customizations programmatically and never really touched the XIB’s after their initial creation so I just quite using them all together. With the addition of AutoLayout in iOS6 all of the initial tutorials and examples used XIB’s so I gave it another try. To me it was MORE frustrating dealing with AutoLayout using IB so I started doing all of the setup programmatically and haven’t had any issues.

XCode Build Settings

Before I add/modify in any project I’ll create target specific xcconfig files. One shared and specific ones for the various build settings. Tweaking specific settings becomes much more manageable that way, in addition, to see change sets via diff tools. Viewing the project files in any diff viewer is a horrible experience.

The Way

The Right Way, The Wrong Way and Your Way

There is no really wrong to setup or maintain your project. If you are developing and shipping apps then your setup is correct. Over the past 7 years certain trends have emerged as common expectations, but I have yet to see an official recommendations or guidelines from Apple so I’ll (and you) should keep doing it your way.

I would love to hear more of how you manage your project setup.