SwiftUI was introduced to the world at WWDC in June 2019. While the main goal for SwiftUI is to build rich user interfaces on iOS and other Apple platforms, it can also be used to do other fun things.

I couldn’t resist using SwiftUI from the command line, and I wrote about it in Rasterizing SwiftUI views from the command line to showcase how to generate static images (PNG, JPEG, etc). I published the code in GitHub at ConsoleUI.

Last December I used this same technique to generate social media preview images for this blog. I wrote Generating Social Media preview images with SwiftUI and GitHub Actions so other people like you could do it too.

Today, I’m writing about generating application icons for iOS with SwiftUI. You guessed it: from the command line.

Getting started

You can start by cloning my ConsoleUI repo, which I have updated today to run on Xcode 12.4 with the latest SwiftUI available. Feel free to begin with an empty project too. If you do this, you’d probably want to follow the steps on the README file from that repo, or refer back to my first article on this topic.

Xcode does not support live previews on Swift packages yet (FB8979344). To allow for realtime previews follow these steps:

  1. Generate an Xcode project (swift package generate-xcodeproj)
  2. Add a macOS target to the project
  3. Add your view to the macOS target (target Foo on the screenshot below)

The generated project and target files don’t need to be committed to your repository, but you can do it if that makes it easier for later use.

MySwiftUIView

If you cloned ConsoleUI, at this point you should be able to run the following commands:

swift build                  # build project
swift run && open test.png   # rasterize image and open in Preview app
swift test                   # run tests

Drawing App Icons

This is why we are here, right? Let’s draw some cool app icons for iOS.

Screen Shot 2021-01-23 at 9 21 05 AM

App Icon Design Guidelines state icons should be a PNG image with the largest size of 1024 x 1024 pixels (@1x) for the App Store.

We will update our view to match those dimensions, both on the rasterization code and the SwiftUI preview:

let wrapper = NSHostingView(rootView: MySwiftUIView())
wrapper.frame = CGRect(x: 0, y: 0, width: 1024, height: 1024)
#if DEBUG
struct MySwiftUIView_Previews : PreviewProvider {
    static var previews: some View {
        MySwiftUIView()
            .frame(width: 1024, height: 1024)
    }
}
#endif

We now should have a nice square to draw on.

Screen Shot 2021-01-23 at 9 25 48 AM

Equally, running swift run from the terminal should now generate a 1024 x 1204 PNG image as output.

Gradient Backgrounds

It’s pretty common for app icons to have a gradient background. Gradients are pretty easy to do in SwiftUI, lets add some:

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))

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]),
                           startPoint: .top, endPoint: .bottom)
            Text("Hello, world!")
                .foregroundColor(.white)
                .font(.largeTitle)
        }
    }
}

Screen Shot 2021-01-23 at 9 33 58 AM

Drawing Paths

Drawing paths in SwiftUI is a pleasure. There are pretty good articles with great examples online (some examples: Paths in SwiftUI, SwiftUI: Paths vs. Shapes).

Here is an example of drawing overlapping squares, as illustrated by Paul Hudson on How to draw a custom path.

struct SpiroSquare: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let rotations = 5
        let amount = .pi / CGFloat(rotations)
        let transform = CGAffineTransform(rotationAngle: amount)

        for _ in 0 ..< rotations {
            path = path.applying(transform)
            path.addRect(CGRect(x: -rect.width / 2, y: -rect.height / 2, width: rect.width, height: rect.height))
        }

        return path
    }
}

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))

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [gradientStart, gradientEnd]),
                           startPoint: .top, endPoint: .bottom)
            SpiroSquare()
                .stroke(lineWidth: 8)
                .frame(width: 600, height: 600)
                .offset(x: 300, y: 300)
        }
    }
}

When we run our rasterizer from the command line with swift run, we get this pretty icon:

Generated App Icon

Drawing Shapes

Besides paths, we can also use shapes to compose our icon.

For example, using rounded rectangle, we can replicate (to some extent) the Photos app icon shape:

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)

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

Live preview:

Screen Shot 2021-01-23 at 9 50 43 AM

And generated PNG icon image:

Generated App Icon

There you go, hope you found this useful. If you do and end up using this technique, please ping me on Twitter, I cannot wait to see the app icons you create in SwiftUI.


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

First draft: 2021-01-23

Published on: 2021-01-23

Last update: 2021-01-23