Article Image
Article Image
read

iOS Behaviours

Behaviour, as definiton, the way in which a machine or natural phenomenon works or functions. Behaviours in programming can be expressed as an object which is responsible for a specific task. In this article, I am going to explain how to use behaviours in Interface Builder, how to extract functionality from view controller implementation and reuse the code anywhere else.

Simple Example

A behaviour can be simple any function you have, which you want to separate from the basic application logic. For instance picking an image from Library and Camera can be a behaviour. A transition effect can be a behaviour. We will create an example which demonstrates a toggle button behaviour. Here are the requirements:

  • Our Toggle behaviour needs two states: Active - Passive, Selected - Nonselected etc.
  • We want to specify two different button images for these two states respectively.
  • Our behaviour should detect the touch events and must change the button image according to the buttons current state.
  • This behaviour will be available only through Interface Builder.

Let’s start creating an empty behaviour in Interface Builder. We assume we are using storyboards, but indeed, there is no difference between .xib files and a storyboard within the aspect of behaviors. It is almost same to use behaviours in both storyboards and .xib files. I picked storyboards for this article.

We have our Main.storyboard file open, expanded the default scene which is ViewControllerScene. At this point we are going to add an Object component to our scene, just like we are adding a UILabel or UIButton.

Object Component

Here is how our ViewControllerScene now looks

Now it is time to create our behaviour. First we will create a generic Behaviour object, which is responsible to cover all common functions a behaviour should have.


@interface Behaviour : NSObject

//! object that this controller life will be bound to
@property(nonatomic, weak) IBOutlet id owner;

@end

Interface is really simple, but the owner property has a meaning deeper than it seems. The property owner actually defines the lifetime of the behaviour, which can be a view, view controller or any other object in the scene.

Once we link the owner property in the ViewControllerScene , we are actually creating a property at runtime on the linked object (view or view controller in our case), and have a strong relationship between the linked object and our behaviour. Let the code express itself:


@implementation Behaviour

- (void)setOwner:(id)owner
{
    if (_owner != owner) {
        [self releaseLifetimeFromObject:_owner];
        _owner = owner;
        [self bindLifetimeToObject:_owner];
    }
}

