├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 | 
9 |
10 | BadgeIcon is a SwiftUI library that helps you create beautiful, scalable icons, using SF Symbols or custom assets.
11 |
12 | 
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 |
--------------------------------------------------------------------------------