Episode #505

Creating Standalone Feature Modules

Series: Modular Project Architecture

17 minutes
Published on September 24, 2021

This video is only available to subscribers. Get access to this video and 572 others.

One big benefit of modularizing an application is working on features as modules. Let's re-create the welcome screen experience as a feature module using Swift Package Manager and Xcodegen.

One big benefit of modularizing an application is working on features as modules.

Let's re-create the welcome screen experience as a feature module.

We'll go to the Modules folder and create a new folder called WelcomeFeature.

We want to develop this as a Swift package, but we also want a standalone application that we can use as a test bed to do our work. This will allow us to build and run straight to the screen we are working in and simulate various environments or situations that may be difficult to do in normal use.

Let's start with our package:

swift package init --type=library

this will create the Package.swift file as well as Sources and Tests.

Next let's create an example project that uses this with Xcodegen.

vim project.yml
name: WelcomeFeature
options:
  bundleIdPrefix: com.ficklebits.dinnertime
packages:
  WelcomeFeature:
    path: .
targets:
  ExampleApp:
    type: application
    platform: iOS
    deploymentTarget: 14.0
    sources: [ExampleApp]
    dependencies:
      - package: WelcomeFeature

Now running xcodegen we can generate our project and open it.

Let's paste in the standard bare-bones content into AppDelegate.swift:

import UIKit

@UIApplicationMain
final class AppDelegate: NSObject, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()

        let vc = UIViewController()
        vc.view.backgroundColor = .black        
        window?.rootViewController = vc

        return true
    }
}

And if we run the app now we should see a black screen. Great.

Ignoring our Example in the Package

One issue we see is that our WelcomeFeature package contains the Example app's code. We don't want this.

While we can specify specific source folders to look for, one easy solution is to put a dummy Package.swift file in the ExampleApp folder, which is enough to tell Xcode not to display it:

import PackageDescription

// dummy package manifest to tell Xcode to ignore this folder
let package = Package(
  name: "example",
  products: [],
  targets: []
)

However this presents another problem... a Package.swift doesn't compile in an iOS project. Let's update our project.yml to ignore this file. Change the sources object to this:

sources:
  - path: ExampleApp
    excludes:
      - Package.swift

Now run xcodegen again and Xcode will no longer have this as a reference in the ExampleApp project.

We can now move on to building out our feature. In this case the feature depends on a Storyboard, so we'll copy that over. We'll also need to specify which bundle this storyboard comes from. When you're working with Swift packages, you have a handy Bundle.module that we can use just for this.

Lastly keep in mind that there is a bug in Xcode that means you may have trouble getting IBDesignables to work in linked packages.

This episode uses Xcodegen 2.24.0, Xcode 12.5.