├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── docc.yml ├── .gitignore ├── .swiftlint.yml ├── LICENSE ├── Package.swift ├── README.md ├── RELEASE_NOTES.md ├── Resources ├── Icon.png └── Preview_Grid.png ├── Sources └── BadgeIcon │ ├── BadgeIcon+Predefined.swift │ ├── BadgeIcon.docc │ ├── BadgeIcon.md │ └── Resources │ │ ├── Logo.png │ │ └── Preview.png │ ├── BadgeIcon.swift │ ├── BadgeIconStyle.swift │ ├── Color+Hex.swift │ └── Image+Symbol.swift ├── Tests └── BadgeIconTests │ └── BadgeIconTests.swift ├── package_version.sh └── scripts ├── build.sh ├── chmod.sh ├── docc.sh ├── framework.sh ├── git_default_branch.sh ├── package_docc.sh ├── package_framework.sh ├── package_name.sh ├── package_version.sh ├── sync_from.sh ├── test.sh ├── version.sh ├── version_bump.sh ├── version_number.sh ├── version_validate_git.sh └── version_validate_target.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [danielsaidi] 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds and tests the project. 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Build Runner 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | pull_request: 10 | branches: ["main"] 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-15 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: maxim-lobanov/setup-xcode@v1 18 | with: 19 | xcode-version: latest-stable 20 | - name: Build all platforms 21 | run: bash scripts/build.sh ${{ github.event.repository.name }} 22 | - name: Test iOS 23 | run: bash scripts/test.sh ${{ github.event.repository.name }} 24 | -------------------------------------------------------------------------------- /.github/workflows/docc.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds publish DocC docs to GitHub Pages. 2 | # Source: https://maxxfrazer.medium.com/deploying-docc-with-github-actions-218c5ca6cad5 3 | # Sample: https://github.com/AgoraIO-Community/VideoUIKit-iOS/blob/main/.github/workflows/deploy_docs.yml 4 | 5 | name: DocC Runner 6 | 7 | on: 8 | push: 9 | branches: ["main"] 10 | 11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | # Allow one concurrent deployment 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | deploy: 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | runs-on: macos-15 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - id: pages 32 | name: Setup Pages 33 | uses: actions/configure-pages@v4 34 | - name: Select Xcode version 35 | uses: maxim-lobanov/setup-xcode@v1 36 | with: 37 | xcode-version: latest-stable 38 | - name: Build DocC 39 | run: bash scripts/docc.sh ${{ github.event.repository.name }} 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v3 42 | with: 43 | path: '.build/docs-iOS' 44 | - id: deployment 45 | name: Deploy to GitHub Pages 46 | uses: actions/deploy-pages@v4 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPM defaults 2 | .DS_Store 3 | /.build 4 | /Packages 5 | .swiftpm/ -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - type_name 4 | - vertical_whitespace 5 | 6 | included: 7 | - Sources 8 | - Tests 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 Daniel Saidi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "BadgeIcon", 7 | platforms: [ 8 | .iOS(.v16), 9 | .tvOS(.v16), 10 | .watchOS(.v9), 11 | .macOS(.v13), 12 | .visionOS(.v1) 13 | ], 14 | products: [ 15 | .library( 16 | name: "BadgeIcon", 17 | targets: ["BadgeIcon"] 18 | ) 19 | ], 20 | targets: [ 21 | .target( 22 | name: "BadgeIcon" 23 | ), 24 | .testTarget( 25 | name: "BadgeIconTests", 26 | dependencies: ["BadgeIcon"] 27 | ) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Project Icon 3 |

4 | 5 |

6 | Version 7 | Swift 6.0 8 | Swift UI 9 | Documentation 10 | MIT License 11 |

12 | 13 | 14 | # BadgeIcon 15 | 16 | BadgeIcon is a SwiftUI library that helps you create beautiful, scalable icons, using SF Symbols or custom assets: 17 | 18 |

19 | BadgeIcon Preview 20 |

