diff --git a/.github/actions/code-signing-setup/action.yml b/.github/actions/code-signing-setup/action.yml
new file mode 100644
index 0000000..c5cfd1f
--- /dev/null
+++ b/.github/actions/code-signing-setup/action.yml
@@ -0,0 +1,39 @@
+name: Set up Apple code signing
+description: Installs code signing certificate(s) to a temporary keychain
+inputs:
+ build-certificate-base64:
+ required: true
+ description: The base64-encoded p12 build certificate
+ p12-password:
+ required: true
+ description: The password for the p12 build certificate
+ keychain-password:
+ required: true
+ description: The password for the temporary keychain
+runs:
+ using: "composite"
+ steps:
+ - name: Install code signing certificate
+ env:
+ BUILD_CERTIFICATE_BASE64: ${{ inputs.build-certificate-base64 }}
+ P12_PASSWORD: ${{ inputs.p12-password }}
+ KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
+ shell: bash
+ # Source:
+ # https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development
+ run: |
+ # Create variables
+ CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+
+ # Import certificate from secrets
+ echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
+
+ # Create temporary keychain
+ security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
+
+ # Import certificate to keychain
+ security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security list-keychain -d user -s $KEYCHAIN_PATH
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..59c9d13
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,119 @@
+name: Run tests and deploy to TestFlight
+
+on:
+ workflow_dispatch:
+ # Uncomment this to run the workflow on every push to the main branch:
+ # push:
+ # branches: ["main"]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ XCODE_PROJECT: TemplateApp.xcodeproj
+ SCHEME: TemplateApp
+ TEST_PLAN: AllTests
+ TEST_RESULT_BUNDLE: TestResults.xcresult
+ ARCHIVE_PATH: TemplateApp.xcarchive
+ EXPORT_OPTIONS_PLIST: ExportOptions.plist
+
+jobs:
+ build:
+ name: Run tests and deploy to TestFlight
+ runs-on: [macos-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Select Xcode version
+ run: sudo xcode-select --switch /Applications/Xcode_15.3.app
+
+ - name: Cache Swift Package Manager dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ runner.temp }}/SourcePackages
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
+
+ - name: Install Swift packages
+ run: |
+ xcodebuild -project "${{ env.XCODE_PROJECT }}" \
+ -scheme "${{ env.SCHEME }}" \
+ -onlyUsePackageVersionsFromResolvedFile \
+ -resolvePackageDependencies \
+ -clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages"
+
+ - name: Install code signing certificate
+ uses: "./.github/actions/code-signing-setup"
+ with:
+ build-certificate-base64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+ p12-password: ${{ secrets.P12_PASSWORD }}
+ keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}
+
+ - name: Install App Store Connect API key
+ run: |
+ echo -n "${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}" | base64 --decode -o "${{ runner.temp }}/AuthKey.p8"
+
+ - name: Run tests
+ env:
+ PLATFORM: ${{ 'iOS Simulator' }}
+ run: |
+ # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
+ DEVICE=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
+ xcodebuild test -project "${{ env.XCODE_PROJECT }}" \
+ -scheme "${{ env.SCHEME }}" \
+ -testPlan "${{ env.TEST_PLAN }}" \
+ -destination "platform=$PLATFORM,name=$DEVICE" \
+ -resultBundlePath "${{ env.TEST_RESULT_BUNDLE }}" \
+ -clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
+ -disableAutomaticPackageResolution
+
+ - name: Upload test result bundle
+ uses: actions/upload-artifact@v4
+ if: ${{ failure() }}
+ with:
+ name: ${{ env.SCHEME }}-${{ github.ref_name }}-${{ github.sha }}.xcresult
+ path: ${{ env.TEST_RESULT_BUNDLE }}
+
+ - name: Archive build
+ run: |
+ xcodebuild clean archive -project "${{ env.XCODE_PROJECT }}" \
+ -scheme "${{ env.SCHEME }}" \
+ -destination generic/platform=iOS \
+ -archivePath "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}" \
+ -allowProvisioningUpdates \
+ -authenticationKeyPath "${{ runner.temp }}/AuthKey.p8" \
+ -authenticationKeyID ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
+ -authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} \
+ -clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
+ -disableAutomaticPackageResolution
+
+ - name: Upload to TestFlight
+ run: |
+ xcodebuild -exportArchive \
+ -allowProvisioningUpdates \
+ -authenticationKeyPath "${{ runner.temp }}/AuthKey.p8" \
+ -authenticationKeyID ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
+ -authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} \
+ -archivePath "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}" \
+ -exportPath ${{ env.ARCHIVE_PATH }} \
+ -exportOptionsPlist ${{ env.EXPORT_OPTIONS_PLIST }}
+
+ # To add Crashlytics dSYM upload, grap the `upload-symbols` script from the Firebase SDK from here:
+ # https://github.com/firebase/firebase-ios-sdk/blob/main/Crashlytics/upload-symbols
+ # and add it to the repository in a folder called `scripts`. Then uncomment the following lines:
+
+ # - name: Upload dSYM files to Firebase Crashlytics
+ # run: |
+ # ./scripts/upload-symbols -p ios \
+ # -gsp "TemplateApp/GoogleService-Info.plist" \
+ # "${{ runner.temp }}/${{ env.ARCHIVE_PATH }}/dSYMs"
+
+ - name: Upload build artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ env.SCHEME }}-${{ github.ref_name }}-${{ github.sha }}.xcarchive
+ path: ${{ runner.temp }}/${{ env.ARCHIVE_PATH }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..fce0e58
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,60 @@
+name: Run tests
+
+on:
+ pull_request:
+ branches: ["main"]
+
+env:
+ XCODE_PROJECT: TemplateApp.xcodeproj
+ SCHEME: TemplateApp
+ TEST_PLAN: AllTests
+ TEST_RESULT_BUNDLE: TestResults.xcresult
+
+jobs:
+ build:
+ name: Run tests
+ runs-on: [macos-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Select Xcode version
+ run: sudo xcode-select --switch /Applications/Xcode_15.3.app
+
+ - name: Cache Swift Package Manager dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ runner.temp }}/SourcePackages
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
+
+ - name: Install Swift packages
+ run: |
+ xcodebuild -project "${{ env.XCODE_PROJECT }}" \
+ -scheme "${{ env.SCHEME }}" \
+ -onlyUsePackageVersionsFromResolvedFile \
+ -resolvePackageDependencies \
+ -clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages"
+
+ - name: Run tests
+ env:
+ PLATFORM: ${{ 'iOS Simulator' }}
+ run: |
+ # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
+ DEVICE=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
+ xcodebuild test -project "${{ env.XCODE_PROJECT }}" \
+ -scheme "${{ env.SCHEME }}" \
+ -testPlan "${{ env.TEST_PLAN }}" \
+ -destination "platform=$PLATFORM,name=$DEVICE" \
+ -resultBundlePath "${{ env.TEST_RESULT_BUNDLE }}" \
+ -clonedSourcePackagesDirPath "${{ runner.temp }}/SourcePackages" \
+ -disableAutomaticPackageResolution
+
+ - name: Upload test result bundle
+ uses: actions/upload-artifact@v4
+ if: ${{ failure() }}
+ with:
+ name: ${{ env.SCHEME }}-${{ github.ref_name }}-${{ github.sha }}.xcresult
+ path: ${{ env.TEST_RESULT_BUNDLE }}
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..5ffbfff
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,3 @@
+--disable unusedArguments
+--disable redundantRawValues
+--indent 4
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 0000000..7ca66e6
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,4 @@
+disabled_rules:
+ - trailing_comma
+ - line_length
+ - unused_closure_parameter
diff --git a/ExportOptions.plist b/ExportOptions.plist
new file mode 100644
index 0000000..4238904
--- /dev/null
+++ b/ExportOptions.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ destination
+ upload
+ manageAppVersionAndBuildNumber
+
+ method
+ app-store
+ signingStyle
+ automatic
+ stripSwiftSymbols
+
+ teamID
+ XXXXXXXXXX
+ testFlightInternalTestingOnly
+
+ uploadSymbols
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f7b7758
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+# Q42 iOS template
+
+This is a template for creating iOS projects at Q42. It has opinionated defaults and boilerplate, based on how we do iOS projects at Q42.
+
+## How to use it
+
+1. In GitHub, press "use this template" to create a new repository.
+2. Rename your project using the included Python script.
+
+## Features
+
+Only basic features that almost all projects use, were added in this template:
+
+* SwiftUI using the SwiftUI lifecycle with an AppDelegate
+* Dependency injection using Factory
+* Unit tests and UI tests using Salad
+* GitHub Actions CI configuration that runs the tests and submits the app to TestFlight
+
+Xcode 15.3 or higher is required.
+
+## Code style
+
+The Xcode project is configured to use 4 spaces for indentation.
+For linting Swift source code, we use [SwiftLint](https://github.com/realm/SwiftLint).
+A configuration for [SwiftFormat](http://github.com/nicklockwood/SwiftFormat) is also included.
+
+## Continuous integration
+
+GitHub Actions is used for continuous integration (CI). The CI runs the automated tests when you make a pull request.
+On a push to the `main` branch, it will also run the tests, and if they pass, a build of the app is made and uploaded to TestFlight.
+
+### CI configuration
+
+Five environment secrets are needed for the workflow to run on GitHub Actions.
+You may configure these in the repository secret settings on GitHub.
+
+* `BUILD_CERTIFICATE_BASE64` contains a base64-encoded string of the .p12 certificate bundle, used to code sign the app. This bundle needs to contain two certificates: **development** and **distribution**.
+* `P12_PASSWORD` contains the password of the certificate bundle.
+* `APP_STORE_CONNECT_API_KEY_BASE64` contains a base64-encoded string of the .p8 App Store Connect API key.
+* `APP_STORE_CONNECT_API_KEY_ID` contains the key ID of the App Store Connect API key.
+* `APP_STORE_CONNECT_API_KEY_ISSUER_ID` contains the issuer ID of the App Store Connect API key.
+
+To create such a certificate bundle, open Keychain Access. Unfold the entries for the development and distribution certificate. Select the certificates and their private keys using shift, then right-click and select "Export 4 items...".
+
+You can encode a file to base64 on the command line like this: `base64 -i ~/Desktop/Certificates.p12 | pbcopy`. This automatically puts the result on your clipboard.
diff --git a/TemplateApp.xcodeproj/project.pbxproj b/TemplateApp.xcodeproj/project.pbxproj
index e3bed72..8edbb94 100644
--- a/TemplateApp.xcodeproj/project.pbxproj
+++ b/TemplateApp.xcodeproj/project.pbxproj
@@ -3,16 +3,21 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 56;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
D5284F302B57C6B600BB32E7 /* TemplateAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F2F2B57C6B600BB32E7 /* TemplateAppApp.swift */; };
- D5284F322B57C6B600BB32E7 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F312B57C6B600BB32E7 /* ContentView.swift */; };
+ D5284F322B57C6B600BB32E7 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F312B57C6B600BB32E7 /* RootView.swift */; };
D5284F342B57C6B700BB32E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5284F332B57C6B700BB32E7 /* Assets.xcassets */; };
D5284F372B57C6B700BB32E7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5284F362B57C6B700BB32E7 /* Preview Assets.xcassets */; };
- D5284F412B57C6B700BB32E7 /* TemplateAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F402B57C6B700BB32E7 /* TemplateAppTests.swift */; };
- D5284F4B2B57C6B700BB32E7 /* TemplateAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F4A2B57C6B700BB32E7 /* TemplateAppUITests.swift */; };
+ D5284F412B57C6B700BB32E7 /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F402B57C6B700BB32E7 /* ExampleTests.swift */; };
+ D5284F4B2B57C6B700BB32E7 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5284F4A2B57C6B700BB32E7 /* ExampleUITests.swift */; };
+ D5F745E82BEE14870064F06A /* TemplateAppAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F745E72BEE14870064F06A /* TemplateAppAppDelegate.swift */; };
+ D5F745EA2BEE14DD0064F06A /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D5F745E92BEE14DD0064F06A /* Localizable.xcstrings */; };
+ D5F745EF2BEE15C20064F06A /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5F745EE2BEE15C20064F06A /* Launch Screen.storyboard */; };
+ D5F745F52BEE48BC0064F06A /* Salad in Frameworks */ = {isa = PBXBuildFile; productRef = D5F745F42BEE48BC0064F06A /* Salad */; };
+ D5F745F82BEE48F50064F06A /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F745F72BEE48F50064F06A /* RootView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -35,13 +40,21 @@
/* Begin PBXFileReference section */
D5284F2C2B57C6B600BB32E7 /* TemplateApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TemplateApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
D5284F2F2B57C6B600BB32E7 /* TemplateAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppApp.swift; sourceTree = ""; };
- D5284F312B57C6B600BB32E7 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ D5284F312B57C6B600BB32E7 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; };
D5284F332B57C6B700BB32E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
D5284F362B57C6B700BB32E7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
D5284F3C2B57C6B700BB32E7 /* TemplateAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemplateAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- D5284F402B57C6B700BB32E7 /* TemplateAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppTests.swift; sourceTree = ""; };
+ D5284F402B57C6B700BB32E7 /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; };
D5284F462B57C6B700BB32E7 /* TemplateAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemplateAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- D5284F4A2B57C6B700BB32E7 /* TemplateAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppUITests.swift; sourceTree = ""; };
+ D5284F4A2B57C6B700BB32E7 /* ExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUITests.swift; sourceTree = ""; };
+ D5F745E72BEE14870064F06A /* TemplateAppAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppAppDelegate.swift; sourceTree = ""; };
+ D5F745E92BEE14DD0064F06A /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; };
+ D5F745EB2BEE15210064F06A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
+ D5F745EE2BEE15C20064F06A /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; };
+ D5F745F02BEE15D70064F06A /* AllTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AllTests.xctestplan; sourceTree = ""; };
+ D5F745F12BEE16E60064F06A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ D5F745F22BEE16FB0064F06A /* TemplateApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TemplateApp.entitlements; sourceTree = ""; };
+ D5F745F72BEE48F50064F06A /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -63,6 +76,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ D5F745F52BEE48BC0064F06A /* Salad in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -72,6 +86,7 @@
D5284F232B57C6B600BB32E7 = {
isa = PBXGroup;
children = (
+ D5F745EB2BEE15210064F06A /* README.md */,
D5284F2E2B57C6B600BB32E7 /* TemplateApp */,
D5284F3F2B57C6B700BB32E7 /* TemplateAppTests */,
D5284F492B57C6B700BB32E7 /* TemplateAppUITests */,
@@ -95,8 +110,11 @@
isa = PBXGroup;
children = (
D5284F2F2B57C6B600BB32E7 /* TemplateAppApp.swift */,
- D5284F312B57C6B600BB32E7 /* ContentView.swift */,
+ D5F745E72BEE14870064F06A /* TemplateAppAppDelegate.swift */,
+ D5284F312B57C6B600BB32E7 /* RootView.swift */,
+ D5F745E92BEE14DD0064F06A /* Localizable.xcstrings */,
D5284F332B57C6B700BB32E7 /* Assets.xcassets */,
+ D5F745ED2BEE15B80064F06A /* Supporting Files */,
D5284F352B57C6B700BB32E7 /* Preview Content */,
);
path = TemplateApp;
@@ -113,7 +131,7 @@
D5284F3F2B57C6B700BB32E7 /* TemplateAppTests */ = {
isa = PBXGroup;
children = (
- D5284F402B57C6B700BB32E7 /* TemplateAppTests.swift */,
+ D5284F402B57C6B700BB32E7 /* ExampleTests.swift */,
);
path = TemplateAppTests;
sourceTree = "";
@@ -121,11 +139,31 @@
D5284F492B57C6B700BB32E7 /* TemplateAppUITests */ = {
isa = PBXGroup;
children = (
- D5284F4A2B57C6B700BB32E7 /* TemplateAppUITests.swift */,
+ D5284F4A2B57C6B700BB32E7 /* ExampleUITests.swift */,
+ D5F745F62BEE48EF0064F06A /* ViewObjects */,
);
path = TemplateAppUITests;
sourceTree = "";
};
+ D5F745ED2BEE15B80064F06A /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ D5F745F02BEE15D70064F06A /* AllTests.xctestplan */,
+ D5F745F12BEE16E60064F06A /* Info.plist */,
+ D5F745EE2BEE15C20064F06A /* Launch Screen.storyboard */,
+ D5F745F22BEE16FB0064F06A /* TemplateApp.entitlements */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ D5F745F62BEE48EF0064F06A /* ViewObjects */ = {
+ isa = PBXGroup;
+ children = (
+ D5F745F72BEE48F50064F06A /* RootView.swift */,
+ );
+ path = ViewObjects;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -178,6 +216,9 @@
D5284F482B57C6B700BB32E7 /* PBXTargetDependency */,
);
name = TemplateAppUITests;
+ packageProductDependencies = (
+ D5F745F42BEE48BC0064F06A /* Salad */,
+ );
productName = TemplateAppUITests;
productReference = D5284F462B57C6B700BB32E7 /* TemplateAppUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
@@ -207,14 +248,18 @@
};
};
buildConfigurationList = D5284F272B57C6B600BB32E7 /* Build configuration list for PBXProject "TemplateApp" */;
- compatibilityVersion = "Xcode 14.0";
+ compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
+ nl,
);
mainGroup = D5284F232B57C6B600BB32E7;
+ packageReferences = (
+ D5F745F32BEE48BC0064F06A /* XCRemoteSwiftPackageReference "Salad" */,
+ );
productRefGroup = D5284F2D2B57C6B600BB32E7 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -233,6 +278,8 @@
files = (
D5284F372B57C6B700BB32E7 /* Preview Assets.xcassets in Resources */,
D5284F342B57C6B700BB32E7 /* Assets.xcassets in Resources */,
+ D5F745EF2BEE15C20064F06A /* Launch Screen.storyboard in Resources */,
+ D5F745EA2BEE14DD0064F06A /* Localizable.xcstrings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -257,7 +304,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D5284F322B57C6B600BB32E7 /* ContentView.swift in Sources */,
+ D5F745E82BEE14870064F06A /* TemplateAppAppDelegate.swift in Sources */,
+ D5284F322B57C6B600BB32E7 /* RootView.swift in Sources */,
D5284F302B57C6B600BB32E7 /* TemplateAppApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -266,7 +314,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D5284F412B57C6B700BB32E7 /* TemplateAppTests.swift in Sources */,
+ D5284F412B57C6B700BB32E7 /* ExampleTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -274,7 +322,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D5284F4B2B57C6B700BB32E7 /* TemplateAppUITests.swift in Sources */,
+ D5F745F82BEE48F50064F06A /* RootView.swift in Sources */,
+ D5284F4B2B57C6B700BB32E7 /* ExampleUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -346,7 +395,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -403,7 +452,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
@@ -418,12 +467,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = TemplateApp/TemplateApp.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"TemplateApp/Preview Content\"";
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = TemplateApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -447,12 +498,14 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = TemplateApp/TemplateApp.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"TemplateApp/Preview Content\"";
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = TemplateApp/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -477,12 +530,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ CURRENT_PROJECT_VERSION = 1.2.3;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateAppTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateApp.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
@@ -497,12 +549,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ CURRENT_PROJECT_VERSION = 1.2.3;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 17.2;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateAppTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateApp.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
@@ -516,11 +567,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ CURRENT_PROJECT_VERSION = 1.2.3;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateAppUITests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateApp.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
@@ -534,11 +585,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 8J6VSUTQNX;
+ CURRENT_PROJECT_VERSION = 1.2.3;
+ DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateAppUITests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.q42.TemplateApp.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
@@ -587,6 +638,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ D5F745F32BEE48BC0064F06A /* XCRemoteSwiftPackageReference "Salad" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/Q42/Salad.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 1.0.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ D5F745F42BEE48BC0064F06A /* Salad */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = D5F745F32BEE48BC0064F06A /* XCRemoteSwiftPackageReference "Salad" */;
+ productName = Salad;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = D5284F242B57C6B600BB32E7 /* Project object */;
}
diff --git a/TemplateApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TemplateApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..e3436c6
--- /dev/null
+++ b/TemplateApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+ "originHash" : "929d2090e52375231c5ebc33be1479d4266be85bbf192a8f578aa5ddb5af112c",
+ "pins" : [
+ {
+ "identity" : "salad",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Q42/Salad.git",
+ "state" : {
+ "revision" : "c10580e0632ddaf1542334d386ee3118e1074204",
+ "version" : "1.0.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/TemplateApp.xcodeproj/xcshareddata/xcschemes/TemplateApp.xcscheme b/TemplateApp.xcodeproj/xcshareddata/xcschemes/TemplateApp.xcscheme
new file mode 100644
index 0000000..d894ae9
--- /dev/null
+++ b/TemplateApp.xcodeproj/xcshareddata/xcschemes/TemplateApp.xcscheme
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TemplateApp/AllTests.xctestplan b/TemplateApp/AllTests.xctestplan
new file mode 100644
index 0000000..d641a47
--- /dev/null
+++ b/TemplateApp/AllTests.xctestplan
@@ -0,0 +1,41 @@
+{
+ "configurations" : [
+ {
+ "id" : "2E268531-76BE-4986-BE2E-F7971FD484AB",
+ "name" : "English",
+ "options" : {
+ "language" : "en",
+ "region" : "GB"
+ }
+ },
+ {
+ "enabled" : false,
+ "id" : "AEB26D7A-9FE8-4E6A-B095-01F3DA26EFCA",
+ "name" : "Dutch",
+ "options" : {
+ "language" : "nl",
+ "region" : "NL"
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true
+ },
+ "testTargets" : [
+ {
+ "target" : {
+ "containerPath" : "container:TemplateApp.xcodeproj",
+ "identifier" : "D5284F3B2B57C6B700BB32E7",
+ "name" : "TemplateAppTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:TemplateApp.xcodeproj",
+ "identifier" : "D5284F452B57C6B700BB32E7",
+ "name" : "TemplateAppUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/TemplateApp/Assets.xcassets/AccentColor.colorset/Contents.json b/TemplateApp/Assets.xcassets/AccentColor.colorset/Contents.json
index eb87897..9d08b4a 100644
--- a/TemplateApp/Assets.xcassets/AccentColor.colorset/Contents.json
+++ b/TemplateApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -1,6 +1,15 @@
{
"colors" : [
{
+ "color" : {
+ "color-space" : "display-p3",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "45",
+ "green" : "188",
+ "red" : "132"
+ }
+ },
"idiom" : "universal"
}
],
diff --git a/TemplateApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/TemplateApp/Assets.xcassets/AppIcon.appiconset/Contents.json
index 13613e3..11f81c8 100644
--- a/TemplateApp/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/TemplateApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,6 +1,7 @@
{
"images" : [
{
+ "filename" : "q42-icon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
diff --git a/TemplateApp/Assets.xcassets/AppIcon.appiconset/q42-icon.png b/TemplateApp/Assets.xcassets/AppIcon.appiconset/q42-icon.png
new file mode 100644
index 0000000..b95fce9
Binary files /dev/null and b/TemplateApp/Assets.xcassets/AppIcon.appiconset/q42-icon.png differ
diff --git a/TemplateApp/Info.plist b/TemplateApp/Info.plist
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/TemplateApp/Info.plist
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/TemplateApp/Launch Screen.storyboard b/TemplateApp/Launch Screen.storyboard
new file mode 100644
index 0000000..dccc0e7
--- /dev/null
+++ b/TemplateApp/Launch Screen.storyboard
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TemplateApp/Localizable.xcstrings b/TemplateApp/Localizable.xcstrings
new file mode 100644
index 0000000..e023044
--- /dev/null
+++ b/TemplateApp/Localizable.xcstrings
@@ -0,0 +1,16 @@
+{
+ "sourceLanguage" : "en",
+ "strings" : {
+ "Hello, world!" : {
+ "localizations" : {
+ "nl" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hallo, wereld!"
+ }
+ }
+ }
+ }
+ },
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/TemplateApp/ContentView.swift b/TemplateApp/RootView.swift
similarity index 58%
rename from TemplateApp/ContentView.swift
rename to TemplateApp/RootView.swift
index 9fc9857..e05e76c 100644
--- a/TemplateApp/ContentView.swift
+++ b/TemplateApp/RootView.swift
@@ -1,13 +1,13 @@
//
-// ContentView.swift
+// RootView.swift
// TemplateApp
//
-// Created by Mathijs Bernson on 17/01/2024.
+// Copyright © 2024 Q42. All rights reserved.
//
import SwiftUI
-struct ContentView: View {
+struct RootView: View {
var body: some View {
VStack {
Image(systemName: "globe")
@@ -16,9 +16,11 @@ struct ContentView: View {
Text("Hello, world!")
}
.padding()
+ .accessibilityElement(children: .contain)
+ .accessibilityIdentifier("RootView")
}
}
#Preview {
- ContentView()
+ RootView()
}
diff --git a/TemplateApp/TemplateApp.entitlements b/TemplateApp/TemplateApp.entitlements
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/TemplateApp/TemplateApp.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/TemplateApp/TemplateAppApp.swift b/TemplateApp/TemplateAppApp.swift
index 79e68dc..faeac5f 100644
--- a/TemplateApp/TemplateAppApp.swift
+++ b/TemplateApp/TemplateAppApp.swift
@@ -2,16 +2,18 @@
// TemplateAppApp.swift
// TemplateApp
//
-// Created by Mathijs Bernson on 17/01/2024.
+// Copyright © 2024 Q42. All rights reserved.
//
import SwiftUI
@main
struct TemplateAppApp: App {
+ @UIApplicationDelegateAdaptor var appDelegate: TemplateAppAppDelegate
+
var body: some Scene {
WindowGroup {
- ContentView()
+ RootView()
}
}
}
diff --git a/TemplateApp/TemplateAppAppDelegate.swift b/TemplateApp/TemplateAppAppDelegate.swift
new file mode 100644
index 0000000..67d4d6b
--- /dev/null
+++ b/TemplateApp/TemplateAppAppDelegate.swift
@@ -0,0 +1,15 @@
+//
+// TemplateAppAppDelegate.swift
+// TemplateApp
+//
+// Copyright © 2024 Q42. All rights reserved.
+//
+
+import UIKit
+
+class TemplateAppAppDelegate: NSObject, UIApplicationDelegate {
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
+ // Additional app setup can be performed here.
+ return true
+ }
+}
diff --git a/TemplateAppTests/TemplateAppTests.swift b/TemplateAppTests/ExampleTests.swift
similarity index 92%
rename from TemplateAppTests/TemplateAppTests.swift
rename to TemplateAppTests/ExampleTests.swift
index 2d734be..c14976d 100644
--- a/TemplateAppTests/TemplateAppTests.swift
+++ b/TemplateAppTests/ExampleTests.swift
@@ -1,15 +1,14 @@
//
-// TemplateAppTests.swift
+// ExampleTests.swift
// TemplateAppTests
//
-// Created by Mathijs Bernson on 17/01/2024.
+// Copyright © 2024 Q42. All rights reserved.
//
import XCTest
@testable import TemplateApp
final class TemplateAppTests: XCTestCase {
-
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
@@ -26,5 +25,4 @@ final class TemplateAppTests: XCTestCase {
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
XCTAssertEqual(21 * 2, 42)
}
-
}
diff --git a/TemplateAppUITests/ExampleUITests.swift b/TemplateAppUITests/ExampleUITests.swift
new file mode 100644
index 0000000..09c1540
--- /dev/null
+++ b/TemplateAppUITests/ExampleUITests.swift
@@ -0,0 +1,36 @@
+//
+// TemplateAppUITests.swift
+// TemplateAppUITests
+//
+// Created by Mathijs Bernson on 17/01/2024.
+//
+
+//
+// ExampleUITests.swift
+// TemplateAppTests
+//
+// Copyright © 2024 Q42. All rights reserved.
+//
+
+import XCTest
+import Salad
+
+final class ExampleUITests: XCTestCase {
+ var scenario: Scenario!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ let app = XCUIApplication()
+ app.launch()
+ scenario = Scenario(given: app)
+ }
+
+ func testExample() {
+ scenario
+ .then { rootView in
+ XCTAssertTrue(rootView.identifyingElement.staticTexts["Hello, world!"].waitForExist(timeout: .asyncUI),
+ "Expected to see 'Hello, world!' label")
+ }
+ }
+}
diff --git a/TemplateAppUITests/TemplateAppUITests.swift b/TemplateAppUITests/TemplateAppUITests.swift
deleted file mode 100644
index 412d550..0000000
--- a/TemplateAppUITests/TemplateAppUITests.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// TemplateAppUITests.swift
-// TemplateAppUITests
-//
-// Created by Mathijs Bernson on 17/01/2024.
-//
-
-import XCTest
-
-final class TemplateAppUITests: XCTestCase {
-
- override func setUpWithError() throws {
- // Put setup code here. This method is called before the invocation of each test method in the class.
-
- // In UI tests it is usually best to stop immediately when a failure occurs.
- continueAfterFailure = false
-
- // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
- }
-
- override func tearDownWithError() throws {
- // Put teardown code here. This method is called after the invocation of each test method in the class.
- }
-
- func testExample() throws {
- // UI tests must launch the application that they test.
- let app = XCUIApplication()
- app.launch()
-
- // Use XCTAssert and related functions to verify your tests produce the correct results.
- }
-
-}
diff --git a/TemplateAppUITests/ViewObjects/RootView.swift b/TemplateAppUITests/ViewObjects/RootView.swift
new file mode 100644
index 0000000..8f775d0
--- /dev/null
+++ b/TemplateAppUITests/ViewObjects/RootView.swift
@@ -0,0 +1,15 @@
+//
+// RootView.swift
+// TemplateAppUITests
+//
+// Created by Mathijs Bernson on 10/05/2024.
+// Copyright © 2024 Q42. All rights reserved.
+//
+
+import XCTest
+import Salad
+
+struct RootView: ViewObject {
+ let root: XCUIElement
+ let identifyingElementId: String = "RootView"
+}
diff --git a/scripts/rename-project.py b/scripts/rename-project.py
new file mode 100644
index 0000000..011fd89
--- /dev/null
+++ b/scripts/rename-project.py
@@ -0,0 +1,64 @@
+"""
+This script renames the template project to the desired name.
+"""
+
+import os
+from pathlib import Path
+
+folder = Path(os.path.abspath(os.path.dirname(__file__))).parent.as_posix()
+
+dryRun = False
+oldProjectName = "TemplateApp"
+print("Enter new project name:")
+newProjectName = input()
+
+# ========= Rename folders:
+
+print(
+ "\nRenaming '%s' to '%s' in folder names.\n" % (oldProjectName, newProjectName)
+)
+
+for root, dirs, files in os.walk(folder, topdown=False):
+ for subDir in dirs:
+ if oldProjectName in subDir:
+ oldFolderName = os.path.join(root, subDir)
+ newFolderName = os.path.join(root, subDir.replace(oldProjectName, newProjectName))
+ if dryRun:
+ print("Would rename folder: %s to %s" % (oldFolderName, newFolderName))
+ else:
+ print("Renaming folder: %s to %s" % (oldFolderName, newFolderName))
+ os.rename(oldFolderName, newFolderName)
+
+# ========= Rename usages in source files: =========
+
+print(
+ "\nReplacing all occurrences of %s in source files with: '%s'.\n" % (oldProjectName, newProjectName)
+)
+
+def replace_package_name_occurences_in_file(filename):
+ print("Would update file: " + filename)
+ with open(filename, "r") as file:
+ filedata = file.read()
+
+ if oldProjectName in filedata:
+ filedata = filedata.replace(oldProjectName, newProjectName)
+
+ if dryRun:
+ print("Would update file: " + filename)
+ else:
+ print("Updating file: " + filename)
+ with open(filename, "w") as file:
+ file.write(filedata)
+ file.close()
+
+for root, dirs, files in os.walk(folder, topdown=False):
+ allowed_extensions = ["swift", "plist", "yml", "pbxproj", "storyboard", "xctestplan", "xcscheme"]
+ for name in files:
+ extension = name.split(".")[-1]
+ if extension in allowed_extensions:
+ file_name = os.path.join(root, name)
+ replace_package_name_occurences_in_file(file_name)
+
+print(
+ "\nDone renaming project to: '%s'.\n" % (newProjectName)
+)