IB_DESIGNABLE UIView using a custom CALayer (for animations)

I’m a very visual person.  If I can use Interface Builder, I do.  I like it mainly for the separation of concerns, and really who doesn’t like a WYSIWYG way of inspecting your UI ??

So, I had a somewhat unique scenario in that I want to have a UIView that has a custom CALayer that has properties I can animate.  (See here for some background on that, though I will touch on it below:  https://www.objc.io/issues/12-animations/animating-custom-layer-properties/ )

But I also want to be able to configure these properties in Interface Builder via the Inspector pane.

I found a way to do this, although it’s tedious.  In short, you have to define a custom layer, deal with it’s display and animation, and wrap the layer properties on your UIView subclass.

What?  Fine.  Here’s the simplified code to show you how it would work for something like a custom progress bar:

//  HSProgressView.h
//  Created by Stephen O'Connor on 17/02/16.
//  MIT License.

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface HSProgressView : UIView

@property (nonatomic, assign) IBInspectable CGFloat progressValue;  // clamps between 0...1
@property (nonatomic, strong) IBInspectable UIColor *barColor;

- (void)setProgressValue:(CGFloat)progress animated:(BOOL)animated;

@end

And the .m file

//  HSProgressView.m
//  Created by Stephen O'Connor on 17/02/16.
//  MIT License.


#import "HSProgressView.h"

@interface HSProgressView()

+ (NSArray*)customLayerProperties;

@end

// HERE WE DEFINE A LAYER THAT IDEALLY WE'D PREFER TO DO IN
// A UIView SUBCLASS IN drawRect:, BUT TO LEVERAGE THE ANIMATION
// PROPERTIES OF COREANIMATION, WE DEFINE THEM IN A CALayer THEN
// WRAP THEM
@interface _HSProgressViewLayer : CALayer

@property (nonatomic, assign) CGFloat progressValue;
@property (nonatomic, strong) UIColor *barColor;

@end


@implementation _HSProgressViewLayer

// these methods are generated at runtime
@synthesize progressValue, barColor;

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    // HERE WE SAY IF ANY OF THESE PROPERTIES CHANGE,
    // SHOULD IT REDRAW THE LAYER
    
    // I JUST SAY YES FOR ALL OF MY CUSTOM PROPERTIES
    // BUT YOU CAN CONTROL THIS HERE
    for (NSString *propertyName in [HSProgressView customLayerProperties]) {
        if ([key isEqualToString:propertyName]) {
            return YES;
        }
    }
    
    return [super needsDisplayForKey:key];
}


// THIS IS WHERE YOU DEFINE WHICH PROPERTIES CAN BE ANIMATED
// AND HOW.  Duration, timing, etc.
// In our case, we only want to animate progress, but not color
- (id)actionForKey:(NSString *)key
{
    // if (key corresponds to a property name I want to animate...)
    if ([key isEqualToString:@"progressValue"])
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        
        if ([key isEqualToString:@"progressValue"])
        {
            // LOOK HERE!  It's outlined in that post listed above, but
            // if you animate, you need to look to the presentationLayer
            // as it holds the currentValue *right now* or *on screen*
            // if you didn't call presentationLayer, the progress
            // value is going to be the value you are trying to animate to
            // and you would see nothing!
            
            
            animation.fromValue = @([self.presentationLayer progressValue]);
        }
        
        // other animatable properties  here...
        
        return animation;
    }
    return [super actionForKey:key];
}

- (void)display
{
    // again, takes the value that it currently is animating to,
    // if you called self.progress it would always return the final value.
    
    // that being said, if we want to inspect it in interface builder
    // we have to omit animation and just look at the value it 'wants' to be
    CGFloat progress;
#if !TARGET_INTERFACE_BUILDER
    progress = [self.presentationLayer progressValue];
#else
    progress = [self progressValue];
#endif
    
    
    //create drawing context
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
    
    // backgroundColor is a property on CALayer, but we still need to draw him!
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(ctx, self.backgroundColor);
    CGContextFillRect(ctx, self.bounds);

    
    // now for our custom properties
    UIBezierPath *path;
    
    CGRect drawRect = self.bounds;
    drawRect.size.width = MAX(0, MIN(1, progress)) * drawRect.size.width;
    
    path = [UIBezierPath bezierPathWithRect:drawRect];
    [self.barColor setFill];
    [path fill];
    
    //set backing image
    self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage;
    UIGraphicsEndImageContext();
}

