├── .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 │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Shared │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-iOS-1024.png │ │ │ ├── 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 │ │ └── Contents.json │ ├── ContentView.swift │ └── DemoApp.swift └── macOS │ └── macOS.entitlements ├── LICENSE ├── Package.swift ├── README.md ├── RELEASE_NOTES.md ├── Resources ├── Demo.gif └── Icon.png ├── Sources └── WebViewKit │ ├── Documentation.docc │ ├── Getting-Started.md │ ├── Resources │ │ ├── Logo.png │ │ └── Page.png │ └── WebViewKit.md │ ├── SafariWebView.swift │ └── WebView.swift ├── Tests └── WebViewKitTests │ └── WebViewTests.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/ 7 | *.xcuserstate -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - type_name 4 | - vertical_whitespace 5 | 6 | included: 7 | - Sources 8 | - Tests 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A90FB9EE27F65D0600D05AA1 /* WebViewKit in Frameworks */ = {isa = PBXBuildFile; productRef = A90FB9ED27F65D0600D05AA1 /* WebViewKit */; }; 11 | A90FB9F027F65D0A00D05AA1 /* WebViewKit in Frameworks */ = {isa = PBXBuildFile; productRef = A90FB9EF27F65D0A00D05AA1 /* WebViewKit */; }; 12 | A91347FE27F65C6100DCBB5F /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91347EE27F65C6000DCBB5F /* DemoApp.swift */; }; 13 | A91347FF27F65C6100DCBB5F /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91347EE27F65C6000DCBB5F /* DemoApp.swift */; }; 14 | A913480027F65C6100DCBB5F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91347EF27F65C6000DCBB5F /* ContentView.swift */; }; 15 | A913480127F65C6100DCBB5F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91347EF27F65C6000DCBB5F /* ContentView.swift */; }; 16 | A913480227F65C6100DCBB5F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A91347F027F65C6100DCBB5F /* Assets.xcassets */; }; 17 | A913480327F65C6100DCBB5F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A91347F027F65C6100DCBB5F /* Assets.xcassets */; }; 18 | A9578C202CB2B793008F777C /* WebViewKit in Frameworks */ = {isa = PBXBuildFile; productRef = A9578C1F2CB2B793008F777C /* WebViewKit */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | A91347EE27F65C6000DCBB5F /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; }; 23 | A91347EF27F65C6000DCBB5F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 24 | A91347F027F65C6100DCBB5F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | A91347F527F65C6100DCBB5F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | A91347FB27F65C6100DCBB5F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | A91347FD27F65C6100DCBB5F /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | A91347F227F65C6100DCBB5F /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | A90FB9EE27F65D0600D05AA1 /* WebViewKit in Frameworks */, 36 | A9578C202CB2B793008F777C /* WebViewKit in Frameworks */, 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | A91347F827F65C6100DCBB5F /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | A90FB9F027F65D0A00D05AA1 /* WebViewKit in Frameworks */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | A90FB9EC27F65D0600D05AA1 /* Frameworks */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | ); 55 | name = Frameworks; 56 | sourceTree = ""; 57 | }; 58 | A91347E827F65C6000DCBB5F = { 59 | isa = PBXGroup; 60 | children = ( 61 | A91347ED27F65C6000DCBB5F /* Shared */, 62 | A91347FC27F65C6100DCBB5F /* macOS */, 63 | A91347F627F65C6100DCBB5F /* Products */, 64 | A90FB9EC27F65D0600D05AA1 /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | A91347ED27F65C6000DCBB5F /* Shared */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | A91347EE27F65C6000DCBB5F /* DemoApp.swift */, 72 | A91347EF27F65C6000DCBB5F /* ContentView.swift */, 73 | A91347F027F65C6100DCBB5F /* Assets.xcassets */, 74 | ); 75 | path = Shared; 76 | sourceTree = ""; 77 | }; 78 | A91347F627F65C6100DCBB5F /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | A91347F527F65C6100DCBB5F /* Demo.app */, 82 | A91347FB27F65C6100DCBB5F /* Demo.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | A91347FC27F65C6100DCBB5F /* macOS */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | A91347FD27F65C6100DCBB5F /* macOS.entitlements */, 91 | ); 92 | path = macOS; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | A91347F427F65C6100DCBB5F /* Demo (iOS) */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = A913480627F65C6100DCBB5F /* Build configuration list for PBXNativeTarget "Demo (iOS)" */; 101 | buildPhases = ( 102 | A91347F127F65C6100DCBB5F /* Sources */, 103 | A91347F227F65C6100DCBB5F /* Frameworks */, 104 | A91347F327F65C6100DCBB5F /* Resources */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = "Demo (iOS)"; 111 | packageProductDependencies = ( 112 | A90FB9ED27F65D0600D05AA1 /* WebViewKit */, 113 | A9578C1F2CB2B793008F777C /* WebViewKit */, 114 | ); 115 | productName = "Demo (iOS)"; 116 | productReference = A91347F527F65C6100DCBB5F /* Demo.app */; 117 | productType = "com.apple.product-type.application"; 118 | }; 119 | A91347FA27F65C6100DCBB5F /* Demo (macOS) */ = { 120 | isa = PBXNativeTarget; 121 | buildConfigurationList = A913480927F65C6100DCBB5F /* Build configuration list for PBXNativeTarget "Demo (macOS)" */; 122 | buildPhases = ( 123 | A91347F727F65C6100DCBB5F /* Sources */, 124 | A91347F827F65C6100DCBB5F /* Frameworks */, 125 | A91347F927F65C6100DCBB5F /* Resources */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = "Demo (macOS)"; 132 | packageProductDependencies = ( 133 | A90FB9EF27F65D0A00D05AA1 /* WebViewKit */, 134 | ); 135 | productName = "Demo (macOS)"; 136 | productReference = A91347FB27F65C6100DCBB5F /* Demo.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | A91347E927F65C6000DCBB5F /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | BuildIndependentTargetsInParallel = 1; 146 | LastSwiftUpdateCheck = 1330; 147 | LastUpgradeCheck = 1330; 148 | ORGANIZATIONNAME = "Daniel Saidi"; 149 | TargetAttributes = { 150 | A91347F427F65C6100DCBB5F = { 151 | CreatedOnToolsVersion = 13.3; 152 | }; 153 | A91347FA27F65C6100DCBB5F = { 154 | CreatedOnToolsVersion = 13.3; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = A91347EC27F65C6000DCBB5F /* Build configuration list for PBXProject "Demo" */; 159 | compatibilityVersion = "Xcode 13.0"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = A91347E827F65C6000DCBB5F; 167 | packageReferences = ( 168 | A9578C1E2CB2B793008F777C /* XCLocalSwiftPackageReference "../../webviewkit" */, 169 | ); 170 | productRefGroup = A91347F627F65C6100DCBB5F /* Products */; 171 | projectDirPath = ""; 172 | projectRoot = ""; 173 | targets = ( 174 | A91347F427F65C6100DCBB5F /* Demo (iOS) */, 175 | A91347FA27F65C6100DCBB5F /* Demo (macOS) */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | A91347F327F65C6100DCBB5F /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | A913480227F65C6100DCBB5F /* Assets.xcassets in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | A91347F927F65C6100DCBB5F /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | A913480327F65C6100DCBB5F /* Assets.xcassets in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXSourcesBuildPhase section */ 200 | A91347F127F65C6100DCBB5F /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | A913480027F65C6100DCBB5F /* ContentView.swift in Sources */, 205 | A91347FE27F65C6100DCBB5F /* DemoApp.swift in Sources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | A91347F727F65C6100DCBB5F /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | A913480127F65C6100DCBB5F /* ContentView.swift in Sources */, 214 | A91347FF27F65C6100DCBB5F /* DemoApp.swift in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | A913480427F65C6100DCBB5F /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_ANALYZER_NONNULL = YES; 226 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_ENABLE_OBJC_WEAK = YES; 231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 232 | CLANG_WARN_BOOL_CONVERSION = YES; 233 | CLANG_WARN_COMMA = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INFINITE_RECURSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 247 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 248 | CLANG_WARN_STRICT_PROTOTYPES = YES; 249 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 250 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 251 | CLANG_WARN_UNREACHABLE_CODE = YES; 252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 253 | COPY_PHASE_STRIP = NO; 254 | DEBUG_INFORMATION_FORMAT = dwarf; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | ENABLE_TESTABILITY = YES; 257 | GCC_C_LANGUAGE_STANDARD = gnu11; 258 | GCC_DYNAMIC_NO_PIC = NO; 259 | GCC_NO_COMMON_BLOCKS = YES; 260 | GCC_OPTIMIZATION_LEVEL = 0; 261 | GCC_PREPROCESSOR_DEFINITIONS = ( 262 | "DEBUG=1", 263 | "$(inherited)", 264 | ); 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 16.6; 272 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 273 | MTL_FAST_MATH = YES; 274 | ONLY_ACTIVE_ARCH = YES; 275 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 277 | }; 278 | name = Debug; 279 | }; 280 | A913480527F65C6100DCBB5F /* Release */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_ANALYZER_NONNULL = YES; 285 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 287 | CLANG_ENABLE_MODULES = YES; 288 | CLANG_ENABLE_OBJC_ARC = YES; 289 | CLANG_ENABLE_OBJC_WEAK = YES; 290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_COMMA = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INFINITE_RECURSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 305 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 306 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 307 | CLANG_WARN_STRICT_PROTOTYPES = YES; 308 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 309 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | COPY_PHASE_STRIP = NO; 313 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 314 | ENABLE_NS_ASSERTIONS = NO; 315 | ENABLE_STRICT_OBJC_MSGSEND = YES; 316 | GCC_C_LANGUAGE_STANDARD = gnu11; 317 | GCC_NO_COMMON_BLOCKS = YES; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 16.6; 325 | MTL_ENABLE_DEBUG_INFO = NO; 326 | MTL_FAST_MATH = YES; 327 | SWIFT_COMPILATION_MODE = wholemodule; 328 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 329 | }; 330 | name = Release; 331 | }; 332 | A913480727F65C6100DCBB5F /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 337 | CODE_SIGN_STYLE = Automatic; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEVELOPMENT_TEAM = PMEDFW438U; 340 | ENABLE_PREVIEWS = YES; 341 | GENERATE_INFOPLIST_FILE = YES; 342 | INFOPLIST_KEY_CFBundleDisplayName = WebViewKit; 343 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 344 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 345 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 346 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 347 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 348 | LD_RUNPATH_SEARCH_PATHS = ( 349 | "$(inherited)", 350 | "@executable_path/Frameworks", 351 | ); 352 | MARKETING_VERSION = 1.0; 353 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.webviewkit.Demo; 354 | PRODUCT_NAME = Demo; 355 | SDKROOT = iphoneos; 356 | SWIFT_EMIT_LOC_STRINGS = YES; 357 | SWIFT_VERSION = 5.0; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | }; 360 | name = Debug; 361 | }; 362 | A913480827F65C6100DCBB5F /* Release */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 366 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 367 | CODE_SIGN_STYLE = Automatic; 368 | CURRENT_PROJECT_VERSION = 1; 369 | DEVELOPMENT_TEAM = PMEDFW438U; 370 | ENABLE_PREVIEWS = YES; 371 | GENERATE_INFOPLIST_FILE = YES; 372 | INFOPLIST_KEY_CFBundleDisplayName = WebViewKit; 373 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 374 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 375 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 376 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 377 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 378 | LD_RUNPATH_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "@executable_path/Frameworks", 381 | ); 382 | MARKETING_VERSION = 1.0; 383 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.webviewkit.Demo; 384 | PRODUCT_NAME = Demo; 385 | SDKROOT = iphoneos; 386 | SWIFT_EMIT_LOC_STRINGS = YES; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | VALIDATE_PRODUCT = YES; 390 | }; 391 | name = Release; 392 | }; 393 | A913480A27F65C6100DCBB5F /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 398 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; 399 | CODE_SIGN_IDENTITY = "-"; 400 | CODE_SIGN_STYLE = Automatic; 401 | COMBINE_HIDPI_IMAGES = YES; 402 | CURRENT_PROJECT_VERSION = 1; 403 | DEVELOPMENT_TEAM = PMEDFW438U; 404 | ENABLE_HARDENED_RUNTIME = YES; 405 | ENABLE_PREVIEWS = YES; 406 | GENERATE_INFOPLIST_FILE = YES; 407 | INFOPLIST_KEY_CFBundleDisplayName = WebViewKit; 408 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 409 | LD_RUNPATH_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "@executable_path/../Frameworks", 412 | ); 413 | MARKETING_VERSION = 1.0; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.webviewkit.Demo; 415 | PRODUCT_NAME = Demo; 416 | SDKROOT = macosx; 417 | SWIFT_EMIT_LOC_STRINGS = YES; 418 | SWIFT_VERSION = 5.0; 419 | }; 420 | name = Debug; 421 | }; 422 | A913480B27F65C6100DCBB5F /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 426 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 427 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; 428 | CODE_SIGN_IDENTITY = "-"; 429 | CODE_SIGN_STYLE = Automatic; 430 | COMBINE_HIDPI_IMAGES = YES; 431 | CURRENT_PROJECT_VERSION = 1; 432 | DEVELOPMENT_TEAM = PMEDFW438U; 433 | ENABLE_HARDENED_RUNTIME = YES; 434 | ENABLE_PREVIEWS = YES; 435 | GENERATE_INFOPLIST_FILE = YES; 436 | INFOPLIST_KEY_CFBundleDisplayName = WebViewKit; 437 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 438 | LD_RUNPATH_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "@executable_path/../Frameworks", 441 | ); 442 | MARKETING_VERSION = 1.0; 443 | PRODUCT_BUNDLE_IDENTIFIER = com.danielsaidi.webviewkit.Demo; 444 | PRODUCT_NAME = Demo; 445 | SDKROOT = macosx; 446 | SWIFT_EMIT_LOC_STRINGS = YES; 447 | SWIFT_VERSION = 5.0; 448 | }; 449 | name = Release; 450 | }; 451 | /* End XCBuildConfiguration section */ 452 | 453 | /* Begin XCConfigurationList section */ 454 | A91347EC27F65C6000DCBB5F /* Build configuration list for PBXProject "Demo" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | A913480427F65C6100DCBB5F /* Debug */, 458 | A913480527F65C6100DCBB5F /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | A913480627F65C6100DCBB5F /* Build configuration list for PBXNativeTarget "Demo (iOS)" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | A913480727F65C6100DCBB5F /* Debug */, 467 | A913480827F65C6100DCBB5F /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | A913480927F65C6100DCBB5F /* Build configuration list for PBXNativeTarget "Demo (macOS)" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | A913480A27F65C6100DCBB5F /* Debug */, 476 | A913480B27F65C6100DCBB5F /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | /* End XCConfigurationList section */ 482 | 483 | /* Begin XCLocalSwiftPackageReference section */ 484 | A9578C1E2CB2B793008F777C /* XCLocalSwiftPackageReference "../../webviewkit" */ = { 485 | isa = XCLocalSwiftPackageReference; 486 | relativePath = ../../webviewkit; 487 | }; 488 | /* End XCLocalSwiftPackageReference section */ 489 | 490 | /* Begin XCSwiftPackageProductDependency section */ 491 | A90FB9ED27F65D0600D05AA1 /* WebViewKit */ = { 492 | isa = XCSwiftPackageProductDependency; 493 | productName = WebViewKit; 494 | }; 495 | A90FB9EF27F65D0A00D05AA1 /* WebViewKit */ = { 496 | isa = XCSwiftPackageProductDependency; 497 | productName = WebViewKit; 498 | }; 499 | A9578C1F2CB2B793008F777C /* WebViewKit */ = { 500 | isa = XCSwiftPackageProductDependency; 501 | productName = WebViewKit; 502 | }; 503 | /* End XCSwiftPackageProductDependency section */ 504 | }; 505 | rootObject = A91347E927F65C6000DCBB5F /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /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/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Demo (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | Demo (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demo/Shared/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/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-iOS-1024.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/Shared/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png -------------------------------------------------------------------------------- /Demo/Shared/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Shared/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WebViewKit 4 | // 5 | // Created by Daniel Saidi on 2022-04-01. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import WebKit 11 | import WebViewKit 12 | 13 | struct ContentView: View { 14 | 15 | let url = URL(string: "https://apple.com") 16 | 17 | enum BrowserType { 18 | case regular, safari 19 | } 20 | 21 | @State 22 | var browserType = BrowserType.regular 23 | 24 | @State 25 | var webViewInstance: WKWebView? 26 | 27 | var body: some View { 28 | #if os(iOS) 29 | NavigationStack { 30 | browser 31 | .ignoresSafeArea(edges: .bottom) 32 | .navigationBarTitleDisplayMode(.inline) 33 | .toolbar { 34 | ToolbarItem(placement: .principal) { 35 | picker 36 | 37 | } 38 | if browserType == .regular { 39 | ToolbarItem(placement: .bottomBar) { 40 | Button("Print Current URL") { 41 | print(webViewInstance?.url?.absoluteString ?? "N/A") 42 | } 43 | } 44 | } 45 | } 46 | } 47 | #else 48 | webView 49 | #endif 50 | } 51 | } 52 | 53 | private extension ContentView { 54 | 55 | @ViewBuilder 56 | var browser: some View { 57 | switch browserType { 58 | case .regular: webView 59 | case .safari: safariWebView 60 | } 61 | } 62 | 63 | var picker: some View { 64 | Picker("Select browser", selection: $browserType) { 65 | Text("WebView").tag(BrowserType.regular) 66 | Text("Safari").tag(BrowserType.safari) 67 | } 68 | .padding(.horizontal) 69 | .pickerStyle(.segmented) 70 | } 71 | 72 | #if os(iOS) 73 | @ViewBuilder 74 | var safariWebView: some View { 75 | if let url = url { 76 | SafariWebView(url: url) { controller in 77 | // Configure the controller in any way you like 78 | } 79 | } else { 80 | Text("URL is nil") 81 | } 82 | } 83 | #endif 84 | 85 | var webView: some View { 86 | WebView(url: url) { webView in 87 | webViewInstance = webView 88 | // Configure the web view in any way you like 89 | // For instance, let's set a custom user agent. 90 | webView.customUserAgent = "foo bar" 91 | } 92 | } 93 | } 94 | 95 | #Preview { 96 | 97 | ContentView() 98 | } 99 | -------------------------------------------------------------------------------- /Demo/Shared/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // WebViewKit 4 | // 5 | // Created by Daniel Saidi on 2022-04-01. 6 | // Copyright © 2022-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/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 Daniel Saidi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "WebViewKit", 7 | platforms: [ 8 | .iOS(.v14), 9 | .macOS(.v11), 10 | .tvOS(.v14), 11 | .visionOS(.v1), 12 | .watchOS(.v9), 13 | ], 14 | products: [ 15 | .library( 16 | name: "WebViewKit", 17 | targets: ["WebViewKit"] 18 | ) 19 | ], 20 | targets: [ 21 | .target( 22 | name: "WebViewKit" 23 | ), 24 | .testTarget( 25 | name: "WebViewKitTests", 26 | dependencies: ["WebViewKit"] 27 | ) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Project Icon 3 |

4 | 5 |

6 | Version 7 | Swift 6.0 8 | Swift UI 9 | Documentation 10 | MIT License 11 | Sponsor my work 12 |

13 | 14 | 15 | 16 | # WebViewKit 17 | 18 | WebViewKit is a SwiftUI library that adds a `WebView` and a `SafariWebView` that can present web sites in any app: 19 | 20 |

21 | 22 |

23 | 24 | The ``WebView`` component supports iOS, macOS, & visionOS, and can be configured to fit your needs, while the iOS exclusive ``SafariWebView`` can be used for more basic needs and a more browser-like experience. 25 | 26 | 27 | 28 | ## Installation 29 | 30 | WebViewKit can be installed with the Swift Package Manager: 31 | 32 | ``` 33 | https://github.com/danielsaidi/WebViewKit.git 34 | ``` 35 | 36 | 37 | ## Support My Work 38 | 39 | You can [become a sponsor][Sponsors] to help me dedicate more time on my various [open-source tools][OpenSource]. Every contribution, no matter the size, makes a real difference in keeping these tools free and actively developed. 40 | 41 | 42 | 43 | ## Getting started 44 | 45 | The library's main view is ``WebView``, which can be used to display any URL: 46 | 47 | ```swift 48 | import SwiftUI 49 | import WebViewKit 50 | 51 | struct MyView { 52 | 53 | var body: some View { 54 | WebView(urlString: "https://danielsaidi.com") 55 | } 56 | } 57 | ``` 58 | 59 | See the online [getting started guide][Getting-Started] for more information. 60 | 61 | 62 | 63 | ## Documentation 64 | 65 | The online [documentation][Documentation] has more information, articles, code examples, etc. 66 | 67 | 68 | 69 | ## Demo Application 70 | 71 | The `Demo` folder has an app that lets you explore the library and try out the views. 72 | 73 | 74 | 75 | ## Contact 76 | 77 | Feel free to reach out if you have questions, or want to contribute in any way: 78 | 79 | * Website: [danielsaidi.com][Website] 80 | * E-mail: [daniel.saidi@gmail.com][Email] 81 | * Bluesky: [@danielsaidi@bsky.social][Bluesky] 82 | * Mastodon: [@danielsaidi@mastodon.social][Mastodon] 83 | 84 | 85 | 86 | ## License 87 | 88 | WebViewKit is available under the MIT license. See the [LICENSE][License] file for more info. 89 | 90 | 91 | 92 | [Email]: mailto:daniel.saidi@gmail.com 93 | [Website]: https://danielsaidi.com 94 | [GitHub]: https://github.com/danielsaidi 95 | [OpenSource]: https://danielsaidi.com/opensource 96 | [Sponsors]: https://github.com/sponsors/danielsaidi 97 | 98 | [Bluesky]: https://bsky.app/profile/danielsaidi.bsky.social 99 | [Mastodon]: https://mastodon.social/@danielsaidi 100 | [Twitter]: https://twitter.com/danielsaidi 101 | 102 | [Documentation]: https://danielsaidi.github.io/WebViewKit 103 | [Getting-Started]: https://danielsaidi.github.io/WebViewKit/documentation/webviewkit/getting-started 104 | [License]: https://github.com/danielsaidi/WebViewKit/blob/master/LICENSE 105 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | WebViewKit will use semver after 1.0. 4 | 5 | 6 | 7 | ## 1.0 8 | 9 | This version bump is just an indication that WebViewKit is production ready and will honor semantic versioning from now on. 10 | 11 | 12 | 13 | ## 0.5 14 | 15 | This version makes WebViewKit use Swift 6. 16 | 17 | 18 | 19 | ## 0.4.1 20 | 21 | This version adds support for strict concurrency. 22 | 23 | 24 | 25 | ## 0.4 26 | 27 | This version adds support for visionOS. 28 | 29 | 30 | 31 | ## 0.3 32 | 33 | ### ✨ New features 34 | 35 | * `WebView` can now be created with an `htmlString` and a `urlString`. 36 | 37 | 38 | 39 | ## 0.2.2 40 | 41 | ### ✨ New features 42 | 43 | * `WebView` now supports providing a `WKWebViewConfiguration`. 44 | 45 | 46 | 47 | ## 0.2.1 48 | 49 | ### ✨ New features 50 | 51 | * `WebView` now supports providing an empty url and no config block. 52 | 53 | 54 | 55 | ## 0.2 56 | 57 | This version adds a new `SafariWebView`. 58 | 59 | ### ✨ New features 60 | 61 | * `SafariWebView` is a new iOS view that wraps an `SFSafariViewController` and can be used to load a custom url. 62 | 63 | 64 | 65 | ## 0.1 66 | 67 | This is the first public beta of the library. 68 | 69 | ### ✨ New features 70 | 71 | * `WebView` is a new view that wraps an `WKWebView` and can be used to load a custom url. 72 | -------------------------------------------------------------------------------- /Resources/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Resources/Demo.gif -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Resources/Icon.png -------------------------------------------------------------------------------- /Sources/WebViewKit/Documentation.docc/Getting-Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | This article describes how to get started with WebViewKit. 4 | 5 | @Metadata { 6 | 7 | @PageImage( 8 | purpose: card, 9 | source: "Page", 10 | alt: "Page icon" 11 | ) 12 | 13 | @PageColor(blue) 14 | } 15 | 16 | 17 | ## WebView 18 | 19 | The library's main view is ``WebView``, which can be used to render any web page URL: 20 | 21 | ```swift 22 | import SwiftUI 23 | import WebViewKit 24 | 25 | struct MyView { 26 | 27 | var body: some View { 28 | WebView(urlString: "https://danielsaidi.com") 29 | } 30 | } 31 | ``` 32 | 33 | The URL can point to any regular web site, but also to local web pages in your app bundle: 34 | 35 | ```swift 36 | let localUrl = Bundle.main.url(forResource: "about", withExtension: "html") 37 | ``` 38 | 39 | You can also provide a custom `WKWebViewConfiguration` and a view configuration function that can configure the `WKWebView`: 40 | 41 | ```swift 42 | WebView( 43 | url: url, 44 | config: ... // Pass in a WKWebViewConfiguration here 45 | ) { webView in 46 | // Configure the view here 47 | } 48 | ``` 49 | 50 | All in all, setting up a ``WebView`` can be as easy as this: 51 | 52 | ```swift 53 | struct ContentView: View { 54 | 55 | var body: some View { 56 | WebView(urlString: "https://apple.com") 57 | } 58 | } 59 | ``` 60 | 61 | and as complex as this: 62 | 63 | ```swift 64 | struct ContentView: View { 65 | 66 | private let url = URL(string: "https://apple.com") 67 | 68 | var body: some View { 69 | WebView(url: url, configuration: configuration) { webView in 70 | webView.customUserAgent = "foo bar" 71 | } 72 | } 73 | 74 | // Example of WKWebViewConfiguration 75 | var configuration: WKWebViewConfiguration { 76 | let disableSelectionScriptString = "document.documentElement.style.webkitUserSelect='none';" 77 | let disableSelectionScript = WKUserScript( 78 | source: disableSelectionScriptString, 79 | injectionTime: .atDocumentEnd, 80 | forMainFrameOnly: true) 81 | let disableCalloutScriptString = "document.documentElement.style.webkitTouchCallout='none';" 82 | let disableCalloutScript = WKUserScript( 83 | source: disableCalloutScriptString, 84 | injectionTime: .atDocumentEnd, 85 | forMainFrameOnly: true) 86 | 87 | let userContentController = WKUserContentController() 88 | userContentController.addUserScript(disableSelectionScript) 89 | userContentController.addUserScript(disableCalloutScript) 90 | 91 | let configuration = WKWebViewConfiguration() 92 | configuration.userContentController = userContentController 93 | configuration.ignoresViewportScaleLimits = false 94 | 95 | return configuration 96 | } 97 | } 98 | ``` 99 | 100 | If you provide a nil initializer `url`, you must load a url into the `WKWebView` in the view configuration, for the view to load a web page. 101 | 102 | If you grab hold of the `WKWebView`, you can use it to evaluate JavaScscript, for instance: 103 | 104 | ```swift 105 | webView.evaluateJavaScript("document.getElementById('someElement').innerText") { (result, error) in 106 | if error == nil { 107 | print(result) 108 | } 109 | } 110 | ``` 111 | 112 | This means that you can parse and manipulate the DOM, inject scripts, etc. 113 | 114 | 115 | ## SafariWebView 116 | 117 | WebViewKit also has a ``SafariWebView``, which can load the same content as ``WebView``: 118 | 119 | ```swift 120 | import SwiftUI 121 | import WebViewKit 122 | 123 | struct MyView { 124 | 125 | var body: some View { 126 | if let url = URL(string: "https://danielsaidi.com") { 127 | SafariWebView(url: url) 128 | } else { 129 | Text("Invalid url") 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | You can provide a custom `SFSafariViewController.Configuration` and a view configuration function that can configure the `SFSafariViewController`: 136 | 137 | ```swift 138 | SafariWebView( 139 | url: url, 140 | configuration: safariConfiguration 141 | ) { controller in 142 | // Controller configuration 143 | } 144 | ``` 145 | 146 | ``SafariWebView`` is less configurable than ``WebView``, but adds a top and bottom toolbar that can dismiss the view, resize it, navigate back and forward, share the current URL, etc. 147 | -------------------------------------------------------------------------------- /Sources/WebViewKit/Documentation.docc/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Sources/WebViewKit/Documentation.docc/Resources/Logo.png -------------------------------------------------------------------------------- /Sources/WebViewKit/Documentation.docc/Resources/Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsaidi/WebViewKit/99412d4d92bff4991d76e897aba974128bb6ffe2/Sources/WebViewKit/Documentation.docc/Resources/Page.png -------------------------------------------------------------------------------- /Sources/WebViewKit/Documentation.docc/WebViewKit.md: -------------------------------------------------------------------------------- 1 | # ``WebViewKit`` 2 | 3 | WebViewKit is a Swift SDK that adds a `WebView` to `SwiftUI`. 4 | 5 | 6 | 7 | ## Overview 8 | 9 | ![WebViewKit logo](Logo.png) 10 | 11 | WebViewKit is a SwiftUI SDK that adds a ``WebView`` and a ``SafariWebView`` that can be used to embed web content and present web sites in your apps. 12 | 13 | The ``WebView`` component supports iOS, macOS, & visionOS, and can be configured to fit your needs, while the iOS exclusive ``SafariWebView`` can be used for more basic needs and a more browser-like experience. 14 | 15 | 16 | 17 | ## Installation 18 | 19 | WebViewKit can be installed with the Swift Package Manager: 20 | 21 | ``` 22 | https://github.com/danielsaidi/WebViewKit.git 23 | ``` 24 | 25 | 26 | ## Support My Work 27 | 28 | You can [become a sponsor][Sponsors] to help me dedicate more time on my various [open-source tools][OpenSource]. Every contribution, no matter the size, makes a real difference in keeping these tools free and actively developed. 29 | 30 | 31 | 32 | ## Getting started 33 | 34 | The article helps you get started with WebViewKit. 35 | 36 | 37 | 38 | ## Repository 39 | 40 | For more information, source code, etc., visit the [project repository](https://github.com/danielsaidi/WebViewKit). 41 | 42 | 43 | 44 | ## License 45 | 46 | WebViewKit is available under the MIT license. 47 | 48 | 49 | 50 | ## Topics 51 | 52 | ### Articles 53 | 54 | - 55 | 56 | ### Views 57 | 58 | - ``SafariWebView`` 59 | - ``WebView`` 60 | 61 | 62 | 63 | [Email]: mailto:daniel.saidi@gmail.com 64 | [Website]: https://danielsaidi.com 65 | [GitHub]: https://github.com/danielsaidi 66 | [OpenSource]: https://danielsaidi.com/opensource 67 | [Sponsors]: https://github.com/sponsors/danielsaidi 68 | -------------------------------------------------------------------------------- /Sources/WebViewKit/SafariWebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebView.swift 3 | // WebViewKit 4 | // 5 | // Created by Daniel Saidi on 2020-12-29. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(visionOS) 10 | import SwiftUI 11 | import SafariServices 12 | 13 | /// This view wraps an `SFSafariViewController` and can load 14 | /// any url you provide it with. 15 | /// 16 | /// Unlike a ``WebView``, which just renders the web content, 17 | /// this view has a topmost navigation bar plus a bottommost 18 | /// toolbar with additional controls. 19 | /// 20 | /// When you create an instance of the view, you can provide 21 | /// it with a `url`, a `SFSafariViewController.Configuration` 22 | /// that's injected into the initializer, plus an additional 23 | /// `SFSafariViewController`-based configuration block, that 24 | /// can be used to configure the created instance. 25 | public struct SafariWebView: UIViewControllerRepresentable { 26 | 27 | /// Create a Safari web view. 28 | /// 29 | /// - Parameters: 30 | /// - url: The URL to load into the web view. 31 | /// - configuration: The configuration to apply, if any. 32 | /// - viewConfig: The configuration to apply, if any. 33 | public init( 34 | url: URL, 35 | configuration: SFSafariViewController.Configuration = .init(), 36 | viewConfig: @escaping (SFSafariViewController) -> Void = { _ in } 37 | ) { 38 | self.url = url 39 | self.configuration = configuration 40 | self.viewConfig = viewConfig 41 | } 42 | 43 | private let url: URL 44 | private let configuration: SFSafariViewController.Configuration 45 | private let viewConfig: (SFSafariViewController) -> Void 46 | 47 | public func makeUIViewController( 48 | context: Context 49 | ) -> SFSafariViewController { 50 | let controller = SFSafariViewController( 51 | url: url, 52 | configuration: configuration 53 | ) 54 | viewConfig(controller) 55 | return controller 56 | } 57 | 58 | public func updateUIViewController( 59 | _ safariViewController: SFSafariViewController, 60 | context: Context 61 | ) {} 62 | } 63 | 64 | #Preview { 65 | 66 | SafariWebView(url: URL(string: "https://danielsaidi.com")!) 67 | .ignoresSafeArea() 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /Sources/WebViewKit/WebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebView.swift 3 | // WebViewKit 4 | // 5 | // Created by Daniel Saidi on 2022-03-24. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(visionOS) 10 | typealias WebViewRepresentable = UIViewRepresentable 11 | #elseif os(macOS) 12 | typealias WebViewRepresentable = NSViewRepresentable 13 | #endif 14 | 15 | #if os(iOS) || os(macOS) || os(visionOS) 16 | import SwiftUI 17 | import WebKit 18 | 19 | /// This view wraps a `WKWebView` and can load any URLs that 20 | /// you provide it with, both local and online ones. 21 | /// 22 | /// When you create an instance of the view, you can provide 23 | /// it with a `WKWebViewConfiguration` that is injected into 24 | /// the web view initializer, as well as an a 25 | /// 26 | /// When you create an instance of the view, you can provide 27 | /// it with a `url`, a `WKWebViewConfiguration` that will be 28 | /// injected into the initializer, plus an `WKWebView`-based 29 | /// configuration block to configure the created instance. 30 | public struct WebView: WebViewRepresentable { 31 | 32 | /// Create a web view. 33 | /// 34 | /// - Parameters: 35 | /// - url: The url to load into the web view, if any. 36 | /// - configuration: The configuration to apply, if any. 37 | /// - viewConfig: The view configuration to apply, if any. 38 | public init( 39 | url: URL? = nil, 40 | configuration: WKWebViewConfiguration? = nil, 41 | viewConfig: @escaping (WKWebView) -> Void = { _ in } 42 | ) { 43 | self.url = url 44 | self.htmlString = nil 45 | self.htmlBaseUrl = nil 46 | self.configuration = configuration 47 | self.viewConfig = viewConfig 48 | } 49 | 50 | /// Create a web view. 51 | /// 52 | /// - Parameters: 53 | /// - urlString: The url to load into the web view, if any. 54 | /// - configuration: The configuration to apply, if any. 55 | /// - viewConfig: The view configuration to apply, if any. 56 | public init( 57 | urlString: String, 58 | configuration: WKWebViewConfiguration? = nil, 59 | viewConfig: @escaping (WKWebView) -> Void = { _ in } 60 | ) { 61 | self.url = URL(string: urlString) 62 | self.htmlString = nil 63 | self.htmlBaseUrl = nil 64 | self.configuration = configuration 65 | self.viewConfig = viewConfig 66 | } 67 | 68 | /// Create a web view that loads a custom HTML string. 69 | /// 70 | /// - Parameters: 71 | /// - htmlString: The HTML to load into the web view. 72 | /// - htmlBaseUrl: The HTML base URL to apply, if any. 73 | /// - configuration: The web view configuration to apply, if any. 74 | /// - viewConfig: The view configuration to apply, if any. 75 | public init( 76 | htmlString: String, 77 | htmlBaseUrl: URL? = nil, 78 | configuration: WKWebViewConfiguration? = nil, 79 | viewConfig: @escaping (WKWebView) -> Void = { _ in } 80 | ) { 81 | self.url = nil 82 | self.htmlString = htmlString 83 | self.htmlBaseUrl = htmlBaseUrl 84 | self.configuration = configuration 85 | self.viewConfig = viewConfig 86 | } 87 | 88 | 89 | private let url: URL? 90 | private let htmlString: String? 91 | private let htmlBaseUrl: URL? 92 | private let configuration: WKWebViewConfiguration? 93 | private let viewConfig: (WKWebView) -> Void 94 | 95 | 96 | #if os(iOS) || os(visionOS) 97 | public func makeUIView(context: Context) -> WKWebView { 98 | makeView() 99 | } 100 | 101 | public func updateUIView(_ uiView: WKWebView, context: Context) {} 102 | #endif 103 | 104 | #if os(macOS) 105 | public func makeNSView(context: Context) -> WKWebView { 106 | makeView() 107 | } 108 | 109 | public func updateNSView(_ view: WKWebView, context: Context) {} 110 | #endif 111 | } 112 | 113 | @MainActor 114 | private extension WebView { 115 | 116 | func makeWebView() -> WKWebView { 117 | guard let configuration = configuration else { return WKWebView() } 118 | return WKWebView(frame: .zero, configuration: configuration) 119 | } 120 | 121 | func makeView() -> WKWebView { 122 | let view = makeWebView() 123 | viewConfig(view) 124 | tryLoadUrl(into: view) 125 | tryLoadHtml(into: view) 126 | return view 127 | } 128 | 129 | func tryLoadUrl(into view: WKWebView) { 130 | guard let url else { return } 131 | view.load(URLRequest(url: url)) 132 | } 133 | 134 | func tryLoadHtml(into view: WKWebView) { 135 | guard let html = htmlString else { return } 136 | view.loadHTMLString(html, baseURL: htmlBaseUrl) 137 | } 138 | } 139 | 140 | #Preview { 141 | 142 | VStack { 143 | WebView(url: URL(string: "https://danielsaidi.com")) 144 | WebView(htmlString: "

Hello, world!

Does this work?

") 145 | } 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /Tests/WebViewKitTests/WebViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewTests.swift 3 | // WebViewKit 4 | // 5 | // Created by Daniel Saidi on 2022-03-24. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import WebViewKit 11 | 12 | /** 13 | This test class is only here to ensure that the package can 14 | compile and the test suite be executed successfully. 15 | */ 16 | final class WebViewTests: XCTestCase { 17 | 18 | func testExample() throws { 19 | XCTAssertEqual(1, 1) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------