├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── docc.yml ├── .gitignore ├── .swiftlint.yml ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── danielsaidi.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Demo │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-macOS-1024.png │ │ ├── Icon-macOS-128.png │ │ ├── Icon-macOS-16.png │ │ ├── Icon-macOS-256.png │ │ ├── Icon-macOS-32.png │ │ ├── Icon-macOS-512.png │ │ ├── Icon-macOS-64.png │ │ └── Icon.png │ └── Contents.json │ ├── ContentView.swift │ ├── Demo.entitlements │ ├── DemoApp.swift │ ├── Files │ ├── clouds.jpg │ ├── document.pdf │ └── graphics.png │ ├── Image+Demo.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── LICENSE ├── Package.swift ├── README.md ├── RELEASE_NOTES.md ├── Resources └── Icon.png ├── Sources └── PrintingKit │ ├── Extensions │ ├── Data+Printing.swift │ ├── FileManager+Printing.swift │ ├── Image+Printing.swift │ └── ImageRenderer+Printing.swift │ ├── Pdf │ ├── Pdf+DataError.swift │ ├── Pdf.swift │ ├── PdfDataSource+NSAttributedString.swift │ └── PdfDataSource.swift │ ├── PrintError.swift │ ├── Printer+PageConfiguration.swift │ ├── Printer+PageMargins.swift │ ├── Printer.swift │ └── PrintingKit.docc │ ├── PrintingKit.md │ └── Resources │ ├── Logo.png │ └── Page.png ├── Tests └── PrintingKitTests │ └── PrinterTests.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 | .DS_Store 2 | /.build 3 | /Packages 4 | .swiftpm/ 5 | xcuserdata/ 6 | DerivedData/ -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - identifier_name 3 | - large_tuple 4 | - line_length 5 | - trailing_whitespace 6 | - type_name 7 | - vertical_whitespace 8 | 9 | included: 10 | - Sources 11 | - Tests 12 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A9619D4E2A939BD000E7ED0F /* clouds.jpg in Resources */ = {isa = PBXBuildFile; fileRef = A9619D4D2A939BD000E7ED0F /* clouds.jpg */; }; 11 | A9619D542A939CFB00E7ED0F /* graphics.png in Resources */ = {isa = PBXBuildFile; fileRef = A9619D532A939CFB00E7ED0F /* graphics.png */; }; 12 | A9619D582A939FFE00E7ED0F /* document.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9619D572A939FFE00E7ED0F /* document.pdf */; }; 13 | A96E0A8E2B2AFE82005DC054 /* Image+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96E0A8D2B2AFE82005DC054 /* Image+Demo.swift */; }; 14 | A99237C62A938D9400EBFAD6 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99237C52A938D9400EBFAD6 /* DemoApp.swift */; }; 15 | A99237C82A938D9400EBFAD6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99237C72A938D9400EBFAD6 /* ContentView.swift */; }; 16 | A99237CA2A938D9500EBFAD6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A99237C92A938D9500EBFAD6 /* Assets.xcassets */; }; 17 | A99237CE2A938D9500EBFAD6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A99237CD2A938D9500EBFAD6 /* Preview Assets.xcassets */; }; 18 | A99237DB2A938F7000EBFAD6 /* PrintingKit in Frameworks */ = {isa = PBXBuildFile; productRef = A99237DA2A938F7000EBFAD6 /* PrintingKit */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | A9619D4D2A939BD000E7ED0F /* clouds.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = clouds.jpg; sourceTree = ""; }; 23 | A9619D532A939CFB00E7ED0F /* graphics.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = graphics.png; sourceTree = ""; }; 24 | A9619D572A939FFE00E7ED0F /* document.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = document.pdf; sourceTree = ""; }; 25 | A96E0A8D2B2AFE82005DC054 /* Image+Demo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Demo.swift"; sourceTree = ""; }; 26 | A99237C22A938D9400EBFAD6 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | A99237C52A938D9400EBFAD6 /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; }; 28 | A99237C72A938D9400EBFAD6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 29 | A99237C92A938D9500EBFAD6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | A99237CB2A938D9500EBFAD6 /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; }; 31 | A99237CD2A938D9500EBFAD6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 32 | A99237D52A938DA900EBFAD6 /* PrintingKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = PrintingKit; path = ..; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | A99237BF2A938D9400EBFAD6 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | A99237DB2A938F7000EBFAD6 /* PrintingKit in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | A99237B92A938D9400EBFAD6 = { 48 | isa = PBXGroup; 49 | children = ( 50 | A99237D42A938DA900EBFAD6 /* Packages */, 51 | A99237C42A938D9400EBFAD6 /* Demo */, 52 | A99237C32A938D9400EBFAD6 /* Products */, 53 | A99237D92A938F5E00EBFAD6 /* Frameworks */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | A99237C32A938D9400EBFAD6 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | A99237C22A938D9400EBFAD6 /* Demo.app */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | A99237C42A938D9400EBFAD6 /* Demo */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | A99237D62A938F0700EBFAD6 /* Files */, 69 | A99237C52A938D9400EBFAD6 /* DemoApp.swift */, 70 | A99237C72A938D9400EBFAD6 /* ContentView.swift */, 71 | A96E0A8D2B2AFE82005DC054 /* Image+Demo.swift */, 72 | A99237C92A938D9500EBFAD6 /* Assets.xcassets */, 73 | A99237CB2A938D9500EBFAD6 /* Demo.entitlements */, 74 | A99237CC2A938D9500EBFAD6 /* Preview Content */, 75 | ); 76 | path = Demo; 77 | sourceTree = ""; 78 | }; 79 | A99237CC2A938D9500EBFAD6 /* Preview Content */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | A99237CD2A938D9500EBFAD6 /* Preview Assets.xcassets */, 83 | ); 84 | path = "Preview Content"; 85 | sourceTree = ""; 86 | }; 87 | A99237D42A938DA900EBFAD6 /* Packages */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | A99237D52A938DA900EBFAD6 /* PrintingKit */, 91 | ); 92 | name = Packages; 93 | sourceTree = ""; 94 | }; 95 | A99237D62A938F0700EBFAD6 /* Files */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | A9619D572A939FFE00E7ED0F /* document.pdf */, 99 | A9619D532A939CFB00E7ED0F /* graphics.png */, 100 | A9619D4D2A939BD000E7ED0F /* clouds.jpg */, 101 | ); 102 | path = Files; 103 | sourceTree = ""; 104 | }; 105 | A99237D92A938F5E00EBFAD6 /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | ); 109 | name = Frameworks; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | A99237C12A938D9400EBFAD6 /* Demo */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = A99237D12A938D9500EBFAD6 /* Build configuration list for PBXNativeTarget "Demo" */; 118 | buildPhases = ( 119 | A99237BE2A938D9400EBFAD6 /* Sources */, 120 | A99237BF2A938D9400EBFAD6 /* Frameworks */, 121 | A99237C02A938D9400EBFAD6 /* Resources */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Demo; 128 | packageProductDependencies = ( 129 | A99237DA2A938F7000EBFAD6 /* PrintingKit */, 130 | ); 131 | productName = Demo; 132 | productReference = A99237C22A938D9400EBFAD6 /* Demo.app */; 133 | productType = "com.apple.product-type.application"; 134 | }; 135 | /* End PBXNativeTarget section */ 136 | 137 | /* Begin PBXProject section */ 138 | A99237BA2A938D9400EBFAD6 /* Project object */ = { 139 | isa = PBXProject; 140 | attributes = { 141 | BuildIndependentTargetsInParallel = 1; 142 | LastSwiftUpdateCheck = 1430; 143 | LastUpgradeCheck = 1430; 144 | ORGANIZATIONNAME = "Daniel Saidi"; 145 | TargetAttributes = { 146 | A99237C12A938D9400EBFAD6 = { 147 | CreatedOnToolsVersion = 14.3; 148 | }; 149 | }; 150 | }; 151 | buildConfigurationList = A99237BD2A938D9400EBFAD6 /* Build configuration list for PBXProject "Demo" */; 152 | compatibilityVersion = "Xcode 14.0"; 153 | developmentRegion = en; 154 | hasScannedForEncodings = 0; 155 | knownRegions = ( 156 | en, 157 | Base, 158 | ); 159 | mainGroup = A99237B92A938D9400EBFAD6; 160 | productRefGroup = A99237C32A938D9400EBFAD6 /* Products */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | A99237C12A938D9400EBFAD6 /* Demo */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | A99237C02A938D9400EBFAD6 /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | A99237CE2A938D9500EBFAD6 /* Preview Assets.xcassets in Resources */, 175 | A9619D582A939FFE00E7ED0F /* document.pdf in Resources */, 176 | A99237CA2A938D9500EBFAD6 /* Assets.xcassets in Resources */, 177 | A9619D542A939CFB00E7ED0F /* graphics.png in Resources */, 178 | A9619D4E2A939BD000E7ED0F /* clouds.jpg in Resources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXResourcesBuildPhase section */ 183 | 184 | /* Begin PBXSourcesBuildPhase section */ 185 | A99237BE2A938D9400EBFAD6 /* Sources */ = { 186 | isa = PBXSourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | A99237C82A938D9400EBFAD6 /* ContentView.swift in Sources */, 190 | A99237C62A938D9400EBFAD6 /* DemoApp.swift in Sources */, 191 | A96E0A8E2B2AFE82005DC054 /* Image+Demo.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | A99237CF2A938D9500EBFAD6 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | CLANG_ANALYZER_NONNULL = YES; 203 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 204 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_ENABLE_OBJC_WEAK = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 224 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 225 | CLANG_WARN_STRICT_PROTOTYPES = YES; 226 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 227 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 228 | CLANG_WARN_UNREACHABLE_CODE = YES; 229 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 230 | COPY_PHASE_STRIP = NO; 231 | DEBUG_INFORMATION_FORMAT = dwarf; 232 | ENABLE_STRICT_OBJC_MSGSEND = YES; 233 | ENABLE_TESTABILITY = YES; 234 | GCC_C_LANGUAGE_STANDARD = gnu11; 235 | GCC_DYNAMIC_NO_PIC = NO; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_OPTIMIZATION_LEVEL = 0; 238 | GCC_PREPROCESSOR_DEFINITIONS = ( 239 | "DEBUG=1", 240 | "$(inherited)", 241 | ); 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 244 | GCC_WARN_UNDECLARED_SELECTOR = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 246 | GCC_WARN_UNUSED_FUNCTION = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 249 | MTL_FAST_MATH = YES; 250 | ONLY_ACTIVE_ARCH = YES; 251 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 252 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 253 | }; 254 | name = Debug; 255 | }; 256 | A99237D02A938D9500EBFAD6 /* Release */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_ANALYZER_NONNULL = YES; 261 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_ENABLE_OBJC_WEAK = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 279 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 282 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 283 | CLANG_WARN_STRICT_PROTOTYPES = YES; 284 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 285 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 290 | ENABLE_NS_ASSERTIONS = NO; 291 | ENABLE_STRICT_OBJC_MSGSEND = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu11; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | MTL_ENABLE_DEBUG_INFO = NO; 301 | MTL_FAST_MATH = YES; 302 | SWIFT_COMPILATION_MODE = wholemodule; 303 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 304 | }; 305 | name = Release; 306 | }; 307 | A99237D22A938D9500EBFAD6 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 312 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; 313 | CODE_SIGN_STYLE = Automatic; 314 | CURRENT_PROJECT_VERSION = 1; 315 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 316 | DEVELOPMENT_TEAM = PMEDFW438U; 317 | ENABLE_HARDENED_RUNTIME = YES; 318 | ENABLE_PREVIEWS = YES; 319 | GENERATE_INFOPLIST_FILE = YES; 320 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 321 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 322 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 323 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 324 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 325 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 326 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 327 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 328 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 329 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 330 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 331 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 332 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 333 | MACOSX_DEPLOYMENT_TARGET = 13.0; 334 | MARKETING_VERSION = 1.0; 335 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.printingkit.Demo; 336 | PRODUCT_NAME = "$(TARGET_NAME)"; 337 | SDKROOT = auto; 338 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 339 | SWIFT_EMIT_LOC_STRINGS = YES; 340 | SWIFT_VERSION = 5.0; 341 | TARGETED_DEVICE_FAMILY = "1,2"; 342 | }; 343 | name = Debug; 344 | }; 345 | A99237D32A938D9500EBFAD6 /* Release */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 350 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; 351 | CODE_SIGN_STYLE = Automatic; 352 | CURRENT_PROJECT_VERSION = 1; 353 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 354 | DEVELOPMENT_TEAM = PMEDFW438U; 355 | ENABLE_HARDENED_RUNTIME = YES; 356 | ENABLE_PREVIEWS = YES; 357 | GENERATE_INFOPLIST_FILE = YES; 358 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 359 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 360 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 361 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 362 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 363 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 364 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 365 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 366 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 367 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 368 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 369 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 370 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 371 | MACOSX_DEPLOYMENT_TARGET = 13.0; 372 | MARKETING_VERSION = 1.0; 373 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.printingkit.Demo; 374 | PRODUCT_NAME = "$(TARGET_NAME)"; 375 | SDKROOT = auto; 376 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 377 | SWIFT_EMIT_LOC_STRINGS = YES; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Release; 382 | }; 383 | /* End XCBuildConfiguration section */ 384 | 385 | /* Begin XCConfigurationList section */ 386 | A99237BD2A938D9400EBFAD6 /* Build configuration list for PBXProject "Demo" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | A99237CF2A938D9500EBFAD6 /* Debug */, 390 | A99237D02A938D9500EBFAD6 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | A99237D12A938D9500EBFAD6 /* Build configuration list for PBXNativeTarget "Demo" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | A99237D22A938D9500EBFAD6 /* Debug */, 399 | A99237D32A938D9500EBFAD6 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Release; 403 | }; 404 | /* End XCConfigurationList section */ 405 | 406 | /* Begin XCSwiftPackageProductDependency section */ 407 | A99237DA2A938F7000EBFAD6 /* PrintingKit */ = { 408 | isa = XCSwiftPackageProductDependency; 409 | productName = PrintingKit; 410 | }; 411 | /* End XCSwiftPackageProductDependency section */ 412 | }; 413 | rootObject = A99237BA2A938D9400EBFAD6 /* Project object */; 414 | } 415 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/danielsaidi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/danielsaidi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "Icon-macOS-16.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-macOS-32.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "Icon-macOS-32.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-macOS-64.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "Icon-macOS-128.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-macOS-256.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "Icon-macOS-256.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-macOS-512.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "Icon-macOS-512.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "Icon-macOS-1024.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import PrintingKit 10 | import QuickLook 11 | import SwiftUI 12 | 13 | @MainActor 14 | struct ContentView: View { 15 | 16 | private let printer = Printer() 17 | 18 | @State var quickLookUrl: URL? 19 | @State var isGreenCheckmarkSelected = true 20 | @State var text = "Type text here..." 21 | 22 | var body: some View { 23 | NavigationStack { 24 | List { 25 | fileSection 26 | dataSection 27 | textSection 28 | viewSection 29 | } 30 | .buttonStyle(.plain) 31 | .navigationTitle("PrinterKit Demo") 32 | } 33 | .quickLookPreview($quickLookUrl) 34 | } 35 | } 36 | 37 | private extension ContentView { 38 | 39 | var fileSection: some View { 40 | Section("Files") { 41 | fileListItem("Print PDF file", .pdf, "document", "pdf") 42 | fileListItem("Print JPG file", .image, "clouds", "jpg") 43 | fileListItem("Print PNG file", .image, "graphics", "png") 44 | } 45 | } 46 | 47 | var dataSection: some View { 48 | Section("Data") { 49 | fileDataListItem("Print PDF data", .pdf, "document", "pdf") 50 | fileDataListItem("Print JPG data", .image, "clouds", "jpg") 51 | fileDataListItem("Print PNG data", .image, "graphics", "png") 52 | } 53 | } 54 | 55 | var textSection: some View { 56 | Section("Text") { 57 | Label( 58 | title: { TextField("Type text here...", text: $text, axis: .vertical) }, 59 | icon: { Image.textInput } 60 | ) 61 | printButton("Print as string", .text) { 62 | tryPrint { 63 | try printer.printString(text, config: .standard) 64 | } 65 | } 66 | printButton("Print as attributed string", .text) { 67 | tryPrint { 68 | try printer.printAttributedString(.init(string: text), config: .standard) 69 | } 70 | } 71 | } 72 | } 73 | 74 | var viewSection: some View { 75 | Section("View") { 76 | Label( 77 | title: { 78 | Picker("Select view:", selection: $isGreenCheckmarkSelected) { 79 | Image.checkmarkBadge.tag(true) 80 | Image.xmarkBadge.tag(false) 81 | } 82 | .pickerStyle(.menu) 83 | }, 84 | icon: { Image.image } 85 | ) 86 | 87 | printButton("Print the selected view", .view) { 88 | tryPrint { 89 | try printer.printView(printableView, withScale: 200) 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | private extension ContentView { 97 | 98 | @ViewBuilder 99 | var printableView: some View { 100 | if isGreenCheckmarkSelected { 101 | Image.checkmarkBadge 102 | } else { 103 | Image.xmarkBadge 104 | } 105 | } 106 | } 107 | 108 | private extension ContentView { 109 | 110 | func data(for file: String, _ ext: String) -> Data? { 111 | guard let url = url(for: file, ext) else { return nil } 112 | return try? Data(contentsOf: url) 113 | } 114 | 115 | func url(for file: String, _ ext: String) -> URL? { 116 | Bundle.main.url(forResource: file, withExtension: ext) 117 | } 118 | 119 | func fileListItem( 120 | _ title: String, 121 | _ icon: Image, 122 | _ fileName: String, 123 | _ fileExtension: String 124 | ) -> some View { 125 | let url = url(for: fileName, fileExtension) 126 | return HStack { 127 | printButton(title, icon) { 128 | tryPrint { try printer.printFile(at: url) } 129 | } 130 | if let url { 131 | quickLookButton(for: url) 132 | } 133 | } 134 | } 135 | 136 | func fileDataListItem( 137 | _ title: String, 138 | _ icon: Image, 139 | _ fileName: String, 140 | _ fileExtension: String 141 | ) -> some View { 142 | let url = url(for: fileName, fileExtension) 143 | return HStack { 144 | printButton(title, icon) { 145 | tryPrint { 146 | guard let url else { return } 147 | let data = try Data(contentsOf: url) 148 | try printer.printData(data, withFileExtension: fileExtension) 149 | } 150 | } 151 | if let url { 152 | quickLookButton(for: url) 153 | } 154 | } 155 | } 156 | } 157 | 158 | private extension ContentView { 159 | 160 | func printButton( 161 | _ title: String, 162 | _ icon: Image, 163 | _ action: @escaping () -> Void 164 | ) -> some View { 165 | Button(action: action) { 166 | Label { Text(title) } icon: { icon } 167 | .frame(maxWidth: .infinity, alignment: .leading) 168 | .contentShape(.rect) 169 | } 170 | } 171 | 172 | func quickLookButton( 173 | for url: URL 174 | ) -> some View { 175 | Button { 176 | quickLookUrl = url 177 | } label: { 178 | Image.eye 179 | } 180 | } 181 | 182 | func tryPrint(_ action: @escaping () throws -> Void) { 183 | do { 184 | try action() 185 | } catch { 186 | print(error) 187 | } 188 | } 189 | } 190 | 191 | #Preview { 192 | 193 | ContentView() 194 | } 195 | -------------------------------------------------------------------------------- /Demo/Demo/Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.print 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Demo/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct DemoApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Demo/Files/clouds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Files/clouds.jpg -------------------------------------------------------------------------------- /Demo/Demo/Files/document.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Files/document.pdf -------------------------------------------------------------------------------- /Demo/Demo/Files/graphics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Demo/Demo/Files/graphics.png -------------------------------------------------------------------------------- /Demo/Demo/Image+Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Demo.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2023-12-14. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Image { 12 | 13 | static let checkmark = symbol("checkmark") 14 | static let eye = symbol("eye") 15 | static let image = symbol("photo") 16 | static let pdf = symbol("doc.richtext") 17 | static let text = symbol("textformat.abc") 18 | static let textInput = symbol("character.cursor.ibeam") 19 | static let view = symbol("apps.iphone") 20 | static let xmark = symbol("xmark") 21 | 22 | static var checkmarkBadge: some View { 23 | Image.checkmark 24 | .badge(iconColor: .white, badgeColor: .green) 25 | } 26 | 27 | static var xmarkBadge: some View { 28 | Image.xmark 29 | .badge(iconColor: .white, badgeColor: .red) 30 | } 31 | 32 | static func symbol(_ name: String) -> Image { 33 | Image(systemName: name) 34 | } 35 | } 36 | 37 | private extension Image { 38 | 39 | func badge( 40 | iconColor: Color, 41 | badgeColor: Color 42 | ) -> some View { 43 | self.resizable() 44 | .aspectRatio(contentMode: .fill) 45 | .symbolVariant(.circle) 46 | .symbolVariant(.fill) 47 | .foregroundStyle(iconColor, badgeColor) 48 | .shadow(radius: 2, x: 0, y: 2) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Demo/Demo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "PrintingKit", 6 | platforms: [ 7 | .iOS(.v13), 8 | .macOS(.v10_15), 9 | .tvOS(.v13), 10 | .watchOS(.v7), 11 | .visionOS(.v1) 12 | ], 13 | products: [ 14 | .library( 15 | name: "PrintingKit", 16 | targets: ["PrintingKit"] 17 | ) 18 | ], 19 | targets: [ 20 | .target( 21 | name: "PrintingKit" 22 | ), 23 | .testTarget( 24 | name: "PrintingKitTests", 25 | dependencies: ["PrintingKit"] 26 | ) 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /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 | # PrintingKit 15 | 16 | PrintingKit is a Swift & SwiftUI SDK that can print images, strings, views, files, PDFs, etc. directly from an app. Just create a ``Printer`` instance or use the ``Printer.shared`` printer, then call any of its print functions to print. 17 | 18 | 19 | ## Installation 20 | 21 | PrintingKit can be installed with the Swift Package Manager: 22 | 23 | ``` 24 | https://github.com/danielsaidi/PrintingKit.git 25 | ``` 26 | 27 | 28 | ## Supported Platforms 29 | 30 | PrintintKit supports `iOS 13` and `macOS 10.5`. 31 | 32 | 33 | ## Getting started 34 | 35 | To print, just create a ``Printer`` instance, or use the ``Printer.shared`` printer, then use it to print any of the following supported printable types: 36 | 37 | * ``printAttributedString(_:config:)`` - print an attributed string. 38 | * ``printData(_:withFileExtension:)`` - try to print generic data. 39 | * ``printFile(at:)`` - try to print a generic file. 40 | * ``printImage(_:)`` - print a `UIImage` or `NSImage`. 41 | * ``printImageData(_:)`` - print JPG or PNG data. 42 | * ``printImageFile(at:)`` - print a JPG or PNG file at a certain URL. 43 | * ``printPdfData(_:)`` - print PDF document data. 44 | * ``printPdfFile(at:)`` - print a PDF document file at a certain URL. 45 | * ``printString(_:config:)`` - print a plain string. 46 | * ``printView(_:withScale:)`` - print a SwiftUI view. 47 | 48 | In SwiftUI, you can either print programatically, when a user taps/clicks a button, etc.: 49 | 50 | ```swift 51 | struct MyView: View { 52 | 53 | var body: some View { 54 | VStack { 55 | Button("Print something") { 56 | do { 57 | try? Printer.shared.printString("Hello, world!") 58 | } catch { 59 | print("Handle this \(error)") 60 | } 61 | } 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | PrintingKit also has PDF utilities, which are used to print certain types. Since these utilies are the only ones that support paper size, page margins, etc. we should aim to make more print functions use PDF as print format. 68 | 69 | > Note: Only some functions support providing a custom page configuration, which can be used to specify paper size and margins. More functions should support this functionality in the future. 70 | 71 | 72 | ## macOS Sandbox Configuration 73 | 74 | For a sandboxed application (default on macOS), you must allow printing in the target's "Signing & Capabilities" > "App Sandbox" section or, you'll be met with the error "This application does not support printing.". 75 | 76 | 77 | ## Documentation 78 | 79 | The online [documentation][Documentation] has more information, articles, code examples, etc. 80 | 81 | 82 | ## Demo Application 83 | 84 | The `Demo` folder has a demo app that lets you explore the library. 85 | 86 | 87 | ## Support my work 88 | 89 | You can [sponsor me][Sponsors] on GitHub Sponsors or [reach out][Email] for paid support, to help support my [open-source projects][OpenSource]. 90 | 91 | Your support makes it possible for me to put more work into these projects and make them the best they can be. 92 | 93 | 94 | ## Contact 95 | 96 | Feel free to reach out if you have questions, or want to contribute in any way: 97 | 98 | * Website: [danielsaidi.com][Website] 99 | * E-mail: [daniel.saidi@gmail.com][Email] 100 | * Bluesky: [@danielsaidi@bsky.social][Bluesky] 101 | * Mastodon: [@danielsaidi@mastodon.social][Mastodon] 102 | 103 | 104 | ## License 105 | 106 | PrintingKit is available under the MIT license. See the [LICENSE][License] file for more info. 107 | 108 | 109 | [Email]: mailto:daniel.saidi@gmail.com 110 | [Website]: https://danielsaidi.com 111 | [GitHub]: https://github.com/danielsaidi 112 | [OpenSource]: https://danielsaidi.com/opensource 113 | [Sponsors]: https://github.com/sponsors/danielsaidi 114 | 115 | [Bluesky]: https://bsky.app/profile/danielsaidi.bsky.social 116 | [Mastodon]: https://mastodon.social/@danielsaidi 117 | [Twitter]: https://twitter.com/danielsaidi 118 | 119 | [Documentation]: https://danielsaidi.github.io/PrintingKit 120 | [License]: https://github.com/danielsaidi/PrintingKit/blob/master/LICENSE 121 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | PrinterKit will use semantic versioning after 1.0. 4 | 5 | Until then, deprecated features may be removed in the next minor version. 6 | 7 | 8 | 9 | ## 0.7.1 10 | 11 | Thanks to [vliegeois](https://github.com/vliegeois), PrintingKit now targets macOS 10.15 instead of 11. 12 | 13 | 14 | 15 | ## 0.7 16 | 17 | This version replaces `PrintItem` with separate `Printer` functions. 18 | 19 | ### ✨ Features 20 | 21 | * `Printer` has separate print functions for each operation. 22 | * `Printer` has new functions for printing views and images. 23 | 24 | ### 💡 Adjustments 25 | 26 | ### 🗑️ Deprecations 27 | 28 | * `PrintItem` is deprecated. 29 | * `Pdf.PageConfiguration` is moved to `Printer`. 30 | * `Pdf.PageMargins` is moved to `Printer`. 31 | 32 | 33 | 34 | ## 0.6 35 | 36 | This version makes PrintingKit use Swift 6. 37 | 38 | 39 | 40 | ## 0.5.2 41 | 42 | This version removes actor information from the PdfDataSource protocol. 43 | 44 | 45 | 46 | ## 0.5.1 47 | 48 | This version enables support for strict concurrency. 49 | 50 | 51 | 52 | ## 0.5 53 | 54 | This version removes previously deprecated code. 55 | 56 | 57 | 58 | ## 0.4.2 59 | 60 | Thanks to [Cihat Gündüz](https://github.com/FlineDevPublic), this version makes PrintingKit work on visionOS. 61 | 62 | 63 | 64 | ## 0.4.1 65 | 66 | This version makes PrintingKit work on macOS Catalyst. 67 | 68 | 69 | 70 | ## 0.4 71 | 72 | This version makes view and image printing work on macOS. 73 | 74 | ### ✨ New Features 75 | 76 | * Image and view printing now works on macOS. 77 | 78 | ### 🗑️ Deprecations 79 | 80 | * All previously deprecated code has been removed. 81 | 82 | * `Printer.canPrintImages` has been deprecated. 83 | * `Printer.canPrintViews` has been deprecated. 84 | 85 | 86 | 87 | ## 0.3 88 | 89 | This version bumps to Swift 5.9 and replaces the `Printer` protocol with a class to make the library less complex. 90 | 91 | Due to this, the `PrinterView` is no longer needed, and has been deprecated. 92 | 93 | Plus, PrintingKit now builds on all Apple platforms, to let you import it without complicated, conditional adjustments. 94 | 95 | ### 💡 Behavior Changes 96 | 97 | * `Printer` is now a single, open class on all supported platforms. 98 | * `PrintItem.string` is now a proper enum case. 99 | 100 | ### 🗑️ Deprecations 101 | 102 | * `Pdf.PdfDataError` has been renamed to `Pdf.DataError`. 103 | * `PrinterError` has been renamed to `Printer.PrintError`. 104 | * `PrinterView` is deprecated. Use `Printer` directly. 105 | 106 | 107 | 108 | ## 0.2 109 | 110 | This version adds PDF utilities and more print item types. 111 | 112 | ### ✨ New Features 113 | 114 | * `Pdf` is a new namespace with PDF-specific types. 115 | * `PdfDataSource` is a new protocol that is implemented by `NSAttributedString`. 116 | * `Printer` has new `canPrint` functions and properties. 117 | * `PrinterView` has new `canPrint` functions. 118 | * `PrintItem` has new `.imageData`, `.pdfData`, `.attributedString`, `.string` and `.view` types. 119 | 120 | ### 💥 Breaking Changes 121 | 122 | * `PrinterItem.image(at:)` has been renamed to `.imageFile`. 123 | * `PrinterItem.pdf(at:)` has been renamed to `.pdfFile`. 124 | 125 | 126 | 127 | ## 0.1 128 | 129 | This is the first beta release. 130 | 131 | ### ✨ New Features 132 | 133 | * `Printer` is a protocol that defines a printer. 134 | * `PrinterError` is a printer-specific error enum. 135 | * `PrinterView` is a `SwiftUI` protocol that adds printing capabilities to SwiftUI views. 136 | * `PrintItem` is an enum that describes the currently supported item types. 137 | * `StandardPrinter` is a standard printer implementation. 138 | -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Resources/Icon.png -------------------------------------------------------------------------------- /Sources/PrintingKit/Extensions/Data+Printing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Printing.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2025-04-01. 6 | // Copyright © 2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Data { 12 | 13 | var canCreatePrintFile: Bool { 14 | FileManager.default.hasCachesDirectory 15 | } 16 | 17 | func canCreatePrintFile( 18 | withExtension ext: String 19 | ) throws -> URL { 20 | try FileManager.default.createCacheFile( 21 | with: self, 22 | fileExtension: ext 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Extensions/FileManager+Printing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+Printing.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2025-04-01. 6 | // Copyright © 2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension FileManager { 12 | 13 | var cachesDirectoryUrl: URL? { 14 | urls(for: .cachesDirectory, in: .userDomainMask).first 15 | } 16 | 17 | var hasCachesDirectory: Bool { 18 | cachesDirectoryUrl != nil 19 | } 20 | 21 | func createCacheFile( 22 | with data: Data, 23 | fileExtension: String 24 | ) throws -> URL { 25 | let id = UUID().uuidString 26 | guard let fileUrl = cachesDirectoryUrl? 27 | .appendingPathComponent(id) 28 | .appendingPathExtension(fileExtension) 29 | else { throw PrintError.cachesDirectoryDoesNotExist } 30 | try? removeItem(at: fileUrl) 31 | createFile(atPath: fileUrl.path, contents: data) 32 | return fileUrl 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Extensions/Image+Printing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+Printing.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2025-04-01. 6 | // Copyright © 2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | #if canImport(UIKit) 12 | import UIKit 13 | 14 | public typealias PrintableImage = UIImage 15 | 16 | public extension UIImage { 17 | 18 | /// Get the image's standard print data. 19 | var standardPrintData: Data? { 20 | pngData() 21 | } 22 | } 23 | #elseif os(macOS) 24 | import AppKit 25 | 26 | public typealias PrintableImage = NSImage 27 | 28 | public extension NSImage { 29 | 30 | /// Get the image's standard print data. 31 | var standardPrintData: Data? { 32 | jpegData(compressionQuality: 1) 33 | } 34 | 35 | /// Get JPEG data from the image. 36 | func jpegData(compressionQuality: CGFloat) -> Data? { 37 | guard let image = cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil } 38 | let bitmap = NSBitmapImageRep(cgImage: image) 39 | return bitmap.representation(using: .jpeg, properties: [.compressionFactor: compressionQuality]) 40 | } 41 | } 42 | #endif 43 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Extensions/ImageRenderer+Printing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageRenderer+Printing.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2025-04-01. 6 | // Copyright © 2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(macOS) || os(visionOS) 10 | import SwiftUI 11 | 12 | @MainActor 13 | @available(iOS 16.0, macOS 13.0, *) 14 | extension ImageRenderer { 15 | 16 | /// Get image data that can be used to print the image. 17 | var imageData: Data? { 18 | #if os(iOS) || os(visionOS) 19 | uiImage?.standardPrintData 20 | #elseif os(macOS) 21 | nsImage?.standardPrintData 22 | #else 23 | nil 24 | #endif 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Pdf/Pdf+DataError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pdf+DataError.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Pdf { 12 | 13 | /// This error type can be thrown when creating PDF data. 14 | enum DataError: Error { 15 | 16 | /// The platform is currently not supported 17 | case unsupportedPlatform 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Pdf/Pdf.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pdf.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This namespace contains PDF-specific types. 12 | public struct Pdf {} 13 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Pdf/PdfDataSource+NSAttributedString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfDataSource+NSAttributedString.swift 3 | // RichTextKit 4 | // 5 | // Created by Daniel Saidi on 2022-06-03. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSAttributedString: @preconcurrency PdfDataSource {} 12 | 13 | @MainActor 14 | public extension NSAttributedString { 15 | 16 | func pdfData() throws -> Data { 17 | try pdfData(withConfiguration: .standard) 18 | } 19 | 20 | func pdfData( 21 | withConfiguration config: Printer.PageConfiguration 22 | ) throws -> Data { 23 | #if os(iOS) || os(visionOS) 24 | try iosPdfData(for: config) 25 | #elseif os(macOS) 26 | try macosPdfData(for: config) 27 | #else 28 | throw Pdf.DataError.unsupportedPlatform 29 | #endif 30 | } 31 | } 32 | 33 | #if os(macOS) 34 | import AppKit 35 | 36 | @MainActor 37 | private extension NSAttributedString { 38 | 39 | func macosPdfData(for config: Printer.PageConfiguration) throws -> Data { 40 | do { 41 | let fileUrl = try macosPdfFileUrl() 42 | let info = try macosPdfPrintInfo(for: config, fileUrl: fileUrl) 43 | let scrollView = NSTextView.scrollableTextView() 44 | scrollView.frame = config.paperRect 45 | let textView = scrollView.documentView as? NSTextView ?? NSTextView() 46 | sleepToPrepareTextView() 47 | textView.textStorage?.setAttributedString(self) 48 | 49 | let operation = NSPrintOperation(view: textView, printInfo: info) 50 | operation.showsPrintPanel = false 51 | operation.showsProgressPanel = false 52 | operation.run() 53 | 54 | return try Data(contentsOf: fileUrl) 55 | } catch { 56 | throw(error) 57 | } 58 | } 59 | 60 | func macosPdfFileUrl() throws -> URL { 61 | let manager = FileManager.default 62 | let cacheUrl = try manager.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) 63 | return cacheUrl 64 | .appendingPathComponent(UUID().uuidString) 65 | .appendingPathExtension("pdf") 66 | } 67 | 68 | func macosPdfPrintInfo( 69 | for config: Printer.PageConfiguration, 70 | fileUrl: URL) throws -> NSPrintInfo { 71 | let options: [NSPrintInfo.AttributeKey: Any] = [ 72 | .jobDisposition: NSPrintInfo.JobDisposition.save, 73 | .jobSavingURL: fileUrl] 74 | let info = NSPrintInfo(dictionary: options) 75 | info.horizontalPagination = .fit 76 | info.verticalPagination = .automatic 77 | info.topMargin = config.pageMargins.top 78 | info.leftMargin = config.pageMargins.left 79 | info.rightMargin = config.pageMargins.right 80 | info.bottomMargin = config.pageMargins.bottom 81 | info.isHorizontallyCentered = false 82 | info.isVerticallyCentered = false 83 | return info 84 | } 85 | 86 | func sleepToPrepareTextView() { 87 | Thread.sleep(forTimeInterval: 0.1) 88 | } 89 | } 90 | #endif 91 | 92 | #if os(iOS) || os(visionOS) 93 | import UIKit 94 | 95 | @MainActor 96 | private extension NSAttributedString { 97 | 98 | func iosPdfData(for config: Printer.PageConfiguration) throws -> Data { 99 | let renderer = iosPdfPageRenderer(for: config) 100 | let paperRect = config.paperRect 101 | let pdfData = NSMutableData() 102 | UIGraphicsBeginPDFContextToData(pdfData, paperRect, nil) 103 | let range = NSRange(location: 0, length: renderer.numberOfPages) 104 | renderer.prepare(forDrawingPages: range) 105 | let bounds = UIGraphicsGetPDFContextBounds() 106 | for i in 0 ..< renderer.numberOfPages { 107 | UIGraphicsBeginPDFPage() 108 | renderer.drawPage(at: i, in: bounds) 109 | } 110 | UIGraphicsEndPDFContext() 111 | return pdfData as Data 112 | } 113 | 114 | func iosPdfPageRenderer(for configuration: Printer.PageConfiguration) -> UIPrintPageRenderer { 115 | let formatter = UISimpleTextPrintFormatter(attributedText: self) 116 | let paperRect = NSValue(cgRect: configuration.paperRect) 117 | let printableRect = NSValue(cgRect: configuration.printableRect) 118 | let renderer = UIPrintPageRenderer() 119 | renderer.addPrintFormatter(formatter, startingAtPageAt: 0) 120 | renderer.setValue(paperRect, forKey: "paperRect") 121 | renderer.setValue(printableRect, forKey: "printableRect") 122 | return renderer 123 | } 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Pdf/PdfDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfDataSource.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This protocol can be implemented by any type that can be 12 | /// used to generate PDF data. 13 | public protocol PdfDataSource { 14 | 15 | /// Generate PDF data. 16 | func pdfData() throws -> Data 17 | 18 | /// Generate PDF data for the provided configuration. 19 | func pdfData( 20 | withConfiguration config: Printer.PageConfiguration 21 | ) throws -> Data 22 | } 23 | -------------------------------------------------------------------------------- /Sources/PrintingKit/PrintError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrinterError.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This error can be thrown by ``Printer`` operations. 12 | public enum PrintError: Error { 13 | 14 | /// The print operation could not generate a cache file. 15 | case cachesDirectoryDoesNotExist 16 | 17 | /// The print opetation failed to extract print data. 18 | case failedToExtractPrintDataFromImage 19 | 20 | /// The print operation was given an invalid URL. 21 | case invalidUrl 22 | 23 | /// The print operation was given an invalid view. 24 | case invalidViewData 25 | 26 | /// The print operation does not support the current platform. 27 | case unsupportedPlatform 28 | } 29 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Printer+PageConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Printer+PageConfiguration.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | public extension Printer { 12 | 13 | /// This type defines a PDF document page configuration. 14 | struct PageConfiguration: Equatable { 15 | 16 | /// Create a PDF document page configuration value. 17 | /// 18 | /// - Parameters: 19 | /// - pageSize: The page size in points. 20 | /// - pageMargins: The page margins, by default `72`. 21 | public init( 22 | pageSize: CGSize = CGSize(width: 595.2, height: 841.8), 23 | pageMargins: PageMargins = .init(all: 72) 24 | ) { 25 | self.pageSize = pageSize 26 | self.pageMargins = pageMargins 27 | } 28 | 29 | /// The page size in points. 30 | public var pageSize: CGSize 31 | 32 | /// The page margins, by default `72`. 33 | public var pageMargins: PageMargins 34 | } 35 | } 36 | 37 | public extension Printer.PageConfiguration { 38 | 39 | /// The standard PDF page configuration. 40 | static var standard: Self { .init() } 41 | } 42 | 43 | public extension Printer.PageConfiguration { 44 | 45 | /// Get the configuration's paper rectangle. 46 | var paperRect: CGRect { 47 | CGRect(x: 0, y: 0, width: pageSize.width, height: pageSize.height) 48 | } 49 | 50 | /// Get the configuration's printable rectangle. 51 | var printableRect: CGRect { 52 | CGRect( 53 | x: pageMargins.left, 54 | y: pageMargins.top, 55 | width: pageSize.width - pageMargins.left - pageMargins.right, 56 | height: pageSize.height - pageMargins.top - pageMargins.bottom 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Printer+PageMargins.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Printer+PageMargins.swift 3 | // PrintingKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | public extension Printer { 12 | 13 | /// This type defines a PDF document page margins. 14 | struct PageMargins: Equatable { 15 | 16 | /// Create a PDF document page margins value. 17 | /// 18 | /// - Parameters: 19 | /// - top: The top margins. 20 | /// - left: The left margins. 21 | /// - bottom: The bottom margins. 22 | /// - right: The right margins. 23 | public init(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) { 24 | self.top = top 25 | self.left = left 26 | self.bottom = bottom 27 | self.right = right 28 | } 29 | 30 | /// Create PDF page margins. 31 | /// 32 | /// - Parameters: 33 | /// - horizontal: The horizontal margins. 34 | /// - vertical: The vertical margins. 35 | public init(horizontal: CGFloat, vertical: CGFloat) { 36 | self.top = vertical 37 | self.left = horizontal 38 | self.bottom = vertical 39 | self.right = horizontal 40 | } 41 | 42 | /// Create PDF page margins. 43 | /// 44 | /// - Parameters: 45 | /// - all: The margins for all edges. 46 | public init(all: CGFloat) { 47 | self.top = all 48 | self.left = all 49 | self.bottom = all 50 | self.right = all 51 | } 52 | 53 | /// The top margins. 54 | public var top: CGFloat 55 | 56 | /// The left margins. 57 | public var left: CGFloat 58 | 59 | /// The bottom margins. 60 | public var bottom: CGFloat 61 | 62 | /// The right margins. 63 | public var right: CGFloat 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/PrintingKit/Printer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Printer_macOS.swift 3 | // SwiftUIKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(visionOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import AppKit 13 | import PDFKit 14 | #endif 15 | 16 | import SwiftUI 17 | 18 | /// This class can be used to print various types, like data, 19 | /// strings, files, images, etc. 20 | /// 21 | /// You can use ``Printer/shared`` if you do not want to use 22 | /// separate instances or custom implementations. 23 | /// 24 | /// > Note: Some print functions support page configurations, 25 | /// which can be used to specify paper size and page margins. 26 | /// This should be extended to cover more print functions in 27 | /// future versions. 28 | @MainActor 29 | open class Printer { 30 | 31 | /// Create a printer instance. 32 | public init() {} 33 | 34 | 35 | /// A shared printer instance. 36 | /// 37 | /// You can replace this to change the global default. 38 | public static var shared = Printer() 39 | 40 | 41 | /// Print the provided attributed string. 42 | open func printAttributedString( 43 | _ string: NSAttributedString, 44 | config: PageConfiguration 45 | ) throws { 46 | let data = try string.pdfData(withConfiguration: config) 47 | try printPdfData(data) 48 | } 49 | 50 | /// Print the provided data for a certain file extension. 51 | open func printData( 52 | _ data: Data, 53 | withFileExtension ext: String 54 | ) throws { 55 | let url = try data.canCreatePrintFile(withExtension: ext) 56 | try printFile(at: url) 57 | } 58 | 59 | /// Print a file at the provided URL. 60 | open func printFile(at url: URL?) throws { 61 | guard let url else { throw PrintError.invalidUrl } 62 | DispatchQueue.main.async { 63 | #if os(iOS) || os(visionOS) 64 | let info = UIPrintInfo(dictionary: nil) 65 | info.outputType = .general 66 | info.jobName = "Standard Printer Job" 67 | let controller = UIPrintInteractionController.shared 68 | controller.printInfo = info 69 | controller.printingItem = url 70 | controller.present(animated: true) 71 | #elseif os(macOS) 72 | let view = PDFView() 73 | let window = NSWindow() 74 | view.document = PDFDocument(url: url) 75 | window.setContentSize(view.frame.size) 76 | window.contentView = view 77 | window.center() 78 | view.print(with: .shared, autoRotate: true) 79 | #endif 80 | } 81 | } 82 | 83 | /// Print the provided image's standard print data. 84 | open func printImage(_ image: PrintableImage) throws { 85 | guard let data = image.standardPrintData else { 86 | throw PrintError.failedToExtractPrintDataFromImage 87 | } 88 | try printImageData(data) 89 | } 90 | 91 | /// Print the provided image data. 92 | open func printImageData(_ data: Data) throws { 93 | #if os(iOS) || os(visionOS) 94 | let url = try data.canCreatePrintFile(withExtension: "img") 95 | try printFile(at: url) 96 | #elseif os(macOS) 97 | guard let image = NSImage(data: data) else { return } 98 | let imageView = NSImageView(image: image) 99 | imageView.frame = NSRect(x: 0, y: 0, width: image.size.width, height: image.size.height) 100 | imageView.imageScaling = .scaleProportionallyDown 101 | let printOperation = NSPrintOperation(view: imageView) 102 | let printInfo = NSPrintInfo.shared 103 | printInfo.horizontalPagination = .fit 104 | printInfo.verticalPagination = .fit 105 | printOperation.printInfo = printInfo 106 | printOperation.showsPrintPanel = true 107 | printOperation.showsProgressPanel = true 108 | printOperation.run() 109 | #else 110 | throw PrintError.unsupportedPlatform 111 | #endif 112 | } 113 | 114 | /// Print an image file at the provided URL. 115 | open func printImageFile(at url: URL?) throws { 116 | guard let url else { throw PrintError.invalidUrl } 117 | #if os(iOS) || os(visionOS) 118 | try printFile(at: url) 119 | #else 120 | Task { 121 | let result = try await URLSession.shared.data(from: url) 122 | try printImageData(result.0) 123 | } 124 | #endif 125 | } 126 | 127 | /// Print the provided PDF formatted data. 128 | open func printPdfData(_ data: Data) throws { 129 | let url = try data.canCreatePrintFile(withExtension: "pdf") 130 | try printFile(at: url) 131 | } 132 | 133 | /// Print a PDF file at the provided URL. 134 | open func printPdfFile(at url: URL) throws { 135 | try printFile(at: url) 136 | } 137 | 138 | /// Print the provided string. 139 | open func printString( 140 | _ string: String, 141 | config: PageConfiguration 142 | ) throws { 143 | let string = NSAttributedString(string: string) 144 | try printAttributedString(string, config: config) 145 | } 146 | 147 | /// Print the provided view as an image. 148 | @available(iOS 16.0, macOS 13.0, *) 149 | open func printView ( 150 | _ view: Content, 151 | withScale scale: CGFloat = 2 152 | ) throws { 153 | #if os(iOS) || os(visionOS) || os(macOS) 154 | let renderer = ImageRenderer(content: view) 155 | renderer.scale = scale 156 | guard let data = renderer.imageData else { throw PrintError.invalidViewData } 157 | try? printImageData(data) 158 | #else 159 | throw PrintError.unsupportedPlatform 160 | #endif 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/PrintingKit/PrintingKit.docc/PrintingKit.md: -------------------------------------------------------------------------------- 1 | # ``PrintingKit`` 2 | 3 | PrintingKit is a Swift SDK that helps you print images, strings, views, PDFs etc. in Swift and SwiftUI. 4 | 5 | 6 | ## Overview 7 | 8 | ![Library logotype](Logo.png) 9 | 10 | PrintingKit is a Swift & SwiftUI SDK that can print images, strings, views, files, PDFs, etc. directly from an app. Just create a ``Printer`` instance or use the ``Printer/shared`` printer, then call any of its print functions to print. 11 | 12 | 13 | ## Installation 14 | 15 | PrintingKit can be installed with the Swift Package Manager: 16 | 17 | ``` 18 | https://github.com/danielsaidi/PrintingKit.git 19 | ``` 20 | 21 | 22 | ## Supported Platforms 23 | 24 | PrintintKit supports `iOS 13` and `macOS 10.5`. 25 | 26 | 27 | ## Getting started 28 | 29 | To print, just create a ``Printer`` instance, or use the ``Printer/shared`` printer, then use it to print any of the following supported printable types: 30 | 31 | * ``Printer/printAttributedString(_:config:)`` - print an attributed string. 32 | * ``Printer/printData(_:withFileExtension:)`` - try to print generic data. 33 | * ``Printer/printFile(at:)`` - try to print a generic file. 34 | * ``Printer/printImage(_:)`` - print a `UIImage` or `NSImage`. 35 | * ``Printer/printImageData(_:)`` - print JPG or PNG data. 36 | * ``Printer/printImageFile(at:)`` - print a JPG or PNG file at a certain URL. 37 | * ``Printer/printPdfData(_:)`` - print PDF document data. 38 | * ``Printer/printPdfFile(at:)`` - print a PDF document file at a certain URL. 39 | * ``Printer/printString(_:config:)`` - print a plain string. 40 | * ``Printer/printView(_:withScale:)`` - print a SwiftUI view. 41 | 42 | In SwiftUI, you can either print programatically, when a user taps/clicks a button, etc.: 43 | 44 | ```swift 45 | struct MyView: View { 46 | 47 | var body: some View { 48 | VStack { 49 | Button("Print something") { 50 | do { 51 | try? Printer.shared.printString("Hello, world!") 52 | } catch { 53 | print("Handle this \(error)") 54 | } 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | PrintingKit also has PDF utilities, which are used to print certain types. Since these utilies are the only ones that support paper size, page margins, etc. we should aim to make more print functions use PDF as print format. 62 | 63 | > Note: Only some functions support providing a custom page configuration, which can be used to specify paper size and margins. More functions should support this functionality in the future. 64 | 65 | 66 | ## macOS Sandbox Configuration 67 | 68 | For a sandboxed application (default on macOS), you must allow printing in the target's "Signing & Capabilities" > "App Sandbox" section or, you'll be met with the error "This application does not support printing.". 69 | 70 | 71 | ## Repository 72 | 73 | For more information, source code, etc., visit the [project repository](https://github.com/danielsaidi/PrintingKit). 74 | 75 | 76 | ## License 77 | 78 | PrintingKit is available under the MIT license. 79 | 80 | 81 | ## Topics 82 | 83 | ### Essentials 84 | 85 | - ``Printer`` 86 | - ``PrintError`` 87 | - ``PrintableImage`` 88 | 89 | ### Pdf 90 | 91 | - ``Pdf`` 92 | - ``PdfDataSource`` 93 | -------------------------------------------------------------------------------- /Sources/PrintingKit/PrintingKit.docc/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Sources/PrintingKit/PrintingKit.docc/Resources/Logo.png -------------------------------------------------------------------------------- /Sources/PrintingKit/PrintingKit.docc/Resources/Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/PrintingKit/9e0a63c23f362a6a6958ce82acd150f07b1d93bd/Sources/PrintingKit/PrintingKit.docc/Resources/Page.png -------------------------------------------------------------------------------- /Tests/PrintingKitTests/PrinterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrinterTests.swift 3 | // SwiftUIKit 4 | // 5 | // Created by Daniel Saidi on 2023-08-21. 6 | // Copyright © 2023-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import PrintingKit 10 | import SwiftUI 11 | import XCTest 12 | 13 | #if os(iOS) || os(macOS) || os(visionOS) 14 | final class PrinterTests: XCTestCase { 15 | 16 | // let printer = Printer() 17 | // 18 | // @available(iOS 16.0, macOS 11.0, *) 19 | // func testCanPrintViewsIdThusImages() async throws { 20 | // let view = Text("Hello") 21 | // let item = try await PrintItem.view(view) 22 | // let canPrint = printer.canPrint(item) 23 | // XCTAssertTrue(canPrint) 24 | // } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------