├── .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 | [![Version](https://img.shields.io/cocoapods/v/VersionIcon.svg?style=flat)](https://cocoapods.org/pods/VersionIcon) 2 | [![License](https://img.shields.io/cocoapods/l/VersionIcon.svg?style=flat)](https://cocoapods.org/pods/VersionIcon) 3 | 4 |

5 | VersionIcon 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 <Title stroke color>` 72 | * The stroke color of version title in `#xxxxxx` hexa format. Default stroke color is black ('#000000'). 73 | 74 | * `--strokeWidth <Version Title Stroke Width>` 75 | * The title stroke width related to icon width. Default value of stroke width is '0.03'. 76 | 77 | * `--font <Version label font>` 78 | * Font used for version title. Default font is 'Impact'. 79 | 80 | * `--titleSize <Version Title Size Ratio>` 81 | * Version title size related to icon width. Default title size is '0.2'. 82 | 83 | * `--horizontalTitlePosition <Version Title Size Ratio>` 84 | * Version title position related to icon width. Default = '0.5'. 85 | 86 | * `--verticalTitlePosition <Version Title Size Ratio>` 87 | * Version title position related to icon width. Default = '0.2'. 88 | 89 | * `--titleAlignment <Version Title Text Alignment>` 90 | * Possible values are left, center, right. Default = 'center'. 91 | 92 | * `--versionStyle <The format of version label>` 93 | * Possible values are _dash_, _parenthesis_, _versionOnly_, _buildOnly_. Default = 'dash'. 94 | 95 | #### Script Setup 96 | * `--resources <VersionIcon resources path>` 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 | <p align="center"> 110 | <img src="https://i.ibb.co/5XC6fT9p/Scheme-Setup.png" width="936" max-width="534" alt="Scheme" /> 111 | </p> 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 <params>" 14 | 15 | // ScriptSetup elements 16 | 17 | let appIcon = moderator.add(Argument<String?> 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<String?> 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<String?> 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<String?> 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<String?> 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<String?> 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<String?> 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<String?> 41 | .optionWithValue("font", name: "Version label font", description: "Font used for version title.").default("Impact")) 42 | 43 | let titleSizeRatio = moderator.add(Argument<String?> 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<String?> 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<String?> 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<String?> 53 | .optionWithValue("titleAlignment", name: "Version Title Text Alignment", description: "Possible values are left, center, right.").default("center")) 54 | 55 | let versionStyle = moderator.add(Argument<String?> 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<String?> 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 | --------------------------------------------------------------------------------