├── .gitignore ├── .semaphore └── semaphore.yml ├── Gemfile ├── Gemfile.lock ├── Images ├── Pipeline.png ├── Secret.png └── TallestTowers.png ├── LICENSE ├── README.md ├── TallestTowers.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── TakeScreenshots.xcscheme │ └── TallestTowers.xcscheme ├── TallestTowers ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── OverlayBackground.colorset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Controllers │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Info.plist ├── Models │ └── Tower.swift └── Views │ ├── MapView.swift │ ├── TowerDetailView.swift │ ├── TowerRow.swift │ └── TowersView.swift ├── TallestTowersScreenshots ├── Info.plist ├── SnapshotHelper.swift └── TallestTowersScreenshots.swift ├── TallestTowersTests ├── Info.plist └── TowerTests.swift ├── TallestTowersUITests ├── Info.plist └── TallestTowersUITests.swift └── fastlane ├── Appfile ├── Fastfile ├── Gymfile ├── Matchfile ├── Pluginfile ├── README.md └── Snapfile /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | # fastlane snapshot 71 | screenshots/ 72 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | # Use the latest stable version of Semaphore 2.0 YML syntax: 2 | version: v1.0 3 | 4 | # Name your pipeline. If you choose to connect multiple pipelines with 5 | # promotions, the pipeline name will help you differentiate between 6 | # them. For example, you might have a build phase and a delivery phase. 7 | # For more information on promotions, see: 8 | # https://docs.semaphoreci.com/article/67-deploying-with-promotions 9 | name: Tallest Towers 10 | 11 | # The agent defines the environment in which your CI runs. It is a combination 12 | # of a machine type and an operating system image. For a project built with 13 | # Xcode you must use one of the Apple machine types, coupled with a macOS image 14 | # running either Xcode 11 or Xcode 12. 15 | # See https://docs.semaphoreci.com/article/20-machine-types 16 | # https://docs.semaphoreci.com/ci-cd-environment/macos-xcode-11-image/ and 17 | # https://docs.semaphoreci.com/ci-cd-environment/macos-xcode-12-image/ 18 | agent: 19 | machine: 20 | type: a1-standard-4 21 | os_image: macos-xcode12 22 | 23 | # Blocks are the heart of a pipeline and are executed sequentially. Each block 24 | # has a task that defines one or more parallel jobs. Jobs define commands that 25 | # should be executed by the pipeline. 26 | # See https://docs.semaphoreci.com/article/62-concepts 27 | blocks: 28 | - name: Run tests 29 | task: 30 | # Set environment variables that your project requires. 31 | # See https://docs.semaphoreci.com/article/66-environment-variables-and-secrets 32 | env_vars: 33 | - name: LANG 34 | value: en_US.UTF-8 35 | prologue: 36 | commands: 37 | # Download source code from GitHub. 38 | - checkout 39 | # Restore dependencies from cache. This command will not fail in 40 | # case of a cache miss. In case of a cache hit, bundle install will 41 | # complete in about a second. 42 | # See https://docs.semaphoreci.com/article/68-caching-dependencies 43 | - cache restore 44 | 45 | # Fix for: `find_spec_for_exe': can't find gem bundler erro 46 | # Look at the end of Gemfile.lock, under BUNDLED WITH 47 | # and simply install the required version before calling bundle install 48 | - gem install bundler -v '2.1.4' 49 | - bundle install --path vendor/bundle 50 | - cache store 51 | jobs: 52 | - name: Test 53 | commands: 54 | # Select an Xcode version. 55 | # See https://docs.semaphoreci.com/article/161-macos-mojave-xcode-10-image and 56 | # https://docs.semaphoreci.com/article/162-macos-mojave-xcode-11-image 57 | - bundle exec xcversion select 12.2 58 | 59 | # Run tests of iOS and Mac app on a simulator or connected device. 60 | # See https://docs.fastlane.tools/actions/scan/ 61 | - bundle exec fastlane test 62 | 63 | - name: Build app 64 | task: 65 | env_vars: 66 | - name: LANG 67 | value: en_US.UTF-8 68 | secrets: 69 | # Make the SSH key for the certificate repository and the MATCH_PASSWORD 70 | # environment variable available. 71 | # See https://docs.semaphoreci.com/article/109-using-private-dependencies 72 | - name: match-secrets 73 | prologue: 74 | commands: 75 | # Add the key for the match certificate repository to ssh 76 | # See https://docs.semaphoreci.com/article/109-using-private-dependencies 77 | - chmod 0600 ~/.ssh/* 78 | - ssh-add ~/.ssh/match-repository-private-key 79 | # Continue with checkout as normal 80 | - checkout 81 | - cache restore 82 | 83 | 84 | # Fix for: `find_spec_for_exe': can't find gem bundler erro 85 | # Look at the end of Gemfile.lock, under BUNDLED WITH 86 | # and simply install the required version before calling bundle install 87 | - gem install bundler -v '2.1.4' 88 | - bundle install --path vendor/bundle 89 | - cache store 90 | jobs: 91 | - name: Build 92 | commands: 93 | - bundle exec xcversion select 12.2 94 | - bundle exec fastlane build 95 | 96 | # Upload the IPA file as a job artifact. 97 | # See https://docs.semaphoreci.com/article/155-artifacts 98 | - artifact push job build/TallestTowers.ipa 99 | - name: Take screenshots 100 | task: 101 | env_vars: 102 | - name: LANG 103 | value: en_US.UTF-8 104 | prologue: 105 | commands: 106 | - checkout 107 | - cache restore 108 | 109 | # Fix for: `find_spec_for_exe': can't find gem bundler erro 110 | # Look at the end of Gemfile.lock, under BUNDLED WITH 111 | # and simply install the required version before calling bundle install 112 | - gem install bundler -v '2.1.4' 113 | - bundle install --path vendor/bundle 114 | - cache store 115 | jobs: 116 | - name: Screenshots 117 | commands: 118 | - bundle exec xcversion select 12.2 119 | 120 | # Fix for fastlane snapshot not allways finding the simular. 121 | # In case you find errors like "Simulator not found", use this workarround 122 | - xcrun simctl list 123 | 124 | - bundle exec fastlane screenshots 125 | 126 | # Upload the screenshots directory as a project artifact. 127 | # See https://docs.semaphoreci.com/article/155-artifacts 128 | - artifact push job screenshots 129 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'fastlane' 4 | gem 'xcode-install' 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | addressable (2.7.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | atomos (0.1.3) 8 | aws-eventstream (1.1.0) 9 | aws-partitions (1.410.0) 10 | aws-sdk-core (3.110.0) 11 | aws-eventstream (~> 1, >= 1.0.2) 12 | aws-partitions (~> 1, >= 1.239.0) 13 | aws-sigv4 (~> 1.1) 14 | jmespath (~> 1.0) 15 | aws-sdk-kms (1.40.0) 16 | aws-sdk-core (~> 3, >= 3.109.0) 17 | aws-sigv4 (~> 1.1) 18 | aws-sdk-s3 (1.86.2) 19 | aws-sdk-core (~> 3, >= 3.109.0) 20 | aws-sdk-kms (~> 1.26) 21 | aws-sigv4 (~> 1.1) 22 | aws-sigv4 (1.2.2) 23 | aws-eventstream (~> 1, >= 1.0.2) 24 | babosa (1.0.4) 25 | claide (1.0.3) 26 | colored (1.2) 27 | colored2 (3.1.2) 28 | commander-fastlane (4.4.6) 29 | highline (~> 1.7.2) 30 | declarative (0.0.20) 31 | declarative-option (0.1.0) 32 | digest-crc (0.6.3) 33 | rake (>= 12.0.0, < 14.0.0) 34 | domain_name (0.5.20190701) 35 | unf (>= 0.0.5, < 1.0.0) 36 | dotenv (2.7.6) 37 | emoji_regex (3.2.1) 38 | excon (0.78.1) 39 | faraday (1.1.0) 40 | multipart-post (>= 1.2, < 3) 41 | ruby2_keywords 42 | faraday-cookie_jar (0.0.7) 43 | faraday (>= 0.8.0) 44 | http-cookie (~> 1.0.0) 45 | faraday_middleware (1.0.0) 46 | faraday (~> 1.0) 47 | fastimage (2.2.0) 48 | fastlane (2.170.0) 49 | CFPropertyList (>= 2.3, < 4.0.0) 50 | addressable (>= 2.3, < 3.0.0) 51 | aws-sdk-s3 (~> 1.0) 52 | babosa (>= 1.0.3, < 2.0.0) 53 | bundler (>= 1.12.0, < 3.0.0) 54 | colored 55 | commander-fastlane (>= 4.4.6, < 5.0.0) 56 | dotenv (>= 2.1.1, < 3.0.0) 57 | emoji_regex (>= 0.1, < 4.0) 58 | excon (>= 0.71.0, < 1.0.0) 59 | faraday (~> 1.0) 60 | faraday-cookie_jar (~> 0.0.6) 61 | faraday_middleware (~> 1.0) 62 | fastimage (>= 2.1.0, < 3.0.0) 63 | gh_inspector (>= 1.1.2, < 2.0.0) 64 | google-api-client (>= 0.37.0, < 0.39.0) 65 | google-cloud-storage (>= 1.15.0, < 2.0.0) 66 | highline (>= 1.7.2, < 2.0.0) 67 | json (< 3.0.0) 68 | jwt (>= 2.1.0, < 3) 69 | mini_magick (>= 4.9.4, < 5.0.0) 70 | multipart-post (~> 2.0.0) 71 | plist (>= 3.1.0, < 4.0.0) 72 | rubyzip (>= 2.0.0, < 3.0.0) 73 | security (= 0.1.3) 74 | simctl (~> 1.6.3) 75 | slack-notifier (>= 2.0.0, < 3.0.0) 76 | terminal-notifier (>= 2.0.0, < 3.0.0) 77 | terminal-table (>= 1.4.5, < 2.0.0) 78 | tty-screen (>= 0.6.3, < 1.0.0) 79 | tty-spinner (>= 0.8.0, < 1.0.0) 80 | word_wrap (~> 1.0.0) 81 | xcodeproj (>= 1.13.0, < 2.0.0) 82 | xcpretty (~> 0.3.0) 83 | xcpretty-travis-formatter (>= 0.0.3) 84 | fastlane-plugin-semaphore (0.2.0) 85 | gh_inspector (1.1.3) 86 | google-api-client (0.38.0) 87 | addressable (~> 2.5, >= 2.5.1) 88 | googleauth (~> 0.9) 89 | httpclient (>= 2.8.1, < 3.0) 90 | mini_mime (~> 1.0) 91 | representable (~> 3.0) 92 | retriable (>= 2.0, < 4.0) 93 | signet (~> 0.12) 94 | google-cloud-core (1.5.0) 95 | google-cloud-env (~> 1.0) 96 | google-cloud-errors (~> 1.0) 97 | google-cloud-env (1.4.0) 98 | faraday (>= 0.17.3, < 2.0) 99 | google-cloud-errors (1.0.1) 100 | google-cloud-storage (1.29.2) 101 | addressable (~> 2.5) 102 | digest-crc (~> 0.4) 103 | google-api-client (~> 0.33) 104 | google-cloud-core (~> 1.2) 105 | googleauth (~> 0.9) 106 | mini_mime (~> 1.0) 107 | googleauth (0.14.0) 108 | faraday (>= 0.17.3, < 2.0) 109 | jwt (>= 1.4, < 3.0) 110 | memoist (~> 0.16) 111 | multi_json (~> 1.11) 112 | os (>= 0.9, < 2.0) 113 | signet (~> 0.14) 114 | highline (1.7.10) 115 | http-cookie (1.0.3) 116 | domain_name (~> 0.5) 117 | httpclient (2.8.3) 118 | jmespath (1.4.0) 119 | json (2.4.1) 120 | jwt (2.2.2) 121 | memoist (0.16.2) 122 | mini_magick (4.11.0) 123 | mini_mime (1.0.2) 124 | multi_json (1.15.0) 125 | multipart-post (2.0.0) 126 | nanaimo (0.3.0) 127 | naturally (2.2.0) 128 | os (1.1.1) 129 | plist (3.5.0) 130 | public_suffix (4.0.6) 131 | rake (13.0.2) 132 | representable (3.0.4) 133 | declarative (< 0.1.0) 134 | declarative-option (< 0.2.0) 135 | uber (< 0.2.0) 136 | retriable (3.1.2) 137 | rouge (2.0.7) 138 | ruby2_keywords (0.0.2) 139 | rubyzip (2.3.0) 140 | security (0.1.3) 141 | signet (0.14.0) 142 | addressable (~> 2.3) 143 | faraday (>= 0.17.3, < 2.0) 144 | jwt (>= 1.5, < 3.0) 145 | multi_json (~> 1.10) 146 | simctl (1.6.8) 147 | CFPropertyList 148 | naturally 149 | slack-notifier (2.3.2) 150 | terminal-notifier (2.0.0) 151 | terminal-table (1.8.0) 152 | unicode-display_width (~> 1.1, >= 1.1.1) 153 | tty-cursor (0.7.1) 154 | tty-screen (0.8.1) 155 | tty-spinner (0.9.3) 156 | tty-cursor (~> 0.7) 157 | uber (0.1.0) 158 | unf (0.1.4) 159 | unf_ext 160 | unf_ext (0.0.7.7) 161 | unicode-display_width (1.7.0) 162 | word_wrap (1.0.0) 163 | xcode-install (2.6.4) 164 | claide (>= 0.9.1, < 1.1.0) 165 | fastlane (>= 2.1.0, < 3.0.0) 166 | xcodeproj (1.19.0) 167 | CFPropertyList (>= 2.3.3, < 4.0) 168 | atomos (~> 0.1.3) 169 | claide (>= 1.0.2, < 2.0) 170 | colored2 (~> 3.1) 171 | nanaimo (~> 0.3.0) 172 | xcpretty (0.3.0) 173 | rouge (~> 2.0.7) 174 | xcpretty-travis-formatter (1.0.0) 175 | xcpretty (~> 0.2, >= 0.0.7) 176 | 177 | PLATFORMS 178 | ruby 179 | 180 | DEPENDENCIES 181 | fastlane 182 | fastlane-plugin-semaphore 183 | xcode-install 184 | 185 | BUNDLED WITH 186 | 2.1.4 187 | -------------------------------------------------------------------------------- /Images/Pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode/6c605ed60ba2ab45021a4b596c088f01d6ed1e60/Images/Pipeline.png -------------------------------------------------------------------------------- /Images/Secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode/6c605ed60ba2ab45021a4b596c088f01d6ed1e60/Images/Secret.png -------------------------------------------------------------------------------- /Images/TallestTowers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode/6c605ed60ba2ab45021a4b596c088f01d6ed1e60/Images/TallestTowers.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rendered Text 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semaphore CI for iOS 2 | 3 | This example iOS application includes a fully configured CI pipeline demonstrating how to build, test, and generate App Store screenshots for an iOS app with [Semaphore CI](https://semaphoreci.com). This project requires Xcode 11, and is written in Swift 5.1 using SwiftUI. 4 | 5 | ## Run the example app and tests locally 6 | 7 | To run the example application, fork this repository and clone it locally. Open `TallestTowers.xcodeproj`, select an installed simulator and choose "Run" from the "Product" menu or press ⌘R. 8 | 9 | This project also includes both unit, and UI tests. To run all tests locally, choose "Test" from the "Product" menu or press ⌘U. 10 | 11 | ![Example project running in the iOS Simulator](Images/TallestTowers.png) 12 | 13 | ## Set up code signing 14 | 15 | Before you run the example app on a real device or on Semaphore, you'll need to configure code signing. 16 | 17 | First, you'll will need to configure the Xcode project to use your development team. Open `TallestTowers.xcodeproj` and set the development team for each of the `TallestTowers`, `TallestTowersTests`, `TallestTowersUITests`, and `TallestTowersScreenshots` targets as follows: 18 | 19 | 1. Select the `TallestTowers` project in the Project navigator. 20 | 2. Switch to the "Signing & Capabilities" tab. 21 | 3. Select each target in turn and select a valid development team in the "Signing" settings. 22 | 23 | Then, open the `Matchfile` in the `fastlane` folder and make the following edits: 24 | 25 | 1. Set the `git_url` to an empty, private git repository that can be used to store encrypted certificates and provisioning profiles for this example project. 26 | 2. Set the `username` to the email address for your Apple developer account. 27 | 28 | Then, generate ad hoc signing certificates and provisioning profiles. Quit Xcode and run the following command in Terminal: 29 | 30 | ```shell 31 | bundle exec fastlane match adhoc 32 | ``` 33 | 34 | Finally, open `TallestTowers.xcodeproj` with Xcode again and make the following changes: 35 | 36 | * Select the `TallestTowers` target in the project and switch to the "Signing and Capabilities" tab. 37 | * Uncheck the "Automatically manage signing" checkbox. 38 | * Select `match AdHoc com.semaphoreci.TallestTowers` from the "Provisioning Profile" drop-down. 39 | 40 | ## Run the CI pipeline locally 41 | 42 | Now that code signing is configured, you should be able to test the CI pipeline locally. An example `Fastfile` has been included in this project which is configured to: 43 | 44 | * `build`: Build the app and archive an ad hoc IPA file. 45 | * `test`: Build the app and run the unit and UI tests. 46 | * `screenshots`: Build the app and generate sample App Store screenshots. 47 | 48 | To run the CI pipeline locally, execute the following commands from the project directory in Terminal: 49 | 50 | ```shell 51 | bundle install --path vendor/gems 52 | bundle exec fastlane build 53 | bundle exec fastlane test 54 | bundle exec fastlane screenshots 55 | ``` 56 | 57 | At this point, if everything is configured correctly you should have a `TallestTowers.ipa` file in the project root and a `screenshots` directory containing sample App Store screenshots. 58 | 59 | ## Run the CI pipeline on Semaphore 60 | 61 | Semaphore pipelines are made up of blocks executed in sequence that are configured in a `.semaphore/semaphore.yml` file in the root of your project. The [pipeline configuration for this example project](.semaphore/semaphore.yml) has been configured to run all tests, build and archive the application as an IPA file, and generate App Store Screenshots. The IPA archive and App Store screenshots will also be uploaded as [job artifacts](https://docs.semaphoreci.com/article/155-artifacts). 62 | 63 | For more information on the full capabilities of the `semaphore.yml` configuration file, read the comments in this project's `semaphore.yml`, or [read the documentation](https://docs.semaphoreci.com/article/50-pipeline-yaml). 64 | 65 | To run the CI for this example app yourself, add your fork of this repository as a new project from [your Semaphore dashboard](https://id.semaphoreci.com/init_auth). However, if you run the pipeline at this point it will fail. For it to pass, Semaphore needs access to the private git repository where `match` stored the encrypted certificates and provisioning profiles that you generated. To allow that, follow these steps: 66 | 67 | 1. Create a new SSH public/private key pair on your local machine with `ssh-keygen`. For more information on doing this, see [Using Private Dependencies](https://docs.semaphoreci.com/article/109-using-private-dependencies). 68 | 2. Add the public key as a deploy key to your private `match` repository. For more information, if you're using GitHub to host the repository, see [Managing Deploy Keys](https://developer.github.com/v3/guides/managing-deploy-keys/). 69 | 3. From [your Semaphore dashboard](https://id.semaphoreci.com/init_auth), select "Secrets" under "Configuration". 70 | 4. Create a new secret with the name `match-secrets`. The secret does not *need* to be named this, but this is the name used in this example project's `semaphore.yml`. 71 | 5. Under "Environment Variables", enter `MATCH_PASSWORD` as the variable name and enter the password you created when running `bundle exec fastlane match adhoc`. 72 | 6. Under "Files", enter `/Users/semaphore/.ssh/match-repository-private-key` as the path to the file that will be created on the CI server, and upload the *private* key. Again, the file path does not *need* to use this path or file name but this is the location that this example project's `semaphore.yml` is configured to expect. 73 | 74 | Your secret should look like this: 75 | 76 | ![Screenshot of a correctly configured secret on Semaphore](Images/Secret.png) 77 | 78 | Then, just push any change to your fork of this project and Semaphore will run the CI. You should see the pipeline run, and pass. 79 | 80 | ![A passing CI pipeline on Semaphore](Images/Pipeline.png) 81 | 82 | **Important Note:** If you receive the error message "Selected machine type is not available in this organization" after setting up this example project on Semaphore, ensure your account is on the Semaphore Pro plan. A 14 day free trial of this plan is available from your dashboard. 83 | 84 | This example Semaphore configuration uses an `a1-standard-4` machine running macOS Mojave with [Xcode 11, fastlane and other build tools pre-installed](https://docs.semaphoreci.com/ci-cd-environment/macos-catalina-xcode-11-image/). Other, larger machine types are [available if needed](https://docs.semaphoreci.com/ci-cd-environment/machine-types/). 85 | 86 | ## Fork-and-Run VS Master branch 87 | 88 | The `master` branch represents a template you can follow to setup your own project on Semaphore. That being said, running it directly on your own account would require first setting the public/private keys as describe in the [Run the CI pipeline on Semaphore](#run-the-ci-pipeline-on-semaphore) section. 89 | 90 | For simplicity, on the `fork-and-run` branch we disabled the signing capabilities of this template project so you can safely `fork` and imediatelly `run` it on Semaphore. 91 | 92 | The lines that have been commented out for this purpose are clearly marked as 93 | 94 | ```##### Disable code signing for fork-and-run branch ######``` 95 | 96 | so you can clearly see what has been removed. 97 | 98 | 99 | ## License 100 | 101 | Copyright ©2019 Rendered Text – Distributed under the MIT License. See the [LICENSE](LICENSE) for full terms. 102 | -------------------------------------------------------------------------------- /TallestTowers.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 55088B87234F444C006CF75A /* TowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55088B86234F444C006CF75A /* TowersView.swift */; }; 11 | 55088B89234F45AF006CF75A /* TowerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55088B88234F45AF006CF75A /* TowerRow.swift */; }; 12 | 55088B8B234F4EEB006CF75A /* TowerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55088B8A234F4EEB006CF75A /* TowerDetailView.swift */; }; 13 | 55088B8D234F4F7D006CF75A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55088B8C234F4F7D006CF75A /* MapView.swift */; }; 14 | 5519C15A234E33420022E332 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5519C159234E33420022E332 /* AppDelegate.swift */; }; 15 | 5519C15C234E33420022E332 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5519C15B234E33420022E332 /* SceneDelegate.swift */; }; 16 | 5519C160234E33430022E332 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5519C15F234E33430022E332 /* Assets.xcassets */; }; 17 | 5519C166234E33430022E332 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5519C164234E33430022E332 /* LaunchScreen.storyboard */; }; 18 | 5519C171234E33440022E332 /* TowerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5519C170234E33440022E332 /* TowerTests.swift */; }; 19 | 5519C17C234E33440022E332 /* TallestTowersUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5519C17B234E33440022E332 /* TallestTowersUITests.swift */; }; 20 | 5519C18A234E35880022E332 /* Tower.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5519C189234E35880022E332 /* Tower.swift */; }; 21 | 5535C79A2382FF7B00E65B03 /* TallestTowersScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5535C7992382FF7B00E65B03 /* TallestTowersScreenshots.swift */; }; 22 | 5535C7A22382FFD400E65B03 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5535C7A12382FFD400E65B03 /* SnapshotHelper.swift */; }; 23 | 55CFADD6235F44390067DBB7 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 55CFADD5235F44380067DBB7 /* README.md */; }; 24 | 55CFADD8235F860E0067DBB7 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 55CFADD7235F860E0067DBB7 /* LICENSE */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 5519C16D234E33440022E332 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 5519C14E234E33420022E332 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 5519C155234E33420022E332; 33 | remoteInfo = TallestTowers; 34 | }; 35 | 5519C178234E33440022E332 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 5519C14E234E33420022E332 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 5519C155234E33420022E332; 40 | remoteInfo = TallestTowers; 41 | }; 42 | 5535C79C2382FF7B00E65B03 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 5519C14E234E33420022E332 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 5519C155234E33420022E332; 47 | remoteInfo = TallestTowers; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | 55088B86234F444C006CF75A /* TowersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TowersView.swift; sourceTree = ""; }; 53 | 55088B88234F45AF006CF75A /* TowerRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TowerRow.swift; sourceTree = ""; }; 54 | 55088B8A234F4EEB006CF75A /* TowerDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TowerDetailView.swift; sourceTree = ""; }; 55 | 55088B8C234F4F7D006CF75A /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; 56 | 5519C156234E33420022E332 /* TallestTowers.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TallestTowers.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 5519C159234E33420022E332 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 58 | 5519C15B234E33420022E332 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 59 | 5519C15F234E33430022E332 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | 5519C165234E33430022E332 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 61 | 5519C167234E33430022E332 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 5519C16C234E33440022E332 /* TallestTowersTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TallestTowersTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 5519C170234E33440022E332 /* TowerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TowerTests.swift; sourceTree = ""; }; 64 | 5519C172234E33440022E332 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 5519C177234E33440022E332 /* TallestTowersUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TallestTowersUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 5519C17B234E33440022E332 /* TallestTowersUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TallestTowersUITests.swift; sourceTree = ""; }; 67 | 5519C17D234E33440022E332 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 5519C189234E35880022E332 /* Tower.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tower.swift; sourceTree = ""; }; 69 | 552B707D236B5D9300ACC210 /* Pluginfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Pluginfile; sourceTree = ""; }; 70 | 552B707E236B5D9300ACC210 /* Appfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Appfile; sourceTree = ""; }; 71 | 552B707F236B5D9300ACC210 /* Fastfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Fastfile; sourceTree = ""; }; 72 | 5535C7972382FF7B00E65B03 /* TallestTowersScreenshots.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TallestTowersScreenshots.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 5535C7992382FF7B00E65B03 /* TallestTowersScreenshots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TallestTowersScreenshots.swift; sourceTree = ""; }; 74 | 5535C79B2382FF7B00E65B03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 75 | 5535C7A12382FFD400E65B03 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = ""; }; 76 | 5535C7A32383038C00E65B03 /* Snapfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Snapfile; sourceTree = ""; }; 77 | 55AE65672360A0D60064B1AB /* semaphore.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = semaphore.yml; sourceTree = ""; }; 78 | 55CFADD5235F44380067DBB7 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 79 | 55CFADD7235F860E0067DBB7 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 80 | 55FC950923995EA6008108F0 /* Matchfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Matchfile; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | 5519C153234E33420022E332 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | 5519C169234E33440022E332 /* Frameworks */ = { 92 | isa = PBXFrameworksBuildPhase; 93 | buildActionMask = 2147483647; 94 | files = ( 95 | ); 96 | runOnlyForDeploymentPostprocessing = 0; 97 | }; 98 | 5519C174234E33440022E332 /* Frameworks */ = { 99 | isa = PBXFrameworksBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | 5535C7942382FF7B00E65B03 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | 55088B8E234F678D006CF75A /* Models */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 5519C189234E35880022E332 /* Tower.swift */, 119 | ); 120 | path = Models; 121 | sourceTree = ""; 122 | }; 123 | 55088B8F234F6799006CF75A /* Views */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 55088B86234F444C006CF75A /* TowersView.swift */, 127 | 55088B88234F45AF006CF75A /* TowerRow.swift */, 128 | 55088B8A234F4EEB006CF75A /* TowerDetailView.swift */, 129 | 55088B8C234F4F7D006CF75A /* MapView.swift */, 130 | ); 131 | path = Views; 132 | sourceTree = ""; 133 | }; 134 | 55088B90234F67AC006CF75A /* Controllers */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 5519C159234E33420022E332 /* AppDelegate.swift */, 138 | 5519C15B234E33420022E332 /* SceneDelegate.swift */, 139 | ); 140 | path = Controllers; 141 | sourceTree = ""; 142 | }; 143 | 5519C14D234E33420022E332 = { 144 | isa = PBXGroup; 145 | children = ( 146 | 55CFADD7235F860E0067DBB7 /* LICENSE */, 147 | 55CFADD5235F44380067DBB7 /* README.md */, 148 | 552B7079236B5D9300ACC210 /* fastlane */, 149 | 55AE65662360A0D60064B1AB /* .semaphore */, 150 | 5519C158234E33420022E332 /* TallestTowers */, 151 | 5519C16F234E33440022E332 /* TallestTowersTests */, 152 | 5519C17A234E33440022E332 /* TallestTowersUITests */, 153 | 5535C7982382FF7B00E65B03 /* TallestTowersScreenshots */, 154 | 5519C157234E33420022E332 /* Products */, 155 | ); 156 | sourceTree = ""; 157 | }; 158 | 5519C157234E33420022E332 /* Products */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 5519C156234E33420022E332 /* TallestTowers.app */, 162 | 5519C16C234E33440022E332 /* TallestTowersTests.xctest */, 163 | 5519C177234E33440022E332 /* TallestTowersUITests.xctest */, 164 | 5535C7972382FF7B00E65B03 /* TallestTowersScreenshots.xctest */, 165 | ); 166 | name = Products; 167 | sourceTree = ""; 168 | }; 169 | 5519C158234E33420022E332 /* TallestTowers */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 55088B8E234F678D006CF75A /* Models */, 173 | 55088B90234F67AC006CF75A /* Controllers */, 174 | 55088B8F234F6799006CF75A /* Views */, 175 | 5519C15F234E33430022E332 /* Assets.xcassets */, 176 | 5519C164234E33430022E332 /* LaunchScreen.storyboard */, 177 | 5519C167234E33430022E332 /* Info.plist */, 178 | ); 179 | path = TallestTowers; 180 | sourceTree = ""; 181 | }; 182 | 5519C16F234E33440022E332 /* TallestTowersTests */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 5519C170234E33440022E332 /* TowerTests.swift */, 186 | 5519C172234E33440022E332 /* Info.plist */, 187 | ); 188 | path = TallestTowersTests; 189 | sourceTree = ""; 190 | }; 191 | 5519C17A234E33440022E332 /* TallestTowersUITests */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 5519C17B234E33440022E332 /* TallestTowersUITests.swift */, 195 | 5519C17D234E33440022E332 /* Info.plist */, 196 | ); 197 | path = TallestTowersUITests; 198 | sourceTree = ""; 199 | }; 200 | 552B7079236B5D9300ACC210 /* fastlane */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 552B707E236B5D9300ACC210 /* Appfile */, 204 | 552B707F236B5D9300ACC210 /* Fastfile */, 205 | 55FC950923995EA6008108F0 /* Matchfile */, 206 | 5535C7A32383038C00E65B03 /* Snapfile */, 207 | 552B707D236B5D9300ACC210 /* Pluginfile */, 208 | ); 209 | path = fastlane; 210 | sourceTree = ""; 211 | }; 212 | 5535C7982382FF7B00E65B03 /* TallestTowersScreenshots */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 5535C7A12382FFD400E65B03 /* SnapshotHelper.swift */, 216 | 5535C7992382FF7B00E65B03 /* TallestTowersScreenshots.swift */, 217 | 5535C79B2382FF7B00E65B03 /* Info.plist */, 218 | ); 219 | path = TallestTowersScreenshots; 220 | sourceTree = ""; 221 | }; 222 | 55AE65662360A0D60064B1AB /* .semaphore */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 55AE65672360A0D60064B1AB /* semaphore.yml */, 226 | ); 227 | path = .semaphore; 228 | sourceTree = ""; 229 | }; 230 | /* End PBXGroup section */ 231 | 232 | /* Begin PBXNativeTarget section */ 233 | 5519C155234E33420022E332 /* TallestTowers */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = 5519C180234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowers" */; 236 | buildPhases = ( 237 | 5519C152234E33420022E332 /* Sources */, 238 | 5519C153234E33420022E332 /* Frameworks */, 239 | 5519C154234E33420022E332 /* Resources */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | ); 245 | name = TallestTowers; 246 | productName = TallestTowers; 247 | productReference = 5519C156234E33420022E332 /* TallestTowers.app */; 248 | productType = "com.apple.product-type.application"; 249 | }; 250 | 5519C16B234E33440022E332 /* TallestTowersTests */ = { 251 | isa = PBXNativeTarget; 252 | buildConfigurationList = 5519C183234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowersTests" */; 253 | buildPhases = ( 254 | 5519C168234E33440022E332 /* Sources */, 255 | 5519C169234E33440022E332 /* Frameworks */, 256 | 5519C16A234E33440022E332 /* Resources */, 257 | ); 258 | buildRules = ( 259 | ); 260 | dependencies = ( 261 | 5519C16E234E33440022E332 /* PBXTargetDependency */, 262 | ); 263 | name = TallestTowersTests; 264 | productName = TallestTowersTests; 265 | productReference = 5519C16C234E33440022E332 /* TallestTowersTests.xctest */; 266 | productType = "com.apple.product-type.bundle.unit-test"; 267 | }; 268 | 5519C176234E33440022E332 /* TallestTowersUITests */ = { 269 | isa = PBXNativeTarget; 270 | buildConfigurationList = 5519C186234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowersUITests" */; 271 | buildPhases = ( 272 | 5519C173234E33440022E332 /* Sources */, 273 | 5519C174234E33440022E332 /* Frameworks */, 274 | 5519C175234E33440022E332 /* Resources */, 275 | ); 276 | buildRules = ( 277 | ); 278 | dependencies = ( 279 | 5519C179234E33440022E332 /* PBXTargetDependency */, 280 | ); 281 | name = TallestTowersUITests; 282 | productName = TallestTowersUITests; 283 | productReference = 5519C177234E33440022E332 /* TallestTowersUITests.xctest */; 284 | productType = "com.apple.product-type.bundle.ui-testing"; 285 | }; 286 | 5535C7962382FF7B00E65B03 /* TallestTowersScreenshots */ = { 287 | isa = PBXNativeTarget; 288 | buildConfigurationList = 5535C7A02382FF7B00E65B03 /* Build configuration list for PBXNativeTarget "TallestTowersScreenshots" */; 289 | buildPhases = ( 290 | 5535C7932382FF7B00E65B03 /* Sources */, 291 | 5535C7942382FF7B00E65B03 /* Frameworks */, 292 | 5535C7952382FF7B00E65B03 /* Resources */, 293 | ); 294 | buildRules = ( 295 | ); 296 | dependencies = ( 297 | 5535C79D2382FF7B00E65B03 /* PBXTargetDependency */, 298 | ); 299 | name = TallestTowersScreenshots; 300 | productName = TallestTowersScreenshots; 301 | productReference = 5535C7972382FF7B00E65B03 /* TallestTowersScreenshots.xctest */; 302 | productType = "com.apple.product-type.bundle.ui-testing"; 303 | }; 304 | /* End PBXNativeTarget section */ 305 | 306 | /* Begin PBXProject section */ 307 | 5519C14E234E33420022E332 /* Project object */ = { 308 | isa = PBXProject; 309 | attributes = { 310 | LastSwiftUpdateCheck = 1120; 311 | LastUpgradeCheck = 1110; 312 | ORGANIZATIONNAME = "Semaphore CI"; 313 | TargetAttributes = { 314 | 5519C155234E33420022E332 = { 315 | CreatedOnToolsVersion = 11.1; 316 | }; 317 | 5519C16B234E33440022E332 = { 318 | CreatedOnToolsVersion = 11.1; 319 | TestTargetID = 5519C155234E33420022E332; 320 | }; 321 | 5519C176234E33440022E332 = { 322 | CreatedOnToolsVersion = 11.1; 323 | TestTargetID = 5519C155234E33420022E332; 324 | }; 325 | 5535C7962382FF7B00E65B03 = { 326 | CreatedOnToolsVersion = 11.2.1; 327 | TestTargetID = 5519C155234E33420022E332; 328 | }; 329 | }; 330 | }; 331 | buildConfigurationList = 5519C151234E33420022E332 /* Build configuration list for PBXProject "TallestTowers" */; 332 | compatibilityVersion = "Xcode 9.3"; 333 | developmentRegion = en; 334 | hasScannedForEncodings = 0; 335 | knownRegions = ( 336 | en, 337 | Base, 338 | ); 339 | mainGroup = 5519C14D234E33420022E332; 340 | productRefGroup = 5519C157234E33420022E332 /* Products */; 341 | projectDirPath = ""; 342 | projectRoot = ""; 343 | targets = ( 344 | 5519C155234E33420022E332 /* TallestTowers */, 345 | 5519C16B234E33440022E332 /* TallestTowersTests */, 346 | 5519C176234E33440022E332 /* TallestTowersUITests */, 347 | 5535C7962382FF7B00E65B03 /* TallestTowersScreenshots */, 348 | ); 349 | }; 350 | /* End PBXProject section */ 351 | 352 | /* Begin PBXResourcesBuildPhase section */ 353 | 5519C154234E33420022E332 /* Resources */ = { 354 | isa = PBXResourcesBuildPhase; 355 | buildActionMask = 2147483647; 356 | files = ( 357 | 55CFADD6235F44390067DBB7 /* README.md in Resources */, 358 | 5519C166234E33430022E332 /* LaunchScreen.storyboard in Resources */, 359 | 55CFADD8235F860E0067DBB7 /* LICENSE in Resources */, 360 | 5519C160234E33430022E332 /* Assets.xcassets in Resources */, 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | }; 364 | 5519C16A234E33440022E332 /* Resources */ = { 365 | isa = PBXResourcesBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | ); 369 | runOnlyForDeploymentPostprocessing = 0; 370 | }; 371 | 5519C175234E33440022E332 /* Resources */ = { 372 | isa = PBXResourcesBuildPhase; 373 | buildActionMask = 2147483647; 374 | files = ( 375 | ); 376 | runOnlyForDeploymentPostprocessing = 0; 377 | }; 378 | 5535C7952382FF7B00E65B03 /* Resources */ = { 379 | isa = PBXResourcesBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXResourcesBuildPhase section */ 386 | 387 | /* Begin PBXSourcesBuildPhase section */ 388 | 5519C152234E33420022E332 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | 55088B8D234F4F7D006CF75A /* MapView.swift in Sources */, 393 | 55088B87234F444C006CF75A /* TowersView.swift in Sources */, 394 | 5519C15A234E33420022E332 /* AppDelegate.swift in Sources */, 395 | 5519C15C234E33420022E332 /* SceneDelegate.swift in Sources */, 396 | 55088B8B234F4EEB006CF75A /* TowerDetailView.swift in Sources */, 397 | 55088B89234F45AF006CF75A /* TowerRow.swift in Sources */, 398 | 5519C18A234E35880022E332 /* Tower.swift in Sources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | 5519C168234E33440022E332 /* Sources */ = { 403 | isa = PBXSourcesBuildPhase; 404 | buildActionMask = 2147483647; 405 | files = ( 406 | 5519C171234E33440022E332 /* TowerTests.swift in Sources */, 407 | ); 408 | runOnlyForDeploymentPostprocessing = 0; 409 | }; 410 | 5519C173234E33440022E332 /* Sources */ = { 411 | isa = PBXSourcesBuildPhase; 412 | buildActionMask = 2147483647; 413 | files = ( 414 | 5519C17C234E33440022E332 /* TallestTowersUITests.swift in Sources */, 415 | ); 416 | runOnlyForDeploymentPostprocessing = 0; 417 | }; 418 | 5535C7932382FF7B00E65B03 /* Sources */ = { 419 | isa = PBXSourcesBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | 5535C79A2382FF7B00E65B03 /* TallestTowersScreenshots.swift in Sources */, 423 | 5535C7A22382FFD400E65B03 /* SnapshotHelper.swift in Sources */, 424 | ); 425 | runOnlyForDeploymentPostprocessing = 0; 426 | }; 427 | /* End PBXSourcesBuildPhase section */ 428 | 429 | /* Begin PBXTargetDependency section */ 430 | 5519C16E234E33440022E332 /* PBXTargetDependency */ = { 431 | isa = PBXTargetDependency; 432 | target = 5519C155234E33420022E332 /* TallestTowers */; 433 | targetProxy = 5519C16D234E33440022E332 /* PBXContainerItemProxy */; 434 | }; 435 | 5519C179234E33440022E332 /* PBXTargetDependency */ = { 436 | isa = PBXTargetDependency; 437 | target = 5519C155234E33420022E332 /* TallestTowers */; 438 | targetProxy = 5519C178234E33440022E332 /* PBXContainerItemProxy */; 439 | }; 440 | 5535C79D2382FF7B00E65B03 /* PBXTargetDependency */ = { 441 | isa = PBXTargetDependency; 442 | target = 5519C155234E33420022E332 /* TallestTowers */; 443 | targetProxy = 5535C79C2382FF7B00E65B03 /* PBXContainerItemProxy */; 444 | }; 445 | /* End PBXTargetDependency section */ 446 | 447 | /* Begin PBXVariantGroup section */ 448 | 5519C164234E33430022E332 /* LaunchScreen.storyboard */ = { 449 | isa = PBXVariantGroup; 450 | children = ( 451 | 5519C165234E33430022E332 /* Base */, 452 | ); 453 | name = LaunchScreen.storyboard; 454 | sourceTree = ""; 455 | }; 456 | /* End PBXVariantGroup section */ 457 | 458 | /* Begin XCBuildConfiguration section */ 459 | 5519C17E234E33440022E332 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_ANALYZER_NONNULL = YES; 464 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 465 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 466 | CLANG_CXX_LIBRARY = "libc++"; 467 | CLANG_ENABLE_MODULES = YES; 468 | CLANG_ENABLE_OBJC_ARC = YES; 469 | CLANG_ENABLE_OBJC_WEAK = YES; 470 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 471 | CLANG_WARN_BOOL_CONVERSION = YES; 472 | CLANG_WARN_COMMA = YES; 473 | CLANG_WARN_CONSTANT_CONVERSION = YES; 474 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 476 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 477 | CLANG_WARN_EMPTY_BODY = YES; 478 | CLANG_WARN_ENUM_CONVERSION = YES; 479 | CLANG_WARN_INFINITE_RECURSION = YES; 480 | CLANG_WARN_INT_CONVERSION = YES; 481 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 482 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 483 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 485 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 486 | CLANG_WARN_STRICT_PROTOTYPES = YES; 487 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 488 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 489 | CLANG_WARN_UNREACHABLE_CODE = YES; 490 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 491 | COPY_PHASE_STRIP = NO; 492 | DEBUG_INFORMATION_FORMAT = dwarf; 493 | ENABLE_STRICT_OBJC_MSGSEND = YES; 494 | ENABLE_TESTABILITY = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu11; 496 | GCC_DYNAMIC_NO_PIC = NO; 497 | GCC_NO_COMMON_BLOCKS = YES; 498 | GCC_OPTIMIZATION_LEVEL = 0; 499 | GCC_PREPROCESSOR_DEFINITIONS = ( 500 | "DEBUG=1", 501 | "$(inherited)", 502 | ); 503 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 504 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 505 | GCC_WARN_UNDECLARED_SELECTOR = YES; 506 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 507 | GCC_WARN_UNUSED_FUNCTION = YES; 508 | GCC_WARN_UNUSED_VARIABLE = YES; 509 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 510 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 511 | MTL_FAST_MATH = YES; 512 | ONLY_ACTIVE_ARCH = YES; 513 | SDKROOT = iphoneos; 514 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 515 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 516 | }; 517 | name = Debug; 518 | }; 519 | 5519C17F234E33440022E332 /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_SEARCH_USER_PATHS = NO; 523 | CLANG_ANALYZER_NONNULL = YES; 524 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 525 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 526 | CLANG_CXX_LIBRARY = "libc++"; 527 | CLANG_ENABLE_MODULES = YES; 528 | CLANG_ENABLE_OBJC_ARC = YES; 529 | CLANG_ENABLE_OBJC_WEAK = YES; 530 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 531 | CLANG_WARN_BOOL_CONVERSION = YES; 532 | CLANG_WARN_COMMA = YES; 533 | CLANG_WARN_CONSTANT_CONVERSION = YES; 534 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 535 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 536 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 537 | CLANG_WARN_EMPTY_BODY = YES; 538 | CLANG_WARN_ENUM_CONVERSION = YES; 539 | CLANG_WARN_INFINITE_RECURSION = YES; 540 | CLANG_WARN_INT_CONVERSION = YES; 541 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 542 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 543 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 544 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 545 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 546 | CLANG_WARN_STRICT_PROTOTYPES = YES; 547 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 548 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 549 | CLANG_WARN_UNREACHABLE_CODE = YES; 550 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 551 | COPY_PHASE_STRIP = NO; 552 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 553 | ENABLE_NS_ASSERTIONS = NO; 554 | ENABLE_STRICT_OBJC_MSGSEND = YES; 555 | GCC_C_LANGUAGE_STANDARD = gnu11; 556 | GCC_NO_COMMON_BLOCKS = YES; 557 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 558 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 559 | GCC_WARN_UNDECLARED_SELECTOR = YES; 560 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 561 | GCC_WARN_UNUSED_FUNCTION = YES; 562 | GCC_WARN_UNUSED_VARIABLE = YES; 563 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 564 | MTL_ENABLE_DEBUG_INFO = NO; 565 | MTL_FAST_MATH = YES; 566 | SDKROOT = iphoneos; 567 | SWIFT_COMPILATION_MODE = wholemodule; 568 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 569 | VALIDATE_PRODUCT = YES; 570 | }; 571 | name = Release; 572 | }; 573 | 5519C181234E33440022E332 /* Debug */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 577 | CODE_SIGN_STYLE = Automatic; 578 | ENABLE_PREVIEWS = YES; 579 | INFOPLIST_FILE = TallestTowers/Info.plist; 580 | LD_RUNPATH_SEARCH_PATHS = ( 581 | "$(inherited)", 582 | "@executable_path/Frameworks", 583 | ); 584 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowers; 585 | PRODUCT_NAME = "$(TARGET_NAME)"; 586 | SWIFT_VERSION = 5.0; 587 | TARGETED_DEVICE_FAMILY = "1,2"; 588 | }; 589 | name = Debug; 590 | }; 591 | 5519C182234E33440022E332 /* Release */ = { 592 | isa = XCBuildConfiguration; 593 | buildSettings = { 594 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 595 | CODE_SIGN_STYLE = Automatic; 596 | ENABLE_PREVIEWS = YES; 597 | INFOPLIST_FILE = TallestTowers/Info.plist; 598 | LD_RUNPATH_SEARCH_PATHS = ( 599 | "$(inherited)", 600 | "@executable_path/Frameworks", 601 | ); 602 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowers; 603 | PRODUCT_NAME = "$(TARGET_NAME)"; 604 | SWIFT_VERSION = 5.0; 605 | TARGETED_DEVICE_FAMILY = "1,2"; 606 | }; 607 | name = Release; 608 | }; 609 | 5519C184234E33440022E332 /* Debug */ = { 610 | isa = XCBuildConfiguration; 611 | buildSettings = { 612 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 613 | BUNDLE_LOADER = "$(TEST_HOST)"; 614 | CODE_SIGN_STYLE = Automatic; 615 | INFOPLIST_FILE = TallestTowersTests/Info.plist; 616 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 617 | LD_RUNPATH_SEARCH_PATHS = ( 618 | "$(inherited)", 619 | "@executable_path/Frameworks", 620 | "@loader_path/Frameworks", 621 | ); 622 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersTests; 623 | PRODUCT_NAME = "$(TARGET_NAME)"; 624 | SWIFT_VERSION = 5.0; 625 | TARGETED_DEVICE_FAMILY = "1,2"; 626 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TallestTowers.app/TallestTowers"; 627 | }; 628 | name = Debug; 629 | }; 630 | 5519C185234E33440022E332 /* Release */ = { 631 | isa = XCBuildConfiguration; 632 | buildSettings = { 633 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 634 | BUNDLE_LOADER = "$(TEST_HOST)"; 635 | CODE_SIGN_STYLE = Automatic; 636 | INFOPLIST_FILE = TallestTowersTests/Info.plist; 637 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 638 | LD_RUNPATH_SEARCH_PATHS = ( 639 | "$(inherited)", 640 | "@executable_path/Frameworks", 641 | "@loader_path/Frameworks", 642 | ); 643 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersTests; 644 | PRODUCT_NAME = "$(TARGET_NAME)"; 645 | SWIFT_VERSION = 5.0; 646 | TARGETED_DEVICE_FAMILY = "1,2"; 647 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TallestTowers.app/TallestTowers"; 648 | }; 649 | name = Release; 650 | }; 651 | 5519C187234E33440022E332 /* Debug */ = { 652 | isa = XCBuildConfiguration; 653 | buildSettings = { 654 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 655 | CODE_SIGN_STYLE = Automatic; 656 | INFOPLIST_FILE = TallestTowersUITests/Info.plist; 657 | LD_RUNPATH_SEARCH_PATHS = ( 658 | "$(inherited)", 659 | "@executable_path/Frameworks", 660 | "@loader_path/Frameworks", 661 | ); 662 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersUITests; 663 | PRODUCT_NAME = "$(TARGET_NAME)"; 664 | SWIFT_VERSION = 5.0; 665 | TARGETED_DEVICE_FAMILY = "1,2"; 666 | TEST_TARGET_NAME = TallestTowers; 667 | }; 668 | name = Debug; 669 | }; 670 | 5519C188234E33440022E332 /* Release */ = { 671 | isa = XCBuildConfiguration; 672 | buildSettings = { 673 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 674 | CODE_SIGN_STYLE = Automatic; 675 | INFOPLIST_FILE = TallestTowersUITests/Info.plist; 676 | LD_RUNPATH_SEARCH_PATHS = ( 677 | "$(inherited)", 678 | "@executable_path/Frameworks", 679 | "@loader_path/Frameworks", 680 | ); 681 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersUITests; 682 | PRODUCT_NAME = "$(TARGET_NAME)"; 683 | SWIFT_VERSION = 5.0; 684 | TARGETED_DEVICE_FAMILY = "1,2"; 685 | TEST_TARGET_NAME = TallestTowers; 686 | }; 687 | name = Release; 688 | }; 689 | 5535C79E2382FF7B00E65B03 /* Debug */ = { 690 | isa = XCBuildConfiguration; 691 | buildSettings = { 692 | CODE_SIGN_STYLE = Automatic; 693 | INFOPLIST_FILE = TallestTowersScreenshots/Info.plist; 694 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 695 | LD_RUNPATH_SEARCH_PATHS = ( 696 | "$(inherited)", 697 | "@executable_path/Frameworks", 698 | "@loader_path/Frameworks", 699 | ); 700 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersScreenshots; 701 | PRODUCT_NAME = "$(TARGET_NAME)"; 702 | SWIFT_VERSION = 5.0; 703 | TARGETED_DEVICE_FAMILY = "1,2"; 704 | TEST_TARGET_NAME = TallestTowers; 705 | }; 706 | name = Debug; 707 | }; 708 | 5535C79F2382FF7B00E65B03 /* Release */ = { 709 | isa = XCBuildConfiguration; 710 | buildSettings = { 711 | CODE_SIGN_STYLE = Automatic; 712 | INFOPLIST_FILE = TallestTowersScreenshots/Info.plist; 713 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 714 | LD_RUNPATH_SEARCH_PATHS = ( 715 | "$(inherited)", 716 | "@executable_path/Frameworks", 717 | "@loader_path/Frameworks", 718 | ); 719 | PRODUCT_BUNDLE_IDENTIFIER = com.semaphoreci.TallestTowersScreenshots; 720 | PRODUCT_NAME = "$(TARGET_NAME)"; 721 | SWIFT_VERSION = 5.0; 722 | TARGETED_DEVICE_FAMILY = "1,2"; 723 | TEST_TARGET_NAME = TallestTowers; 724 | }; 725 | name = Release; 726 | }; 727 | /* End XCBuildConfiguration section */ 728 | 729 | /* Begin XCConfigurationList section */ 730 | 5519C151234E33420022E332 /* Build configuration list for PBXProject "TallestTowers" */ = { 731 | isa = XCConfigurationList; 732 | buildConfigurations = ( 733 | 5519C17E234E33440022E332 /* Debug */, 734 | 5519C17F234E33440022E332 /* Release */, 735 | ); 736 | defaultConfigurationIsVisible = 0; 737 | defaultConfigurationName = Release; 738 | }; 739 | 5519C180234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowers" */ = { 740 | isa = XCConfigurationList; 741 | buildConfigurations = ( 742 | 5519C181234E33440022E332 /* Debug */, 743 | 5519C182234E33440022E332 /* Release */, 744 | ); 745 | defaultConfigurationIsVisible = 0; 746 | defaultConfigurationName = Release; 747 | }; 748 | 5519C183234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowersTests" */ = { 749 | isa = XCConfigurationList; 750 | buildConfigurations = ( 751 | 5519C184234E33440022E332 /* Debug */, 752 | 5519C185234E33440022E332 /* Release */, 753 | ); 754 | defaultConfigurationIsVisible = 0; 755 | defaultConfigurationName = Release; 756 | }; 757 | 5519C186234E33440022E332 /* Build configuration list for PBXNativeTarget "TallestTowersUITests" */ = { 758 | isa = XCConfigurationList; 759 | buildConfigurations = ( 760 | 5519C187234E33440022E332 /* Debug */, 761 | 5519C188234E33440022E332 /* Release */, 762 | ); 763 | defaultConfigurationIsVisible = 0; 764 | defaultConfigurationName = Release; 765 | }; 766 | 5535C7A02382FF7B00E65B03 /* Build configuration list for PBXNativeTarget "TallestTowersScreenshots" */ = { 767 | isa = XCConfigurationList; 768 | buildConfigurations = ( 769 | 5535C79E2382FF7B00E65B03 /* Debug */, 770 | 5535C79F2382FF7B00E65B03 /* Release */, 771 | ); 772 | defaultConfigurationIsVisible = 0; 773 | defaultConfigurationName = Release; 774 | }; 775 | /* End XCConfigurationList section */ 776 | }; 777 | rootObject = 5519C14E234E33420022E332 /* Project object */; 778 | } 779 | -------------------------------------------------------------------------------- /TallestTowers.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TallestTowers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TallestTowers.xcodeproj/xcshareddata/xcschemes/TakeScreenshots.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /TallestTowers.xcodeproj/xcshareddata/xcschemes/TallestTowers.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /TallestTowers/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TallestTowers/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TallestTowers/Assets.xcassets/OverlayBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xEA", 13 | "alpha" : "1.000", 14 | "blue" : "0xEA", 15 | "green" : "0xEA" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0x28", 31 | "alpha" : "1.000", 32 | "blue" : "0x28", 33 | "green" : "0x28" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /TallestTowers/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TallestTowers/Controllers/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 6 | return true 7 | } 8 | 9 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 10 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TallestTowers/Controllers/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | 4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 5 | var window: UIWindow? 6 | 7 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 8 | if let windowScene = scene as? UIWindowScene { 9 | let window = UIWindow(windowScene: windowScene) 10 | window.rootViewController = UIHostingController(rootView: TowersView()) 11 | self.window = window 12 | window.makeKeyAndVisible() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TallestTowers/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /TallestTowers/Models/Tower.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreLocation 3 | 4 | struct Tower: Identifiable { 5 | let id = UUID() 6 | var name: String 7 | var city: String 8 | var country: String 9 | var height: Int 10 | var yearBuilt: Int 11 | var latitude: Double 12 | var longitude: Double 13 | 14 | var location: CLLocationCoordinate2D { 15 | CLLocationCoordinate2D(latitude: latitude, longitude: longitude) 16 | } 17 | 18 | var cityAndCountry: String { 19 | "\(city), \(country)" 20 | } 21 | 22 | var formattedHeight: String { 23 | "\(height)m" 24 | } 25 | 26 | static var tallestTowers: [ Tower ] { 27 | [ Tower(name: "Burj Khalifa", city: "Dubai", country: "United Arab Emirates", height: 828, yearBuilt: 2010, latitude: 25.186016587, longitude: 55.275198221), 28 | Tower(name: "Shanghai Tower", city: "Shanghai", country: "China", height: 632, yearBuilt: 2015, latitude: 31.226676271, longitude: 121.501873778), 29 | Tower(name: "Abraj Al-Bait Clock Tower", city: "Mecca", country: "Saudi Arabia", height: 601, yearBuilt: 2012, latitude: 21.413935301, longitude: 39.826183555), 30 | Tower(name: "Ping An Finance Center", city: "Shenzhen", country: "China", height: 599, yearBuilt: 2017, latitude: 22.532267288, longitude: 114.054613333), 31 | Tower(name: "Goldin Finance 117", city: "Tianjin", country: "China", height: 596, yearBuilt: 2019, latitude: 39.081763739, longitude: 117.088042333), 32 | Tower(name: "Lotte World Tower", city: "Seoul", country: "South Korea", height: 554, yearBuilt: 2016, latitude: 37.503934185, longitude: 127.103008333), 33 | Tower(name: "One World Trade Center", city: "New York City", country: "USA", height: 541, yearBuilt: 2014, latitude: 40.701346341, longitude: -74.012335000), 34 | Tower(name: "Guangzhou CTF Finance Center", city: "Guangzhou", country: "China", height: 530, yearBuilt: 2016,latitude: 23.110777163, longitude: 113.325901443), 35 | Tower(name: "Tianjin CTF Finance Center", city: "Tianjin", country: "China", height: 530, yearBuilt: 2018, latitude: 39.012823491, longitude: 117.698257222), 36 | Tower(name: "China Zun", city: "Beijing", country: "China", height: 528, yearBuilt: 2018, latitude: 39.907692870, longitude: 116.465015111) ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TallestTowers/Views/MapView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MapKit 3 | 4 | struct MapView: UIViewRepresentable { 5 | var coordinate: CLLocationCoordinate2D 6 | var latLonDelta: CLLocationDegrees 7 | 8 | func makeUIView(context: Context) -> MKMapView { 9 | let mapView = MKMapView(frame: .zero) 10 | mapView.mapType = .satellite 11 | return mapView 12 | } 13 | 14 | func updateUIView(_ mapView: MKMapView, context: Context) { 15 | let span = MKCoordinateSpan(latitudeDelta: latLonDelta, longitudeDelta: latLonDelta) 16 | let region = MKCoordinateRegion(center: coordinate, span: span) 17 | mapView.setRegion(region, animated: false) 18 | } 19 | } 20 | 21 | struct MapViewPreview: PreviewProvider { 22 | static var previews: some View { 23 | MapView(coordinate: Tower.tallestTowers[0].location, latLonDelta: 0.025) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TallestTowers/Views/TowerDetailView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TowerDetailView: View { 4 | var tower: Tower 5 | 6 | var body: some View { 7 | ZStack(alignment: .bottom) { 8 | MapView(coordinate: tower.location, latLonDelta: 0.025) 9 | VStack { 10 | Text(tower.name) 11 | .font(.system(size: 30, weight: .bold)) 12 | .multilineTextAlignment(.center) 13 | .foregroundColor(.primary) 14 | Text(tower.cityAndCountry) 15 | .font(.system(size: 20, weight: .bold)) 16 | .multilineTextAlignment(.center) 17 | .foregroundColor(.secondary) 18 | Text(tower.formattedHeight) 19 | .font(.system(size: 70, weight: .bold, design: .serif)) 20 | .foregroundColor(.primary) 21 | .padding() 22 | HStack(spacing: 4) { 23 | Text("Constructed in") 24 | .font(.system(size: 15, weight: .semibold)) 25 | .foregroundColor(.secondary) 26 | Text(String(tower.yearBuilt)) 27 | .font(.system(size: 15, weight: .semibold)) 28 | .foregroundColor(.secondary) 29 | } 30 | } 31 | .frame(minWidth: 0, maxWidth: .infinity) 32 | .padding().padding(.vertical, 30) 33 | .background(Color("OverlayBackground").opacity(0.8)) 34 | } 35 | .navigationBarTitle(Text(""), displayMode: .inline) 36 | .edgesIgnoringSafeArea(.bottom) 37 | } 38 | } 39 | 40 | struct TowerDetailViewPreviews: PreviewProvider { 41 | static var previews: some View { 42 | TowerDetailView(tower: Tower.tallestTowers[0]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TallestTowers/Views/TowerRow.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TowerRow: View { 4 | var tower: Tower 5 | 6 | var body: some View { 7 | HStack { 8 | VStack(alignment: .leading) { 9 | Text(tower.name) 10 | Text(tower.cityAndCountry) 11 | .foregroundColor(.secondary) 12 | .font(.subheadline) 13 | } 14 | Spacer() 15 | Text(tower.formattedHeight) 16 | .font(.title) 17 | } 18 | } 19 | } 20 | 21 | struct TowerRowPreviews: PreviewProvider { 22 | static var previews: some View { 23 | TowerRow(tower: Tower.tallestTowers[0]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TallestTowers/Views/TowersView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TowersView: View { 4 | var body: some View { 5 | NavigationView { 6 | List(Tower.tallestTowers) { tower in 7 | NavigationLink(destination: TowerDetailView(tower: tower)) { 8 | TowerRow(tower: tower) 9 | } 10 | } 11 | .navigationBarTitle("Tallest Towers") 12 | } 13 | } 14 | } 15 | 16 | struct TowersViewPreview: PreviewProvider { 17 | static var previews: some View { 18 | TowersView() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TallestTowersScreenshots/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TallestTowersScreenshots/SnapshotHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotHelper.swift 3 | // Example 4 | // 5 | // Created by Felix Krause on 10/8/15. 6 | // 7 | 8 | // ----------------------------------------------------- 9 | // IMPORTANT: When modifying this file, make sure to 10 | // increment the version number at the very 11 | // bottom of the file to notify users about 12 | // the new SnapshotHelper.swift 13 | // ----------------------------------------------------- 14 | 15 | import Foundation 16 | import XCTest 17 | 18 | var deviceLanguage = "" 19 | var locale = "" 20 | 21 | func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 22 | Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations) 23 | } 24 | 25 | func snapshot(_ name: String, waitForLoadingIndicator: Bool) { 26 | if waitForLoadingIndicator { 27 | Snapshot.snapshot(name) 28 | } else { 29 | Snapshot.snapshot(name, timeWaitingForIdle: 0) 30 | } 31 | } 32 | 33 | /// - Parameters: 34 | /// - name: The name of the snapshot 35 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait. 36 | func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 37 | Snapshot.snapshot(name, timeWaitingForIdle: timeout) 38 | } 39 | 40 | enum SnapshotError: Error, CustomDebugStringConvertible { 41 | case cannotFindSimulatorHomeDirectory 42 | case cannotRunOnPhysicalDevice 43 | 44 | var debugDescription: String { 45 | switch self { 46 | case .cannotFindSimulatorHomeDirectory: 47 | return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." 48 | case .cannotRunOnPhysicalDevice: 49 | return "Can't use Snapshot on a physical device." 50 | } 51 | } 52 | } 53 | 54 | @objcMembers 55 | open class Snapshot: NSObject { 56 | static var app: XCUIApplication? 57 | static var waitForAnimations = true 58 | static var cacheDirectory: URL? 59 | static var screenshotsDirectory: URL? { 60 | return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true) 61 | } 62 | 63 | open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) { 64 | 65 | Snapshot.app = app 66 | Snapshot.waitForAnimations = waitForAnimations 67 | 68 | do { 69 | let cacheDir = try getCacheDirectory() 70 | Snapshot.cacheDirectory = cacheDir 71 | setLanguage(app) 72 | setLocale(app) 73 | setLaunchArguments(app) 74 | } catch let error { 75 | NSLog(error.localizedDescription) 76 | } 77 | } 78 | 79 | class func setLanguage(_ app: XCUIApplication) { 80 | guard let cacheDirectory = self.cacheDirectory else { 81 | NSLog("CacheDirectory is not set - probably running on a physical device?") 82 | return 83 | } 84 | 85 | let path = cacheDirectory.appendingPathComponent("language.txt") 86 | 87 | do { 88 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 89 | deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 90 | app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"] 91 | } catch { 92 | NSLog("Couldn't detect/set language...") 93 | } 94 | } 95 | 96 | class func setLocale(_ app: XCUIApplication) { 97 | guard let cacheDirectory = self.cacheDirectory else { 98 | NSLog("CacheDirectory is not set - probably running on a physical device?") 99 | return 100 | } 101 | 102 | let path = cacheDirectory.appendingPathComponent("locale.txt") 103 | 104 | do { 105 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines 106 | locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet) 107 | } catch { 108 | NSLog("Couldn't detect/set locale...") 109 | } 110 | 111 | if locale.isEmpty && !deviceLanguage.isEmpty { 112 | locale = Locale(identifier: deviceLanguage).identifier 113 | } 114 | 115 | if !locale.isEmpty { 116 | app.launchArguments += ["-AppleLocale", "\"\(locale)\""] 117 | } 118 | } 119 | 120 | class func setLaunchArguments(_ app: XCUIApplication) { 121 | guard let cacheDirectory = self.cacheDirectory else { 122 | NSLog("CacheDirectory is not set - probably running on a physical device?") 123 | return 124 | } 125 | 126 | let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt") 127 | app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"] 128 | 129 | do { 130 | let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8) 131 | let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: []) 132 | let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count)) 133 | let results = matches.map { result -> String in 134 | (launchArguments as NSString).substring(with: result.range) 135 | } 136 | app.launchArguments += results 137 | } catch { 138 | NSLog("Couldn't detect/set launch_arguments...") 139 | } 140 | } 141 | 142 | open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) { 143 | if timeout > 0 { 144 | waitForLoadingIndicatorToDisappear(within: timeout) 145 | } 146 | 147 | NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work 148 | 149 | if Snapshot.waitForAnimations { 150 | sleep(1) // Waiting for the animation to be finished (kind of) 151 | } 152 | 153 | #if os(OSX) 154 | guard let app = self.app else { 155 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 156 | return 157 | } 158 | 159 | app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: []) 160 | #else 161 | 162 | guard self.app != nil else { 163 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 164 | return 165 | } 166 | 167 | let screenshot = XCUIScreen.main.screenshot() 168 | #if os(iOS) 169 | let image = XCUIDevice.shared.orientation.isLandscape ? fixLandscapeOrientation(image: screenshot.image) : screenshot.image 170 | #else 171 | let image = screenshot.image 172 | #endif 173 | 174 | guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } 175 | 176 | do { 177 | // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices 178 | let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") 179 | let range = NSRange(location: 0, length: simulator.count) 180 | simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") 181 | 182 | let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") 183 | #if swift(<5.0) 184 | UIImagePNGRepresentation(image)?.write(to: path, options: .atomic) 185 | #else 186 | try image.pngData()?.write(to: path, options: .atomic) 187 | #endif 188 | } catch let error { 189 | NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") 190 | NSLog(error.localizedDescription) 191 | } 192 | #endif 193 | } 194 | 195 | class func fixLandscapeOrientation(image: UIImage) -> UIImage { 196 | if #available(iOS 10.0, *) { 197 | let format = UIGraphicsImageRendererFormat() 198 | format.scale = image.scale 199 | let renderer = UIGraphicsImageRenderer(size: image.size, format: format) 200 | return renderer.image { context in 201 | image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) 202 | } 203 | } else { 204 | return image 205 | } 206 | } 207 | 208 | class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) { 209 | #if os(tvOS) 210 | return 211 | #endif 212 | 213 | guard let app = self.app else { 214 | NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 215 | return 216 | } 217 | 218 | let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element 219 | let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator) 220 | _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout) 221 | } 222 | 223 | class func getCacheDirectory() throws -> URL { 224 | let cachePath = "Library/Caches/tools.fastlane" 225 | // on OSX config is stored in /Users//Library 226 | // and on iOS/tvOS/WatchOS it's in simulator's home dir 227 | #if os(OSX) 228 | let homeDir = URL(fileURLWithPath: NSHomeDirectory()) 229 | return homeDir.appendingPathComponent(cachePath) 230 | #elseif arch(i386) || arch(x86_64) 231 | guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else { 232 | throw SnapshotError.cannotFindSimulatorHomeDirectory 233 | } 234 | let homeDir = URL(fileURLWithPath: simulatorHostHome) 235 | return homeDir.appendingPathComponent(cachePath) 236 | #else 237 | throw SnapshotError.cannotRunOnPhysicalDevice 238 | #endif 239 | } 240 | } 241 | 242 | private extension XCUIElementAttributes { 243 | var isNetworkLoadingIndicator: Bool { 244 | if hasAllowListedIdentifier { return false } 245 | 246 | let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20) 247 | let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3) 248 | 249 | return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize 250 | } 251 | 252 | var hasAllowListedIdentifier: Bool { 253 | let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"] 254 | 255 | return allowListedIdentifiers.contains(identifier) 256 | } 257 | 258 | func isStatusBar(_ deviceWidth: CGFloat) -> Bool { 259 | if elementType == .statusBar { return true } 260 | guard frame.origin == .zero else { return false } 261 | 262 | let oldStatusBarSize = CGSize(width: deviceWidth, height: 20) 263 | let newStatusBarSize = CGSize(width: deviceWidth, height: 44) 264 | 265 | return [oldStatusBarSize, newStatusBarSize].contains(frame.size) 266 | } 267 | } 268 | 269 | private extension XCUIElementQuery { 270 | var networkLoadingIndicators: XCUIElementQuery { 271 | let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in 272 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 273 | 274 | return element.isNetworkLoadingIndicator 275 | } 276 | 277 | return self.containing(isNetworkLoadingIndicator) 278 | } 279 | 280 | var deviceStatusBars: XCUIElementQuery { 281 | guard let app = Snapshot.app else { 282 | fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") 283 | } 284 | 285 | let deviceWidth = app.windows.firstMatch.frame.width 286 | 287 | let isStatusBar = NSPredicate { (evaluatedObject, _) in 288 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false } 289 | 290 | return element.isStatusBar(deviceWidth) 291 | } 292 | 293 | return self.containing(isStatusBar) 294 | } 295 | } 296 | 297 | private extension CGFloat { 298 | func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool { 299 | return numberA...numberB ~= self 300 | } 301 | } 302 | 303 | // Please don't remove the lines below 304 | // They are used to detect outdated configuration files 305 | // SnapshotHelperVersion [1.24] 306 | -------------------------------------------------------------------------------- /TallestTowersScreenshots/TallestTowersScreenshots.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class TallestTowersScreenshots: XCTestCase { 4 | override func setUp() { 5 | } 6 | 7 | override func tearDown() { 8 | } 9 | 10 | func testTakeScreenshots() { 11 | let app = XCUIApplication() 12 | setupSnapshot(app) 13 | app.launch() 14 | 15 | snapshot("01-ListOfTowers") 16 | 17 | let burjKhalifaPredicate = NSPredicate(format: "label beginswith 'Burj Khalifa'") 18 | app.tables.buttons.element(matching: burjKhalifaPredicate).tap() 19 | 20 | snapshot("02-TowerDetail") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TallestTowersTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TallestTowersTests/TowerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import TallestTowers 3 | 4 | class TowerInstanceTests: XCTestCase { 5 | var subject: Tower! 6 | 7 | override func setUp() { 8 | subject = Tower(name: "Empire State Building", city: "New York City", country: "USA", height: 381, yearBuilt: 1931, latitude: 40.748457, longitude: -73.985525) 9 | } 10 | 11 | func testLocationShouldBeCreatedFromLatitudeAndLongitudeProperties() { 12 | XCTAssertEqual(subject.location.latitude, 40.748457, accuracy: 0.00001) 13 | XCTAssertEqual(subject.location.longitude, -73.985525, accuracy: 0.00001) 14 | } 15 | 16 | func testCityAndCountryShouldConcatenateCityAndCountry() { 17 | XCTAssertEqual(subject.cityAndCountry, "New York City, USA") 18 | } 19 | 20 | func testFormattedHeightIncludesUnits() { 21 | XCTAssertEqual(subject.formattedHeight, "381m") 22 | } 23 | } 24 | 25 | class TowerStaticTests: XCTestCase { 26 | func testTallestTowersShouldNotBeEmpty() { 27 | XCTAssert(Tower.tallestTowers.count > 0) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TallestTowersUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TallestTowersUITests/TallestTowersUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class TallestTowersUITests: XCTestCase { 4 | let app = XCUIApplication() 5 | 6 | override func setUp() { 7 | continueAfterFailure = false 8 | app.launch() 9 | } 10 | 11 | func testNavigation() { 12 | let burjKhalifaPredicate = NSPredicate(format: "label beginswith 'Burj Khalifa'") 13 | app.tables.buttons.element(matching: burjKhalifaPredicate).tap() 14 | app.navigationBars.buttons["Tallest Towers"].tap() 15 | 16 | let shanghaiTowerPredicate = NSPredicate(format: "label beginswith 'Shanghai Tower'") 17 | app.tables.buttons.element(matching: shanghaiTowerPredicate).tap() 18 | app.navigationBars.buttons["Tallest Towers"].tap() 19 | } 20 | 21 | func testTowerDetailView() { 22 | let chinaZunPredicate = NSPredicate(format: "label beginswith 'China Zun'") 23 | app.tables.buttons.element(matching: chinaZunPredicate).tap() 24 | 25 | XCTAssert(app.staticTexts["China Zun"].exists) 26 | XCTAssert(app.staticTexts["Beijing, China"].exists) 27 | XCTAssert(app.staticTexts["528m"].exists) 28 | XCTAssert(app.staticTexts["Constructed in"].exists) 29 | XCTAssert(app.staticTexts["2018"].exists) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # For more information about the Appfile, see: 2 | # https://docs.fastlane.tools/advanced/#appfile 3 | 4 | app_identifier 'com.semaphoreci.TallestTowers' 5 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | default_platform(:ios) 14 | 15 | before_all do 16 | # Install with `fastlane add_plugin semaphore` 17 | setup_semaphore 18 | end 19 | 20 | platform :ios do 21 | lane :build do 22 | match(type: 'adhoc') 23 | gym(scheme: 'TallestTowers', clean: true) 24 | end 25 | 26 | lane :test do 27 | run_tests(scheme: 'TallestTowers', devices: ['iPhone 11 Pro'], prelaunch_simulator: true) 28 | end 29 | 30 | lane :screenshots do 31 | snapshot 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /fastlane/Gymfile: -------------------------------------------------------------------------------- 1 | # For more information about this configuration visit 2 | # https://docs.fastlane.tools/actions/gym/#gymfile 3 | 4 | project('TallestTowers.xcodeproj') 5 | scheme('TallestTowers') 6 | 7 | export_options({ 8 | method: 'development', 9 | }) 10 | 11 | output_directory('build') 12 | -------------------------------------------------------------------------------- /fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | # For all available options run `fastlane match --help` 2 | # Remove the # in the beginning of the line to enable the other options 3 | 4 | # The docs are available on https://docs.fastlane.tools/actions/match 5 | 6 | storage_mode('git') 7 | git_url('ENTER THE URL FOR AN EMPTY PRIVATE GIT REPOSITORY HERE') 8 | 9 | username('ENTER YOUR APP STORE EMAIL/USERNAME HERE') 10 | 11 | type('adhoc') 12 | app_identifier('com.semaphoreci.TallestTowers') 13 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-semaphore' 6 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios build 20 | ``` 21 | fastlane ios build 22 | ``` 23 | 24 | ### ios test 25 | ``` 26 | fastlane ios test 27 | ``` 28 | 29 | ### ios screenshots 30 | ``` 31 | fastlane ios screenshots 32 | ``` 33 | 34 | 35 | ---- 36 | 37 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 38 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 39 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 40 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | scheme("TakeScreenshots") 2 | 3 | devices([ 4 | "iPhone 11 Pro" 5 | ]) 6 | 7 | clear_previous_screenshots(true) 8 | output_directory("screenshots") 9 | skip_open_summary(true) 10 | --------------------------------------------------------------------------------