It is possible to embed SwiftUI views in Swift packages with Swift Package Manager, so they can be distributed, online or internally in your organization, and used across multiple applications.

Benefits

Moving SwiftUI views to libraries has many benefits:

  • Improved modularization
  • Reduced dependencies
  • Improved reusability
  • Improved replaceability
  • Improved collaboration

Improved Modularization

Modularization brings many benefits, as described below. Putting SwiftUI views in separate modules is a great way to improve modularization.

Reduced Dependencies

Placing views (or any other code) in separate modules forces us into a pattern of removing dependencies. This is good. Having less dependencies means we have to write less interfaces and mocks to test our code.

Improved Reusability

Putting our views (or any other code) in libraries makes it super easy to use them in multiple apps. Specially in the case of open source, we also let other people use our views and libraries in their own projects.

Improved Replaceability

Because of the reduced number of dependencies, replacing views will be much easier down the road. Dependencies are the source of many problems, including making our code hard to replace.

Improved Collaboration

Separating your views from the rest of your application makes it much easier to delegate work. Different developers or even teams can work on separate views and other libraries.

Example

One SwiftUI library I published is Stripes, a simple SwiftUI view to compose stripped backgrounds and other patterns.

Let’s see how to use it in our tool to generate iOS Application Icons with SwiftUI.

Add Stripes Dependency

In the case of the command line tool to generate application icons, I had a Swift package with the command line executable, and also a faux Xcode project for live previews. I will add Stripes to both.

Add Swift Package to the project (for Xcode projects)

Screen Shot 2021-01-24 at 8 49 50 AM

Add the library to the target (for Xcode projects)

Screen Shot 2021-01-24 at 8 50 10 AM

Add the dependency to the package (for Packages)

let package = Package(
    name: "ConsoleUI",
    platforms: [
        .macOS(.v10_15)
    ],
    products: [
        .executable(name: "consoleui", targets: ["ConsoleUI"])
    ],
    dependencies: [
        .package(url: "https://github.com/eneko/Stripes", from: "0.2.0")
    ],
    targets: [
        .target(name: "ConsoleUI", dependencies: ["Stripes"]),
        .testTarget(name: "ConsoleUITests", dependencies: ["ConsoleUI"])
    ]
)

Use Stripes in our icon generator tool

import SwiftUI
import Stripes

struct MySwiftUIView : View {
    let gradientStart = Color(#colorLiteral(red: 0.9098039269, green: 0.4784313738, blue: 0.6431372762, alpha: 1))
    let gradientEnd = Color(#colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1))
    let petalLength: CGFloat = 400

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]),
                           startPoint: .top, endPoint: .bottom)

            Stripes(config: StripesConfig(background: Color.white.opacity(0.1),
                                          foreground: Color.white.opacity(0.1),
                                          degrees: 0,
                                          barWidth: 258,
                                          barSpacing: 100))

            ForEach(0..<8) { index in
                RoundedRectangle(cornerRadius: petalLength / 2)
                    .frame(width: petalLength, height: petalLength / 2)
                    .offset(x: petalLength / 2, y: 0)
                    .opacity(0.4 + 0.05 * Double(index))
                    .rotationEffect(Angle(degrees: Double(index) * 45))
            }

            ForEach(0..<8) { index in
                Circle()
                    .frame(width: petalLength / 4, height: petalLength / 4)
                    .offset(x: petalLength * 0.75, y: 0)
                    .opacity(0.4 + 0.05 * Double(index))
                    .rotationEffect(Angle(degrees: Double(index) * 45))
            }
        }
    }
}

Screen Shot 2021-01-24 at 9 29 02 AM

Here is the final icon image, from the example above:

test


This article was written as an issue on my Blog repository on GitHub (see Issue #25)

First draft: 2021-01-24

Published on: 2021-01-24

Last update: 2021-01-24