21 | 22 | BadgeIcon comes with 49 predefined icons, and lets you create custom icons with rich customization options. 23 | 24 | 25 | 26 | 27 | ## Installation 28 | 29 | BadgeIcon can be installed with the Swift Package Manager: 30 | 31 | ``` 32 | https://github.com/danielsaidi/BadgeIcon.git 33 | ``` 34 | 35 | 36 | 37 | ## Getting started 38 | 39 | BadgeIcon has 49 predefined icons, like `.alert`, `.bug`, and `.heart`, which will scale to fill the available space: 40 | 41 | ```swift 42 | struct ContentView: View { 43 | 44 | var body: some View { 45 | BadgeIcon.calendar 46 | BadgeIcon.heart.frame(width: 150) 47 | } 48 | } 49 | ``` 50 | 51 | You can also create your own badge icons, with a rich set of icon and badge style options: 52 | 53 | ```swift 54 | extension BadgeIcon { 55 | 56 | public static let prominentError = Self( 57 | icon: MyCustomErrorIcon(), 58 | style: .init( 59 | badgeColor: .red 60 | ) 61 | ) 62 | } 63 | ``` 64 | 65 | You can use both `Image` values or custom views as the icon that is shown inside the badge. 66 | 67 | 68 | 69 | ## Documentation 70 | 71 | The online [documentation][Documentation] has more information, articles, code examples, etc. 72 | 73 | 74 | 75 | ## Support my work 76 | 77 | You can [sponsor me][Sponsors] on GitHub Sponsors or [reach out][Email] for paid support, to help support my [open-source projects][OpenSource]. 78 | 79 | Your support makes it possible for me to put more work into these projects and make them the best they can be. 80 | 81 | 82 | 83 | ## Contact 84 | 85 | Feel free to reach out if you have questions or want to contribute in any way: 86 | 87 | * Website: [danielsaidi.com][Website] 88 | * E-mail: [daniel.saidi@gmail.com][Email] 89 | * Bluesky: [@danielsaidi@bsky.social][Bluesky] 90 | * Mastodon: [@danielsaidi@mastodon.social][Mastodon] 91 | 92 | 93 | 94 | ## License 95 | 96 | BadgeIcon is available under the MIT license. See the [LICENSE][License] file for more info. 97 | 98 | 99 | 100 | [Email]: mailto:daniel.saidi@gmail.com 101 | [Website]: https://www.danielsaidi.com 102 | [GitHub]: https://www.github.com/danielsaidi 103 | [Bluesky]: https://bsky.app/profile/danielsaidi.bsky.social 104 | [Twitter]: https://www.twitter.com/danielsaidi 105 | [Mastodon]: https://mastodon.social/@danielsaidi 106 | [Sponsors]: https://github.com/sponsors/danielsaidi 107 | [OpenSource]: https://www.danielsaidi.com/opensource 108 | 109 | [Documentation]: https://danielsaidi.github.io/BadgeIcon/ 110 | [Getting-Started]: https://danielsaidi.github.io/BadgeIcon/documentation/badgeicon/getting-started 111 | [License]: https://github.com/danielsaidi/BadgeIcon/blob/master/LICENSE 112 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | BadgeIcon will use semver after 1.0. 4 | 5 | Until then, breaking changes can happen in any version, and deprecated features may be removed in any minor version bump. 6 | 7 | 8 | 9 | ## 1.0 10 | 11 | This version adds more icons to the library, bringing the number of predefined icons up to 49. 12 | 13 | 14 | 15 | ## 0.6 16 | 17 | This version makes the SDK use Swift 6, enables strict concurrency, and adjusts a few things to conform to this new requirement. 18 | 19 | ### 💡 Adjustments 20 | 21 | * `BadgeIconStyle` is no longer mutable. 22 | * `BadgeIcon+Predefined` now uses static let instead of static var. 23 | 24 | ### 🗑️ Deprecations 25 | 26 | * All previously deprecated code has been removed. 27 | 28 | 29 | 30 | 31 | ## 0.5 32 | 33 | ### ✨ New Features 34 | 35 | * `BadgeIcon` has 7 new icons. 36 | * `BadgeIcon` has new icon templates, like `.icon`. 37 | * `BadgeIcon` now supports generic views as the main icon. 38 | * `BadgeIconStyle` is a new type that is used to provide icon styling. 39 | 40 | ### 🗑️ Deprecations 41 | 42 | * `BadgeIcon` has a new style-based initializer. The old initializer is deprecated. 43 | * `BadgeIcon.key` has been renamed to `passwords`. 44 | 45 | 46 | 47 | ## 0.4 48 | 49 | ### ✨ New Features 50 | 51 | * BadgeIcon now supports visionOS. 52 | 53 | 54 | 55 | ## 0.3 56 | 57 | This version adds more icons and adjusts `BadgeIcon` to use percentage-based sizes and a smaller default padding. 58 | 59 | ### ✨ Features 60 | 61 | * `BadgeIcon` has even more predefined icons. 62 | 63 | ### 💡 Adjustments 64 | 65 | * `BadgeIcon` now uses `0.15` as the default icon padding. 66 | 67 | ### 🐛 Bug Fixes 68 | 69 | * `BadgeIcon` now uses `iconPadding` instead of the incorrect `badgeCornerRadius`. 70 | 71 | 72 | 73 | ## 0.2.1 74 | 75 | This version adds more icons and features. 76 | 77 | ### ✨ Features 78 | 79 | * `BadgeIcon` has even more predefined icons. 80 | * `BadgeIcon` has a new `iconColorScheme` parameter. 81 | 82 | ### 🐛 Bug Fixes 83 | 84 | * `BadgeIcon` now honors custom stroke widths. 85 | 86 | 87 | 88 | ## 0.2 89 | 90 | This version adds more functionality to the `BadgeIcon`. 91 | 92 | `BadgeIcon` no longer takes size parameter, but now automatically fits the available frame and adjust the default padding and corner radius to the available space. 93 | 94 | ### ✨ Features 95 | 96 | * `BadgeIcon` has new `badgeStrokeWidth` and `badgeCornerRadius` properties. 97 | 98 | ### 💥 Breaking Changes 99 | 100 | * `BadgeIcon` doesn't have a size property anymore, but will automatically adjust. 101 | 102 | 103 | 104 | ## 0.1 105 | 106 | This is the first version of the BadgeIcon library. 107 | 108 | ### ✨ Features 109 | 110 | * `BadgeIcon` can be used to create custom badge icons. 111 | * `BadgeIcon` also has a couple of static, predefined icons. 112 | -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/BadgeIcon/aa7969df65837aa6d29872fa80e877b46a945833/Resources/Icon.png -------------------------------------------------------------------------------- /Resources/Preview_Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/BadgeIcon/aa7969df65837aa6d29872fa80e877b46a945833/Resources/Preview_Grid.png -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIcon+Predefined.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeIcon+Predefined.swift 3 | // BadgeIcon 4 | // 5 | // Created by Daniel Saidi on 2023-12-15. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public extension BadgeIcon where Icon == Image { 12 | 13 | static let accessibility = prominent("accessibility", .blue, fill: false) 14 | static let airplaneMode = prominent("airplane", .orange) 15 | static let alert = icon("exclamationmark.triangle", .orange) 16 | static let appStore = icon("apple.logo", .white.opacity(0.6), .black) 17 | static let battery = prominent("battery.100percent", .green) 18 | static let bug = multicolorIcon("ladybug") 19 | static let calendar = icon("calendar", .red) 20 | static let camera = prominent("camera", .gray) 21 | static let checkmark = icon("checkmark.circle", .green) 22 | static let controlCenter = prominent("switch.2", .gray) 23 | static let developer = prominent("hammer", .gray) 24 | static let displayAndBrightness = prominent("sun.max", .blue) 25 | static let email = prominent("envelope", .blue) 26 | static let error = icon("exclamationmark.triangle", .red) 27 | static let export = shareIcon("square.and.arrow.up.on.square") 28 | static let faceId = prominent("faceid", .green) 29 | static let featureRequest = icon("gift", .pink) 30 | static let focus = prominent("moon", .indigo) 31 | static let headphones = prominent("beats.headphones", .gray) 32 | static let heart = icon("heart", .red) 33 | static let passwords = prominent("key", .gray) 34 | static let iCloud = icon("icloud.fill", .cyan.opacity(0.8)) 35 | static let languageSettings = icon("globe", .cyan) 36 | static let lightbulb = multicolorIcon("lightbulb", .yellow) 37 | static let lock = prominent("lock", .gray) 38 | static let message = prominent("message", .green) 39 | static let mobileService = prominent("antenna.radiowaves.left.and.right", .green) 40 | static let notifications = prominent("bell.badge.fill", .red) 41 | static let palette = multicolorIcon("paintpalette") 42 | static let person = Self(icon: .symbol("person")) 43 | static let personalHotspot = prominent("personalhotspot", .green) 44 | static let phone = prominent("phone", .green) 45 | static let premium = icon("crown", .orange) 46 | static let privacy = prominent("hand.raised.fill", .blue) 47 | static let prominentAlert = prominent("exclamationmark.triangle", .orange) 48 | static let prominentCheckmark = prominent("checkmark.circle", .green) 49 | static let prominentError = prominent("exclamationmark.triangle", .red) 50 | static let safari = icon("safari", .blue) 51 | static let screenTime = prominent("hourglass", .indigo) 52 | static let search = prominent("magnifyingglass", .gray) 53 | static let settings = prominent("gearshape", .gray) 54 | static let share = shareIcon("square.and.arrow.up") 55 | static let shield = icon("checkmark.shield.fill", .green) 56 | static let sideButton = prominent("button.vertical.left.press", .blue) 57 | static let soundAndHaptics = prominent("speaker.wave.3", .pink) 58 | static let sos = prominent("sos", .red) 59 | static let star = icon("star", .yellow) 60 | static let touchId = icon("touchid", .pink) 61 | static let wifi = prominent("wifi", .blue) 62 | } 63 | 64 | private extension BadgeIcon where Icon == Image { 65 | 66 | /// An icon with a certain fill color. 67 | static func icon( 68 | _ iconName: String, 69 | _ iconColor: Color, 70 | _ badgeIcon: Color = .white 71 | ) -> Self { 72 | .init( 73 | icon: .symbol(iconName), 74 | style: .init( 75 | iconColor: iconColor, 76 | badgeColor: badgeIcon 77 | ) 78 | ) 79 | } 80 | 81 | /// A multicolor icon has an image icon on a white badge. 82 | static func multicolorIcon( 83 | _ iconName: String, 84 | _ color: Color = .black 85 | ) -> Self { 86 | .init( 87 | icon: .symbol(iconName), 88 | style: .init( 89 | iconColor: color, 90 | iconColorScheme: .light, 91 | iconRenderingMode: .multicolor 92 | ) 93 | ) 94 | } 95 | 96 | /// A prominent icon has a white icon on a colored badge. 97 | static func prominent( 98 | _ iconName: String, 99 | _ badgeColor: Color, 100 | fill: Bool = true 101 | ) -> Self { 102 | .init( 103 | icon: .symbol(iconName), 104 | style: .init( 105 | iconFill: fill, 106 | badgeColor: badgeColor 107 | ) 108 | ) 109 | } 110 | 111 | /// A share icon has a different icon padding and offset. 112 | static func shareIcon( 113 | _ iconName: String 114 | ) -> Self { 115 | .init( 116 | icon: .symbol(iconName), 117 | style: .init( 118 | iconFill: false, 119 | iconOffset: .init(x: 0, y: -0.03), 120 | iconPadding: 0.15 121 | ) 122 | ) 123 | } 124 | } 125 | 126 | @MainActor 127 | @ViewBuilder 128 | private var previewItems: some View { 129 | item(.accessibility, "accessibility") 130 | item(.airplaneMode, "airplaneMode") 131 | item(.alert, "alert") 132 | item(.appStore, "appStore") 133 | item(.battery, "battery") 134 | item(.bug, "bug") 135 | item(.calendar, "calendar") 136 | item(.camera, "camera") 137 | item(.checkmark, "checkmark") 138 | item(.controlCenter, "controlCenter") 139 | item(.displayAndBrightness, "displayAndBrightness") 140 | item(.developer, "developer") 141 | item(.email, "email") 142 | item(.error, "error") 143 | item(.export, "export") 144 | item(.faceId, "faceId") 145 | item(.featureRequest, "featureRequest") 146 | item(.focus, "focus") 147 | item(.headphones, "headphones") 148 | item(.heart, "heart") 149 | item(.iCloud, "iCloud") 150 | item(.languageSettings, "languageSettings") 151 | item(.lightbulb, "lightbulb") 152 | item(.lock, "lock") 153 | item(.message, "messages") 154 | item(.mobileService, "mobileService") 155 | item(.notifications, "notifications") 156 | item(.palette, "palette") 157 | item(.passwords, "passwords") 158 | item(.person, "person") 159 | item(.personalHotspot, "personalHotspot") 160 | item(.phone, "phone") 161 | item(.premium, "premium") 162 | item(.privacy, "privacy") 163 | item(.prominentAlert, "prominentAlert") 164 | item(.prominentCheckmark, "prominentCheckmark") 165 | item(.prominentError, "prominentError") 166 | item(.safari, "safari") 167 | item(.screenTime, "screenTime") 168 | item(.search, "search") 169 | item(.settings, "settings") 170 | item(.share, "share") 171 | item(.shield, "shield") 172 | item(.sideButton, "sideButton") 173 | item(.soundAndHaptics, "soundAndHaptics") 174 | item(.sos, "sos") 175 | item(.star, "star") 176 | item(.touchId, "touchId") 177 | item(.wifi, "wifi") 178 | } 179 | 180 | #Preview("Grid") { 181 | ScrollView(.vertical) { 182 | LazyVGrid(columns: [.init(.adaptive(minimum: 40, maximum: 50))]) { 183 | previewItems 184 | } 185 | .padding() 186 | } 187 | .labelStyle(.iconOnly) 188 | } 189 | 190 | #Preview("List") { 191 | List { 192 | previewItems 193 | } 194 | } 195 | 196 | private func item( 197 | _ view: BadgeIcon, 198 | _ name: String 199 | ) -> some View { 200 | Label( 201 | title: { Text(name) }, 202 | icon: { view } 203 | ) 204 | } 205 | -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIcon.docc/BadgeIcon.md: -------------------------------------------------------------------------------- 1 | # ``BadgeIcon`` 2 | 3 | BadgeIcon is a SwiftUI library that helps you create beautiful, scalable icons. 4 | 5 | 6 | ## Overview 7 | 8 | ![Library logotype](Logo.png) 9 | 10 | BadgeIcon is a SwiftUI library that helps you create beautiful, scalable icons, using SF Symbols or custom assets. 11 | 12 | ![BadgeIcon preview](Preview.png) 13 | 14 | BadgeIcon comes with 49 predefined icons, and lets you create custom icons with rich customization options. 15 | 16 | 17 | 18 | ## Installation 19 | 20 | BadgeIcon can be installed with the Swift Package Manager: 21 | 22 | ``` 23 | https://github.com/danielsaidi/BadgeIcon.git 24 | ``` 25 | 26 | 27 | 28 | ## Getting started 29 | 30 | BadgeIcon has 49 predefined icons, like ``BadgeIcon/alert``, ``BadgeIcon/bug``, and ``BadgeIcon/heart``, which will scale to fill the available space: 31 | 32 | ```swift 33 | struct ContentView: View { 34 | 35 | var body: some View { 36 | BadgeIcon.calendar 37 | BadgeIcon.heart.frame(width: 150) 38 | } 39 | } 40 | ``` 41 | 42 | You can also create your own badge icons, with a rich set of icon and badge style options: 43 | 44 | ```swift 45 | extension BadgeIcon { 46 | 47 | public static let prominentError = Self( 48 | icon: MyCustomErrorIcon(), 49 | style: .init( 50 | badgeColor: .red 51 | ) 52 | ) 53 | } 54 | ``` 55 | 56 | You can use both `Image` values or custom views as the icon that is shown inside the badge. 57 | 58 | 59 | 60 | ## Repository 61 | 62 | For more information, source code, etc., visit the [project repository](https://github.com/danielsaidi/BadgeIcon). 63 | 64 | 65 | 66 | ## License 67 | 68 | BadgeIcon is available under the MIT license. 69 | 70 | 71 | 72 | ## Topics 73 | 74 | ### Essentials 75 | 76 | - ``BadgeIcon/BadgeIcon`` 77 | - ``BadgeIcon/BadgeIconStyle`` 78 | -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIcon.docc/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/BadgeIcon/aa7969df65837aa6d29872fa80e877b46a945833/Sources/BadgeIcon/BadgeIcon.docc/Resources/Logo.png -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIcon.docc/Resources/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/BadgeIcon/aa7969df65837aa6d29872fa80e877b46a945833/Sources/BadgeIcon/BadgeIcon.docc/Resources/Preview.png -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeIcon.swift 3 | // BadgeIcon 4 | // 5 | // Created by Daniel Saidi on 2023-12-15. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /// This view mimics the color badge icons that can be found 12 | /// in e.g. System Settings on iOS and macOS. 13 | /// 14 | /// Note that a custom ``BadgeIconStyle`` can be provided in 15 | /// the initializer and not as an environment value. This is 16 | /// because each icon is unique, which makes the environment 17 | /// a bad tool for applying such styles in this case. 18 | public struct BadgeIcon: View { 19 | 20 | /// Create a badge icon with an image as main icon. 21 | /// 22 | /// - Parameters: 23 | /// - icon: The image to use as icon. 24 | /// - style: The style to apply, by default ``BadgeIconStyle/standard``. 25 | public init( 26 | icon: Image, 27 | style: BadgeIconStyle = .standard 28 | ) where Icon == Image { 29 | self.icon = icon.resizable() 30 | self.style = style 31 | } 32 | 33 | /// Create a badge icon with a custom view as main icon. 34 | /// 35 | /// - Parameters: 36 | /// - iconView: The view to use as icon. 37 | /// - style: The style to apply, by default ``BadgeIconStyle/standard``. 38 | public init( 39 | iconView: Icon, 40 | style: BadgeIconStyle = .standard 41 | ) { 42 | self.icon = iconView 43 | self.style = style 44 | } 45 | 46 | public var icon: Icon 47 | public var style: BadgeIconStyle 48 | 49 | @Environment(\.colorScheme) 50 | private var colorScheme 51 | 52 | public var body: some View { 53 | GeometryReader { geo in 54 | ZStack(alignment: .center) { 55 | badge(style.badgeColor, gradient: style.badgeGradient) 56 | .cornerRadius(cornerRadius(for: geo)) 57 | .overlay( 58 | RoundedRectangle(cornerRadius: cornerRadius(for: geo)) 59 | .stroke(style.badgeStrokeColor, lineWidth: strokeWidth(for: geo)) 60 | ) 61 | .aspectRatio(1, contentMode: .fit) 62 | .overlay( 63 | icon.environment(\.colorScheme, style.iconColorScheme ?? colorScheme) 64 | .aspectRatio(contentMode: .fit) 65 | .symbolRenderingMode(style.iconRenderingMode) 66 | .symbolVariant(style.iconFill ? .fill : .none) 67 | .padding(iconPadding(for: geo)) 68 | .offset(iconOffset(for: geo)) 69 | .foreground(style.iconColor, gradient: style.iconGradient) 70 | ) 71 | } 72 | } 73 | .aspectRatio(1, contentMode: .fit) 74 | } 75 | } 76 | 77 | private extension View { 78 | 79 | func offset(_ point: CGPoint) -> some View { 80 | self.offset(x: point.x, y: point.y) 81 | } 82 | } 83 | 84 | extension BadgeIcon { 85 | 86 | func cornerRadius(for geo: GeometryProxy) -> Double { 87 | style.badgeCornerRadius * geo.size.width 88 | } 89 | 90 | func iconOffset(for geo: GeometryProxy) -> CGPoint { 91 | let width = geo.size.width 92 | let offset = style.iconOffset 93 | return CGPoint( 94 | x: width * offset.x, 95 | y: width * offset.y 96 | ) 97 | } 98 | 99 | func iconPadding(for geo: GeometryProxy) -> Double { 100 | style.iconPadding * geo.size.width 101 | } 102 | 103 | func strokeWidth(for geo: GeometryProxy) -> Double { 104 | max(style.badgeStrokeWidth * geo.size.width, 1) 105 | } 106 | } 107 | 108 | private extension Color { 109 | 110 | func asGradientBackground() -> some View { 111 | Color.clear.overlay(self.gradient) 112 | } 113 | } 114 | 115 | private extension View { 116 | 117 | @ViewBuilder 118 | func badge( 119 | _ color: Color, 120 | gradient condition: Bool 121 | ) -> some View { 122 | if condition { 123 | color.overlay(color.gradient) 124 | } else { 125 | color 126 | } 127 | } 128 | 129 | @ViewBuilder 130 | func foreground( 131 | _ color: Color?, 132 | gradient: Bool 133 | ) -> some View { 134 | if let color, gradient { 135 | self.foregroundStyle(color.gradient) 136 | } else if let color { 137 | self.foregroundStyle(color) 138 | } else { 139 | self 140 | } 141 | } 142 | } 143 | 144 | private extension BadgeIconStyle { 145 | 146 | static var purplePreview: Self { 147 | .init( 148 | iconColor: .yellow, 149 | iconColorScheme: nil, 150 | iconFill: true, 151 | iconGradient: false, 152 | iconOffset: .init(x: -0.4, y: -0.4), 153 | iconPadding: 0.2, 154 | iconRenderingMode: .monochrome, 155 | badgeColor: .indigo, 156 | badgeCornerRadius: 0.4, 157 | badgeGradient: true, 158 | badgeStrokeColor: .teal, 159 | badgeStrokeWidth: 0.05 160 | ) 161 | } 162 | } 163 | 164 | #Preview { 165 | VStack(spacing: 50) { 166 | BadgeIcon(icon: Image.symbol("checkmark")) 167 | .bold() 168 | 169 | // BadgeIcon(icon: Text("A")) 170 | // .bold() 171 | 172 | 173 | BadgeIcon( 174 | icon: .symbol("face.smiling"), 175 | style: .purplePreview 176 | ) 177 | 178 | BadgeIcon( 179 | iconView: Circle(), 180 | style: .purplePreview 181 | ) 182 | 183 | BadgeIcon( 184 | icon: .symbol("checkmark"), 185 | style: .init( 186 | iconColor: .green, 187 | iconColorScheme: nil, 188 | iconFill: true, 189 | iconGradient: true, 190 | iconOffset: .init(x: 0.25, y: -0.25), 191 | iconPadding: 0.1, 192 | iconRenderingMode: .monochrome, 193 | badgeColor: .red, 194 | badgeCornerRadius: 0.3, 195 | badgeGradient: true, 196 | badgeStrokeColor: .blue, 197 | badgeStrokeWidth: 0.05 198 | ) 199 | ) 200 | .bold() 201 | 202 | BadgeIcon.calendar 203 | } 204 | .padding(100) 205 | } 206 | -------------------------------------------------------------------------------- /Sources/BadgeIcon/BadgeIconStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeIconStyle.swift 3 | // BadgeIcon 4 | // 5 | // Created by Daniel Saidi on 2024-04-29. 6 | // Copyright © 2024-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /// This style can be used to style a ``BadgeIcon``. 12 | public struct BadgeIconStyle { 13 | 14 | /// Create a badge icon style. 15 | /// 16 | /// The size-based parameters, like `iconOffset`, should 17 | /// be expressed as a `0.0-1.0` ratio of the icon's size, 18 | /// to let them scale with the icon. For instance, a 0.5 19 | /// `badgeCornerRadius` will make the badge circular. 20 | /// 21 | /// - Parameters: 22 | /// - iconColor: The icon color, by default `semi-black` or `white`. 23 | /// - iconColorScheme: The icon color scheme, by default `nil`. 24 | /// - iconFill: The icon fill mode, by default `true`. 25 | /// - iconGradient: Whether or not to add a gradient to the icon color, by default `true`. 26 | /// - iconOffset: The icon offset, by default `0` of the icon size. 27 | /// - iconPadding: The icon padding, by default `0.15` of the icon size. 28 | /// - iconRenderingMode: The icon symbol rendering mode, by default `.monochrome`. 29 | /// - badgeColor: The badge color, by default `.white`. 30 | /// - badgeCornerRadius: The badge corner radius, by default `0.3` of the icon size. 31 | /// - badgeGradient: Whether or not to add a gradient to the icon color, by default `true`. 32 | /// - badgeStrokeColor: The badge stroke color, if any. 33 | /// - badgeStrokeWidth: The badge stroke width, by default `0.3` of the icon size. 34 | public init( 35 | iconColor: Color? = nil, 36 | iconColorScheme: ColorScheme? = nil, 37 | iconFill: Bool = true, 38 | iconGradient: Bool = true, 39 | iconOffset: CGPoint = .zero, 40 | iconPadding: Double = 0.15, 41 | iconRenderingMode: SymbolRenderingMode = .monochrome, 42 | badgeColor: Color = .white, 43 | badgeCornerRadius: Double = 0.3, 44 | badgeGradient: Bool = true, 45 | badgeStrokeColor: Color? = nil, 46 | badgeStrokeWidth: Double = 0.001 47 | ) { 48 | let whiteBadge = badgeColor == .white 49 | let whiteBadgeStroke = Color.hex(0xe7e7e7) 50 | let whiteBadgeIconColor = Color.black.opacity(0.8) 51 | let fallbackIconColor = whiteBadge ? whiteBadgeIconColor : .white 52 | let fallbackStroke = whiteBadge ? whiteBadgeStroke : .clear 53 | 54 | self.iconColor = iconColor ?? fallbackIconColor 55 | self.iconColorScheme = iconColorScheme 56 | self.iconFill = iconFill 57 | self.iconGradient = iconGradient 58 | self.iconOffset = iconOffset 59 | self.iconPadding = iconPadding 60 | self.iconRenderingMode = iconRenderingMode 61 | self.badgeColor = badgeColor 62 | self.badgeCornerRadius = badgeCornerRadius 63 | self.badgeGradient = badgeGradient 64 | self.badgeStrokeColor = badgeStrokeColor ?? fallbackStroke 65 | self.badgeStrokeWidth = badgeStrokeWidth 66 | } 67 | 68 | /// The icon color. 69 | public var iconColor: Color? 70 | 71 | /// The icon color scheme, if any. 72 | public var iconColorScheme: ColorScheme? 73 | 74 | /// The icon fill mode. 75 | public var iconGradient: Bool 76 | 77 | /// Whether to add a gradient to the icon color. 78 | public var iconFill: Bool 79 | 80 | /// The icon offset. 81 | public var iconOffset: CGPoint 82 | 83 | /// The icon padding. 84 | public var iconPadding: Double 85 | 86 | /// The icon symbol rendering mode. 87 | public var iconRenderingMode: SymbolRenderingMode 88 | 89 | /// The badge color. 90 | public var badgeColor: Color 91 | 92 | /// The badge corner radius. 93 | public var badgeCornerRadius: Double 94 | 95 | /// Whether or not to add a gradient to the icon color. 96 | public var badgeGradient: Bool 97 | 98 | /// The badge stroke color, if any. 99 | public var badgeStrokeColor: Color 100 | 101 | /// The badge stroke width. 102 | public var badgeStrokeWidth: Double 103 | } 104 | 105 | public extension BadgeIconStyle { 106 | 107 | /// The standard badge icon style. 108 | static var standard: Self { .init() } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/BadgeIcon/Color+Hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Hex.swift 3 | // SwiftUIKit 4 | // 5 | // Created by Daniel Saidi on 2022-05-06. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | #if os(macOS) 12 | typealias ColorRepresentable = NSColor 13 | #else 14 | typealias ColorRepresentable = UIColor 15 | #endif 16 | 17 | extension Color { 18 | 19 | /// Create a color with an integer hex, e.g. `0xabcdef`. 20 | /// 21 | /// - Parameters: 22 | /// - hex: The hex value to apply. 23 | /// - alpha: The alpha value to apply, from 0 to 1. 24 | init(hex: UInt64, alpha: CGFloat = 1) { 25 | let color = ColorRepresentable(hex: hex, alpha: alpha) 26 | self.init(color) 27 | } 28 | 29 | /// Create a color with a string hex, e.g. `#abcdef`, 30 | /// 31 | /// This supports multiple string formats, like `abcdef`, 32 | /// `#abcdef`, `0xabcdef`, and `#abcdef`. 33 | /// 34 | /// - Parameters: 35 | /// - hex: The hex string to parse. 36 | /// - alpha: The alpha value to apply, from 0 to 1. 37 | init?(hex: String, alpha: CGFloat = 1) { 38 | guard let color = ColorRepresentable(hex: hex, alpha: alpha) else { return nil } 39 | self.init(color) 40 | } 41 | 42 | /// Create a color with an integer hex, e.g. `0xabcdef`. 43 | /// 44 | /// - Parameters: 45 | /// - hex: The hex value to apply. 46 | /// - alpha: The alpha value to apply, from 0 to 1. 47 | static func hex(_ hex: UInt64, alpha: CGFloat = 1) -> Color { 48 | Color(hex: hex, alpha: alpha) 49 | } 50 | 51 | /// Create a color with a string hex, e.g. `#abcdef`. 52 | /// 53 | /// This supports multiple string formats, like `abcdef`, 54 | /// `#abcdef`, `0xabcdef`, `#abcdef`. 55 | /// 56 | /// - Parameters: 57 | /// - hex: The hex string to parse. 58 | /// - alpha: The alpha value to apply, from 0 to 1. 59 | static func hex(_ hex: String, alpha: CGFloat = 1) -> Color? { 60 | Color(hex: hex, alpha: alpha) 61 | } 62 | } 63 | 64 | #Preview { 65 | 66 | struct Preview: View { 67 | 68 | @State private var font = "" 69 | 70 | var body: some View { 71 | VStack { 72 | Color(hex: "0xabcdef") 73 | Color(hex: "#abcdef", alpha: 0) 74 | Color(hex: "#abcdef", alpha: 0.5) 75 | Color(hex: "#abcdef", alpha: 1) 76 | Color(hex: 0x000000).frame(height: 10) 77 | Color(hex: 0xffffff).frame(height: 10) 78 | Color(hex: 0xabcdef) 79 | Color(hex: 0xabcdef, alpha: 0) 80 | Color(hex: 0xabcdef, alpha: 0.5) 81 | Color(hex: 0xabcdef, alpha: 1) 82 | }.padding() 83 | } 84 | } 85 | 86 | return Preview() 87 | } 88 | 89 | extension ColorRepresentable { 90 | 91 | /// Initialize a color with a hex value, e.g. `0xabcdef`. 92 | /// 93 | /// - Parameters: 94 | /// - hex: The hex value to apply. 95 | /// - alpha: The alpha value to apply, from 0 to 1. 96 | convenience init(hex: UInt64, alpha: CGFloat = 1) { 97 | let red = CGFloat((hex >> 16) & 0xff) / 255 98 | let green = CGFloat((hex >> 08) & 0xff) / 255 99 | let blue = CGFloat((hex >> 00) & 0xff) / 255 100 | self.init(red: red, green: green, blue: blue, alpha: alpha) 101 | } 102 | 103 | /// Initialize a color with a hex string, e.g. `#abcdef`. 104 | /// 105 | /// This supports multiple string formats, like `abcdef`, 106 | /// `#abcdef`, `0xabcdef`, `#abcdef`. 107 | /// 108 | /// - Parameters: 109 | /// - hex: The hex string to parse. 110 | /// - alpha: The alpha value to apply, from 0 to 1. 111 | convenience init?(hex: String, alpha: CGFloat = 1) { 112 | let hex = hex.cleanedForHex() 113 | guard hex.conforms(to: "[a-fA-F0-9]+") else { return nil } 114 | let scanner = Scanner(string: hex) 115 | var hexNumber: UInt64 = 0 116 | guard scanner.scanHexInt64(&hexNumber) else { return nil } 117 | self.init(hex: hexNumber, alpha: alpha) 118 | } 119 | } 120 | 121 | private extension String { 122 | 123 | func cleanedForHex() -> String { 124 | if hasPrefix("0x") { 125 | return String(dropFirst(2)) 126 | } 127 | if hasPrefix("#") { 128 | return String(dropFirst(1)) 129 | } 130 | return self 131 | } 132 | 133 | func conforms(to pattern: String) -> Bool { 134 | let pattern = NSPredicate(format: "SELF MATCHES %@", pattern) 135 | return pattern.evaluate(with: self) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/BadgeIcon/Image+Symbol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Symbol.swift 3 | // BadgeIcon 4 | // 5 | // Created by Daniel Saidi on 2023-05-29. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Image { 12 | 13 | static func symbol(_ name: String) -> Image { 14 | Image(systemName: name) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/BadgeIconTests/BadgeIconTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import BadgeIcon 3 | 4 | final class BadgeIconTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new project version for the current project. 5 | # You can customize this to fit your project when you copy these scripts. 6 | # You can pass in a custom branch if you don't want to use the default one. 7 | 8 | SCRIPT="scripts/package_version.sh" 9 | chmod +x $SCRIPT 10 | bash $SCRIPT 11 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds a for all provided . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # build.sh [ default:iOS macOS tvOS watchOS xrOS] 10 | # e.g. `bash scripts/build.sh MyTarget iOS macOS` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Verify that all required arguments are provided 16 | if [ $# -eq 0 ]; then 17 | echo "Error: This script requires at least one argument" 18 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 19 | echo "For instance: $0 MyTarget iOS macOS" 20 | exit 1 21 | fi 22 | 23 | # Define argument variables 24 | TARGET=$1 25 | 26 | # Remove TARGET from arguments list 27 | shift 28 | 29 | # Define platforms variable 30 | if [ $# -eq 0 ]; then 31 | set -- iOS macOS tvOS watchOS xrOS 32 | fi 33 | PLATFORMS=$@ 34 | 35 | # A function that builds $TARGET for a specific platform 36 | build_platform() { 37 | 38 | # Define a local $PLATFORM variable 39 | local PLATFORM=$1 40 | 41 | # Build $TARGET for the $PLATFORM 42 | echo "Building $TARGET for $PLATFORM..." 43 | if ! xcodebuild -scheme $TARGET -derivedDataPath .build -destination generic/platform=$PLATFORM; then 44 | echo "Failed to build $TARGET for $PLATFORM" 45 | return 1 46 | fi 47 | 48 | # Complete successfully 49 | echo "Successfully built $TARGET for $PLATFORM" 50 | } 51 | 52 | # Start script 53 | echo "" 54 | echo "Building $TARGET for [$PLATFORMS]..." 55 | echo "" 56 | 57 | # Loop through all platforms and call the build function 58 | for PLATFORM in $PLATFORMS; do 59 | if ! build_platform "$PLATFORM"; then 60 | exit 1 61 | fi 62 | done 63 | 64 | # Complete successfully 65 | echo "" 66 | echo "Building $TARGET completed successfully!" 67 | echo "" 68 | -------------------------------------------------------------------------------- /scripts/chmod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script makes all scripts in this folder executable. 5 | 6 | # Usage: 7 | # scripts_chmod.sh 8 | # e.g. `bash scripts/chmod.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Use the script folder to refer to other scripts. 14 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 15 | 16 | # Find all .sh files in the FOLDER except chmod.sh 17 | find "$FOLDER" -name "*.sh" ! -name "chmod.sh" -type f | while read -r script; do 18 | chmod +x "$script" 19 | done 20 | -------------------------------------------------------------------------------- /scripts/docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | # The documentation ends up in to .build/docs-. 8 | 9 | # Usage: 10 | # docc.sh [ default:iOS macOS tvOS watchOS xrOS] 11 | # e.g. `bash scripts/docc.sh MyTarget iOS macOS` 12 | 13 | # Exit immediately if a command exits with a non-zero status 14 | set -e 15 | 16 | # Fail if any command in a pipeline fails 17 | set -o pipefail 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | echo "For instance: $0 MyTarget iOS macOS" 24 | exit 1 25 | fi 26 | 27 | # Define argument variables 28 | TARGET=$1 29 | TARGET_LOWERCASED=$(echo "$1" | tr '[:upper:]' '[:lower:]') 30 | 31 | # Remove TARGET from arguments list 32 | shift 33 | 34 | # Define platforms variable 35 | if [ $# -eq 0 ]; then 36 | set -- iOS macOS tvOS watchOS xrOS 37 | fi 38 | PLATFORMS=$@ 39 | 40 | # Prepare the package for DocC 41 | swift package resolve; 42 | 43 | # A function that builds $TARGET for a specific platform 44 | build_platform() { 45 | 46 | # Define a local $PLATFORM variable and set an exit code 47 | local PLATFORM=$1 48 | local EXIT_CODE=0 49 | 50 | # Define the build folder name, based on the $PLATFORM 51 | case $PLATFORM in 52 | "iOS") 53 | DEBUG_PATH="Debug-iphoneos" 54 | ;; 55 | "macOS") 56 | DEBUG_PATH="Debug" 57 | ;; 58 | "tvOS") 59 | DEBUG_PATH="Debug-appletvos" 60 | ;; 61 | "watchOS") 62 | DEBUG_PATH="Debug-watchos" 63 | ;; 64 | "xrOS") 65 | DEBUG_PATH="Debug-xros" 66 | ;; 67 | *) 68 | echo "Error: Unsupported platform '$PLATFORM'" 69 | exit 1 70 | ;; 71 | esac 72 | 73 | # Build $TARGET docs for the $PLATFORM 74 | echo "Building $TARGET docs for $PLATFORM..." 75 | if ! xcodebuild docbuild -scheme $TARGET -derivedDataPath .build/docbuild -destination "generic/platform=$PLATFORM"; then 76 | echo "Error: Failed to build documentation for $PLATFORM" >&2 77 | return 1 78 | fi 79 | 80 | # Transform docs for static hosting 81 | if ! $(xcrun --find docc) process-archive \ 82 | transform-for-static-hosting .build/docbuild/Build/Products/$DEBUG_PATH/$TARGET.doccarchive \ 83 | --output-path .build/docs-$PLATFORM \ 84 | --hosting-base-path "$TARGET"; then 85 | echo "Error: Failed to transform documentation for $PLATFORM" >&2 86 | return 1 87 | fi 88 | 89 | # Inject a root redirect script on the root page 90 | echo "" > .build/docs-$PLATFORM/index.html; 91 | 92 | # Complete successfully 93 | echo "Successfully built $TARGET docs for $PLATFORM" 94 | return 0 95 | } 96 | 97 | # Start script 98 | echo "" 99 | echo "Building $TARGET docs for [$PLATFORMS]..." 100 | echo "" 101 | 102 | # Loop through all platforms and call the build function 103 | for PLATFORM in $PLATFORMS; do 104 | if ! build_platform "$PLATFORM"; then 105 | exit 1 106 | fi 107 | done 108 | 109 | # Complete successfully 110 | echo "" 111 | echo "Building $TARGET docs completed successfully!" 112 | echo "" 113 | -------------------------------------------------------------------------------- /scripts/framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Important: 9 | # This script doesn't work on packages, only on .xcproj projects that generate a framework. 10 | 11 | # Usage: 12 | # framework.sh [ default:iOS macOS tvOS watchOS xrOS] 13 | # e.g. `bash scripts/framework.sh MyTarget iOS macOS` 14 | 15 | # Exit immediately if a command exits with a non-zero status 16 | set -e 17 | 18 | # Verify that all required arguments are provided 19 | if [ $# -eq 0 ]; then 20 | echo "Error: This script requires exactly one argument" 21 | echo "Usage: $0 " 22 | exit 1 23 | fi 24 | 25 | # Define argument variables 26 | TARGET=$1 27 | 28 | # Remove TARGET from arguments list 29 | shift 30 | 31 | # Define platforms variable 32 | if [ $# -eq 0 ]; then 33 | set -- iOS macOS tvOS watchOS xrOS 34 | fi 35 | PLATFORMS=$@ 36 | 37 | # Define local variables 38 | BUILD_FOLDER=.build 39 | BUILD_FOLDER_ARCHIVES=.build/framework_archives 40 | BUILD_FILE=$BUILD_FOLDER/$TARGET.xcframework 41 | BUILD_ZIP=$BUILD_FOLDER/$TARGET.zip 42 | 43 | # Start script 44 | echo "" 45 | echo "Building $TARGET XCFramework for [$PLATFORMS]..." 46 | echo "" 47 | 48 | # Delete old builds 49 | echo "Cleaning old builds..." 50 | rm -rf $BUILD_ZIP 51 | rm -rf $BUILD_FILE 52 | rm -rf $BUILD_FOLDER_ARCHIVES 53 | 54 | 55 | # Generate XCArchive files for all platforms 56 | echo "Generating XCArchives..." 57 | 58 | # Initialize the xcframework command 59 | XCFRAMEWORK_CMD="xcodebuild -create-xcframework" 60 | 61 | # Build iOS archives and append to the xcframework command 62 | if [[ " ${PLATFORMS[@]} " =~ " iOS " ]]; then 63 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 64 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 65 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 66 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 67 | fi 68 | 69 | # Build iOS archive and append to the xcframework command 70 | if [[ " ${PLATFORMS[@]} " =~ " macOS " ]]; then 71 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=macOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-macOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 72 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-macOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 73 | fi 74 | 75 | # Build tvOS archives and append to the xcframework command 76 | if [[ " ${PLATFORMS[@]} " =~ " tvOS " ]]; then 77 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 78 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 79 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 80 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 81 | fi 82 | 83 | # Build watchOS archives and append to the xcframework command 84 | if [[ " ${PLATFORMS[@]} " =~ " watchOS " ]]; then 85 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 86 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 87 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 88 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 89 | fi 90 | 91 | # Build xrOS archives and append to the xcframework command 92 | if [[ " ${PLATFORMS[@]} " =~ " xrOS " ]]; then 93 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 94 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 95 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 96 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 97 | fi 98 | 99 | # Genererate XCFramework 100 | echo "Generating XCFramework..." 101 | XCFRAMEWORK_CMD+=" -output $BUILD_FILE" 102 | eval "$XCFRAMEWORK_CMD" 103 | 104 | # Genererate iOS XCFramework zip 105 | echo "Generating XCFramework zip..." 106 | zip -r $BUILD_ZIP $BUILD_FILE 107 | echo "" 108 | echo "***** CHECKSUM *****" 109 | swift package compute-checksum $BUILD_ZIP 110 | echo "********************" 111 | echo "" 112 | 113 | # Complete successfully 114 | echo "" 115 | echo "$TARGET XCFramework created successfully!" 116 | echo "" 117 | -------------------------------------------------------------------------------- /scripts/git_default_branch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script echos the default git branch name. 5 | 6 | # Usage: 7 | # git_default_branch.sh 8 | # e.g. `bash scripts/git_default_branch.sh` 9 | 10 | BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') 11 | echo $BRANCH 12 | -------------------------------------------------------------------------------- /scripts/package_docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC documentation for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_docc.sh [ default:iOS] 9 | # e.g. `bash scripts/package_docc.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_DOCC="$FOLDER/docc.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package documentation 29 | bash $SCRIPT_DOCC $PACKAGE_NAME $PLATFORMS || { echo "DocC script failed"; exit 1; } 30 | -------------------------------------------------------------------------------- /scripts/package_framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script generates an XCFramework for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_framework.sh [ default:iOS] 9 | # e.g. `bash scripts/package_framework.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_FRAMEWORK="$FOLDER/framework.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package framework 29 | bash $SCRIPT_FRAMEWORK $PACKAGE_NAME $PLATFORMS 30 | -------------------------------------------------------------------------------- /scripts/package_name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script finds the main target name in `Package.swift`. 5 | 6 | # Usage: 7 | # package_name.sh 8 | # e.g. `bash scripts/package_name.sh` 9 | 10 | # Exit immediately if a command exits with non-zero status 11 | set -e 12 | 13 | # Check that a Package.swift file exists 14 | if [ ! -f "Package.swift" ]; then 15 | echo "Error: Package.swift not found in current directory" 16 | exit 1 17 | fi 18 | 19 | # Using grep and sed to extract the package name 20 | # 1. grep finds the line containing "name:" 21 | # 2. sed extracts the text between quotes 22 | package_name=$(grep -m 1 'name:.*"' Package.swift | sed -n 's/.*name:[[:space:]]*"\([^"]*\)".*/\1/p') 23 | 24 | if [ -z "$package_name" ]; then 25 | echo "Error: Could not find package name in Package.swift" 26 | exit 1 27 | else 28 | echo "$package_name" 29 | fi 30 | -------------------------------------------------------------------------------- /scripts/package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for `Package.swift`. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # package_version.sh 9 | # e.g. `bash scripts/package_version.sh master` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_BRANCH_NAME="$FOLDER/git_default_branch.sh" 17 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 18 | SCRIPT_VERSION="$FOLDER/version.sh" 19 | 20 | # Get branch name 21 | DEFAULT_BRANCH=$("$SCRIPT_BRANCH_NAME") || { echo "Failed to get branch name"; exit 1; } 22 | BRANCH_NAME=${1:-$DEFAULT_BRANCH} 23 | 24 | # Get package name 25 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 26 | 27 | # Build package version 28 | bash $SCRIPT_VERSION $PACKAGE_NAME $BRANCH_NAME 29 | -------------------------------------------------------------------------------- /scripts/sync_from.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script syncs Swift Package Scripts from a . 5 | # This script will overwrite the existing "scripts" folder. 6 | # Only pass in the full path to a Swift Package Scripts root. 7 | 8 | # Usage: 9 | # package_name.sh 10 | # e.g. `bash sync_from.sh ../SwiftPackageScripts` 11 | 12 | # Define argument variables 13 | SOURCE=$1 14 | 15 | # Define variables 16 | FOLDER="scripts/" 17 | SOURCE_FOLDER="$SOURCE/$FOLDER" 18 | 19 | # Start script 20 | echo "" 21 | echo "Syncing scripts from $SOURCE_FOLDER..." 22 | echo "" 23 | 24 | # Remove existing folder 25 | rm -rf $FOLDER 26 | 27 | # Copy folder 28 | cp -r "$SOURCE_FOLDER/" "$FOLDER/" 29 | 30 | # Complete successfully 31 | echo "" 32 | echo "Script syncing from $SOURCE_FOLDER completed successfully!" 33 | echo "" 34 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script tests a for all provided . 5 | 6 | # Usage: 7 | # test.sh [ default:iOS macOS tvOS watchOS xrOS] 8 | # e.g. `bash scripts/test.sh MyTarget iOS macOS` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Verify that all required arguments are provided 14 | if [ $# -eq 0 ]; then 15 | echo "Error: This script requires at least one argument" 16 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 17 | echo "For instance: $0 MyTarget iOS macOS" 18 | exit 1 19 | fi 20 | 21 | # Define argument variables 22 | TARGET=$1 23 | 24 | # Remove TARGET from arguments list 25 | shift 26 | 27 | # Define platforms variable 28 | if [ $# -eq 0 ]; then 29 | set -- iOS macOS tvOS watchOS xrOS 30 | fi 31 | PLATFORMS=$@ 32 | 33 | # Start script 34 | echo "" 35 | echo "Testing $TARGET for [$PLATFORMS]..." 36 | echo "" 37 | 38 | # A function that gets the latest simulator for a certain OS. 39 | get_latest_simulator() { 40 | local PLATFORM=$1 41 | local SIMULATOR_TYPE 42 | 43 | case $PLATFORM in 44 | "iOS") 45 | SIMULATOR_TYPE="iPhone" 46 | ;; 47 | "tvOS") 48 | SIMULATOR_TYPE="Apple TV" 49 | ;; 50 | "watchOS") 51 | SIMULATOR_TYPE="Apple Watch" 52 | ;; 53 | "xrOS") 54 | SIMULATOR_TYPE="Apple Vision" 55 | ;; 56 | *) 57 | echo "Error: Unsupported platform for simulator '$PLATFORM'" 58 | return 1 59 | ;; 60 | esac 61 | 62 | # Get the latest simulator for the platform 63 | xcrun simctl list devices available | grep "$SIMULATOR_TYPE" | tail -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/' 64 | } 65 | 66 | # A function that tests $TARGET for a specific platform 67 | test_platform() { 68 | 69 | # Define a local $PLATFORM variable 70 | local PLATFORM="${1//_/ }" 71 | 72 | # Define the destination, based on the $PLATFORM 73 | case $PLATFORM in 74 | "iOS"|"tvOS"|"watchOS"|"xrOS") 75 | local SIMULATOR_UDID=$(get_latest_simulator "$PLATFORM") 76 | if [ -z "$SIMULATOR_UDID" ]; then 77 | echo "Error: No simulator found for $PLATFORM" 78 | return 1 79 | fi 80 | DESTINATION="id=$SIMULATOR_UDID" 81 | ;; 82 | "macOS") 83 | DESTINATION="platform=macOS" 84 | ;; 85 | *) 86 | echo "Error: Unsupported platform '$PLATFORM'" 87 | return 1 88 | ;; 89 | esac 90 | 91 | # Test $TARGET for the $DESTINATION 92 | echo "Testing $TARGET for $PLATFORM..." 93 | xcodebuild test -scheme $TARGET -derivedDataPath .build -destination "$DESTINATION" -enableCodeCoverage YES 94 | local TEST_RESULT=$? 95 | 96 | if [[ $TEST_RESULT -ne 0 ]]; then 97 | return $TEST_RESULT 98 | fi 99 | 100 | # Complete successfully 101 | echo "Successfully tested $TARGET for $PLATFORM" 102 | return 0 103 | } 104 | 105 | # Loop through all platforms and call the test function 106 | for PLATFORM in $PLATFORMS; do 107 | if ! test_platform "$PLATFORM"; then 108 | exit 1 109 | fi 110 | done 111 | 112 | # Complete successfully 113 | echo "" 114 | echo "Testing $TARGET completed successfully!" 115 | echo "" 116 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for the provided and . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `scripts/version.sh MyTarget master iOS macOS` 11 | 12 | # This script will: 13 | # * Call version_validate_git.sh to validate the git repo. 14 | # * Call version_validate_target to run tests, swiftlint, etc. 15 | # * Call version_bump.sh if all validation steps above passed. 16 | 17 | # Exit immediately if a command exits with a non-zero status 18 | set -e 19 | 20 | # Verify that all required arguments are provided 21 | if [ $# -lt 2 ]; then 22 | echo "Error: This script requires at least two arguments" 23 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 24 | echo "For instance: $0 MyTarget master iOS macOS" 25 | exit 1 26 | fi 27 | 28 | # Define argument variables 29 | TARGET=$1 30 | BRANCH=${2:-main} 31 | 32 | # Remove TARGET and BRANCH from arguments list 33 | shift 34 | shift 35 | 36 | # Read platform arguments or use default value 37 | if [ $# -eq 0 ]; then 38 | set -- iOS macOS tvOS watchOS xrOS 39 | fi 40 | 41 | # Use the script folder to refer to other scripts. 42 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 43 | SCRIPT_VALIDATE_GIT="$FOLDER/version_validate_git.sh" 44 | SCRIPT_VALIDATE_TARGET="$FOLDER/version_validate_target.sh" 45 | SCRIPT_VERSION_BUMP="$FOLDER/version_bump.sh" 46 | 47 | # A function that run a certain script and checks for errors 48 | run_script() { 49 | local script="$1" 50 | shift # Remove the first argument (the script path) 51 | 52 | if [ ! -f "$script" ]; then 53 | echo "Error: Script not found: $script" 54 | exit 1 55 | fi 56 | 57 | chmod +x "$script" 58 | if ! "$script" "$@"; then 59 | echo "Error: Script $script failed" 60 | exit 1 61 | fi 62 | } 63 | 64 | # Start script 65 | echo "" 66 | echo "Creating a new version for $TARGET on the $BRANCH branch..." 67 | echo "" 68 | 69 | # Validate git and project 70 | echo "Validating..." 71 | run_script "$SCRIPT_VALIDATE_GIT" "$BRANCH" 72 | run_script "$SCRIPT_VALIDATE_TARGET" "$TARGET" 73 | 74 | # Bump version 75 | echo "Bumping version..." 76 | run_script "$SCRIPT_VERSION_BUMP" 77 | 78 | # Complete successfully 79 | echo "" 80 | echo "Version created successfully!" 81 | echo "" 82 | -------------------------------------------------------------------------------- /scripts/version_bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script bumps the project version number. 5 | # You can append --no-semver to disable semantic version validation. 6 | 7 | # Usage: 8 | # version_bump.sh [--no-semver] 9 | # e.g. `bash scripts/version_bump.sh` 10 | # e.g. `bash scripts/version_bump.sh --no-semver` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Use the script folder to refer to other scripts. 16 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 17 | SCRIPT_VERSION_NUMBER="$FOLDER/version_number.sh" 18 | 19 | 20 | # Parse --no-semver argument 21 | VALIDATE_SEMVER=true 22 | for arg in "$@"; do 23 | case $arg in 24 | --no-semver) 25 | VALIDATE_SEMVER=false 26 | shift # Remove --no-semver from processing 27 | ;; 28 | esac 29 | done 30 | 31 | # Start script 32 | echo "" 33 | echo "Bumping version number..." 34 | echo "" 35 | 36 | # Get the latest version 37 | VERSION=$($SCRIPT_VERSION_NUMBER) 38 | if [ $? -ne 0 ]; then 39 | echo "Failed to get the latest version" 40 | exit 1 41 | fi 42 | 43 | # Print the current version 44 | echo "The current version is: $VERSION" 45 | 46 | # Function to validate semver format, including optional -rc. suffix 47 | validate_semver() { 48 | if [ "$VALIDATE_SEMVER" = false ]; then 49 | return 0 50 | fi 51 | 52 | if [[ $1 =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then 53 | return 0 54 | else 55 | return 1 56 | fi 57 | } 58 | 59 | # Prompt user for new version 60 | while true; do 61 | read -p "Enter the new version number: " NEW_VERSION 62 | 63 | # Validate the version number to ensure that it's a semver version 64 | if validate_semver "$NEW_VERSION"; then 65 | break 66 | else 67 | echo "Invalid version format. Please use semver format (e.g., 1.2.3, v1.2.3, 1.2.3-rc.1, etc.)." 68 | exit 1 69 | fi 70 | done 71 | 72 | # Push the new tag 73 | git push -u origin HEAD 74 | git tag $NEW_VERSION 75 | git push --tags 76 | 77 | # Complete successfully 78 | echo "" 79 | echo "Version tag pushed successfully!" 80 | echo "" 81 | -------------------------------------------------------------------------------- /scripts/version_number.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script returns the latest project version. 5 | 6 | # Usage: 7 | # version_number.sh 8 | # e.g. `bash scripts/version_number.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Check if the current directory is a Git repository 14 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 15 | echo "Error: Not a Git repository" 16 | exit 1 17 | fi 18 | 19 | # Fetch all tags 20 | git fetch --tags > /dev/null 2>&1 21 | 22 | # Get the latest semver tag 23 | latest_version=$(git tag -l --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) 24 | 25 | # Check if we found a version tag 26 | if [ -z "$latest_version" ]; then 27 | echo "Error: No semver tags found in this repository" >&2 28 | exit 1 29 | fi 30 | 31 | # Print the latest version 32 | echo "$latest_version" 33 | -------------------------------------------------------------------------------- /scripts/version_validate_git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates the Git repository for release. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # version_validate_git.sh " 9 | # e.g. `bash scripts/version_validate_git.sh master` 10 | 11 | # This script will: 12 | # * Validate that the script is run within a git repository. 13 | # * Validate that the git repository doesn't have any uncommitted changes. 14 | # * Validate that the current git branch matches the provided one. 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires exactly one argument" 22 | echo "Usage: $0 " 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | BRANCH=$1 28 | 29 | # Start script 30 | echo "" 31 | echo "Validating git repository..." 32 | echo "" 33 | 34 | # Check if the current directory is a Git repository 35 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 36 | echo "Error: Not a Git repository" 37 | exit 1 38 | fi 39 | 40 | # Check for uncommitted changes 41 | if [ -n "$(git status --porcelain)" ]; then 42 | echo "Error: Git repository is dirty. There are uncommitted changes." 43 | exit 1 44 | fi 45 | 46 | # Verify that we're on the correct branch 47 | current_branch=$(git rev-parse --abbrev-ref HEAD) 48 | if [ "$current_branch" != "$BRANCH" ]; then 49 | echo "Error: Not on the specified branch. Current branch is $current_branch, expected $1." 50 | exit 1 51 | fi 52 | 53 | # The Git repository validation succeeded. 54 | echo "" 55 | echo "Git repository validated successfully!" 56 | echo "" 57 | -------------------------------------------------------------------------------- /scripts/version_validate_target.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates a for release. 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version_validate_target.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `bash scripts/version_validate_target.sh iOS macOS` 11 | 12 | # This script will: 13 | # * Validate that swiftlint passes. 14 | # * Validate that all unit tests passes for all . 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all requires at least one argument" 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | TARGET=$1 28 | 29 | # Remove TARGET from arguments list 30 | shift 31 | 32 | # Define platforms variable 33 | if [ $# -eq 0 ]; then 34 | set -- iOS macOS tvOS watchOS xrOS 35 | fi 36 | PLATFORMS=$@ 37 | 38 | # Use the script folder to refer to other scripts. 39 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 40 | SCRIPT_TEST="$FOLDER/test.sh" 41 | 42 | # A function that run a certain script and checks for errors 43 | run_script() { 44 | local script="$1" 45 | shift # Remove the first argument (script path) from the argument list 46 | 47 | if [ ! -f "$script" ]; then 48 | echo "Error: Script not found: $script" 49 | exit 1 50 | fi 51 | 52 | chmod +x "$script" 53 | if ! "$script" "$@"; then 54 | echo "Error: Script $script failed" 55 | exit 1 56 | fi 57 | } 58 | 59 | # Start script 60 | echo "" 61 | echo "Validating project..." 62 | echo "" 63 | 64 | # Run SwiftLint 65 | echo "Running SwiftLint" 66 | if ! swiftlint --strict; then 67 | echo "Error: SwiftLint failed" 68 | exit 1 69 | fi 70 | 71 | # Run unit tests 72 | echo "Testing..." 73 | run_script "$SCRIPT_TEST" "$TARGET" "$PLATFORMS" 74 | 75 | # Complete successfully 76 | echo "" 77 | echo "Project successfully validated!" 78 | echo "" 79 | --------------------------------------------------------------------------------