DEV Community

Cover image for The magic of Behaviors in iOS
Marta Tenés for Playtomic

Posted on

The magic of Behaviors in iOS

What are them

A Behaviour can be understood as an object which is responsible for a specific task. They are made as small components, so they can be used in conjunction with other behaviors and from any desired place.

Why we use them

In iOS it is very common to deal with writing a lot of code for view configuration and control view changes (keyboard, animations, scrollview, etc). Typically, this code would be placed inside view controllers or maybe better in a base view controller with hundred of lines.

However, here at Playtomic we wanted take advantage of Behaviors and write them as separate components which can be reused among different parts of our views.

A bit of code

But first of all let's see how they look like:

@objc
open class Behavior: UIControl {

    @objc convenience init() {
        self.init(frame: CGRect.zero)
    }

    @IBOutlet weak public var owner: AnyObject? {
        willSet (new) {
            if let new: AnyObject = new {
                self.bindLifetimeToObject(new)
            }
            if let owner: AnyObject = owner {
                self.releaseLifetimeFromObject(owner)
            }
        }
    }

    fileprivate func bindLifetimeToObject(_ object: AnyObject) {
        let pointer = Unmanaged.passUnretained(self).toOpaque()
        objc_setAssociatedObject(object, pointer, self, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    fileprivate func releaseLifetimeFromObject(_ object: AnyObject) {
        let pointer = Unmanaged.passUnretained(self).toOpaque()
        objc_setAssociatedObject(object, pointer, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    override open var next: UIResponder? {
        return super.next ?? self.owner as? UIResponder
    }
}

Enter fullscreen mode Exit fullscreen mode

Basically it's an UIControl which holds an owner property. This property could be a view, view controller or any other object and defines the lifetime of the behavior. When we link it to our target object a strong relationship will be created between them.

ImagePickerBehavior

This is a fragment of a custom behavior we implemented for picking images either the gallery or the camera. It extends from our base class behavior and here we handle all the necessary code for managing pictures (permissions, prompting the user, image sizes and resize).

Besides that, we declared some @IBInspectables which allows us to set properties through InterfaceBuilder.

public class ImagePickerBehavior: Behavior, UINavigationControllerDelegate {

    @IBInspectable public var cameraEnabled: Bool = true
    @IBInspectable public var photoLibraryEnabled: Bool = true

    @IBInspectable public var cameraButtonTitle: String = "Camera"
    @IBInspectable public var photoLibraryButtonTitle: String = "Photo Library"
    @IBInspectable public var maxSize: CGSize = CGSize(width: 2048, height: 2048)

    @IBOutlet public var targetImageView: UIImageView?
    public var pickedImage: UIImage?

    public var cameraAvailable: Bool {
        return UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera)
    }

    public var photoLibraryAvailable: Bool {
        return UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary)
    }

    private var callback: ((UIImage?) -> Void)?

    @IBAction public func pickImage(sender: AnyObject?) {
        pickImage(sender: sender, callback: nil)
    }

    public func pickImage(sender: AnyObject?, fromController: UIViewController? = nil, callback: ((UIImage?) -> Void)?) {
        guard let rootController = fromController ?? (owner as? UIViewController) ?? UIApplication.shared.keyWindow?.rootViewController else { return }

        self.pickedImage = nil
        self.callback = callback
        var options: [(title: String, action: (() -> Void))] = []

        if cameraAvailable && cameraEnabled {
            options.append((title: cameraButtonTitle, action: {
                if self.isCameraPermissionDenied() {
                    self.showCameraPermissionDialog()
                } else {
                    self.showPicker(withSourceType: .camera, fromController: rootController)
                }
            }))
        }

        if photoLibraryAvailable && photoLibraryEnabled {
            options.append((title: photoLibraryButtonTitle, action: {
                self.showPicker(withSourceType: .photoLibrary, fromController: rootController)
            }))
        }
....
Enter fullscreen mode Exit fullscreen mode

How we use them

And now a practical example. In the next gift you can see a view controller which allows the user changing his profile picture. We have an image view which will show the picked image and a camera button for open the gallery or camera.

App

The steps needed:

  1. Drag and drop an object into the view controller.
  2. In the class name put ImagePickerBehavior
  3. Connect the outlets.
  • Owner: our view controller to create the strong relationship.
  • TargetImageView: The ImageView showing the picture
  • valueChanged: The target action that needs to be executed once image is returned (logic behind)
  • pickImageWithSender: The action which will fire the behavior. In this case our camera button touch event.

Interface Builder

So simply and clean, right? We didn't need to write any code. Just put an object, configure it using the interface builder and magic!

 Other examples

This was only one example but we have multiple behaviors already implemented for managing keyboard, scrolling, animations, validate fields, etc. As a team we save a lot of time because we don't need to worry about dealing with these long tasks.

Behaviors

Conclusion

So to sum up, we saw that using behaviors give us a lot of advantages:

  • Reduce code: The amount of view configuring code is drastically reduced in your view controllers.
  • Reuse them: Write once, use multiple times. Same behavior through your components and also useful for sharing them across different modules.
  • Isolate: As they are independents of view controllers we can understand them as a black box so they are easy to test.

Magic

Discussion (0)