├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ └── VersionIcon.xcscheme
├── Bin
├── Font
│ ├── TrumpGothicPro-Bold.ttf
│ ├── TrumpGothicPro-BoldItalic.ttf
│ ├── TrumpGothicPro-Italic.ttf
│ ├── TrumpGothicPro-Medium.ttf
│ ├── TrumpGothicPro-MediumIt.ttf
│ └── TrumpGothicPro.ttf
├── PSD
│ ├── Ribbon.psd
│ └── Title.psd
├── Ribbons
│ ├── Blue-BottomLeft.png
│ ├── Blue-BottomRight.png
│ ├── Blue-TopLeft.png
│ ├── Blue-TopRight.png
│ ├── Cyan-BottomLeft.png
│ ├── Cyan-BottomRight.png
│ ├── Cyan-TopLeft.png
│ ├── Cyan-TopRight.png
│ ├── Gold-BottomLeft.png
│ ├── Gold-BottomRight.png
│ ├── Gold-TopLeft.png
│ ├── Gold-TopRight.png
│ ├── Green-BottomLeft.png
│ ├── Green-BottomRight.png
│ ├── Green-TopLeft.png
│ ├── Green-TopRight.png
│ ├── Purple-BottomLeft.png
│ ├── Purple-BottomRight.png
│ ├── Purple-TopLeft.png
│ ├── Purple-TopRight.png
│ ├── Red-BottomLeft.png
│ ├── Red-BottomRight.png
│ ├── Red-TopLeft.png
│ └── Red-TopRight.png
├── Titles
│ ├── Alpha-BottomLeft.png
│ ├── Alpha-BottomRight.png
│ ├── Alpha-TopLeft.png
│ ├── Alpha-TopRight.png
│ ├── Beta-BottomLeft.png
│ ├── Beta-BottomRight.png
│ ├── Beta-TopLeft.png
│ ├── Beta-TopRight.png
│ ├── Debug-BottomLeft.png
│ ├── Debug-BottomRight.png
│ ├── Debug-TopLeft.png
│ ├── Debug-TopRight.png
│ ├── Demo-BottomLeft.png
│ ├── Demo-BottomRight.png
│ ├── Demo-TopLeft.png
│ ├── Demo-TopRight.png
│ ├── Dev-BottomLeft.png
│ ├── Dev-BottomRight.png
│ ├── Dev-TopLeft.png
│ ├── Dev-TopRight.png
│ ├── Devel-BottomLeft.png
│ ├── Devel-BottomRight.png
│ ├── Devel-TopLeft.png
│ ├── Devel-TopRight.png
│ ├── MVP-BottomLeft.png
│ ├── MVP-BottomRight.png
│ ├── MVP-TopLeft.png
│ ├── MVP-TopRight.png
│ ├── Prod-BottomLeft.png
│ ├── Prod-BottomRight.png
│ ├── Prod-TopLeft.png
│ ├── Prod-TopRight.png
│ ├── Staging-BottomLeft.png
│ ├── Staging-BottomRight.png
│ ├── Staging-TopLeft.png
│ └── Staging-TopRight.png
└── VersionIcon
├── Documentation
├── AppIcon60x60@2x.png
├── AppIcon60x60@3x.png
└── SchemeSetup.png
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── VersionIcon
│ ├── VersionIconDefinitions.swift
│ └── main.swift
├── Tests
├── LinuxMain.swift
└── VersionIconTests
│ ├── VersionIconTests.swift
│ └── XCTestManifests.swift
├── VersionIcon.podspec
└── install.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 | .swiftpm
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 | Pods/
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | # Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots/**/*.png
69 | fastlane/test_output
70 | fastlane/.env*
71 |
72 | # Ruby gems + tooling Binaries
73 | vendor/
74 |
75 | # Because our self-hosted runner is an Apple Silicon machine some Ruby dependencies need binaries for the ARM architecture
76 | # but the .lock file captures the x64 versions. So we rather have specific versions in Gemfile and do not commit the .lock file
77 | Gemfile.lock
78 |
79 | .DS_Store
80 |
81 | /*.xcodeproj
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/VersionIcon.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
70 |
71 |
74 |
75 |
78 |
79 |
82 |
83 |
86 |
87 |
90 |
91 |
92 |
93 |
97 |
98 |
102 |
103 |
107 |
108 |
109 |
110 |
116 |
118 |
124 |
125 |
126 |
127 |
129 |
130 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro-Bold.ttf
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro-BoldItalic.ttf
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro-Italic.ttf
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro-Medium.ttf
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro-MediumIt.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro-MediumIt.ttf
--------------------------------------------------------------------------------
/Bin/Font/TrumpGothicPro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Font/TrumpGothicPro.ttf
--------------------------------------------------------------------------------
/Bin/PSD/Ribbon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/PSD/Ribbon.psd
--------------------------------------------------------------------------------
/Bin/PSD/Title.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/PSD/Title.psd
--------------------------------------------------------------------------------
/Bin/Ribbons/Blue-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Blue-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Blue-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Blue-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Blue-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Blue-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Blue-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Blue-TopRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Cyan-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Cyan-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Cyan-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Cyan-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Cyan-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Cyan-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Cyan-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Cyan-TopRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Gold-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Gold-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Gold-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Gold-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Gold-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Gold-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Gold-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Gold-TopRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Green-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Green-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Green-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Green-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Green-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Green-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Green-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Green-TopRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Purple-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Purple-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Purple-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Purple-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Purple-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Purple-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Purple-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Purple-TopRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Red-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Red-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Red-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Red-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Red-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Red-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Ribbons/Red-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Ribbons/Red-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Alpha-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Alpha-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Alpha-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Alpha-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Alpha-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Alpha-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Alpha-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Alpha-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Beta-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Beta-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Beta-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Beta-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Beta-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Beta-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Beta-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Beta-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Debug-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Debug-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Debug-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Debug-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Debug-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Debug-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Debug-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Debug-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Demo-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Demo-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Demo-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Demo-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Demo-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Demo-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Demo-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Demo-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Dev-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Dev-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Dev-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Dev-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Dev-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Dev-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Dev-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Dev-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Devel-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Devel-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Devel-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Devel-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Devel-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Devel-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Devel-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Devel-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/MVP-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/MVP-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/MVP-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/MVP-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/MVP-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/MVP-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/MVP-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/MVP-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Prod-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Prod-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Prod-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Prod-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Prod-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Prod-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Prod-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Prod-TopRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Staging-BottomLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Staging-BottomLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Staging-BottomRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Staging-BottomRight.png
--------------------------------------------------------------------------------
/Bin/Titles/Staging-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Staging-TopLeft.png
--------------------------------------------------------------------------------
/Bin/Titles/Staging-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/Titles/Staging-TopRight.png
--------------------------------------------------------------------------------
/Bin/VersionIcon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Bin/VersionIcon
--------------------------------------------------------------------------------
/Documentation/AppIcon60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Documentation/AppIcon60x60@2x.png
--------------------------------------------------------------------------------
/Documentation/AppIcon60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Documentation/AppIcon60x60@3x.png
--------------------------------------------------------------------------------
/Documentation/SchemeSetup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/ios-version-icon/e34eb32e36efd7aa4fe78e863d65c4fb6c7fc8ce/Documentation/SchemeSetup.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Daniel Čech
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.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Files",
6 | "repositoryURL": "https://github.com/JohnSundell/Files.git",
7 | "state": {
8 | "branch": "master",
9 | "revision": "9c9d5f65123742d0180b25d754b942bcc8010d4e",
10 | "version": null
11 | }
12 | },
13 | {
14 | "package": "FileSmith",
15 | "repositoryURL": "https://github.com/kareman/FileSmith.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "476472740a38c64634b388f2b311ec518bfa3265",
19 | "version": "0.3.0"
20 | }
21 | },
22 | {
23 | "package": "Moderator",
24 | "repositoryURL": "https://github.com/DanielCech/Moderator.git",
25 | "state": {
26 | "branch": "master",
27 | "revision": "b9c7e870caba30204903cf768367b7a5dc4ae3fa",
28 | "version": null
29 | }
30 | },
31 | {
32 | "package": "ScriptToolkit",
33 | "repositoryURL": "https://github.com/DanielCech/ScriptToolkit.git",
34 | "state": {
35 | "branch": "master",
36 | "revision": "903c83406454fb0584010cdaf92de7a57720033a",
37 | "version": null
38 | }
39 | },
40 | {
41 | "package": "SwiftShell",
42 | "repositoryURL": "https://github.com/kareman/SwiftShell.git",
43 | "state": {
44 | "branch": "master",
45 | "revision": "a6014fe94c3dbff0ad500e8da4f251a5d336530b",
46 | "version": null
47 | }
48 | }
49 | ]
50 | },
51 | "version": 1
52 | }
53 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "VersionIcon",
8 | platforms: [
9 | .macOS(.v10_15),
10 | ],
11 | dependencies: [
12 | .package(url: "https://github.com/JohnSundell/Files.git", from: "4.1.1"),
13 | .package(url: "https://github.com/DanielCech/Moderator.git", from: "0.5.1"),
14 | .package(url: "https://github.com/DanielCech/ScriptToolkit.git", .branch("master")),
15 | .package(url: "https://github.com/kareman/SwiftShell.git", from: "5.0.1"),
16 | .package(url: "https://github.com/kareman/FileSmith.git", from: "0.3.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "VersionIcon",
23 | dependencies: ["Files", "FileSmith", "SwiftShell", "ScriptToolkit", "Moderator"],
24 | swiftSettings:
25 | [
26 | // Macro definition - uncomment only when debugging
27 | // .define("DEBUGGING")
28 | ]
29 | ),
30 | .testTarget(
31 | name: "VersionIconTests",
32 | dependencies: ["VersionIcon"]
33 | ),
34 | ]
35 | )
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://cocoapods.org/pods/VersionIcon)
2 | [](https://cocoapods.org/pods/VersionIcon)
3 |
4 |
5 |
6 |
7 |
8 | # VersionIcon
9 |
10 | A simple tool that can add an icon overlay with app version to your iOS app icon. Overlays can include the ribbon with app version (_Dev_, _Staging_, _Production_, _MVP_...) and/or version number. The icon overlays can be customized many ways. You can also use your own graphic resources. The VersionIcon tool is distributed in binary form, so it is independent on your project setup.
11 |
12 | - [Requirements](#requirements)
13 | - [Installation](#installation)
14 | - [Usage](#usage)
15 | - [License](#license)
16 |
17 | ## Requirements
18 |
19 | - Xcode 10.0+
20 |
21 | ## Installation
22 |
23 | ### Cocoapods
24 |
25 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
26 |
27 | ```bash
28 | $ gem install cocoapods
29 | ```
30 |
31 | To integrate VersionIcon into your Xcode project using CocoaPods, specify it in your `Podfile`:
32 |
33 | ```ruby
34 | pod 'VersionIcon', '~> 1.0.8'
35 | ```
36 |
37 | Then, run the following command:
38 |
39 | ```bash
40 | $ pod install
41 | ```
42 |
43 | ## Usage
44 |
45 | * Make a duplicate of your app icon resource in asset catalog - let's have for example _AppIcon_ and _AppIconOriginal_. The copy is used as a backup. Production builds typically have no icon overlays. (if your project contains icon resource with other than this default name, you need to specify it using `--appIcon` and/or `--appIconOriginal` parameter.
46 | * Create a new Run Script Phase in Build Settings > Build Phases in your app
47 | * Use this shell script:
48 | ```shell
49 | if [ "${CONFIGURATION}" = "Release" ]; then
50 | "Pods/VersionIcon/Bin/VersionIcon" --resources "Pods/VersionIcon/Bin" --original
51 | else
52 | "Pods/VersionIcon/Bin/VersionIcon" --ribbon Blue-TopRight.png --title Devel-TopRight.png --resources "Pods/VersionIcon/Bin" --strokeWidth 0.07
53 | fi
54 | ```
55 | * If your projects contains different configuration names, you'll need to adjust the script.
56 | * Move this script phase above the Copy Bundle Resources phase.
57 | * If you need to use your own ribbon or title asset, you can specify full path to image file
58 |
59 | ## Parameters
60 | #### Ribbon Style
61 | * `--ribbon `
62 | * Icon ribbon. The folder Ribbons contains variety of ribbons .png files with different colors and positions. You can also specify the absolute path to your custom .png.
63 |
64 | * `--title `
65 | * The title on ribbon. You can choose from a several predefined titles in different positions in Titles folder. Or you can provide absolute path to your custom ribbon title image. (Ribbon titles are images with transparency, custom text is not supported yet)
66 |
67 | #### Icon version Title
68 | * `--fillColor `
69 | * The fill color of version title in `#xxxxxx` hexa format. Default fill color is white ('#FFFFFF').
70 |
71 | * `--strokeColor `
72 | * The stroke color of version title in `#xxxxxx` hexa format. Default stroke color is black ('#000000').
73 |
74 | * `--strokeWidth `
75 | * The title stroke width related to icon width. Default value of stroke width is '0.03'.
76 |
77 | * `--font `
78 | * Font used for version title. Default font is 'Impact'.
79 |
80 | * `--titleSize `
81 | * Version title size related to icon width. Default title size is '0.2'.
82 |
83 | * `--horizontalTitlePosition `
84 | * Version title position related to icon width. Default = '0.5'.
85 |
86 | * `--verticalTitlePosition `
87 | * Version title position related to icon width. Default = '0.2'.
88 |
89 | * `--titleAlignment `
90 | * Possible values are left, center, right. Default = 'center'.
91 |
92 | * `--versionStyle `
93 | * Possible values are _dash_, _parenthesis_, _versionOnly_, _buildOnly_. Default = 'dash'.
94 |
95 | #### Script Setup
96 | * `--resources `
97 | * Default path where Ribbons and Titles folders are located. It is not necessary to set when script is executed as a build phase in Xcode
98 |
99 | * `--original`
100 | * If you need to use just original icon without any modifications, use this parameter. The production app typically has no icon overlay.
101 |
102 | * `--help`
103 | * Full description of parameters is available when you run VersionIcon with `--help` parameter
104 |
105 | ## Debugging
106 |
107 | If you want to modify the behavior and debug VersionIcon in context of your project, you need a special setup of the scheme. The screenshot shows the commandline arguments passed on launch. These parameters can be copied from the existing VersionIcon call build phase. And three environment variables that are necessary to propagate. The values of these environment are visible in the Xcode's Report navigator. All checkboxes should be on.
108 |
109 |
110 |
111 |
112 |
113 |
114 | ## Contributing
115 |
116 | Issues and pull requests are welcome!
117 |
118 | ## Author
119 |
120 | * Daniel Čech [GitHub](https://github.com/DanielCech)
121 |
122 | ## License
123 |
124 | VersionIcon is released under the MIT license. See [LICENSE](https://github.com/DanielCech/DeallocTests/blob/master/LICENSE) for details.
125 |
126 |
--------------------------------------------------------------------------------
/Sources/VersionIcon/VersionIconDefinitions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VersionIconDefinitions.swift
3 | // VersionIcon
4 | //
5 | // Created by Daniel Cech on 05/06/2020.
6 | //
7 |
8 | import AppKit
9 | import Files
10 | import Foundation
11 | import Moderator
12 | import ScriptToolkit
13 | import SwiftShell
14 |
15 | /// Icon overlay design style description
16 | struct DesignStyle {
17 | var ribbon: String?
18 | var title: String?
19 | var titleFillColor: NSColor
20 | var titleStrokeColor: NSColor
21 | var titleStrokeWidth: Double
22 | var titleFont: String
23 | var titleSizeRatio: Double
24 | var horizontalTitlePositionRatio: Double
25 | var verticalTitlePositionRatio: Double
26 | var titleAlignment: String
27 | var versionStyle: String
28 | }
29 |
30 | /// Information about script running context
31 | struct ScriptSetup {
32 | var appIcon: String
33 | var appIconOriginal: String
34 | var resourcesPath: String
35 | }
36 |
37 | /// Information about the modified app
38 | struct AppSetup {
39 | var sourceRootPath: String
40 | var projectDir: String
41 | var infoPlistFile: String
42 | var appIconFolder: Folder
43 | var appIconContents: IconMetadata
44 | var originalAppIconFolder: Folder
45 | var originalAppIconContents: IconMetadata
46 | }
47 |
48 | // MARK: - Image JSON structs
49 |
50 | /// Structure of Contents.json
51 | struct IconMetadata: Codable {
52 | var images: [ImageInfo]
53 |
54 | func imageInfo(forSize size: String, scale: String) -> ImageInfo? {
55 | for image in images {
56 | if image.fits(size: size) && image.fits(scale: scale) {
57 | return image
58 | }
59 | }
60 | return nil
61 | }
62 | }
63 |
64 | /// Image description structure
65 | struct ImageInfo: Codable {
66 | var size: String
67 | var idiom: String
68 | var filename: String?
69 | var scale: String?
70 |
71 | static let singleScale = "1x"
72 |
73 | func fits(size: String) -> Bool {
74 | self.size == size
75 | }
76 |
77 | func fits(scale: String?) -> Bool {
78 | self.scale ?? ImageInfo.singleScale == scale
79 | }
80 | }
81 |
82 | // MARK: - Helpers
83 |
84 | /// Getting information about the app with modified icon
85 | func getAppSetup(scriptSetup: ScriptSetup) throws -> AppSetup {
86 | #if DEBUGGING
87 | let sourceRootPath = "/Users/danielcech/Documents/ios-project-template"
88 | let projectDir = "/Users/danielcech/Documents/ios-project-template"
89 | let infoPlistFile = "Example/Application/Info.plist"
90 | #else
91 | guard
92 | let sourceRootPath = main.env["SRCROOT"],
93 | let projectDir = main.env["PROJECT_DIR"],
94 | let infoPlistFile = main.env["INFOPLIST_FILE"]
95 | else {
96 | print("Missing environment variables")
97 | throw ScriptError.moreInfoNeeded(message: "Missing required environment variables: SRCROOT, PROJECT_DIR, INFOPLIST_FILE. Please run script from Xcode script build phase.")
98 | }
99 | #endif
100 |
101 | print(" sourceRootPath: \(sourceRootPath)")
102 | print(" projectDir: \(projectDir)")
103 | print(" infoPlistFile: \(infoPlistFile)")
104 |
105 | let sourceFolder = try Folder(path: sourceRootPath)
106 |
107 | guard let appIconFolder = sourceFolder.findFirstFolder(name: "\(scriptSetup.appIcon).appiconset") else {
108 | throw ScriptError.folderNotFound(message: "\(scriptSetup.appIcon).appiconset - icon asset folder")
109 | }
110 |
111 | guard let originalAppIconFolder = sourceFolder.findFirstFolder(name: "\(scriptSetup.appIconOriginal).appiconset") else {
112 | throw ScriptError.folderNotFound(message: "\(scriptSetup.appIconOriginal).appiconset - source icon asset for modifications")
113 | }
114 |
115 | return try AppSetup(
116 | sourceRootPath: sourceRootPath,
117 | projectDir: projectDir,
118 | infoPlistFile: infoPlistFile,
119 | appIconFolder: appIconFolder,
120 | appIconContents: iconMetadata(iconFolder: appIconFolder),
121 | originalAppIconFolder: originalAppIconFolder,
122 | originalAppIconContents: iconMetadata(iconFolder: originalAppIconFolder)
123 | )
124 | }
125 |
126 | /// Getting information about the app icon images
127 | func iconMetadata(iconFolder: Folder) throws -> IconMetadata {
128 | let contentsFile = try iconFolder.file(named: "Contents.json")
129 | let jsonData = try contentsFile.read()
130 | do {
131 | let iconMetadata = try JSONDecoder().decode(IconMetadata.self, from: jsonData)
132 | return iconMetadata
133 | } catch {
134 | throw ScriptError.generalError(message: String(describing: error))
135 | }
136 | }
137 |
138 | /// Get current version and build of the app in prefered format
139 | func getVersionText(appSetup: AppSetup, designStyle: DesignStyle) -> String {
140 | #if DEBUGGING
141 | return "1.0 - 20"
142 | #endif
143 |
144 | let versionNumberResult = run("/usr/libexec/PlistBuddy", "-c", "Print CFBundleShortVersionString", appSetup.infoPlistFile)
145 | let buildNumberResult = run("/usr/libexec/PlistBuddy", "-c", "Print CFBundleVersion", appSetup.infoPlistFile)
146 |
147 | var versionNumber = versionNumberResult.stdout
148 | if versionNumber == "$(MARKETING_VERSION)" {
149 | versionNumber = main.env["MARKETING_VERSION"] ?? ""
150 | }
151 |
152 | var buildNumber = buildNumberResult.stdout
153 | if buildNumber == "$(CURRENT_PROJECT_VERSION)" {
154 | buildNumber = main.env["CURRENT_PROJECT_VERSION"] ?? ""
155 | }
156 |
157 | switch designStyle.versionStyle {
158 | case "dash":
159 | return "\(versionNumber) - \(buildNumber)"
160 | case "parenthesis":
161 | return "\(versionNumber)(\(buildNumber))"
162 | case "versionOnly":
163 | return "\(versionNumber)"
164 | case "buildOnly":
165 | return "\(buildNumber)"
166 | default:
167 | return ""
168 | }
169 | }
170 |
171 | /// Image resizing
172 | func resizeImage(image: NSImage?, size: CGSize) -> NSImage? {
173 | guard let originalImage = image else {
174 | return nil
175 | }
176 |
177 | return resizeImageInternal(originalImage: originalImage, size: size)
178 | }
179 |
180 | /// Function to resize an image from a file
181 | func resizeImage(fileName: String?, size: CGSize) -> NSImage? {
182 | guard let path = fileName, let originalImage = NSImage(contentsOfFile: path) else {
183 | return nil
184 | }
185 |
186 | return resizeImageInternal(originalImage: originalImage, size: size)
187 | }
188 |
189 | /// Internal helper function to resize an image
190 | private func resizeImageInternal(originalImage: NSImage, size: CGSize) -> NSImage {
191 | // Create a new bitmap with exact pixel dimensions
192 | let bitmapRep = NSBitmapImageRep(
193 | bitmapDataPlanes: nil,
194 | pixelsWide: Int(size.width),
195 | pixelsHigh: Int(size.height),
196 | bitsPerSample: 8,
197 | samplesPerPixel: 4,
198 | hasAlpha: true,
199 | isPlanar: false,
200 | colorSpaceName: .calibratedRGB,
201 | bytesPerRow: 0,
202 | bitsPerPixel: 0
203 | )!
204 |
205 | bitmapRep.size = size
206 |
207 | NSGraphicsContext.saveGraphicsState()
208 | NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
209 |
210 | originalImage.draw(in: NSRect(origin: .zero, size: size),
211 | from: NSRect(origin: .zero, size: originalImage.size),
212 | operation: .copy,
213 | fraction: 1.0)
214 |
215 | NSGraphicsContext.restoreGraphicsState()
216 |
217 | // Create a new NSImage with the exact representation
218 | let newImage = NSImage(size: size)
219 | newImage.addRepresentation(bitmapRep)
220 |
221 | return newImage
222 | }
223 |
224 | /// Generate icon overlay
225 | func generateIcon(
226 | size: String,
227 | scale: String,
228 | realSize: CGSize,
229 | designStyle: DesignStyle,
230 | scriptSetup: ScriptSetup,
231 | appSetup: AppSetup
232 | ) throws {
233 | guard
234 | let originalAppIconFileName = appSetup.originalAppIconContents.imageInfo(forSize: size, scale: scale)?.filename,
235 | let originalAppIconFile = appSetup.originalAppIconFolder.findFirstFile(name: originalAppIconFileName)
236 | else {
237 | return
238 | }
239 |
240 | try restoreIcon(size: size, scale: scale, scriptSetup: scriptSetup, appSetup: appSetup)
241 |
242 | guard
243 | let appIconFileName = appSetup.appIconContents.imageInfo(forSize: size, scale: scale)?.filename,
244 | let appIconFile = appSetup.appIconFolder.findFirstFile(name: appIconFileName)
245 | else { return }
246 |
247 | print(appIconFileName.lastPathComponent)
248 |
249 | let version = getVersionText(appSetup: appSetup, designStyle: designStyle)
250 |
251 | let newSize = CGSize(
252 | width: realSize.width,
253 | height: realSize.height
254 | )
255 |
256 | // Resizing ribbon
257 | let resizedRibbonImage = resizeImage(fileName: designStyle.ribbon, size: newSize)
258 |
259 | // Resizing title
260 | let resizedTitleImage = resizeImage(fileName: designStyle.title, size: newSize)
261 |
262 | guard
263 | let iconImageData = try? Data(contentsOf: URL(fileURLWithPath: originalAppIconFile.path))
264 | else {
265 | return
266 | }
267 |
268 | let iconImage = NSImage(size: NSSize(width: 1024, height: 1024))
269 | if let bitmap = NSBitmapImageRep(data: iconImageData) {
270 | iconImage.addRepresentation(bitmap)
271 | }
272 |
273 | var combinedImage = iconImage
274 | if let unwrappedResizedRibbonImage = resizedRibbonImage {
275 | combinedImage = try combinedImage.combine(withImage: unwrappedResizedRibbonImage)
276 | }
277 | if let unwrappedResizedTitleImage = resizedTitleImage {
278 | combinedImage = try combinedImage.combine(withImage: unwrappedResizedTitleImage)
279 | }
280 |
281 | // Annotating
282 | let resultImage = try combinedImage.annotate(
283 | text: version,
284 | font: designStyle.titleFont,
285 | size: realSize.width * CGFloat(designStyle.titleSizeRatio),
286 | horizontalTitlePosition: CGFloat(designStyle.horizontalTitlePositionRatio),
287 | verticalTitlePosition: CGFloat(designStyle.verticalTitlePositionRatio),
288 | titleAlignment: designStyle.titleAlignment,
289 | fill: designStyle.titleFillColor,
290 | stroke: designStyle.titleStrokeColor,
291 | strokeWidth: CGFloat(designStyle.titleStrokeWidth)
292 | )
293 |
294 | guard let resizedIcon = resizeImage(image: resultImage, size: newSize) else { return }
295 |
296 | try resizedIcon.savePNGRepresentationToURL(url: URL(fileURLWithPath: appIconFile.path), onlyChange: true)
297 | }
298 |
299 | /// Restore icon from original folder
300 | func restoreIcon(
301 | size: String,
302 | scale: String,
303 | scriptSetup _: ScriptSetup,
304 | appSetup: AppSetup
305 | ) throws {
306 | guard
307 | let originalAppIconFileName = appSetup.originalAppIconContents.imageInfo(forSize: size, scale: scale)?.filename,
308 | let originalAppIconImageFile = appSetup.originalAppIconFolder.findFirstFile(name: originalAppIconFileName)
309 | else {
310 | return
311 | }
312 |
313 | guard
314 | let appIconFileName = appSetup.appIconContents.imageInfo(forSize: size, scale: scale)?.filename
315 | else {
316 | print(" Icon with size \(size):\(scale) not found")
317 | return
318 | }
319 |
320 | let appIconFilePath = appSetup.appIconFolder.path.appendingPathComponent(path: appIconFileName)
321 |
322 | if FileManager.default.fileExists(atPath: appIconFilePath) {
323 | try FileManager.default.removeItem(atPath: appIconFilePath)
324 | }
325 |
326 | let originalFile = try originalAppIconImageFile.copy(to: appSetup.appIconFolder)
327 | try originalFile.rename(to: appIconFileName)
328 | }
329 |
--------------------------------------------------------------------------------
/Sources/VersionIcon/main.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 | import Files
3 | import Foundation
4 | import Moderator
5 | import ScriptToolkit
6 | import SwiftShell
7 |
8 | // ========================================================================================================================================
9 |
10 | // MARK: - Main script
11 |
12 | let moderator = Moderator(description: "VersionIcon prepares iOS icon with ribbon, text and version info overlay")
13 | moderator.usageFormText = "versionIcon "
14 |
15 | // ScriptSetup elements
16 |
17 | let appIcon = moderator.add(Argument
18 | .optionWithValue("appIcon", name: "The name of app icon asset", description: "The asset that is modified by script.").default("AppIcon"))
19 |
20 | let appIconOriginal = moderator.add(Argument
21 | .optionWithValue("appIconOriginal", name: "The name of original app icon asset", description: "This asset is used as backup of original icon.").default("AppIconOriginal"))
22 |
23 | // DesignStyle elements
24 |
25 | var ribbon = moderator.add(Argument
26 | .optionWithValue("ribbon", name: "Icon ribbon color", description: "Name of PNG file in Ribbons folder or absolute path to Ribbon image"))
27 |
28 | var title = moderator.add(Argument
29 | .optionWithValue("title", name: "Icon ribbon title", description: "Name of PNG file in Titles folder or absolute path to Title image"))
30 |
31 | let titleFillColor = moderator.add(Argument
32 | .optionWithValue("fillColor", name: "Title fill color", description: "The fill color of version title in #xxxxxx hexa format.").default("#FFFFFF"))
33 |
34 | let titleStrokeColor = moderator.add(Argument
35 | .optionWithValue("strokeColor", name: "Title stroke color", description: "The stroke color of version title in #xxxxxx hexa format.").default("#000000"))
36 |
37 | let titleStrokeWidth = moderator.add(Argument
38 | .optionWithValue("strokeWidth", name: "Version Title Stroke Width", description: "Version title stroke width related to icon width.").default("0.03"))
39 |
40 | let titleFont = moderator.add(Argument
41 | .optionWithValue("font", name: "Version label font", description: "Font used for version title.").default("Impact"))
42 |
43 | let titleSizeRatio = moderator.add(Argument
44 | .optionWithValue("titleSize", name: "Version Title Size Ratio", description: "Version title size related to icon width.").default("0.25"))
45 |
46 | let horizontalTitlePositionRatio = moderator.add(Argument
47 | .optionWithValue("horizontalTitlePosition", name: "Version Title Size Ratio", description: "Version title position related to icon width.").default("0.5"))
48 |
49 | let verticalTitlePositionRatio = moderator.add(Argument
50 | .optionWithValue("verticalTitlePosition", name: "Version Title Size Ratio", description: "Version title position related to icon width.").default("0.2"))
51 |
52 | let titleAlignment = moderator.add(Argument
53 | .optionWithValue("titleAlignment", name: "Version Title Text Alignment", description: "Possible values are left, center, right.").default("center"))
54 |
55 | let versionStyle = moderator.add(Argument
56 | .optionWithValue("versionStyle", name: "The format of version label", description: "Possible values are dash, parenthesis, versionOnly, buildOnly.").default("dash"))
57 |
58 | // AppSetup elements
59 |
60 | let resourcesPath = moderator.add(Argument
61 | .optionWithValue("resources", name: "VersionIcon resources path", description: "Default path where Ribbons and Titles folders are located. It is not necessary to set when script is executed as a build phase in Xcode"))
62 |
63 | let original = moderator.add(.option("original", description: "Use original icon with no modifications (for production)"))
64 |
65 | let help = moderator.add(.option("help", description: "Shows this info summary"))
66 |
67 | do {
68 | try moderator.parse()
69 |
70 | print("⌚️ Processing")
71 |
72 | if help.value {
73 | print(moderator.usagetext)
74 | exit(0)
75 | }
76 |
77 | guard let resourcesPath = resourcesPath.value ?? main.env["PODS_ROOT"]?.appendingPathComponent(path: "VersionIcon/Bin") else {
78 | throw ScriptError.argumentError(message: "You must specify the resources path using --resourcesPath parameter")
79 | }
80 |
81 | let scriptSetup = ScriptSetup(appIcon: appIcon.value, appIconOriginal: appIconOriginal.value, resourcesPath: resourcesPath)
82 | let appSetup = try getAppSetup(scriptSetup: scriptSetup)
83 |
84 | guard !original.value else {
85 | // iPhone App Icon @2x
86 | try restoreIcon(
87 | size: "60x60",
88 | scale: "2x",
89 | scriptSetup: scriptSetup,
90 | appSetup: appSetup
91 | )
92 |
93 | // iPhone App Icon @3x
94 | try restoreIcon(
95 | size: "60x60",
96 | scale: "3x",
97 | scriptSetup: scriptSetup,
98 | appSetup: appSetup
99 | )
100 |
101 | // iPad App Icon @2x
102 | try restoreIcon(
103 | size: "76x76",
104 | scale: "2x",
105 | scriptSetup: scriptSetup,
106 | appSetup: appSetup
107 | )
108 |
109 | // iPad Pro App Icon @2x
110 | try restoreIcon(
111 | size: "83.5x83.5",
112 | scale: "2x",
113 | scriptSetup: scriptSetup,
114 | appSetup: appSetup
115 | )
116 |
117 | // Universal iOS marketing icon
118 | try restoreIcon(
119 | size: "1024x1024",
120 | scale: "1x",
121 | scriptSetup: scriptSetup,
122 | appSetup: appSetup
123 | )
124 |
125 | exit(0)
126 | }
127 |
128 | if let unwrappedRibbon = ribbon.value, unwrappedRibbon.lastPathComponent == unwrappedRibbon {
129 | ribbon.value = resourcesPath.appendingPathComponent(path: "Ribbons/\(unwrappedRibbon)")
130 | }
131 |
132 | if let unwrappedTitle = title.value, unwrappedTitle.lastPathComponent == unwrappedTitle {
133 | title.value = resourcesPath.appendingPathComponent(path: "Titles/\(unwrappedTitle)")
134 | }
135 |
136 | guard let convertedTitleSizeRatio = Double(titleSizeRatio.value) else { throw ScriptError.argumentError(message: "Invalid titlesize argument") }
137 | guard let convertedHorizontalTitlePosition = Double(horizontalTitlePositionRatio.value) else { throw ScriptError.argumentError(message: "Invalid horizontalTitlePosition argument") }
138 | guard let convertedVerticalTitlePosition = Double(verticalTitlePositionRatio.value) else { throw ScriptError.argumentError(message: "Invalid verticalTitlePosition argument") }
139 | guard titleAlignment.value == "left" || titleAlignment.value == "center" || titleAlignment.value == "right" else { throw ScriptError.argumentError(message: "Invalid titleAlignment argument") }
140 | guard versionStyle.value == "dash" || versionStyle.value == "parenthesis" || versionStyle.value == "versionOnly" || versionStyle.value == "buildOnly" else { throw ScriptError.argumentError(message: "Invalid versionStyle argument") }
141 | guard let convertedTitleFillColor = NSColor(hexString: titleFillColor.value) else { throw ScriptError.argumentError(message: "Invalid fillcolor argument") }
142 | guard let convertedTitleStrokeColor = NSColor(hexString: titleStrokeColor.value) else { throw ScriptError.argumentError(message: "Invalid strokecolor argument") }
143 | guard let convertedTitleStrokeWidth = Double(titleStrokeWidth.value) else { throw ScriptError.argumentError(message: "Invalid strokewidth argument") }
144 |
145 | let designStyle = DesignStyle(
146 | ribbon: ribbon.value,
147 | title: title.value,
148 | titleFillColor: convertedTitleFillColor,
149 | titleStrokeColor: convertedTitleStrokeColor,
150 | titleStrokeWidth: convertedTitleStrokeWidth,
151 | titleFont: titleFont.value,
152 | titleSizeRatio: convertedTitleSizeRatio,
153 | horizontalTitlePositionRatio: convertedHorizontalTitlePosition,
154 | verticalTitlePositionRatio: convertedVerticalTitlePosition,
155 | titleAlignment: titleAlignment.value,
156 | versionStyle: versionStyle.value
157 | )
158 |
159 | // iPhone App Icon @2x
160 | try generateIcon(
161 | size: "60x60",
162 | scale: "2x",
163 | realSize: CGSize(width: 120, height: 120),
164 | designStyle: designStyle,
165 | scriptSetup: scriptSetup,
166 | appSetup: appSetup
167 | )
168 |
169 | // iPhone App Icon @3x
170 | try generateIcon(
171 | size: "60x60",
172 | scale: "3x",
173 | realSize: CGSize(width: 180, height: 180),
174 | designStyle: designStyle,
175 | scriptSetup: scriptSetup,
176 | appSetup: appSetup
177 | )
178 |
179 | // iPad App Icon @2x
180 | try generateIcon(
181 | size: "76x76",
182 | scale: "2x",
183 | realSize: CGSize(width: 152, height: 152),
184 | designStyle: designStyle,
185 | scriptSetup: scriptSetup,
186 | appSetup: appSetup
187 | )
188 |
189 | // iPad Pro App Icon @2x
190 | try generateIcon(
191 | size: "83.5x83.5",
192 | scale: "2x",
193 | realSize: CGSize(width: 167, height: 167),
194 | designStyle: designStyle,
195 | scriptSetup: scriptSetup,
196 | appSetup: appSetup
197 | )
198 |
199 | // Universal iOS marketing icon
200 | try generateIcon(
201 | size: "1024x1024",
202 | scale: "1x",
203 | realSize: CGSize(width: 1024, height: 1024),
204 | designStyle: designStyle,
205 | scriptSetup: scriptSetup,
206 | appSetup: appSetup
207 | )
208 |
209 | print("✅ Done")
210 | } catch {
211 | if let printableError = error as? PrintableError { print(printableError.errorDescription) }
212 | else {
213 | print(error.localizedDescription)
214 | }
215 |
216 | exit(Int32(error._code))
217 | }
218 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import VersionIconTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += VersionIconTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/VersionIconTests/VersionIconTests.swift:
--------------------------------------------------------------------------------
1 | import class Foundation.Bundle
2 | import XCTest
3 |
4 | final class VersionIconTests: XCTestCase {
5 | func testExample() throws {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 |
10 | // Some of the APIs that we use below are available in macOS 10.13 and above.
11 | guard #available(macOS 10.13, *) else {
12 | return
13 | }
14 |
15 | let fooBinary = productsDirectory.appendingPathComponent("VersionIcon")
16 |
17 | let process = Process()
18 | process.executableURL = fooBinary
19 |
20 | let pipe = Pipe()
21 | process.standardOutput = pipe
22 |
23 | try process.run()
24 | process.waitUntilExit()
25 |
26 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
27 | let output = String(data: data, encoding: .utf8)
28 |
29 | XCTAssertEqual(output, "Hello, world!\n")
30 | }
31 |
32 | /// Returns path to the built products directory.
33 | var productsDirectory: URL {
34 | #if os(macOS)
35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
36 | return bundle.bundleURL.deletingLastPathComponent()
37 | }
38 | fatalError("couldn't find the products directory")
39 | #else
40 | return Bundle.main.bundleURL
41 | #endif
42 | }
43 |
44 | static var allTests = [
45 | ("testExample", testExample),
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/Tests/VersionIconTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !os(macOS)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(VersionIconTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/VersionIcon.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'VersionIcon'
3 | s.module_name = 'VersionIcon'
4 | s.version = '1.0.9'
5 | s.summary = 'Script written in Swift that prepares the iOS app icon overlay with ribbon, build type and version (build) info'
6 | s.homepage = 'https://github.com/DanielCech/VersionIcon'
7 | s.license = 'MIT'
8 | s.author = { "Daniel Cech" => "daniel.cech@gmail.com" }
9 | s.platform = :ios, '8.0'
10 | s.ios.deployment_target = '8.0'
11 | s.requires_arc = true
12 | s.source = { :git => 'https://github.com/strvcom/ios-version-icon.git', :tag => s.version.to_s }
13 | s.preserve_paths = 'Bin/**/*'
14 | s.swift_version = '4.0'
15 | end
16 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | swift build
3 | cp -rf .build/arm64-apple-macosx/debug/VersionIcon Bin
4 |
--------------------------------------------------------------------------------