SwiftUI is an incredibly powerful UI building tool. It has limitations, but it brings a lot of new and interesting concepts to the table. One of those new concepts is that it not only provides UI primitives like
Picker (to name just a few), it also provides drawing primitives like
Circle, etc. So now instead of digging down to CoreGraphics, SwiftUI provides an appealing alternative to mix UI and custom drawing together.
We can take advantage of that drawing ability to generate our app Icon and leverage the benefits of the built-in SwiftUI previewing system to get live, updating previews of our code-based icon 🎉
How to integrate into your project
Check out the project over on GitHub for the latest instructions, but the basic outline is:
- Download and expand the latest release zip and drag the
Iconfolder into the root of your project, copying the files into the project, creating a group and adding the files to your App target.
- Un-include the
IconGenerator.swiftfiles from your App’s target since those are only going to be run from the Run Script build phase and won’t compile as part of your app.
- Add a Run Script build phase before your Copy Resources phase and specify the path to
Icon.swiftas the first Input File:
Now you should be able to add your drawing code to the
body property and get all the benefits of live-updating from SwiftUI. And every time you build and run, you’ll get your new icon generated for all the appropriate sizes.
Previewing your App icon
We can even use some helpers in the included
Icon+PreviewHelpers.swift to generate a live preview of the app icon…on a simulated home screen!
How does this work?
In order to generate an icon, we have to essentially render a SwiftUI View into a hosting view, resize the view, create bitmaps for each of those view sizes and insert those into an
AppIcon.appiconset inside the app’s
Assets.xcassets. One of the benefits of using SwiftUI will be that we can render the View at different sizes first and generate a new bitmap from each of those instead of just scaling a bitmap. Yay, vectors! SwiftUI even does some tricks under-the-hood to pixel fit your shapes or text to whatever pixel size you are representing, so the bitmaps at smaller scales should look as good as possible.
Then, to render these bitmaps as a build step, we can use AppKit’s implementation of SwiftUI (in Catalina only) to render the Views from the command-line using a Swift script that ‘imports’ your Icon. So in your iOS target, you will have an
Icon.swift that is shared between the Run Script phase and the iOS target, but all the other helper code will just be used by the Run Script phase.
Generating the Icon during Builds
generate.swift script that will run in your build is fairly simple and can check the build environment for
TARGETED_DEVICE_FAMILY in order to generate only the necessary icons:
Then you’ll need to actually execute the script as a Build Phase (see Step 3 of Quick Setup above), which is made slightly more complicated by the fact that we need to smush together several swift files in order to run this as one ‘script’.
Now when you re-build, you get your new icon set automatically generated and based on whatever device families (iPad or iPhone) you have selected in your project. While not currently supported here, it wouldn’t be terribly difficult to add support for macOS, watchOS, tvOS, etc. Reach out if you do!
The end-result should now be a fully generated icon using SwiftUI drawing primitives. Now you can even embed
Icon() into your app’s UI alongside all your other SwiftUI views!
This is a far from perfect setup. In fact, I’d go as far as to say it’s a bit of a hack. Ideally we’d be able to include this script as easily as a dependency, but because the icon has to be both included in your app and compiled on the build system, we have to jump through some hoops.
We’ve been using this in an upcoming product and are loving the flexibility and ease of expressing your app’s icon as a just another view.
We’re always open to feedback, so feel free to reach out if you have some ideas!