@end


static NSArray *HSProgressViewLayerProperties = nil;

@implementation HSProgressView

+ (Class)layerClass
{
    return [_HSProgressViewLayer class];
}
    
+ (NSArray*)customLayerProperties
{
    if (!HSProgressViewLayerProperties) {
        
        HSProgressViewLayerProperties = @[
                                          @"barColor",
                                          @"progressValue"
                                          ];
    }
    return HSProgressViewLayerProperties;
}

#pragma mark - Generic Accessors

// we wrap the properties on the layer, so we override this
// for integration into Interface Builder's IBInspectable properties

- (void)setValue:(id)value forKey:(NSString *)key
{
    for (NSString *propertyName in [HSProgressView customLayerProperties]) {
        if ([key isEqualToString:propertyName]) {
            
            [self.layer setValue:value forKey:key];
            
            return;
        }
    }
    [super setValue:value forKey:key];
}

- (id)valueForKey:(NSString *)key
{
    for (NSString *propertyName in [HSProgressView customLayerProperties]) {
        if ([key isEqualToString:propertyName]) {
            return [self.layer valueForKey:key];
        }
    }
    return [super valueForKey:key];
}

#pragma mark - Public Methods

- (void)setProgressValue:(CGFloat)progress animated:(BOOL)animated
{
    // if animated is yes, it gets it's information about how to
    // animate it via actionForKey: on the custom layer implementation!
    
    if (!animated) {
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
    }
    
    _HSProgressViewLayer *layer = (_HSProgressViewLayer*)self.layer;
    layer.progressValue = progress;
    
    if (!animated) {
        [CATransaction commit];
    }
}

#pragma mark - Layer-backed Accessors

// SADLY WE HAVE TO WRITE A LOT OF BORING CODE HERE TO WRAP THE ACCESSORS.
// IF SOMEONE KNOWS BETTER, PLEASE TELL!
- (void)setProgressValue:(CGFloat)progressValue
{
    [self setProgressValue:progressValue animated:NO];
}

- (void)setBarColor:(UIColor *)barColor
{
    _HSProgressViewLayer *layer = (_HSProgressViewLayer*)self.layer;
    layer.barColor = barColor;
}

- (UIColor*)barColor
{
    _HSProgressViewLayer *layer = (_HSProgressViewLayer*)self.layer;
    return layer.barColor;
}

- (CGFloat)progressValue
{
    _HSProgressViewLayer *layer = (_HSProgressViewLayer*)self.layer;
    return layer.progressValue;
}


#pragma mark - Layout and Drawing

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.layer.bounds = self.bounds;
    [self.layer setNeedsDisplay];  // because the layer follows the size of the view, whose contents fill the view's frame.
}

@end

There it is! Copy-paste this into your own class and try it out in Interface Builder. Now you should see how you can extend this for your own purposes.

Proportionally Sized Views, Views that inherit backgroundColor

So, I’m a bit of a late adopter. The problem with working intensively on a project that is grossly understaffed is that you don’t have time to keep up with all the technology. So just now am I getting a deeper understanding of Autolayout on iOS.

There is no way to avoid using Autolayout, so if you haven’t started using it, please do! The various screen sizes of the various iPhones, not to mention multi-tasking on iPad will basically demand you use it.

I’ve run into a situation where I might design some elements for iPhone 5s, but on iPhone 6 they could be bigger. I don’t like the complexity of Size classes, and don’t even think they work very well for the different iPhones. I think it was a way to make one layout work on iPhone and iPad. Hmm… the topic here.

On my latest project I found two things that I needed. So I built them. I wanted a UI Element (a UIView) to always maintain a certain proportion of its superview. Say 75% of the screen width. I didn’t know how to do this in Autolayout. (I suspect it might have something to do with Multipliers??)

Anyway, I also wanted to be able to define a view that would just inherit the background color of its superview. This makes it easier to make changes later in Interface Builder without having to go and alter the whole view hierarchy by hand, or to make outlets for all those views and change them in code.

Well, try for yourself. Here’s the code:

// HSProportionallySizedView.h
//
// Created by Stephen O'Connor on 30/09/15.
// Copyright © 2015 Iconoclasm Spasms. All rights reserved.
// Actually, MIT License. Hack to your heart's content!
  
#import <UIKit/UIKit.h>
  
