├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Example ├── .gitignore ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata ├── Package.swift ├── README.md └── Sources │ ├── ExampleAppIcon │ └── ExampleAppIconView.swift │ └── Export │ └── main.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── AppIconCreator │ ├── IconConfig.swift │ ├── IconImage.swift │ ├── IconPreview.swift │ ├── IconPreviews.swift │ ├── IconView.swift │ └── View+If.swift ├── screenshot_dark.png └── screenshot_light.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Example/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "example-app-icon", 7 | platforms: [ 8 | .macOS(.v11) 9 | ], 10 | products: [ 11 | .library( 12 | name: "ExampleAppIcon", 13 | targets: [ 14 | "ExampleAppIcon" 15 | ] 16 | ), 17 | .executable( 18 | name: "export", 19 | targets: [ 20 | "Export" 21 | ] 22 | ) 23 | ], 24 | dependencies: [ 25 | .package(path: "../") 26 | ], 27 | targets: [ 28 | .target( 29 | name: "ExampleAppIcon", 30 | dependencies: [ 31 | .product( 32 | name: "AppIconCreator", 33 | package: "swiftui-app-icon-creator" 34 | ) 35 | ] 36 | ), 37 | .target( 38 | name: "Export", 39 | dependencies: [ 40 | "ExampleAppIcon" 41 | ] 42 | ) 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /Example/README.md: -------------------------------------------------------------------------------- 1 | # Example App Icon 2 | 3 | SwiftUI App Icon Creator Usage Example 4 | -------------------------------------------------------------------------------- /Example/Sources/ExampleAppIcon/ExampleAppIconView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AppIconCreator 3 | 4 | public struct ExampleAppIconView: View { 5 | public enum Platform { 6 | case iOS 7 | case macOS 8 | } 9 | 10 | public init(_ platform: Platform) { 11 | self.platform = platform 12 | } 13 | 14 | var platform: Platform 15 | 16 | public var body: some View { 17 | GeometryReader { geometry in 18 | Image(systemName: "leaf.fill") 19 | .resizable() 20 | .scaledToFit() 21 | .font(Font.body.bold()) 22 | .overlayMask(LinearGradient( 23 | gradient: Gradient(colors: [.green, .yellow]), 24 | startPoint: .top, 25 | endPoint: .bottom 26 | )) 27 | .padding(geometry.size.width * 0.18) 28 | .frame(maxWidth: .infinity, maxHeight: .infinity) 29 | .background( 30 | LinearGradient( 31 | gradient: Gradient(colors: [.blue, .purple]), 32 | startPoint: .top, 33 | endPoint: .bottom 34 | ) 35 | ) 36 | .if(platform == .macOS) { 37 | $0.overlay( 38 | RoundedRectangle( 39 | cornerRadius: geometry.size.width * 0.4, 40 | style: .continuous 41 | ) 42 | .strokeBorder( 43 | lineWidth: geometry.size.width * 0.06 44 | ) 45 | .overlayMask( 46 | LinearGradient( 47 | gradient: Gradient(colors: [.purple, .blue]), 48 | startPoint: .top, 49 | endPoint: .bottom 50 | ) 51 | ) 52 | .shadow( 53 | color: Color.black.opacity(0.5), 54 | radius: geometry.size.width * 0.015 55 | ) 56 | ) 57 | .clipShape( 58 | RoundedRectangle( 59 | cornerRadius: geometry.size.width * 0.4, 60 | style: .continuous 61 | ) 62 | ) 63 | } 64 | } 65 | } 66 | } 67 | 68 | extension View { 69 | func overlayMask(_ overlay: Overlay) -> some View { 70 | self.overlay(overlay).mask(self) 71 | } 72 | } 73 | 74 | struct ExampleAppIconView_Preivews: PreviewProvider { 75 | static var previews: some View { 76 | IconPreviews( 77 | icon: ExampleAppIconView(.iOS), 78 | configs: .iOS 79 | ) 80 | IconPreviews( 81 | icon: ExampleAppIconView(.macOS), 82 | configs: .macOS, 83 | clip: false 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Example/Sources/Export/main.swift: -------------------------------------------------------------------------------- 1 | import AppIconCreator 2 | import ExampleAppIcon 3 | import Foundation 4 | 5 | let exportURL = FileManager.default 6 | .homeDirectoryForCurrentUser 7 | .appendingPathComponent("Desktop") 8 | .appendingPathComponent("ExampleAppIcon") 9 | 10 | [IconImage] 11 | .images(for: ExampleAppIconView(.iOS), with: .iOS) 12 | .forEach { $0.save(to: exportURL) } 13 | 14 | [IconImage] 15 | .images(for: ExampleAppIconView(.macOS), with: .macOS) 16 | .forEach { $0.save(to: exportURL) } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dariusz Rybicki Darrarski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swiftui-app-icon-creator", 7 | platforms: [ 8 | .macOS(.v11), 9 | .iOS(.v13), 10 | ], 11 | products: [ 12 | .library( 13 | name: "AppIconCreator", 14 | targets: [ 15 | "AppIconCreator" 16 | ] 17 | ) 18 | ], 19 | targets: [ 20 | .target( 21 | name: "AppIconCreator" 22 | ) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI App Icon Creator 2 | 3 | ![Swift 5](https://img.shields.io/badge/swift-5-orange.svg) 4 | ![platform macOS 11](https://img.shields.io/badge/platform-macOS%2011-blue.svg) 5 | 6 | Create iOS and macOS application icon in Xcode with SwiftUI 7 | 8 | *Xcode 12 and macOS 11 required* 9 | 10 | ![Creating app icon in Xcode - screenshot](screenshot_light.png) 11 | 12 |
13 | Dark mode supported 14 | 15 | ![Creating app icon in Xcode - screenshot](screenshot_dark.png) 16 | 17 |
18 | 19 | ## 📝 How to 20 | 21 | **TL;DR:** check out [example project](Example) in this repository. 22 | 23 | #### 1️⃣ Create a new Swift Package 24 | 25 | Define two products in your `Package.swift`: 26 | 27 | - Library that will contain your icon source code 28 | - Executable that you will use to export icon images 29 | 30 | Add `swiftui-app-icon-creator` package as a dependency. 31 | 32 | Your `Package.swift` should like this: 33 | 34 | ```swift 35 | // swift-tools-version:5.3 36 | import PackageDescription 37 | 38 | let package = Package( 39 | name: "my-app-icon", 40 | platforms: [.macOS(.v11)], 41 | products: [ 42 | .library(name: "MyAppIcon", targets: ["MyAppIcon"]), 43 | .executable(name: "export", targets: ["Export"]) 44 | ], 45 | dependencies: [ 46 | .package(url: "https://github.com/darrarski/swiftui-app-icon-creator.git", from: "1.0.0") 47 | ], 48 | targets: [ 49 | .target(name: "MyAppIcon", dependencies: [ 50 | .product(name: "AppIconCreator", package: "swiftui-app-icon-creator") 51 | ]), 52 | .target(name: "Export", dependencies: ["MyAppIcon"]) 53 | ] 54 | ) 55 | ``` 56 | 57 | #### 2️⃣ Create an icon view in the library target, using SwiftUI 58 | 59 | Just create a new SwiftUI view in the library target: 60 | 61 | ```swift 62 | import SwiftUI 63 | 64 | public struct MyAppIconView: View { 65 | public init() {} 66 | 67 | public var body: some View { 68 | // ... 69 | } 70 | } 71 | ``` 72 | 73 | #### 3️⃣ Use `IconPreviews` to live-preview your icon in Xcode 74 | 75 | Add this code to the file which contains your icon view: 76 | 77 | ```swift 78 | import AppIconCreator 79 | 80 | struct MyAppIconView_Preivews: PreviewProvider { 81 | static var previews: some View { 82 | IconPreviews( 83 | icon: MyAppIconView(), 84 | configs: .iOS 85 | ) 86 | } 87 | } 88 | ``` 89 | 90 | Make sure you have selected the build scheme connected with your library target (`MyAppIcon` in this example). 91 | 92 | You should be able to live-preview the icon in Xcode previews. 93 | 94 | You can adjust the `configs` parameter to specify which types of icons you want to preview. Check out [`IconConfig.swift`](Sources/AppIconCreator/IconConfig.swift) for possible options. 95 | 96 | #### 4️⃣ Add exporting code to the executable target 97 | 98 | Add this code to `main.swift` file in your executable target: 99 | 100 | ```swift 101 | import AppIconCreator 102 | import MyAppIcon 103 | import Foundation 104 | 105 | let icon = MyAppIconView() 106 | let configs = [IconConfig].iOS 107 | let images = [IconImage].images(for: icon, with: configs) 108 | let exportURL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Desktop").appendingPathComponent("MyAppIcon") 109 | images.forEach { $0.save(to: exportURL) } 110 | ``` 111 | 112 | You can adjust the `configs` variable to specify which types of icons you want to export. Check out [`IconConfig.swift`](Sources/AppIconCreator/IconConfig.swift) for possible options. 113 | 114 | In the above example, the images will be exported to `MyAppIcon` directory on the current user's desktop. Feel free to adjust the `exportURL` variable to your needs. 115 | 116 | #### 5️⃣ Run the executable from Xcode to export your icon images 117 | 118 | Make sure you have selected the build scheme connected with your executable target (`export` in this example). 119 | 120 | Images of your icon should be exported into a directory specified in `exportURL` variable. 121 | 122 | ## ☕️ Do you like the project? 123 | 124 | Buy Me A Coffee 125 | 126 | ## 📄 License 127 | 128 | Copyright © 2020 Dariusz Rybicki Darrarski 129 | 130 | License: [MIT](LICENSE) 131 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/IconConfig.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct IconConfig: Equatable { 4 | public var name: String 5 | public var size: Float 6 | public var scales: [Int] 7 | 8 | public init(name: String, size: Float, scales: [Int]) { 9 | self.name = name 10 | self.size = size 11 | self.scales = scales 12 | } 13 | } 14 | 15 | extension IconConfig: Identifiable { 16 | public var id: String { "\(name) \(sizeString)" } 17 | } 18 | 19 | extension IconConfig { 20 | var sizeString: String { 21 | "\(Self.pointSizeFormatter.string(from: NSNumber(value: size))!)pt" 22 | } 23 | 24 | private static let pointSizeFormatter: NumberFormatter = { 25 | let formatter = NumberFormatter() 26 | formatter.decimalSeparator = "." 27 | formatter.minimumFractionDigits = 0 28 | formatter.maximumFractionDigits = 1 29 | return formatter 30 | }() 31 | } 32 | 33 | public extension Sequence where Element == IconConfig { 34 | static var iOS: [Element] {[ 35 | .iPhone_Notification, 36 | .iPhone_Settings, 37 | .iPhone_Spotlight, 38 | .iPhone_App, 39 | .iPad_Notification, 40 | .iPad_Settings, 41 | .iPad_Spotlight, 42 | .iPad_App, 43 | .iPad_Pro_12_9_App, 44 | .iOS_App_Store 45 | ]} 46 | } 47 | 48 | public extension IconConfig { 49 | static let iPhone_Notification = IconConfig(name: "iPhone Notification", size: 20, scales: [2, 3]) 50 | static let iPhone_Settings = IconConfig(name: "iPhone Settings", size: 29, scales: [2, 3]) 51 | static let iPhone_Spotlight = IconConfig(name: "iPhone Spotlight", size: 40, scales: [2, 3]) 52 | static let iPhone_App = IconConfig(name: "iPhone App", size: 60, scales: [2, 3]) 53 | static let iPad_Notification = IconConfig(name: "iPad Notification", size: 20, scales: [1, 2]) 54 | static let iPad_Settings = IconConfig(name: "iPad Settings", size: 29, scales: [1, 2]) 55 | static let iPad_Spotlight = IconConfig(name: "iPad Spotlight", size: 40, scales: [1, 2]) 56 | static let iPad_App = IconConfig(name: "iPad App", size: 76, scales: [1, 2]) 57 | static let iPad_Pro_12_9_App = IconConfig(name: "iPad Pro (12.9-inch) App", size: 83.5, scales: [2]) 58 | static let iOS_App_Store = IconConfig(name: "iOS App Store", size: 1024, scales: [1]) 59 | } 60 | 61 | public extension Sequence where Element == IconConfig { 62 | static var macOS: [IconConfig] {[ 63 | .macOS_16pt, 64 | .macOS_32pt, 65 | .macOS_128pt, 66 | .macOS_256pt, 67 | .macOS_512pt 68 | ]} 69 | } 70 | 71 | public extension IconConfig { 72 | static let macOS_16pt = IconConfig(name: "Mac", size: 16, scales: [1, 2]) 73 | static let macOS_32pt = IconConfig(name: "Mac", size: 32, scales: [1, 2]) 74 | static let macOS_128pt = IconConfig(name: "Mac", size: 128, scales: [1, 2]) 75 | static let macOS_256pt = IconConfig(name: "Mac", size: 256, scales: [1, 2]) 76 | static let macOS_512pt = IconConfig(name: "Mac", size: 512, scales: [1, 2]) 77 | } 78 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/IconImage.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import SwiftUI 3 | 4 | public struct IconImage { 5 | public var name: String 6 | public var image: NSImage 7 | } 8 | 9 | public extension Sequence where Element == IconImage { 10 | static func images( 11 | for icon: Icon, 12 | with configs: [IconConfig] 13 | ) -> [Element] { 14 | configs.flatMap { config in 15 | images(for: icon, with: config) 16 | } 17 | } 18 | 19 | static func images( 20 | for icon: Icon, 21 | with config: IconConfig 22 | ) -> [Element] { 23 | config.scales.map { scale in 24 | IconImage( 25 | name: "\(config.id) \(scale)x", 26 | image: NSView.icon(icon, size: config.size * Float(scale)) 27 | .toImage() 28 | .unscaled() 29 | ) 30 | } 31 | } 32 | } 33 | 34 | public extension IconImage { 35 | func save(to directoryUrl: URL) { 36 | let fileManager = FileManager.default 37 | if fileManager.fileExists(atPath: directoryUrl.path) == false { 38 | try! fileManager.createDirectory(at: directoryUrl, withIntermediateDirectories: true) 39 | } 40 | let data = image.pngData() 41 | let url = directoryUrl 42 | .appendingPathComponent(name) 43 | .appendingPathExtension("png") 44 | try! data.write(to: url) 45 | } 46 | } 47 | 48 | extension NSView { 49 | static func icon(_ icon: Icon, size: Float) -> NSView { 50 | let view = NSHostingView(rootView: icon) 51 | let viewSize = CGFloat(size) 52 | view.frame = NSRect( 53 | origin: .zero, 54 | size: NSSize(width: viewSize, height: viewSize) 55 | ) 56 | return view 57 | } 58 | 59 | func toImage() -> NSImage { 60 | let cacheRep = bitmapImageRepForCachingDisplay(in: bounds)! 61 | cacheDisplay(in: bounds, to: cacheRep) 62 | let image = NSImage(size: bounds.size) 63 | image.addRepresentation(cacheRep) 64 | return image 65 | } 66 | } 67 | 68 | extension NSImage { 69 | func unscaled() -> NSImage { 70 | let image = NSImage(size: size) 71 | image.addRepresentation(unscaledBitmapImageRep()) 72 | return image 73 | } 74 | 75 | func unscaledBitmapImageRep() -> NSBitmapImageRep { 76 | let imageRep = NSBitmapImageRep( 77 | bitmapDataPlanes: nil, 78 | pixelsWide: Int(size.width), 79 | pixelsHigh: Int(size.height), 80 | bitsPerSample: 8, 81 | samplesPerPixel: 4, 82 | hasAlpha: true, 83 | isPlanar: false, 84 | colorSpaceName: .deviceRGB, 85 | bytesPerRow: 0, 86 | bitsPerPixel: 0 87 | )! 88 | imageRep.size = size 89 | NSGraphicsContext.saveGraphicsState() 90 | NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: imageRep) 91 | draw(at: .zero, from: .zero, operation: .sourceOver, fraction: 1) 92 | NSGraphicsContext.restoreGraphicsState() 93 | return imageRep 94 | } 95 | 96 | func pngData() -> Data { 97 | let tiffData = tiffRepresentation! 98 | let imageRep = NSBitmapImageRep(data: tiffData)! 99 | return imageRep.representation(using: .png, properties: [.compressionFactor: 1])! 100 | } 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/IconPreview.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import SwiftUI 3 | 4 | public struct IconPreview: View { 5 | public var icon: Icon 6 | public var config: IconConfig 7 | public var clip: Bool 8 | 9 | public init(icon: Icon, config: IconConfig, clip: Bool = true) { 10 | self.icon = icon 11 | self.config = config 12 | self.clip = clip 13 | } 14 | 15 | public var body: some View { 16 | VStack(alignment: .leading, spacing: 16) { 17 | Text(config.id).font(.title2) 18 | HStack(alignment: .top, spacing: 16) { 19 | ForEach(config.scales.sorted(by: >), id: \.self) { scale in 20 | VStack(alignment: .center, spacing: 8) { 21 | Text("\(scale)x").font(.title3) 22 | iconView(size: config.size, scale: scale) 23 | } 24 | } 25 | } 26 | } 27 | .padding() 28 | .fixedSize() 29 | } 30 | 31 | private func iconView(size: Float, scale: Int) -> some View { 32 | let screenScale = NSScreen.main!.backingScaleFactor 33 | let viewSize = CGFloat(config.size) * CGFloat(scale) / screenScale 34 | return icon 35 | .environment(\.colorScheme, .light) 36 | .frame(width: viewSize, height: viewSize, alignment: .center) 37 | .if(clip) { 38 | $0.clipShape(RoundedRectangle( 39 | cornerRadius: viewSize * 0.2, 40 | style: .continuous 41 | )) 42 | } 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/IconPreviews.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import SwiftUI 3 | 4 | public struct IconPreviews: View { 5 | public var icon: Icon 6 | public var configs: [IconConfig] 7 | public var clip: Bool 8 | 9 | public init(icon: Icon, configs: [IconConfig], clip: Bool = true) { 10 | self.icon = icon 11 | self.configs = configs 12 | self.clip = clip 13 | } 14 | 15 | public var body: some View { 16 | VStack(alignment: .leading, spacing: 16) { 17 | ForEach(configs) { config in 18 | IconPreview(icon: icon, config: config, clip: clip) 19 | } 20 | } 21 | .fixedSize() 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/IconView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct IconView: View, Equatable { 4 | public init() {} 5 | 6 | public var body: some View { 7 | HStack(spacing: 0) { 8 | Color.red 9 | Color.green 10 | Color.blue 11 | } 12 | } 13 | } 14 | 15 | struct IconView_Preivews: PreviewProvider { 16 | static var previews: some View { 17 | #if os(macOS) 18 | IconPreviews( 19 | icon: IconView(), 20 | configs: .iOS 21 | ) 22 | #endif 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/AppIconCreator/View+If.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public extension View { 4 | @ViewBuilder 5 | func `if`(_ condition: Bool, content: (Self) -> Content) -> some View { 6 | if condition { 7 | content(self) 8 | } else { 9 | self 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /screenshot_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrarski/swiftui-app-icon-creator/f0c7ba4e66d3dc8135ccf9146afc05f9dff3c4ff/screenshot_dark.png -------------------------------------------------------------------------------- /screenshot_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darrarski/swiftui-app-icon-creator/f0c7ba4e66d3dc8135ccf9146afc05f9dff3c4ff/screenshot_light.png --------------------------------------------------------------------------------