Fran Fernandez

iOS UI development, ludicrously fast.

One of the biggest complaints you can find between iOS developers is that sometimes, the feedback loop you have when you are doing some fine-tuning of the UI is long. It produces a new Swift compilation, creates a new binary, then the app gets deployed to the simulator, once that's done, the system will start the app, and then you will be able to navigate to the right screen to check if those small changes are ok.

Sounds familiar?

Meanwhile, you can see your colleges developing websites or React Native apps that they get feedback almost immediately, and they can even modify the UI with a designer in "real-time."

Feel any envy yet?

What if I tell you that in iOS, we can achieve something similar to that with almost no effort? Would you believe me?

In Karumi, we've been playing a bit with a tool that will help us to hot-swap our code so we can shrink our feedback loop duration to just a couple of seconds.

Injection III to the rescue.

This tool created by John Holdsworth will be the one doing all the magic under the hood; it will be checking which files are being changed, compile them, and dynamically insert that code in your running app. Let's create a tiny example so we can test how this works.

First, we need to install this app from the AppStore and run it.

HotSwapp.

So, let's create a brand new iOS project, a "Single View App" with Swift and Storyboards, nothing fancy.

Although Injection III has no issues with Storyboards and xibs, I do ( 😬 ), so let's get rid of the main storyboard and modify our ViewController to contain a label center on the screen.

Now it's time to setup Injection III to be able to edit our label in real-time.

First, we have to load a bundle into our app that will help us with the method swizzling. In our AppDelegate we will add the following lines:

func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         ...
    #if DEBUG
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
     #endif
        ...
    return true
}

then, once the Injection III app is running, we have to open our project in it so it can track any changes in our files.

Re-run your app, and you should see something like:

πŸ’‰ Injection connected πŸ‘
πŸ’‰ Watching /Users/Fran/Development/HotSwapp/**

That means that your app is ready to handle code injection at runtime, let's give it a go. Without stopping the app, change the label text color and save it.

Your Xcode output will display something like:

πŸ’‰ *** Compiling /Users/Fran/Development/HotSwapp/HotSwapp/ViewController.swift ***
πŸ’‰ Loading .dylib ...
objc[7606]: Class _TtC8HotSwapp14ViewController is implemented in both /Users/Fran/Library/Developer/CoreSimulator/Devices/5625C398-4862-4C4E-A5AF-E51A78D1113F/data/Containers/Bundle/Application/BDCBDDA4-A09B-4D72-8048-C24FF1193279/HotSwapp.app/HotSwapp (0x101d72a10) and /Users/Fran/Library/Containers/com.johnholdsworth.InjectionIII/Data/eval101.dylib (0x104b19278). One of the two will be used. Which one is undefined.
πŸ’‰ Loaded .dylib - Ignore any duplicate class warning ^
πŸ’‰ Injected 'ViewController'

But your app label keeps having the same color, what's going on?

Your app has a new code, but you have to respond to that change. Create a new file, called it UIViewController+HotSwap.swift and add this content:

import UIKit

#if DEBUG
    extension UIViewController {
        @objc func injected() {
            for subview in view.subviews {
                subview.removeFromSuperview()
            }
            if let sublayers = self.view.layer.sublayers {
                for sublayer in sublayers {
                    sublayer.removeFromSuperlayer()
                }
            }

            viewDidLoad()
        }
    }
#endif

What this method is doing is removing all views and layers from your view controller and will invoke the viewDidLoad method again. Stop your app, and rerun it, now it will show the new color.

Now, change the label color and save it.

πŸŽ‰πŸŽ‰πŸŽ‰

Without stopping it, replace one of the constraints to adjust the label to the left border. Once you save it, in one second, the UI will be reflecting that change.

Now imagine that instead of this app, how this could improve your development speed in some scenarios with a big app, with thousands of lines of code with a lot of third parties dependencies.

Our experience so far.

After using this for a few weeks, we would like to share with you what we've learned about this so you can get up to speed without making the same mistakes we did.

  • Check the documentation from the project.
  • This does not work well when there are new methods or properties; in those cases, rebuild the whole app. What we do is define upfront the methods we are going to need, and then we fill them with the logic.
  • Do not create your views outside the viewDidLoad method; otherwise after the first run, the UI could end up in an invalid state. Keep this in mind, viewDidLoad should init every view and their constraints.
  • You cannot use this on a real device.
  • Code swapping does not get along code coverage, so we recommend you to keep a different scheme for the usage of Injection III while you are fine-tuning your UI.

References


Photo by Jean Gerber on Unsplash