extern CGFloat const HSProportionallySizedViewNoEffect;
  
@interface HSProportionallySizedView : UIView
  
@property (nonatomic, assign) IBInspectable BOOL inheritsBGColor;  // default: NO
  
// between 0...1.f. Defaults to HSProportionallySizedViewNoEffect
@property (nonatomic, assign) IBInspectable CGFloat widthProportion;
@property (nonatomic, assign) IBInspectable CGFloat heightProportion;
  
@end
//  HSProportionallySizedView.m
//
//  Created by Stephen O'Connor on 30/09/15.
//  Copyright © 2015 Iconoclasm Spasms. All rights reserved.
//  Actually, MIT License.  Hack to your heart's content!

#import "HSProportionallySizedView.h"

CGFloat const HSProportionallySizedViewNoEffect = -1.f;

@implementation HSProportionallySizedView

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    if (self.inheritsBGColor) {
        self.backgroundColor = newSuperview.backgroundColor;
    }
}

- (void)awakeFromNib
{
    if (self.inheritsBGColor && self.superview) {
        self.backgroundColor = self.superview.backgroundColor;
    }
}

- (void)makeDefaultValues
{
    _inheritsBGColor = NO;
    _widthProportion = HSProportionallySizedViewNoEffect;
    _heightProportion = HSProportionallySizedViewNoEffect;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self makeDefaultValues];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self makeDefaultValues];
    }
    return self;
}

- (CGSize)intrinsicContentSize
{
    CGSize size = [super intrinsicContentSize];
    if (self.superview) {
        
        if (_widthProportion != HSProportionallySizedViewNoEffect) {
            
            CGFloat proportionalWidth = MAX(0, MIN(1, _widthProportion));
            
            if (proportionalWidth == 0) {
                NSLog(@"One of your proportional size values is set to 0!!");
            }
            size.width = proportionalWidth * self.superview.bounds.size.width;
        }
        
        if (_heightProportion != HSProportionallySizedViewNoEffect) {
            
            CGFloat proportionalHeight = MAX(0, MIN(1, _heightProportion));
            if (proportionalHeight == 0) {
                NSLog(@"One of your proportional size values is set to 0!!");
            }
            size.height = proportionalHeight * self.superview.bounds.size.height;
        }
        
    }
    return size;
}
  
@end

You can see that with the default values it behaves exactly like a UIView. So it makes a suitable baseclass in your codebase.

NOTE: I’m sure there’s a way to do the proportional size in Autolayout without this, so please help me in the comments!

Custom UITableViewHeaderFooterView with Interface Builder

So, one area that Apple needs to tighten up as it’s not clear and the Internet is also a bit divided on is setting up UITableViewHeaderFooterView objects in Interface Builder. Now, sadly you can’t do this in a table view in a Storyboard much like you’d design a prototype cell. No, but there are still ways to visually design these and write minimal code.

So you will have to subclass UITableViewHeaderFooterView and override -(UIView*)contentView. That’s the jist of it. You probably have to subclass it anyway because you have custom content!

@implementation MySettingsHeaderView
- (UIView*)contentView
{
  return self.subviews[0];
}
@end

And then create a View NIB file, where the root view’s class is set to this above. The trick is that the first subview is going to be the contentView of the UITableViewHeaderFooterView, but strangely it is a readonly property. So that’s why you need to override.

Custom UITableViewHeaderFooterView in IB

So treat this view as the contentView and add everything to that. This way you can preserve AutoLayout stuff and avoid warnings like:

Setting the background color on UITableViewHeaderFooterView has been deprecated. Please use contentView.backgroundColor instead

Then, all you do is in your viewDidLoad method, you put something like:

[self.tableView registerNib:[UINib nibWithNibName:@"MySettingsHeaderView" bundle:nil] forHeaderFooterViewReuseIdentifier:@"MyHeaderIdentifier"]; // though, be a good coder, and don't pass a hard-coded string, but define a constant for your header identifier!!

Boom! I love you too.

How to center a UIPopoverController and also without arrows

