├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── Document
└── Images
│ └── appicon.gif
├── INSTALL_RECEIPT.json
├── LICENSE.md
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── AppIcon
│ └── main.swift
└── AppIconCore
│ ├── Command
│ └── Command.swift
│ ├── Data
│ ├── Icon
│ │ ├── AppIcon.swift
│ │ ├── AppIconSet.swift
│ │ ├── IosAppIconSet.swift
│ │ ├── MacAppIconSet.swift
│ │ ├── Scale.swift
│ │ └── WatchAppIconSet.swift
│ ├── Json
│ │ └── ContentsJson.swift
│ └── Platform.swift
│ ├── Error
│ └── LocalError.swift
│ └── Extractor
│ ├── ImageExtractor.swift
│ └── JsonExtractor.swift
└── Tests
└── AppIconTests
└── AppIconTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | ### OSX ###
2 |
3 | .DS_Store
4 |
5 | ### Swift ###
6 |
7 | /.build
8 | /DerivedData
9 | /Packages
10 | /*.xcodeproj
11 | /*.tar.gz
12 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode14
3 |
4 | install:
5 | - make build
6 |
7 | script:
8 | - make test
9 |
--------------------------------------------------------------------------------
/Document/Images/appicon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nonchalant/AppIcon/ddc4662a854dbd3f6f6b2159c914d90d2dfbbf51/Document/Images/appicon.gif
--------------------------------------------------------------------------------
/INSTALL_RECEIPT.json:
--------------------------------------------------------------------------------
1 | {
2 | "HEAD": null,
3 | "built_as_bottle": true,
4 | "compiler": "clang",
5 | "poured_from_bottle": false,
6 | "stdlib": null,
7 | "tapped_from": "nonchalant/appicon",
8 | "time": 0,
9 | "unused_options": [],
10 | "used_options": []
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Takeshi Ihara
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BINARY?=appicon
2 | BUILD_FOLDER?=.build
3 | OS?=ventura
4 | PREFIX?=/usr/local
5 | PROJECT?=AppIcon
6 | RELEASE_BINARY_FOLDER?=$(BUILD_FOLDER)/release/$(PROJECT)
7 | VERSION?=1.0.6
8 |
9 | debug:
10 | swift build
11 |
12 | build:
13 | swift build -c release --disable-sandbox --manifest-cache none
14 |
15 | test:
16 | swift test
17 |
18 | clean:
19 | swift package clean
20 | rm -rf DerivedData
21 | rm -rf $(BUILD_FOLDER)
22 |
23 | install: build
24 | mkdir -p $(PREFIX)/bin
25 | cp -f $(RELEASE_BINARY_FOLDER) $(PREFIX)/bin/$(BINARY)
26 |
27 | bottle: clean build
28 | mkdir -p $(BINARY)/$(VERSION)/bin
29 | cp README.md $(BINARY)/$(VERSION)/README.md
30 | cp LICENSE.md $(BINARY)/$(VERSION)/LICENSE.md
31 | cp INSTALL_RECEIPT.json $(BINARY)/$(VERSION)/INSTALL_RECEIPT.json
32 | cp -f $(RELEASE_BINARY_FOLDER) $(BINARY)/$(VERSION)/bin/$(BINARY)
33 | tar cfvz $(BINARY)-$(VERSION).$(OS).bottle.tar.gz --exclude='*/.*' $(BINARY)
34 | shasum -a 256 $(BINARY)-$(VERSION).$(OS).bottle.tar.gz
35 | rm -rf $(BINARY)
36 |
37 | sha256:
38 | wget https://github.com/Nonchalant/$(PROJECT)/archive/$(VERSION).tar.gz -O $(PROJECT)-$(VERSION).tar.gz
39 | shasum -a 256 $(PROJECT)-$(VERSION).tar.gz
40 | rm $(PROJECT)-$(VERSION).tar.gz
41 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "swift-argument-parser",
6 | "repositoryURL": "https://github.com/apple/swift-argument-parser",
7 | "state": {
8 | "branch": null,
9 | "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
10 | "version": "0.3.1"
11 | }
12 | },
13 | {
14 | "package": "SwiftShell",
15 | "repositoryURL": "https://github.com/kareman/SwiftShell.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "99680b2efc7c7dbcace1da0b3979d266f02e213c",
19 | "version": "5.1.0"
20 | }
21 | },
22 | {
23 | "package": "Tablier",
24 | "repositoryURL": "https://github.com/akkyie/Tablier",
25 | "state": {
26 | "branch": null,
27 | "revision": "1e8e0e0d92b5c1412cfec30cd3c4c761333133b3",
28 | "version": "0.3.3"
29 | }
30 | }
31 | ]
32 | },
33 | "version": 1
34 | }
35 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.6
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "AppIcon",
7 | products: [
8 | .executable(name: "AppIcon", targets: ["AppIcon"])
9 | ],
10 | dependencies: [
11 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.1"),
12 | .package(url: "https://github.com/kareman/SwiftShell.git", from: "5.1.0"),
13 | .package(url: "https://github.com/akkyie/Tablier", from: "0.2.0")
14 | ],
15 | targets: [
16 | .executableTarget(
17 | name: "AppIcon",
18 | dependencies: [
19 | "AppIconCore"
20 | ]
21 | ),
22 | .target(
23 | name: "AppIconCore",
24 | dependencies: [
25 | .product(name: "ArgumentParser", package: "swift-argument-parser"),
26 | "SwiftShell",
27 | ]
28 | ),
29 | .testTarget(
30 | name: "AppIconTests",
31 | dependencies: [
32 | "AppIconCore",
33 | "Tablier"
34 | ]
35 | )
36 | ]
37 | )
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppIcon
2 |
3 | [](https://travis-ci.com/Nonchalant/AppIcon)
4 | 
5 | [](https://raw.githubusercontent.com/Nonchalant/AppIcon/master/LICENSE.md)
6 | [](https://github.com/Nonchalant/AppIcon/releases)
7 | 
8 | 
9 | [](https://github.com/apple/swift-package-manager)
10 |
11 | `AppIcon` generates `*.appiconset` contains each resolution image for iOS, MacOS.
12 |
13 | ```
14 | AppIcon.appiconset
15 | ├── Contents.json
16 | ├── AppIcon-20.0x20.0@2x.png
17 | ├── AppIcon-20.0x20.0@3x.png
18 | ├── AppIcon-29.0x29.0@2x.png
19 | ├── AppIcon-29.0x29.0@3x.png
20 | ├── AppIcon-40.0x40.0@2x.png
21 | ├── AppIcon-40.0x40.0@3x.png
22 | ├── AppIcon-60.0x60.0@2x.png
23 | ├── AppIcon-60.0x60.0@3x.png
24 | └── AppIcon-1024.0x1024.0@1x.png
25 | ```
26 |
27 | ## Demo
28 |
29 | 
30 |
31 | ## Installation
32 |
33 | ### Homebrew
34 |
35 | ```
36 | $ brew install Nonchalant/appicon/appicon
37 | ```
38 |
39 | ### [Mint](https://github.com/yonaskolb/Mint)
40 |
41 | ```bash
42 | $ mint run nonchalant/appicon
43 | ```
44 |
45 | ### Manual
46 |
47 | Clone the master branch of the repository, then run make install.
48 |
49 | ```
50 | $ git clone https://github.com/Nonchalant/AppIcon.git
51 | $ make install
52 | ```
53 |
54 | ## Usage
55 |
56 | `AppIcon` needs path of base image(`.png`). The size of base image is 1024x1024 pixel preferably.
57 |
58 | ```
59 | $ appicon iTunesIcon-1024x1024.png
60 | ```
61 |
62 | ## Option
63 |
64 | You can see options by `appicon --help`.
65 |
66 | #### --icon-name
67 |
68 | Default: `AppIcon`
69 |
70 | #### --output-path
71 |
72 | Default: `./AppIcon.appiconset`
73 |
74 | #### --mac
75 |
76 | Default: false
77 |
78 | #### --watch
79 |
80 | Default: false
81 |
82 | ## Develop
83 |
84 | ### Runs debug build
85 |
86 | ```
87 | $ make debug
88 | ```
89 |
90 | ### Runs release build
91 |
92 | ```
93 | $ make build
94 | ```
95 |
96 | ### Runs tests
97 |
98 | ```
99 | $ make test
100 | ```
101 |
102 | ## Author
103 |
104 | Takeshi Ihara
105 |
106 | ## License
107 |
108 | Appicon is available under the MIT license. See the LICENSE file for more info.
109 |
--------------------------------------------------------------------------------
/Sources/AppIcon/main.swift:
--------------------------------------------------------------------------------
1 | import AppIconCore
2 | import ArgumentParser
3 | import Foundation
4 |
5 | struct AppIcon: ParsableCommand {
6 | static let configuration = CommandConfiguration(
7 | commandName: "appicon",
8 | abstract: "AppIcon generates *.appiconset contains each resolution image for iOS",
9 | version: "1.0.6"
10 | )
11 |
12 | @Argument(help: "The path to the base image (1024x1024.png)", completion: .file(extensions: ["png"]))
13 | var image: String
14 |
15 | @Option(help: "The name of the generated image")
16 | var iconName = "AppIcon"
17 |
18 | @Option(help: "The path of the generated appiconset")
19 | var outputPath = "AppIcon"
20 |
21 | @Flag(help: "Generate also Mac icons")
22 | var mac = false
23 |
24 | @Flag(help: "Generate also Apple Watch icons")
25 | var watch = false
26 |
27 | func run() throws {
28 | guard let `extension` = image.split(separator: ".").last,
29 | `extension`.caseInsensitiveCompare("png") == .orderedSame else {
30 | throw ValidationError("image path should have .png extension")
31 | }
32 |
33 | guard FileManager.default.fileExists(atPath: image) else {
34 | throw ValidationError("an input image is not exists")
35 | }
36 |
37 | let outputExpansion = ".appiconset"
38 | let outputPath = self.outputPath.hasSuffix(outputExpansion) ? self.outputPath : "\(self.outputPath)\(outputExpansion)"
39 | let platform = Platform(mac: mac, watch: watch)
40 |
41 | do {
42 | try ImageExtractor.extract(base: image, output: outputPath, iconName: iconName, platform: platform)
43 | } catch {
44 | print("Image Extraction Error has occurred 😱")
45 | throw ExitCode(1)
46 | }
47 |
48 | do {
49 | try JsonExtractor.extract(output: outputPath, iconName: iconName, platform: platform)
50 | } catch {
51 | print("Json Extraction Error has occurred 😱")
52 | throw ExitCode(1)
53 | }
54 |
55 | print("\(outputPath) has been generated 🎉")
56 | }
57 | }
58 |
59 | AppIcon.main()
60 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Command/Command.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftShell
3 |
4 | enum Command {
5 | case createDirectory(output: String)
6 | case extractImage(base: String, output: String, size: Float)
7 | case createJSON(json: String, output: String)
8 |
9 | func execute() throws {
10 | switch self {
11 | case .createDirectory(let output):
12 | try execute("/bin/mkdir", "-p", output)
13 | case .extractImage(let base, let output, let size):
14 | try execute("/usr/bin/sips", "-Z", "\(size)", base, "--out", output)
15 | case .createJSON(let json, let output):
16 | let writefile = try open(forWriting: output, overwrite: true)
17 | writefile.write(json)
18 | writefile.close()
19 | }
20 | }
21 |
22 | private func execute(_ executable: String, _ args: String...) throws {
23 | let runOutput = run(executable, args)
24 |
25 | guard runOutput.succeeded else {
26 | throw LocalError.execution
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/AppIcon.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct AppIcon: Hashable {
4 | let name: String
5 | let platform: Platform
6 | let scale: Scale
7 | let size: Float
8 | }
9 |
10 | extension AppIcon {
11 | var scaleSize: Float {
12 | size * scale.magnification
13 | }
14 |
15 | var scaleForJson: String? {
16 | platform.isSingleTrimmed && (scale == ._1x) ? nil : scale.rawValue
17 | }
18 |
19 | var baseSizeStr: String {
20 | (size == round(size)) ? String(format: "%.f", size) : "\(size)"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/AppIconSet.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol AppIconSet {
4 | var size: Float { get }
5 | var platform: Platform { get }
6 | var scales: Set { get }
7 | }
8 |
9 | extension AppIconSet {
10 | func icons(iconName: String) -> [AppIcon] {
11 | scales.map { scale in
12 | AppIcon(
13 | name: "\(iconName)-\(size)x\(size)@\(scale.rawValue).png",
14 | platform: platform,
15 | scale: scale,
16 | size: size
17 | )
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/IosAppIconSet.swift:
--------------------------------------------------------------------------------
1 | enum IosAppIconSet: CaseIterable {
2 | case _20
3 | case _29
4 | case _38
5 | case _40
6 | case _60
7 | case _64
8 | case _68
9 | case _76
10 | case _83_5
11 | case _1024
12 | }
13 |
14 | extension IosAppIconSet: AppIconSet {
15 | var size: Float {
16 | switch self {
17 | case ._20:
18 | return 20
19 | case ._29:
20 | return 29
21 | case ._38:
22 | return 38
23 | case ._40:
24 | return 40
25 | case ._60:
26 | return 60
27 | case ._64:
28 | return 64
29 | case ._68:
30 | return 68
31 | case ._76:
32 | return 76
33 | case ._83_5:
34 | return 83.5
35 | case ._1024:
36 | return 1024
37 | }
38 | }
39 |
40 | var platform: Platform {
41 | .ios
42 | }
43 |
44 | var scales: Set {
45 | switch self {
46 | case ._20, ._29, ._38, ._40, ._60, ._64:
47 | return Set([._2x, ._3x])
48 | case ._68, ._76, ._83_5:
49 | return Set([._2x])
50 | case ._1024:
51 | return Set([._1x])
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/MacAppIconSet.swift:
--------------------------------------------------------------------------------
1 | enum MacAppIconSet: CaseIterable {
2 | case _16
3 | case _32
4 | case _128
5 | case _256
6 | case _512
7 | }
8 |
9 | extension MacAppIconSet: AppIconSet {
10 | var size: Float {
11 | switch self {
12 | case ._16:
13 | return 16
14 | case ._32:
15 | return 32
16 | case ._128:
17 | return 128
18 | case ._256:
19 | return 256
20 | case ._512:
21 | return 512
22 | }
23 | }
24 |
25 | var platform: Platform {
26 | .mac
27 | }
28 |
29 | var scales: Set {
30 | Set([._1x, ._2x])
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/Scale.swift:
--------------------------------------------------------------------------------
1 | enum Scale: Hashable {
2 | case _1x
3 | case _2x
4 | case _3x
5 | }
6 |
7 | extension Scale {
8 | var rawValue: String {
9 | switch self {
10 | case ._1x:
11 | return "1x"
12 | case ._2x:
13 | return "2x"
14 | case ._3x:
15 | return "3x"
16 | }
17 | }
18 |
19 | var magnification: Float {
20 | switch self {
21 | case ._1x:
22 | return 1.0
23 | case ._2x:
24 | return 2.0
25 | case ._3x:
26 | return 3.0
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Icon/WatchAppIconSet.swift:
--------------------------------------------------------------------------------
1 | enum WatchAppIconSet: CaseIterable {
2 | case _22
3 | case _24
4 | case _27_5
5 | case _29
6 | case _30
7 | case _32
8 | case _33
9 | case _40
10 | case _43_5
11 | case _44
12 | case _46
13 | case _50
14 | case _51
15 | case _54
16 | case _86
17 | case _98
18 | case _108
19 | case _117
20 | case _129
21 | case _1024
22 | }
23 |
24 | extension WatchAppIconSet: AppIconSet {
25 | var size: Float {
26 | switch self {
27 | case ._22:
28 | return 22
29 | case ._24:
30 | return 24
31 | case ._27_5:
32 | return 27.5
33 | case ._29:
34 | return 29
35 | case ._30:
36 | return 30
37 | case ._32:
38 | return 32
39 | case ._33:
40 | return 33
41 | case ._40:
42 | return 40
43 | case ._43_5:
44 | return 43.5
45 | case ._44:
46 | return 44
47 | case ._46:
48 | return 46
49 | case ._50:
50 | return 50
51 | case ._51:
52 | return 51
53 | case ._54:
54 | return 54
55 | case ._86:
56 | return 86
57 | case ._98:
58 | return 98
59 | case ._108:
60 | return 108
61 | case ._117:
62 | return 117
63 | case ._129:
64 | return 129
65 | case ._1024:
66 | return 1024
67 | }
68 | }
69 |
70 | var platform: Platform {
71 | .watch
72 | }
73 |
74 | var scales: Set {
75 | switch self {
76 | case ._22,
77 | ._24,
78 | ._27_5,
79 | ._29,
80 | ._30,
81 | ._32,
82 | ._33,
83 | ._40,
84 | ._43_5,
85 | ._44,
86 | ._46,
87 | ._50,
88 | ._51,
89 | ._54,
90 | ._86,
91 | ._98,
92 | ._108,
93 | ._117,
94 | ._129:
95 | return Set([._2x])
96 | case ._1024:
97 | return Set([._1x])
98 | }
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Json/ContentsJson.swift:
--------------------------------------------------------------------------------
1 | struct ContentsJson: Encodable {
2 | let images: [Image]
3 | let info: Info
4 | }
5 |
6 | extension ContentsJson {
7 | struct Image: Encodable {
8 | let filename: String
9 | let idiom: String
10 | let platform: String?
11 | let scale: String?
12 | let size: String
13 | }
14 |
15 | struct Info: Encodable {
16 | let version: Int
17 | let author: String
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Data/Platform.swift:
--------------------------------------------------------------------------------
1 | public enum Platform: Equatable {
2 | case ios
3 | case mac
4 | case watch
5 | }
6 |
7 | extension Platform {
8 | public init(mac: Bool, watch: Bool) {
9 | if mac {
10 | self = .mac
11 | } else if watch {
12 | self = .watch
13 | } else {
14 | self = .ios
15 | }
16 | }
17 |
18 | func icons(iconName: String) -> Set {
19 | let icons: [AppIconSet] = {
20 | switch self {
21 | case .ios:
22 | return IosAppIconSet.allCases
23 | case .mac:
24 | return MacAppIconSet.allCases
25 | case .watch:
26 | return WatchAppIconSet.allCases
27 | }
28 | }()
29 |
30 | return Set(
31 | icons
32 | .map { $0.icons(iconName: iconName) }
33 | .flatMap { $0 }
34 | )
35 | }
36 | }
37 |
38 | extension Platform {
39 | var idiom: String {
40 | switch self {
41 | case .ios, .watch:
42 | return "universal"
43 | case .mac:
44 | return "mac"
45 | }
46 | }
47 |
48 | var rawValue: String? {
49 | switch self {
50 | case .ios:
51 | return "ios"
52 | case .mac:
53 | return nil
54 | case .watch:
55 | return "watchos"
56 | }
57 | }
58 |
59 | var isSingleTrimmed: Bool {
60 | switch self {
61 | case .ios, .mac:
62 | return false
63 | case .watch:
64 | return true
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Error/LocalError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum LocalError: Error {
4 | case execution
5 | case extraction
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Extractor/ImageExtractor.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum ImageExtractor {
4 | public static func extract(
5 | base: String,
6 | output: String,
7 | iconName: String,
8 | platform: Platform
9 | ) throws {
10 | do {
11 | try Command.createDirectory(output: output).execute()
12 |
13 | let icons = platform.icons(iconName: iconName)
14 | try extract(base: base, output: output, icons: icons)
15 | } catch {
16 | throw LocalError.extraction
17 | }
18 | }
19 |
20 | private static func extract(
21 | base: String,
22 | output: String,
23 | icons: Set
24 | ) throws {
25 | for icon in icons {
26 | try Command
27 | .extractImage(
28 | base: base,
29 | output: "\(output)/\(icon.name)",
30 | size: icon.scaleSize
31 | )
32 | .execute()
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/AppIconCore/Extractor/JsonExtractor.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum JsonExtractor {
4 | public static func extract(
5 | output: String,
6 | iconName: String,
7 | platform: Platform
8 | ) throws {
9 | do {
10 | let icons = platform.icons(iconName: iconName)
11 | try Command
12 | .createJSON(
13 | json: generate(icons: icons),
14 | output: "\(output)/Contents.json"
15 | )
16 | .execute()
17 | } catch {
18 | throw LocalError.extraction
19 | }
20 | }
21 |
22 | private static func generate(icons: Set) -> String {
23 | let images = icons.map { icon in
24 | ContentsJson.Image(
25 | filename: icon.name,
26 | idiom: icon.platform.idiom,
27 | platform: icon.platform.rawValue,
28 | scale: icon.scaleForJson,
29 | size: "\(icon.baseSizeStr)x\(icon.baseSizeStr)"
30 | )
31 | }
32 |
33 | let json = ContentsJson(
34 | images: images,
35 | info: ContentsJson.Info(
36 | version: 1,
37 | author: "appicon"
38 | )
39 | )
40 |
41 | do {
42 | let data = try JSONEncoder().encode(json)
43 | return String(data: data, encoding: .utf8) ?? ""
44 | } catch {
45 | return ""
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/AppIconTests/AppIconTests.swift:
--------------------------------------------------------------------------------
1 | import Tablier
2 | import XCTest
3 | @testable import AppIconCore
4 |
5 | class AppIconTests: XCTestCase {
6 |
7 | func test_scale() {
8 | let recipe = Recipe { input in
9 | input.scaleForJson
10 | }
11 |
12 | recipe.assert(with: self) {
13 | $0.when(.init(name: "", platform: .ios, scale: ._1x, size: 0))
14 | .expect("1x")
15 |
16 | $0.when(.init(name: "", platform: .watch, scale: ._1x, size: 0))
17 | .expect(nil)
18 |
19 | $0.when(.init(name: "", platform: .watch, scale: ._2x, size: 0))
20 | .expect("2x")
21 | }
22 | }
23 |
24 | func test_size() {
25 | let recipe = Recipe { input in
26 | input.baseSizeStr
27 | }
28 |
29 | recipe.assert(with: self) {
30 | $0.when(.init(name: "", platform: .ios, scale: ._1x, size: 10.0))
31 | .expect("10")
32 |
33 | $0.when(.init(name: "", platform: .watch, scale: ._1x, size: 10.1))
34 | .expect("10.1")
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------