- (void)bindLifetimeToObject:(id)object
{
    objc_setAssociatedObject(object, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)releaseLifetimeFromObject:(id)object
{
    objc_setAssociatedObject(object, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

This is actually the same as we have a property in our view controller:

#import "Behaviour.h"

@interface ViewController : UIViewController

@property (nonatomic,weak) IBOutlet Behaviour *behaviour;

@end

Instead, we are doing this at runtime, because we do not want to messup with the code in view controller. We just want to manage things in Interface Builder. This approach is called Reverse Lifetime Binding. For more detail on this approach, you can read the article from Krzysztof Zabłocki

Next, we are going to link the owner property of our behaviour.

In order to test the lifecycle of the behaviour, we add log for -awakeFromNib and -dealloc methods of our behaviour and present/dismiss the linked view controller:

@implementation Behaviour
.
.
.
-(void)awakeFromNib
{
    [super awakeFromNib];
    
    NSLog(@"Created %@",NSStringFromClass([self class]));
}

-(void)dealloc
{
    NSLog(@"Dealloc %@",NSStringFromClass([self class]));
}

@end

Logs shown that we successfully allocate and release the behaviour object:

2016-05-30 14:30:18.616 Behaviours[2315:2113885] Created Behaviour
2016-05-30 14:30:19.121 Behaviours[2315:2113885] View Controller presented
2016-05-30 14:30:21.122 Behaviours[2315:2113885] View Controller dismissed
2016-05-30 14:30:21.122 Behaviours[2315:2113885] Dealloc Behaviour

Toggle Button Behaviour

As we already have a generic behaviour implementation, we start adding our Toggle behaviour. First create a class named ToggleButtonBehaviour, subclass of the Behaviour.

@interface ToggleButtonBehaviour : Behaviour

/**
 *  The button, which the behaviour will be applied to
 */
@property(nonatomic, weak) IBOutlet UIButton *button;

/**
 *  UIImage reference for normal state
 */
@property (nonatomic,strong) IBInspectable UIImage *normalImage;


/**
 *  UIImage reference for selected state
 */
@property (nonatomic,strong) IBInspectable UIImage *selectedImage;


/**
 *  Toggle state for the button
 */
@property (nonatomic,assign) BOOL toggled;

@end

Next, we will link these IBOutlets:

Notice that we specified two UIImage references with the help of IBInspectable, which are displayed according to the toggled state of the behaviour.

Here is the implementation of ToggleButtonBehaviour :

@implementation ToggleButtonBehaviour

-(void)setButton:(UIButton *)button
{
    _button = button;
    [self configureButton:button];
}


-(void)configureButton:(UIButton *)button
{
    // Middleware actions
    [button removeTarget:self   action:@selector(buttonTap:) forControlEvents:UIControlEventTouchUpInside];
    [button addTarget:self      action:@selector(buttonTap:) forControlEvents:UIControlEventTouchUpInside];
    
    self.toggled = NO;
    [self reverseImages:button];
}

-(void)buttonTap:(UIButton *)button
{
    self.toggled = !self.toggled;
    [self reverseImages:button];
}

-(void)reverseImages:(UIButton *)button
{
    if (!self.toggled) {
        [button setImage:self.normalImage       forState:UIControlStateNormal];
    }
    else{
        [button setImage:self.selectedImage     forState:UIControlStateNormal];
    }
}

@end

Let’s explain it step by step.

  • Once the link between behaviour and its button IBOutlet, we call -configureButton: method, to add a middleware action for button, and set the initial image.

  • Everytime the button is tapped, we change the state toggled, and refresh the button’s image due to the toggled.

Here is the result:

Go ahead and change the images, or move the behaviour to another scene, link to another UIButton. As long as you properly linked the behaviour, it works exactly as you defined.

Let’s make things little bit fancy. We add another property for our behaviour, animated, which is also Inspectable, controls whether the change the images of button displayed with a fade-in, fade-out animation.

We add the animated property:

@interface ToggleButtonBehaviour : Behaviour
.
.
.

@property (nonatomic,assign) IBInspectable BOOL animated;

@end

Now we have the UI in Interface Builder for this flag:

And modify the -reverseImages: as follows:

@interface ToggleButtonBehaviour : Behaviour
.
.
.

-(void)reverseImages:(UIButton *)button
{
    if (self.animated) {
        [self reverseAnimatedImages:button];
    }
    else
    {
        UIImage *targetImage = !self.toggled ? self.normalImage : self.selectedImage;
        [button setImage:targetImage forState:UIControlStateNormal];
    }
}

-(void)reverseAnimatedImages:(UIButton *)button
{
    UIImage *targetImage = !self.toggled ? self.normalImage : self.selectedImage;
    
    [UIView animateWithDuration:0.7 animations:^{
        button.alpha = 0.0;
    } completion:^(BOOL finished) {
       
        [button setImage:targetImage forState:UIControlStateNormal];
        
        [UIView animateWithDuration:0.7 animations:^{
            button.alpha = 1.0;
        } completion:^(BOOL finished) {
            
            button.alpha = 1.0;
        }];
    }];
}

@end

When the animated property is false, images will swapped as before. If it is true, there will be a fade-in, fade-out animation during the change:

Why Behaviours

You do not have to use behaviours everywhere, but there are certainly good reasons to use them.

First, we will split up the code from view controller. As we are opposed to Massive View Controller, we should decouple and reuse as much code as we can. When you get used to have behaviours in your view controller, you end up with lighter view controllers.

You can reuse the behaviour you create. In the above example, we can copy that behaviour in any scene, link the button and owner property, and go on. Moreover, you can easily extend the behaviour to add another functionality, swap the behaviours between scenes, even concatenate them. This pattern gives us more flexibility and reusability.

Because your behaviour code not attached to any other code, you can easily write unit tests for it. It is very hard when compared the code in the view controller.

For more example of behaviours, please read the detailed article.

Blog Logo

Selim Bakdemir


Published

Image

Selim Bakdemir

iOS Developer - Peak Games

Back to Overview