Short recipe here. This blog isn’t always about you… it’s about augmenting my memory! 😀


  UIViewController *contentController = [[UIViewController alloc] init]; // obviously your own...
    
  UIPopoverArrowDirection arrows;
  CGRect rectForWindow;
  BOOL centeredInWindow = YES;
    
  if (centeredInWindow) {
    rectForWindow = CGRectMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2, 1, 1);
    arrows = 0;
  }
  else
  {
    // view being some view like a UIButton, that triggered the popup...
    rectForWindow = [self.view convertRect:view.frame fromView: view.superview];  // or whatever...
    arrows = UIPopoverArrowDirectionDown;  // or whatever...
  }

  self.popover = [[UIPopoverController alloc] initWithContentViewController:contentController];
  self.popover.delegate = self;
  
  // set your contentSize
  CGSize contentSize = (CGSize){320, 460};
  [self.popover setPopoverContentSize:contentSize];
  
  [self.popover presentPopoverFromRect:rectForWindow 
                                inView:self.view 
              permittedArrowDirections:arrows 
                              animated:YES];

UITableView or UICollectionView Lazy Registration – dispatch_once

(UPDATE:  Don’t do this.  See discussion at the end)

Starting with iOS 6, UIKit added the following family of methods to UITableView and UICollectionView:

// UICollectionView
- (void)registerClass:(Class)aClass forCellWithReuseIdentifier:(NSString*)someIdentifier;

// UITableView  
- (void)registerClass:(Class)aClass forCellReuseIdentifier:(NSString*)someIdentifier;

Which makes it convenient to get a cell when you need one, without too much extra code. Doing it this way also ensures that you are designing your custom cells in their own subclass, rather than adding subviews to a view in a controller class. (I really like how Apple tries to make you become a better programmer by forcing you to write cleaner code and separate your concerns. See Why I DO use Interface Builder for more on that).

What I find sometimes annoying is forgetting to register this class somewhere (likely viewDidLoad), and it crashes at runtime. I also hate having to jump around code too much if it makes it less readable, so lately I’ve been using the following approach, which takes advantage of the pattern used when creating singletons; use the dispatch_once approach:

// in tableView:cellForRowAtIndexPath:

    static NSString *CellIdentifier = @"MyCell";
    static dispatch_once_t myCellOnceToken;  // define a variable name here that is specific to the context
    dispatch_once(&myCellOnceToken, ^{
        [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
    });

Maybe this approach will work well for you as well. Currently I’m not aware of the potential dangers of this, if there are any. Have to admit my knowledge of gcd up until this point has had a lot to do with recipes found on stack overflow.

UPDATE – There are dangers associated with this, so DON’T DO THIS. If you execute a dispatch_once block, but your collectionView is dealloc’d for whatever reason, the next time the newly created _different_ collectionView needs a cell, it won’t be registered.

SO, Moral of the Story:  Don’t forget to register your classes/nibs.  And do that in viewDidLoad, or somewhere right after you UICollectionView or UITableView is created.

Avoid UIViewController abuse – separate view and model code

We’ve all been there.  UIViewController implementation.  A massive viewDidLoad implementation.  Methods that also do similar things to viewDidLoad once data from a webservice changes.  This sort of thing.

I’m gonna keep this post short and sweet.  I made a sort of template UIViewController implementation that you should take a look at over on my github page here.

I felt inclined to write this because there are design patterns in iOS development that involve Apple’s best practices, but Apple’s best practices avoid getting too off topic; Their best practices for UIViewControllers focus a lot on the View and Controller, but iOS is typically a MODEL-View-Controller paradigm.

So, I created a template for how I write my UIViewController subclasses (In general… every project is different).  But let’s assume you have a principal data model your view controller is managing.  This data model may have a collection of objects associated with it that is used for a table/collection view.

The pattern I came up with below I think does a pretty good job of keeping the models to themselves, and the views to themselves, and knows when the view needs to refresh itself based on the state of the model.

Check it out.  Would appreciate some feedback!

Cocos2D and UIKit working together

I have been refactoring BocceBeers lately, and as a fan of tools such as Interface Builder, I didn’t have the time to really learn the ins and outs of CocosBuilder, and I already had working gameplay code, so I wouldn’t really need to rebuild the game in CocosBuilder because that would be a make-work project.

What I did need to do is make User Interfaces for the iPad version of the game that I am making.  So I thought, now that I’m an experienced App Developer, can I not just use UIKit and make NIB files?

The answer is yes.  I decided to go about making a general solution (demo project) for any of  you seeking to use UIKit for menu systems, but then leverage the power of Cocos2D for your gameplay.

I also created an asynchronous scene loading class which you can check out as well.

Please check out the demo project on github here.