├── Gemfile
├── images
├── gif.gif
├── image1.png
├── image2.png
├── image3.png
├── image4.png
└── image5.png
├── Snaptake
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.swift
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── AppDelegate.swift
├── fastlane
├── screenshots
│ ├── en-US
│ │ └── iPhone 8-testExample-final.mp4
│ └── screenshots.html
├── Appfile
├── README.md
├── Snapfile
├── report.xml
├── Fastfile
└── SnapshotHelper.swift
├── Snaptake.xcodeproj
├── xcuserdata
│ └── stephanlerner.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── Snaptake.xcscheme
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── stephanlerner.xcuserdatad
│ │ │ ├── UserInterfaceState.xcuserstate
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ └── xcschemes
│ │ └── SnaptakeUITests.xcscheme
└── project.pbxproj
├── SnaptakeUITests
├── Info.plist
└── SnaptakeUITests.swift
├── bin
├── ww
├── dotenv
├── rougify
├── fastlane
├── xcpretty
├── bin-proxy
├── xcodeproj
├── httpclient
├── generate-api
├── terminal-notifier
├── xcpretty-travis-formatter
└── bundle
├── Gemfile.lock
└── README.md
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 | gem "listen"
5 |
--------------------------------------------------------------------------------
/images/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/gif.gif
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/image1.png
--------------------------------------------------------------------------------
/images/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/image2.png
--------------------------------------------------------------------------------
/images/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/image3.png
--------------------------------------------------------------------------------
/images/image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/image4.png
--------------------------------------------------------------------------------
/images/image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/images/image5.png
--------------------------------------------------------------------------------
/Snaptake/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/fastlane/screenshots/en-US/iPhone 8-testExample-final.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/fastlane/screenshots/en-US/iPhone 8-testExample-final.mp4
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/xcuserdata/stephanlerner.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.xcworkspace/xcuserdata/stephanlerner.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lausbert/Snaptake/HEAD/Snaptake.xcodeproj/project.xcworkspace/xcuserdata/stephanlerner.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier("de.lausbert.Snaptake") # The bundle identifier of your app
2 | # apple_id("[[APPLE_ID]]") # Your Apple email address
3 |
4 |
5 | # For more information about the Appfile, see:
6 | # https://docs.fastlane.tools/advanced/#appfile
7 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Latest
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Snaptake/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Snaptake
4 | //
5 | // Created by Stephan Lerner on 14.04.18.
6 | // Copyright © 2018 Stephan Lerner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.xcworkspace/xcuserdata/stephanlerner.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | EnabledFullIndexStoreVisibility
12 |
13 | IssueFilterStyle
14 | ShowActiveSchemeOnly
15 | LiveSourceIssuesEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/SnaptakeUITests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/xcuserdata/stephanlerner.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Snaptake.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 | SnaptakeUITests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 8D8A725F20824F840021D344
21 |
22 | primary
23 |
24 |
25 | 8D8A7288208252340021D344
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/bin/ww:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'ww' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("word_wrap", "ww")
30 |
--------------------------------------------------------------------------------
/bin/dotenv:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'dotenv' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("dotenv", "dotenv")
30 |
--------------------------------------------------------------------------------
/bin/rougify:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rougify' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rouge", "rougify")
30 |
--------------------------------------------------------------------------------
/bin/fastlane:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'fastlane' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("fastlane", "fastlane")
30 |
--------------------------------------------------------------------------------
/bin/xcpretty:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'xcpretty' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("xcpretty", "xcpretty")
30 |
--------------------------------------------------------------------------------
/bin/bin-proxy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bin-proxy' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("fastlane", "bin-proxy")
30 |
--------------------------------------------------------------------------------
/bin/xcodeproj:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'xcodeproj' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("xcodeproj", "xcodeproj")
30 |
--------------------------------------------------------------------------------
/bin/httpclient:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'httpclient' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("httpclient", "httpclient")
30 |
--------------------------------------------------------------------------------
/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 cask install fastlane`
16 |
17 | # Available Actions
18 | ## iOS
19 | ### ios screenshots
20 | ```
21 | fastlane ios screenshots
22 | ```
23 | Generate new localized screenshots
24 | ### ios videos
25 | ```
26 | fastlane ios videos
27 | ```
28 | Generate new localized videos
29 |
30 | ----
31 |
32 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
33 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
34 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
35 |
--------------------------------------------------------------------------------
/bin/generate-api:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'generate-api' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("google-api-client", "generate-api")
30 |
--------------------------------------------------------------------------------
/bin/terminal-notifier:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'terminal-notifier' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("terminal-notifier", "terminal-notifier")
30 |
--------------------------------------------------------------------------------
/fastlane/Snapfile:
--------------------------------------------------------------------------------
1 | # Uncomment the lines below you want to change by removing the # in the beginning
2 |
3 | # A list of devices you want to take the screenshots from
4 | devices([
5 | "iPhone 8",
6 | # "iPhone 8 Plus",
7 | # "iPhone SE",
8 | # "iPhone X",
9 | # "iPad Pro (12.9-inch)",
10 | # "iPad Pro (9.7-inch)",
11 | # "Apple TV 1080p"
12 | ])
13 |
14 | languages([
15 | "en-US",
16 | # "de-DE"
17 | # "it-IT",
18 | # ["pt", "pt_BR"] # Portuguese with Brazilian locale
19 | ])
20 |
21 | # The name of the scheme which contains the UI Tests
22 | scheme("SnaptakeUITests")
23 |
24 | # remove the '#' to clear all previously generated screenshots before creating new ones
25 | clear_previous_screenshots(true)
26 |
27 | # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
28 | # launch_arguments(["-favColor red"])
29 |
30 | # For more information about all available options run
31 | # fastlane action snapshot
32 |
--------------------------------------------------------------------------------
/bin/xcpretty-travis-formatter:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'xcpretty-travis-formatter' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("xcpretty-travis-formatter", "xcpretty-travis-formatter")
30 |
--------------------------------------------------------------------------------
/fastlane/report.xml:
--------------------------------------------------------------------------------
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 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/SnaptakeUITests/SnaptakeUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnaptakeUITests.swift
3 | // SnaptakeUITests
4 | //
5 | // Created by Stephan Lerner on 14.04.18.
6 | // Copyright © 2018 Stephan Lerner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class SnaptakeUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | let app = XCUIApplication()
22 | setupSnapshot(app)
23 | app.launch()
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | snaptake("testExample") {
33 | XCUIApplication().buttons["button"].tap()
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Snaptake/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Snaptake/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 |
--------------------------------------------------------------------------------
/Snaptake/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 | }
--------------------------------------------------------------------------------
/Snaptake/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Snaptake
4 | //
5 | // Created by Stephan Lerner on 14.04.18.
6 | // Copyright © 2018 Stephan Lerner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1 || ">= 0.a"
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_version
64 | @bundler_version ||= begin
65 | env_var_version || cli_arg_version ||
66 | lockfile_version || "#{Gem::Requirement.default}.a"
67 | end
68 | end
69 |
70 | def load_bundler!
71 | ENV["BUNDLE_GEMFILE"] ||= gemfile
72 |
73 | # must dup string for RG < 1.8 compatibility
74 | activate_bundler(bundler_version.dup)
75 | end
76 |
77 | def activate_bundler(bundler_version)
78 | if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
79 | bundler_version = "< 2"
80 | end
81 | gem_error = activation_error_handling do
82 | gem "bundler", bundler_version
83 | end
84 | return if gem_error.nil?
85 | require_error = activation_error_handling do
86 | require "bundler/version"
87 | end
88 | return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
89 | warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
90 | exit 42
91 | end
92 |
93 | def activation_error_handling
94 | yield
95 | nil
96 | rescue StandardError, LoadError => e
97 | e
98 | end
99 | end
100 |
101 | m.load_bundler!
102 |
103 | if m.invoked_as_script?
104 | load Gem.bin_path("bundler", "bundle")
105 | end
106 |
--------------------------------------------------------------------------------
/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 |
9 | # Uncomment the line if you want fastlane to automatically update itself
10 | # update_fastlane
11 |
12 | default_platform(:ios)
13 |
14 | platform :ios do
15 |
16 | desc "Generate new localized screenshots"
17 | lane :screenshots do
18 | capture_screenshots(scheme: "SnaptakeUITests")
19 | end
20 |
21 | desc "Generate new localized videos"
22 | lane :videos do |options|
23 |
24 | ### RECORDING VIDEOS
25 |
26 | # Delete all existing videos
27 | mp4_file_paths = Find.find('screenshots').select { |p| /.*\.mp4$/ =~ p}
28 | for mp4_file_path in mp4_file_paths
29 | File.delete(mp4_file_path)
30 | end
31 |
32 | # Ensure that caching folder for screenshots and recording flags exists
33 | Dir.mkdir(File.expand_path('~/Library/Caches/tools.fastlane/screenshots')) unless Dir.exist?(File.expand_path('~/Library/Caches/tools.fastlane/screenshots'))
34 |
35 | # Setup listeners for starting and ending recording
36 | fastlane_require 'listen'
37 | path = nil
38 | process = nil
39 | trimming_time_dictionary = {}
40 | recordingListener = Listen.to(File.expand_path('~/Library/Caches/tools.fastlane/screenshots'), only: /\.txt$/) do |modified, added, removed|
41 | if (!added.empty?) && File.basename(added.first) == 'recordingFlag.txt'
42 | recording_flag_path = added.first
43 | path = File.read(recording_flag_path)
44 | process = IO.popen("xcrun simctl io booted recordVideo '#{path}'") # Start recording of current simulator to path determined in recordingFlag.txt
45 | end
46 | if (!removed.empty?) && File.basename(removed.first) == 'recordingFlag.txt'
47 | pid = process.pid
48 | Process.kill("INT", pid) # Stop recording by killing process with id pid
49 | trimming_flag_path = File.expand_path('~/Library/Caches/tools.fastlane/screenshots/trimmingFlag.txt')
50 | trimming_time = File.read(trimming_flag_path)
51 | trimming_time_dictionary[path] = trimming_time # Storing trimming time determined in trimmingFlag.txt for recorded video (necessary due to initial black simulator screen after starting recording)
52 | end
53 | end
54 |
55 | # Build SnaptakeUITests and Snaptake and run UITests
56 | recordingListener.start
57 | sh("cd .. && fastlane snapshot --concurrent_simulators false && cd fastlane")
58 | recordingListener.stop
59 |
60 | ### EDIT VIDEOS
61 |
62 | sleep(3)
63 |
64 | # Trim videos and reencode
65 | mp4_file_paths = Find.find('screenshots').select { |p| /.*\.mp4$/ =~ p}
66 | for mp4_file_path in mp4_file_paths
67 |
68 | trimmed_path = mp4_file_path.chomp('.mp4') + '-trimmed.mp4'
69 | trimming_time = trimming_time_dictionary[mp4_file_path]
70 | sh("ffmpeg -ss '#{trimming_time}' -i '#{mp4_file_path}' -c:v copy -r 30 '#{trimmed_path}'") # Trimming the Beginning of the Videos
71 | File.delete(mp4_file_path)
72 |
73 | final_path = trimmed_path.chomp('-trimmed.mp4') + '-final.mp4'
74 | sh("ffmpeg -i '#{trimmed_path}' -ar 44100 -ab 256k -r 30 -crf 22 -profile:v main -pix_fmt yuv420p -y -max_muxing_queue_size 1000 '#{final_path}'")
75 | File.delete(trimmed_path)
76 | end
77 |
78 | end
79 |
80 | end
81 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/xcshareddata/xcschemes/SnaptakeUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
74 |
80 |
81 |
82 |
83 |
85 |
86 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/Snaptake/Base.lproj/Main.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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/xcuserdata/stephanlerner.xcuserdatad/xcschemes/Snaptake.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.0)
5 | addressable (2.5.2)
6 | public_suffix (>= 2.0.2, < 4.0)
7 | atomos (0.1.2)
8 | babosa (1.0.2)
9 | claide (1.0.2)
10 | colored (1.2)
11 | colored2 (3.1.2)
12 | commander-fastlane (4.4.6)
13 | highline (~> 1.7.2)
14 | declarative (0.0.10)
15 | declarative-option (0.1.0)
16 | domain_name (0.5.20170404)
17 | unf (>= 0.0.5, < 1.0.0)
18 | dotenv (2.2.2)
19 | emoji_regex (0.1.1)
20 | excon (0.62.0)
21 | faraday (0.14.0)
22 | multipart-post (>= 1.2, < 3)
23 | faraday-cookie_jar (0.0.6)
24 | faraday (>= 0.7.4)
25 | http-cookie (~> 1.0.0)
26 | faraday_middleware (0.12.2)
27 | faraday (>= 0.7.4, < 1.0)
28 | fastimage (2.1.1)
29 | fastlane (2.91.0)
30 | CFPropertyList (>= 2.3, < 4.0.0)
31 | addressable (>= 2.3, < 3.0.0)
32 | babosa (>= 1.0.2, < 2.0.0)
33 | bundler (>= 1.12.0, < 2.0.0)
34 | colored
35 | commander-fastlane (>= 4.4.6, < 5.0.0)
36 | dotenv (>= 2.1.1, < 3.0.0)
37 | emoji_regex (~> 0.1)
38 | excon (>= 0.45.0, < 1.0.0)
39 | faraday (~> 0.9)
40 | faraday-cookie_jar (~> 0.0.6)
41 | faraday_middleware (~> 0.9)
42 | fastimage (>= 2.1.0, < 3.0.0)
43 | gh_inspector (>= 1.1.2, < 2.0.0)
44 | google-api-client (>= 0.13.1, < 0.14.0)
45 | highline (>= 1.7.2, < 2.0.0)
46 | json (< 3.0.0)
47 | mini_magick (~> 4.5.1)
48 | multi_json
49 | multi_xml (~> 0.5)
50 | multipart-post (~> 2.0.0)
51 | plist (>= 3.1.0, < 4.0.0)
52 | public_suffix (~> 2.0.0)
53 | rubyzip (>= 1.1.0, < 2.0.0)
54 | security (= 0.1.3)
55 | simctl (~> 1.6.3)
56 | slack-notifier (>= 2.0.0, < 3.0.0)
57 | terminal-notifier (>= 1.6.2, < 2.0.0)
58 | terminal-table (>= 1.4.5, < 2.0.0)
59 | tty-screen (>= 0.6.3, < 1.0.0)
60 | tty-spinner (>= 0.8.0, < 1.0.0)
61 | word_wrap (~> 1.0.0)
62 | xcodeproj (>= 1.5.7, < 2.0.0)
63 | xcpretty (>= 0.2.4, < 1.0.0)
64 | xcpretty-travis-formatter (>= 0.0.3)
65 | ffi (1.9.18)
66 | gh_inspector (1.1.3)
67 | google-api-client (0.13.6)
68 | addressable (~> 2.5, >= 2.5.1)
69 | googleauth (~> 0.5)
70 | httpclient (>= 2.8.1, < 3.0)
71 | mime-types (~> 3.0)
72 | representable (~> 3.0)
73 | retriable (>= 2.0, < 4.0)
74 | googleauth (0.6.2)
75 | faraday (~> 0.12)
76 | jwt (>= 1.4, < 3.0)
77 | logging (~> 2.0)
78 | memoist (~> 0.12)
79 | multi_json (~> 1.11)
80 | os (~> 0.9)
81 | signet (~> 0.7)
82 | highline (1.7.10)
83 | http-cookie (1.0.3)
84 | domain_name (~> 0.5)
85 | httpclient (2.8.3)
86 | json (2.1.0)
87 | jwt (2.1.0)
88 | listen (3.1.5)
89 | rb-fsevent (~> 0.9, >= 0.9.4)
90 | rb-inotify (~> 0.9, >= 0.9.7)
91 | ruby_dep (~> 1.2)
92 | little-plugger (1.1.4)
93 | logging (2.2.2)
94 | little-plugger (~> 1.1)
95 | multi_json (~> 1.10)
96 | memoist (0.16.0)
97 | mime-types (3.1)
98 | mime-types-data (~> 3.2015)
99 | mime-types-data (3.2016.0521)
100 | mini_magick (4.5.1)
101 | multi_json (1.13.1)
102 | multi_xml (0.6.0)
103 | multipart-post (2.0.0)
104 | nanaimo (0.2.5)
105 | naturally (2.1.0)
106 | os (0.9.6)
107 | plist (3.4.0)
108 | public_suffix (2.0.5)
109 | rb-fsevent (0.10.2)
110 | rb-inotify (0.9.10)
111 | ffi (>= 0.5.0, < 2)
112 | representable (3.0.4)
113 | declarative (< 0.1.0)
114 | declarative-option (< 0.2.0)
115 | uber (< 0.2.0)
116 | retriable (3.1.1)
117 | rouge (2.0.7)
118 | ruby_dep (1.5.0)
119 | rubyzip (1.2.1)
120 | security (0.1.3)
121 | signet (0.8.1)
122 | addressable (~> 2.3)
123 | faraday (~> 0.9)
124 | jwt (>= 1.5, < 3.0)
125 | multi_json (~> 1.10)
126 | simctl (1.6.3)
127 | CFPropertyList
128 | naturally
129 | slack-notifier (2.3.2)
130 | terminal-notifier (1.8.0)
131 | terminal-table (1.8.0)
132 | unicode-display_width (~> 1.1, >= 1.1.1)
133 | tty-cursor (0.5.0)
134 | tty-screen (0.6.4)
135 | tty-spinner (0.8.0)
136 | tty-cursor (>= 0.5.0)
137 | uber (0.1.0)
138 | unf (0.1.4)
139 | unf_ext
140 | unf_ext (0.0.7.5)
141 | unicode-display_width (1.3.0)
142 | word_wrap (1.0.0)
143 | xcodeproj (1.5.7)
144 | CFPropertyList (>= 2.3.3, < 4.0)
145 | atomos (~> 0.1.2)
146 | claide (>= 1.0.2, < 2.0)
147 | colored2 (~> 3.1)
148 | nanaimo (~> 0.2.4)
149 | xcpretty (0.2.8)
150 | rouge (~> 2.0.7)
151 | xcpretty-travis-formatter (1.0.0)
152 | xcpretty (~> 0.2, >= 0.0.7)
153 |
154 | PLATFORMS
155 | ruby
156 |
157 | DEPENDENCIES
158 | fastlane
159 | listen
160 |
161 | BUNDLED WITH
162 | 1.16.1
163 |
--------------------------------------------------------------------------------
/fastlane/screenshots/screenshots.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fastlane/snapshot
5 |
6 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ### 🚀 Give My Budgeting App a Try!
4 |
5 | Try out my budgeting app! 🙂 → [monee-app.com](https://monee-app.com)
6 |
7 | Just like all the content here on the site, it’s completely **free**—so I’d really appreciate a rating and a review!
8 |
9 | ---
10 |
11 | # Snaptake
12 |
13 | There are already some tries (here and here ) to automate creating videos with fastlane snapshot. Anyway Felix definitely does not want to have an http server in his code base :)
14 |
15 |
16 |
17 |
18 | The following step by step guide shows an alternative way of solution. The outline corresponds to the commit history.
19 |
20 |
21 |
22 |
23 | ## Prerequisites
24 |
25 | - Fastlane for recording videos
26 | - FFmpeg for editing videos
27 |
28 | ## Creating project
29 |
30 | Just create a single view application in XCode without any tests.
31 |
32 |
33 |
34 |
35 | ## Setting up fastlane snapshot
36 |
37 | ```cd``` to your project folder and run ```fastlane init```. When fastlane is asking what you would like to use it for press "1".
38 |
39 |
40 |
41 |
42 | Do exactly what fastlane is telling you afterwards. After finishing the instructions open the newly created Snapfile in your projects fastlane folder. Uncomment at least one device and one language. Make sure ``` snapshot("0Launch") ``` is called in one of your UITests. Run ``` fastlane snapshot ``` in your project folder to verify everything is working fine.
43 |
44 | ## Setting up storyboard
45 |
46 | To test the later added video recording feature, we need something to record. Therefore just add a button to your first ViewController and add a second ViewController with a distinguishable background. Push the second ViewController, when the button is clicked. Also set the buttons accessibility identifier to ```"button"```.
47 |
48 |
49 |
50 |
51 | ## Setting up UITests
52 |
53 | Now that your storyboard is set up, let's add video related code to the SnapshotHelper file. To keep the original snapshot logic, add the following two functions to the SnapshotHelper file scope.
54 |
55 | ```swift
56 | func snaptake(_ name: String, waitForLoadingIndicator: Bool, plot: ()->()) {
57 | if waitForLoadingIndicator {
58 | Snapshot.snaptake(name, plot: plot)
59 | } else {
60 | Snapshot.snaptake(name, timeWaitingForIdle: 0, plot: plot)
61 | }
62 | }
63 | ```
64 |
65 | ```swift
66 | /// - Parameters:
67 | /// - name: The name of the snaptake
68 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
69 | /// - plot: Plot which should be recorded.
70 | func snaptake(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20, plot: ()->()) {
71 | Snapshot.snaptake(name, timeWaitingForIdle: timeout, plot: plot)
72 | }
73 | ```
74 | These two functions are pretty similar to the already existing snapshot functions. The only difference lies in the additional argument ```plot: ()->()```, which is a closure with no parameters and return values. ```plot``` contains all the interface interactions you want to record. You will see how to use it later.
75 |
76 | Within your Snapshot class add the actual recording logic. ```snaptake``` takes ```plot``` as an argument and successively calls ```snaptakeStart()```, ```snaptakeSetTrimmingFlag()```, ```plot()``` and ```snaptakeStop()```.
77 |
78 | ```swift
79 | open class func snaptake(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20, plot: ()->()) {
80 |
81 | guard let recordingFlagPath = snaptakeStart(name, timeWaitingForIdle: timeout) else { return }
82 |
83 | snaptakeSetTrimmingFlag()
84 |
85 | plot()
86 |
87 | snaptakeStop(recordingFlagPath)
88 | }
89 | ```
90 |
91 | Within ```snaptakeStart``` a recordingFlag is saved to your hard drive. This recordingFlag contains the path of the later recorded video. The saving of this recordingFlag is watched outside of XCode to start the actual recording process. You will see how this works later.
92 |
93 | ```swift
94 | class func snaptakeStart(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) -> URL? {
95 | if timeout > 0 {
96 | waitForLoadingIndicatorToDisappear(within: timeout)
97 | }
98 |
99 | print("snaptake: \(name)")
100 |
101 | sleep(1) // Waiting for the animation to be finished (kind of)
102 |
103 | #if os(OSX)
104 | XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
105 | #else
106 | guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return nil }
107 |
108 | let path = "screenshots/\(locale)/\(simulator)-\(name).mp4"
109 | let recordingFlagPath = screenshotsDir.appendingPathComponent("recordingFlag.txt")
110 |
111 | do {
112 | try path.write(to: recordingFlagPath, atomically: false, encoding: String.Encoding.utf8)
113 | } catch let error {
114 | print("Problem setting recording flag: \(recordingFlagPath)")
115 | print(error)
116 | }
117 | #endif
118 | return recordingFlagPath
119 | }
120 | ```
121 |
122 | There is a pretty annoying bug, when recording videos via console: The first few frames appear black until somethings happens within your application. That's why we are going to rotate the device and save related duration in ```snaptakeSetTrimmingFlag```. Later we will trim the recorded video accordingly.
123 |
124 | ```swift
125 | class func snaptakeSetTrimmingFlag() {
126 |
127 | let start = Date()
128 | sleep(2)
129 | XCUIDevice.shared.orientation = .landscapeLeft
130 | sleep(2)
131 | XCUIDevice.shared.orientation = .portrait
132 | let trimmingTime = -start.timeIntervalSinceNow - 2
133 |
134 | let hours = Int(trimmingTime)/3600
135 | let minutes = (Int(trimmingTime)/60)%60
136 | let seconds = Int(trimmingTime)%60
137 | let milliseconds = Int((trimmingTime - Double(Int(trimmingTime))) * 1000)
138 | let trimmingTimeString = String(format:"%02i:%02i:%02i.%03i", hours, minutes, seconds, milliseconds)
139 |
140 | #if os(OSX)
141 | XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
142 | #else
143 | guard let screenshotsDir = screenshotsDirectory else { return }
144 |
145 | let trimmingFlagPath = screenshotsDir.appendingPathComponent("trimmingFlag.txt")
146 |
147 | do {
148 | try trimmingTimeString.write(to: trimmingFlagPath, atomically: false, encoding: String.Encoding.utf8)
149 | } catch let error {
150 | print("Problem setting recording flag: \(trimmingFlagPath)")
151 | print(error)
152 | }
153 |
154 | #endif
155 | }
156 | ```
157 |
158 | After we called ```plot``` in ```snaptake``` we finally are going to stop recording in ```snaptakeStop```. We are doing so by removing the ```recordingFlag``` we added earlier in ```snaptakeStart```.
159 |
160 | ```swift
161 | class func snaptakeStop(_ recordingFlagPath: URL) {
162 | let fileManager = FileManager.default
163 |
164 | do {
165 | try fileManager.removeItem(at: recordingFlagPath)
166 | } catch let error {
167 | print("Problem removing recording flag: \(recordingFlagPath)")
168 | print(error)
169 | }
170 | }
171 | ```
172 |
173 | Finally add the following test function within SnaptakeUITests file. The function contains our plot where our button is simply tapped.
174 |
175 | ```swift
176 | func testExample() {
177 | snaptake("testExample") {
178 | XCUIApplication().buttons["button"].tap()
179 | }
180 | }
181 | ```
182 |
183 | ## Setting up fastfile, gemfile and snapfile
184 |
185 | After your UITests are fully set up we need to add related logic outside of XCode. Within your Gemfile in your fastlane folder add ```gem "listen"```. Within your Snapfile remove ```output_directory("./screenshots")```. Now we are ready to create a videos lane in your Fastfile. The videos lane is more or less self-explaining. The most relevant part is the ```recordingListener```. Within its handlers the video reording process is started and stopped, when the recordingFlag is added or removed. When recording is stopped, the trimming time for the resulting video is read from our trimmingFlag and stored in ```trimming_time_dictionary```. ```sh("cd .. && fastlane snapshot --concurrent_simulators false && cd fastlane")``` builds Snaptake and runs SnaptakeUITests, so our ```recordingListener``` could actually be triggered. After recording any videos, they are trimmed and reencoded.
186 |
187 | ```ruby
188 | desc "Generate new localized videos"
189 | lane :videos do |options|
190 |
191 | ### RECORDING VIDEOS
192 |
193 | # Delete all existing videos
194 | mp4_file_paths = Find.find('screenshots').select { |p| /.*\.mp4$/ =~ p}
195 | for mp4_file_path in mp4_file_paths
196 | File.delete(mp4_file_path)
197 | end
198 |
199 | # Ensure that caching folder for screenshots and recording flags exists
200 | Dir.mkdir(File.expand_path('~/Library/Caches/tools.fastlane/screenshots')) unless Dir.exist?(File.expand_path('~/Library/Caches/tools.fastlane/screenshots'))
201 |
202 | # Setup listeners for starting and ending recording
203 | fastlane_require 'listen'
204 | path = nil
205 | process = nil
206 | trimming_time_dictionary = {}
207 | recordingListener = Listen.to(File.expand_path('~/Library/Caches/tools.fastlane/screenshots'), only: /\.txt$/) do |modified, added, removed|
208 | if (!added.empty?) && File.basename(added.first) == 'recordingFlag.txt'
209 | recording_flag_path = added.first
210 | path = File.read(recording_flag_path)
211 | process = IO.popen("xcrun simctl io booted recordVideo '#{path}'") # Start recording of current simulator to path determined in recordingFlag.txt
212 | end
213 | if (!removed.empty?) && File.basename(removed.first) == 'recordingFlag.txt'
214 | pid = process.pid
215 | Process.kill("INT", pid) # Stop recording by killing process with id pid
216 | trimming_flag_path = File.expand_path('~/Library/Caches/tools.fastlane/screenshots/trimmingFlag.txt')
217 | trimming_time = File.read(trimming_flag_path)
218 | trimming_time_dictionary[path] = trimming_time # Storing trimming time determined in trimmingFlag.txt for recorded video (necessary due to initial black simulator screen after starting recording)
219 | end
220 | end
221 |
222 | # Build SnaptakeUITests and Snaptake and run UITests
223 | recordingListener.start
224 | sh("cd .. && fastlane snapshot --concurrent_simulators false && cd fastlane")
225 | recordingListener.stop
226 |
227 | ### EDIT VIDEOS
228 |
229 | sleep(3)
230 |
231 | # Trim videos and reencode
232 | mp4_file_paths = Find.find('screenshots').select { |p| /.*\.mp4$/ =~ p}
233 | for mp4_file_path in mp4_file_paths
234 |
235 | trimmed_path = mp4_file_path.chomp('.mp4') + '-trimmed.mp4'
236 | trimming_time = trimming_time_dictionary[mp4_file_path]
237 | sh("ffmpeg -ss '#{trimming_time}' -i '#{mp4_file_path}' -c:v copy -r 30 '#{trimmed_path}'") # Trimming the Beginning of the Videos
238 | File.delete(mp4_file_path)
239 |
240 | final_path = trimmed_path.chomp('-trimmed.mp4') + '-final.mp4'
241 | sh("ffmpeg -i '#{trimmed_path}' -ar 44100 -ab 256k -r 30 -crf 22 -profile:v main -pix_fmt yuv420p -y -max_muxing_queue_size 1000 '#{final_path}'")
242 | File.delete(trimmed_path)
243 | end
244 | end
245 | ```
246 |
247 | ## Running videos lane
248 |
249 | By calling ```fastlane videos``` we are creating our test video:
250 |
251 |
252 |
253 |
254 |
255 | ## Call to action
256 |
257 | You want to know what is possible with this procedure?
258 |
259 | **Checkout Bonprix with your iPhone in the (e.g. German) Appstore!**
260 |
261 | You want to solve similarly exciting technical questions?
262 |
263 | **Join us at apploft !**
264 |
--------------------------------------------------------------------------------
/fastlane/SnapshotHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotHelper.swift
3 | // Example
4 | //
5 | // Created by Felix Krause on 10/8/15.
6 | // Copyright © 2015 Felix Krause. All rights reserved.
7 | //
8 |
9 | // -----------------------------------------------------
10 | // IMPORTANT: When modifying this file, make sure to
11 | // increment the version number at the very
12 | // bottom of the file to notify users about
13 | // the new SnapshotHelper.swift
14 | // -----------------------------------------------------
15 |
16 | import Foundation
17 | import XCTest
18 |
19 | var deviceLanguage = ""
20 | var locale = ""
21 |
22 | func setupSnapshot(_ app: XCUIApplication) {
23 | Snapshot.setupSnapshot(app)
24 | }
25 |
26 | func snapshot(_ name: String, waitForLoadingIndicator: Bool) {
27 | if waitForLoadingIndicator {
28 | Snapshot.snapshot(name)
29 | } else {
30 | Snapshot.snapshot(name, timeWaitingForIdle: 0)
31 | }
32 | }
33 |
34 | func snaptake(_ name: String, waitForLoadingIndicator: Bool, plot: ()->()) {
35 | if waitForLoadingIndicator {
36 | Snapshot.snaptake(name, plot: plot)
37 | } else {
38 | Snapshot.snaptake(name, timeWaitingForIdle: 0, plot: plot)
39 | }
40 | }
41 |
42 | /// - Parameters:
43 | /// - name: The name of the snapshot
44 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
45 | func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
46 | Snapshot.snapshot(name, timeWaitingForIdle: timeout)
47 | }
48 |
49 | /// - Parameters:
50 | /// - name: The name of the snaptake
51 | /// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
52 | /// - plot: Plot which should be recorded.
53 | func snaptake(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20, plot: ()->()) {
54 | Snapshot.snaptake(name, timeWaitingForIdle: timeout, plot: plot)
55 | }
56 |
57 | enum SnapshotError: Error, CustomDebugStringConvertible {
58 | case cannotDetectUser
59 | case cannotFindHomeDirectory
60 | case cannotFindSimulatorHomeDirectory
61 | case cannotAccessSimulatorHomeDirectory(String)
62 | case cannotRunOnPhysicalDevice
63 |
64 | var debugDescription: String {
65 | switch self {
66 | case .cannotDetectUser:
67 | return "Couldn't find Snapshot configuration files - can't detect current user "
68 | case .cannotFindHomeDirectory:
69 | return "Couldn't find Snapshot configuration files - can't detect `Users` dir"
70 | case .cannotFindSimulatorHomeDirectory:
71 | return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
72 | case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
73 | return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
74 | case .cannotRunOnPhysicalDevice:
75 | return "Can't use Snapshot on a physical device."
76 | }
77 | }
78 | }
79 |
80 | @objcMembers
81 | open class Snapshot: NSObject {
82 | static var app: XCUIApplication?
83 | static var cacheDirectory: URL?
84 | static var screenshotsDirectory: URL? {
85 | return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true)
86 | }
87 |
88 | open class func setupSnapshot(_ app: XCUIApplication) {
89 |
90 | Snapshot.app = app
91 |
92 | do {
93 | let cacheDir = try pathPrefix()
94 | Snapshot.cacheDirectory = cacheDir
95 | setLanguage(app)
96 | setLocale(app)
97 | setLaunchArguments(app)
98 | } catch let error {
99 | print(error)
100 | }
101 | }
102 |
103 | class func setLanguage(_ app: XCUIApplication) {
104 | guard let cacheDirectory = self.cacheDirectory else {
105 | print("CacheDirectory is not set - probably running on a physical device?")
106 | return
107 | }
108 |
109 | let path = cacheDirectory.appendingPathComponent("language.txt")
110 |
111 | do {
112 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines
113 | deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
114 | app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"]
115 | } catch {
116 | print("Couldn't detect/set language...")
117 | }
118 | }
119 |
120 | class func setLocale(_ app: XCUIApplication) {
121 | guard let cacheDirectory = self.cacheDirectory else {
122 | print("CacheDirectory is not set - probably running on a physical device?")
123 | return
124 | }
125 |
126 | let path = cacheDirectory.appendingPathComponent("locale.txt")
127 |
128 | do {
129 | let trimCharacterSet = CharacterSet.whitespacesAndNewlines
130 | locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
131 | } catch {
132 | print("Couldn't detect/set locale...")
133 | }
134 | if locale.isEmpty {
135 | locale = Locale(identifier: deviceLanguage).identifier
136 | }
137 | app.launchArguments += ["-AppleLocale", "\"\(locale)\""]
138 | }
139 |
140 | class func setLaunchArguments(_ app: XCUIApplication) {
141 | guard let cacheDirectory = self.cacheDirectory else {
142 | print("CacheDirectory is not set - probably running on a physical device?")
143 | return
144 | }
145 |
146 | let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
147 | app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
148 |
149 | do {
150 | let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
151 | let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
152 | let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
153 | let results = matches.map { result -> String in
154 | (launchArguments as NSString).substring(with: result.range)
155 | }
156 | app.launchArguments += results
157 | } catch {
158 | print("Couldn't detect/set launch_arguments...")
159 | }
160 | }
161 |
162 | open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
163 | if timeout > 0 {
164 | waitForLoadingIndicatorToDisappear(within: timeout)
165 | }
166 |
167 | print("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work
168 |
169 | sleep(1) // Waiting for the animation to be finished (kind of)
170 |
171 | #if os(OSX)
172 | XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
173 | #else
174 |
175 | guard let app = self.app else {
176 | print("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
177 | return
178 | }
179 |
180 | let screenshot = app.windows.firstMatch.screenshot()
181 | guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
182 | let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
183 | do {
184 | try screenshot.pngRepresentation.write(to: path)
185 | } catch let error {
186 | print("Problem writing screenshot: \(name) to \(path)")
187 | print(error)
188 | }
189 | #endif
190 | }
191 |
192 | open class func snaptake(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20, plot: ()->()) {
193 |
194 | guard let recordingFlagPath = snaptakeStart(name, timeWaitingForIdle: timeout) else { return }
195 |
196 | snaptakeSetTrimmingFlag()
197 |
198 | plot()
199 |
200 | snaptakeStop(recordingFlagPath)
201 |
202 | }
203 |
204 | class func snaptakeStart(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) -> URL? {
205 | if timeout > 0 {
206 | waitForLoadingIndicatorToDisappear(within: timeout)
207 | }
208 |
209 | print("snaptake: \(name)")
210 |
211 | sleep(1) // Waiting for the animation to be finished (kind of)
212 |
213 | #if os(OSX)
214 | XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
215 | #else
216 | guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return nil }
217 |
218 | let path = "screenshots/\(locale)/\(simulator)-\(name).mp4"
219 | let recordingFlagPath = screenshotsDir.appendingPathComponent("recordingFlag.txt")
220 |
221 | do {
222 | try path.write(to: recordingFlagPath, atomically: false, encoding: String.Encoding.utf8)
223 | } catch let error {
224 | print("Problem setting recording flag: \(recordingFlagPath)")
225 | print(error)
226 | }
227 | #endif
228 | return recordingFlagPath
229 | }
230 |
231 | class func snaptakeSetTrimmingFlag() {
232 |
233 | let start = Date()
234 | sleep(2)
235 | XCUIDevice.shared.orientation = .landscapeLeft
236 | sleep(2)
237 | XCUIDevice.shared.orientation = .portrait
238 | let trimmingTime = -start.timeIntervalSinceNow - 2
239 |
240 | let hours = Int(trimmingTime)/3600
241 | let minutes = (Int(trimmingTime)/60)%60
242 | let seconds = Int(trimmingTime)%60
243 | let milliseconds = Int((trimmingTime - Double(Int(trimmingTime))) * 1000)
244 | let trimmingTimeString = String(format:"%02i:%02i:%02i.%03i", hours, minutes, seconds, milliseconds)
245 |
246 | #if os(OSX)
247 | XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
248 | #else
249 | guard let screenshotsDir = screenshotsDirectory else { return }
250 |
251 | let trimmingFlagPath = screenshotsDir.appendingPathComponent("trimmingFlag.txt")
252 |
253 | do {
254 | try trimmingTimeString.write(to: trimmingFlagPath, atomically: false, encoding: String.Encoding.utf8)
255 | } catch let error {
256 | print("Problem setting recording flag: \(trimmingFlagPath)")
257 | print(error)
258 | }
259 |
260 | #endif
261 | }
262 |
263 | class func snaptakeStop(_ recordingFlagPath: URL) {
264 | let fileManager = FileManager.default
265 |
266 | do {
267 | try fileManager.removeItem(at: recordingFlagPath)
268 | } catch let error {
269 | print("Problem removing recording flag: \(recordingFlagPath)")
270 | print(error)
271 | }
272 | }
273 |
274 | class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
275 | #if os(tvOS)
276 | return
277 | #endif
278 |
279 | let networkLoadingIndicator = XCUIApplication().otherElements.deviceStatusBars.networkLoadingIndicators.element
280 | let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
281 | _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
282 | }
283 |
284 | class func pathPrefix() throws -> URL? {
285 | let homeDir: URL
286 | // on OSX config is stored in /Users//Library
287 | // and on iOS/tvOS/WatchOS it's in simulator's home dir
288 | #if os(OSX)
289 | guard let user = ProcessInfo().environment["USER"] else {
290 | throw SnapshotError.cannotDetectUser
291 | }
292 |
293 | guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
294 | throw SnapshotError.cannotFindHomeDirectory
295 | }
296 |
297 | homeDir = usersDir.appendingPathComponent(user)
298 | #else
299 | #if arch(i386) || arch(x86_64)
300 | guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
301 | throw SnapshotError.cannotFindSimulatorHomeDirectory
302 | }
303 | guard let homeDirUrl = URL(string: simulatorHostHome) else {
304 | throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
305 | }
306 | homeDir = URL(fileURLWithPath: homeDirUrl.path)
307 | #else
308 | throw SnapshotError.cannotRunOnPhysicalDevice
309 | #endif
310 | #endif
311 | return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
312 | }
313 | }
314 |
315 | private extension XCUIElementAttributes {
316 | var isNetworkLoadingIndicator: Bool {
317 | if hasWhiteListedIdentifier { return false }
318 |
319 | let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
320 | let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)
321 |
322 | return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
323 | }
324 |
325 | var hasWhiteListedIdentifier: Bool {
326 | let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
327 |
328 | return whiteListedIdentifiers.contains(identifier)
329 | }
330 |
331 | func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
332 | if elementType == .statusBar { return true }
333 | guard frame.origin == .zero else { return false }
334 |
335 | let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
336 | let newStatusBarSize = CGSize(width: deviceWidth, height: 44)
337 |
338 | return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
339 | }
340 | }
341 |
342 | private extension XCUIElementQuery {
343 | var networkLoadingIndicators: XCUIElementQuery {
344 | let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
345 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
346 |
347 | return element.isNetworkLoadingIndicator
348 | }
349 |
350 | return self.containing(isNetworkLoadingIndicator)
351 | }
352 |
353 | var deviceStatusBars: XCUIElementQuery {
354 | let deviceWidth = XCUIApplication().frame.width
355 |
356 | let isStatusBar = NSPredicate { (evaluatedObject, _) in
357 | guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
358 |
359 | return element.isStatusBar(deviceWidth)
360 | }
361 |
362 | return self.containing(isStatusBar)
363 | }
364 | }
365 |
366 | private extension CGFloat {
367 | func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
368 | return numberA...numberB ~= self
369 | }
370 | }
371 |
372 | // Please don't remove the lines below
373 | // They are used to detect outdated configuration files
374 | // SnapshotHelperVersion [1.10]
375 |
--------------------------------------------------------------------------------
/Snaptake.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8D8A726420824F840021D344 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8A726320824F840021D344 /* AppDelegate.swift */; };
11 | 8D8A726620824F840021D344 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8A726520824F840021D344 /* ViewController.swift */; };
12 | 8D8A726920824F840021D344 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D8A726720824F840021D344 /* Main.storyboard */; };
13 | 8D8A726B20824F850021D344 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8D8A726A20824F850021D344 /* Assets.xcassets */; };
14 | 8D8A726E20824F850021D344 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D8A726C20824F850021D344 /* LaunchScreen.storyboard */; };
15 | 8D8A728C208252340021D344 /* SnaptakeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8A728B208252340021D344 /* SnaptakeUITests.swift */; };
16 | 8D8A7294208252440021D344 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8A7293208252440021D344 /* SnapshotHelper.swift */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXContainerItemProxy section */
20 | 8D8A728E208252340021D344 /* PBXContainerItemProxy */ = {
21 | isa = PBXContainerItemProxy;
22 | containerPortal = 8D8A725820824F840021D344 /* Project object */;
23 | proxyType = 1;
24 | remoteGlobalIDString = 8D8A725F20824F840021D344;
25 | remoteInfo = Snaptake;
26 | };
27 | /* End PBXContainerItemProxy section */
28 |
29 | /* Begin PBXFileReference section */
30 | 8D8A726020824F840021D344 /* Snaptake.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Snaptake.app; sourceTree = BUILT_PRODUCTS_DIR; };
31 | 8D8A726320824F840021D344 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
32 | 8D8A726520824F840021D344 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
33 | 8D8A726820824F840021D344 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
34 | 8D8A726A20824F850021D344 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
35 | 8D8A726D20824F850021D344 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
36 | 8D8A726F20824F850021D344 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
37 | 8D8A7289208252340021D344 /* SnaptakeUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnaptakeUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
38 | 8D8A728B208252340021D344 /* SnaptakeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnaptakeUITests.swift; sourceTree = ""; };
39 | 8D8A728D208252340021D344 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
40 | 8D8A7293208252440021D344 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | 8D8A725D20824F840021D344 /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | 8D8A7286208252340021D344 /* Frameworks */ = {
52 | isa = PBXFrameworksBuildPhase;
53 | buildActionMask = 2147483647;
54 | files = (
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | /* End PBXFrameworksBuildPhase section */
59 |
60 | /* Begin PBXGroup section */
61 | 8D8A725720824F840021D344 = {
62 | isa = PBXGroup;
63 | children = (
64 | 8D8A726220824F840021D344 /* Snaptake */,
65 | 8D8A728A208252340021D344 /* SnaptakeUITests */,
66 | 8D8A726120824F840021D344 /* Products */,
67 | );
68 | sourceTree = "";
69 | };
70 | 8D8A726120824F840021D344 /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 8D8A726020824F840021D344 /* Snaptake.app */,
74 | 8D8A7289208252340021D344 /* SnaptakeUITests.xctest */,
75 | );
76 | name = Products;
77 | sourceTree = "";
78 | };
79 | 8D8A726220824F840021D344 /* Snaptake */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 8D8A726320824F840021D344 /* AppDelegate.swift */,
83 | 8D8A726520824F840021D344 /* ViewController.swift */,
84 | 8D8A726720824F840021D344 /* Main.storyboard */,
85 | 8D8A726A20824F850021D344 /* Assets.xcassets */,
86 | 8D8A726C20824F850021D344 /* LaunchScreen.storyboard */,
87 | 8D8A726F20824F850021D344 /* Info.plist */,
88 | );
89 | path = Snaptake;
90 | sourceTree = "";
91 | };
92 | 8D8A728A208252340021D344 /* SnaptakeUITests */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 8D8A728B208252340021D344 /* SnaptakeUITests.swift */,
96 | 8D8A7293208252440021D344 /* SnapshotHelper.swift */,
97 | 8D8A728D208252340021D344 /* Info.plist */,
98 | );
99 | path = SnaptakeUITests;
100 | sourceTree = "";
101 | };
102 | /* End PBXGroup section */
103 |
104 | /* Begin PBXNativeTarget section */
105 | 8D8A725F20824F840021D344 /* Snaptake */ = {
106 | isa = PBXNativeTarget;
107 | buildConfigurationList = 8D8A727220824F850021D344 /* Build configuration list for PBXNativeTarget "Snaptake" */;
108 | buildPhases = (
109 | 8D8A725C20824F840021D344 /* Sources */,
110 | 8D8A725D20824F840021D344 /* Frameworks */,
111 | 8D8A725E20824F840021D344 /* Resources */,
112 | );
113 | buildRules = (
114 | );
115 | dependencies = (
116 | );
117 | name = Snaptake;
118 | productName = Snaptake;
119 | productReference = 8D8A726020824F840021D344 /* Snaptake.app */;
120 | productType = "com.apple.product-type.application";
121 | };
122 | 8D8A7288208252340021D344 /* SnaptakeUITests */ = {
123 | isa = PBXNativeTarget;
124 | buildConfigurationList = 8D8A7292208252340021D344 /* Build configuration list for PBXNativeTarget "SnaptakeUITests" */;
125 | buildPhases = (
126 | 8D8A7285208252340021D344 /* Sources */,
127 | 8D8A7286208252340021D344 /* Frameworks */,
128 | 8D8A7287208252340021D344 /* Resources */,
129 | );
130 | buildRules = (
131 | );
132 | dependencies = (
133 | 8D8A728F208252340021D344 /* PBXTargetDependency */,
134 | );
135 | name = SnaptakeUITests;
136 | productName = SnaptakeUITests;
137 | productReference = 8D8A7289208252340021D344 /* SnaptakeUITests.xctest */;
138 | productType = "com.apple.product-type.bundle.ui-testing";
139 | };
140 | /* End PBXNativeTarget section */
141 |
142 | /* Begin PBXProject section */
143 | 8D8A725820824F840021D344 /* Project object */ = {
144 | isa = PBXProject;
145 | attributes = {
146 | LastSwiftUpdateCheck = 0930;
147 | LastUpgradeCheck = 0930;
148 | ORGANIZATIONNAME = "Stephan Lerner";
149 | TargetAttributes = {
150 | 8D8A725F20824F840021D344 = {
151 | CreatedOnToolsVersion = 9.3;
152 | };
153 | 8D8A7288208252340021D344 = {
154 | CreatedOnToolsVersion = 9.3;
155 | TestTargetID = 8D8A725F20824F840021D344;
156 | };
157 | };
158 | };
159 | buildConfigurationList = 8D8A725B20824F840021D344 /* Build configuration list for PBXProject "Snaptake" */;
160 | compatibilityVersion = "Xcode 9.3";
161 | developmentRegion = en;
162 | hasScannedForEncodings = 0;
163 | knownRegions = (
164 | en,
165 | Base,
166 | );
167 | mainGroup = 8D8A725720824F840021D344;
168 | productRefGroup = 8D8A726120824F840021D344 /* Products */;
169 | projectDirPath = "";
170 | projectRoot = "";
171 | targets = (
172 | 8D8A725F20824F840021D344 /* Snaptake */,
173 | 8D8A7288208252340021D344 /* SnaptakeUITests */,
174 | );
175 | };
176 | /* End PBXProject section */
177 |
178 | /* Begin PBXResourcesBuildPhase section */
179 | 8D8A725E20824F840021D344 /* Resources */ = {
180 | isa = PBXResourcesBuildPhase;
181 | buildActionMask = 2147483647;
182 | files = (
183 | 8D8A726E20824F850021D344 /* LaunchScreen.storyboard in Resources */,
184 | 8D8A726B20824F850021D344 /* Assets.xcassets in Resources */,
185 | 8D8A726920824F840021D344 /* Main.storyboard in Resources */,
186 | );
187 | runOnlyForDeploymentPostprocessing = 0;
188 | };
189 | 8D8A7287208252340021D344 /* Resources */ = {
190 | isa = PBXResourcesBuildPhase;
191 | buildActionMask = 2147483647;
192 | files = (
193 | );
194 | runOnlyForDeploymentPostprocessing = 0;
195 | };
196 | /* End PBXResourcesBuildPhase section */
197 |
198 | /* Begin PBXSourcesBuildPhase section */
199 | 8D8A725C20824F840021D344 /* Sources */ = {
200 | isa = PBXSourcesBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | 8D8A726620824F840021D344 /* ViewController.swift in Sources */,
204 | 8D8A726420824F840021D344 /* AppDelegate.swift in Sources */,
205 | );
206 | runOnlyForDeploymentPostprocessing = 0;
207 | };
208 | 8D8A7285208252340021D344 /* Sources */ = {
209 | isa = PBXSourcesBuildPhase;
210 | buildActionMask = 2147483647;
211 | files = (
212 | 8D8A728C208252340021D344 /* SnaptakeUITests.swift in Sources */,
213 | 8D8A7294208252440021D344 /* SnapshotHelper.swift in Sources */,
214 | );
215 | runOnlyForDeploymentPostprocessing = 0;
216 | };
217 | /* End PBXSourcesBuildPhase section */
218 |
219 | /* Begin PBXTargetDependency section */
220 | 8D8A728F208252340021D344 /* PBXTargetDependency */ = {
221 | isa = PBXTargetDependency;
222 | target = 8D8A725F20824F840021D344 /* Snaptake */;
223 | targetProxy = 8D8A728E208252340021D344 /* PBXContainerItemProxy */;
224 | };
225 | /* End PBXTargetDependency section */
226 |
227 | /* Begin PBXVariantGroup section */
228 | 8D8A726720824F840021D344 /* Main.storyboard */ = {
229 | isa = PBXVariantGroup;
230 | children = (
231 | 8D8A726820824F840021D344 /* Base */,
232 | );
233 | name = Main.storyboard;
234 | sourceTree = "";
235 | };
236 | 8D8A726C20824F850021D344 /* LaunchScreen.storyboard */ = {
237 | isa = PBXVariantGroup;
238 | children = (
239 | 8D8A726D20824F850021D344 /* Base */,
240 | );
241 | name = LaunchScreen.storyboard;
242 | sourceTree = "";
243 | };
244 | /* End PBXVariantGroup section */
245 |
246 | /* Begin XCBuildConfiguration section */
247 | 8D8A727020824F850021D344 /* Debug */ = {
248 | isa = XCBuildConfiguration;
249 | buildSettings = {
250 | ALWAYS_SEARCH_USER_PATHS = NO;
251 | CLANG_ANALYZER_NONNULL = YES;
252 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
254 | CLANG_CXX_LIBRARY = "libc++";
255 | CLANG_ENABLE_MODULES = YES;
256 | CLANG_ENABLE_OBJC_ARC = YES;
257 | CLANG_ENABLE_OBJC_WEAK = YES;
258 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
259 | CLANG_WARN_BOOL_CONVERSION = YES;
260 | CLANG_WARN_COMMA = YES;
261 | CLANG_WARN_CONSTANT_CONVERSION = YES;
262 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
263 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
264 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
265 | CLANG_WARN_EMPTY_BODY = YES;
266 | CLANG_WARN_ENUM_CONVERSION = YES;
267 | CLANG_WARN_INFINITE_RECURSION = YES;
268 | CLANG_WARN_INT_CONVERSION = YES;
269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
274 | CLANG_WARN_STRICT_PROTOTYPES = YES;
275 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
276 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
277 | CLANG_WARN_UNREACHABLE_CODE = YES;
278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
279 | CODE_SIGN_IDENTITY = "iPhone Developer";
280 | COPY_PHASE_STRIP = NO;
281 | DEBUG_INFORMATION_FORMAT = dwarf;
282 | ENABLE_STRICT_OBJC_MSGSEND = YES;
283 | ENABLE_TESTABILITY = YES;
284 | GCC_C_LANGUAGE_STANDARD = gnu11;
285 | GCC_DYNAMIC_NO_PIC = NO;
286 | GCC_NO_COMMON_BLOCKS = YES;
287 | GCC_OPTIMIZATION_LEVEL = 0;
288 | GCC_PREPROCESSOR_DEFINITIONS = (
289 | "DEBUG=1",
290 | "$(inherited)",
291 | );
292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
294 | GCC_WARN_UNDECLARED_SELECTOR = YES;
295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
296 | GCC_WARN_UNUSED_FUNCTION = YES;
297 | GCC_WARN_UNUSED_VARIABLE = YES;
298 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
299 | MTL_ENABLE_DEBUG_INFO = YES;
300 | ONLY_ACTIVE_ARCH = YES;
301 | SDKROOT = iphoneos;
302 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
303 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
304 | };
305 | name = Debug;
306 | };
307 | 8D8A727120824F850021D344 /* Release */ = {
308 | isa = XCBuildConfiguration;
309 | buildSettings = {
310 | ALWAYS_SEARCH_USER_PATHS = NO;
311 | CLANG_ANALYZER_NONNULL = YES;
312 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
314 | CLANG_CXX_LIBRARY = "libc++";
315 | CLANG_ENABLE_MODULES = YES;
316 | CLANG_ENABLE_OBJC_ARC = YES;
317 | CLANG_ENABLE_OBJC_WEAK = YES;
318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
319 | CLANG_WARN_BOOL_CONVERSION = YES;
320 | CLANG_WARN_COMMA = YES;
321 | CLANG_WARN_CONSTANT_CONVERSION = YES;
322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
324 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
325 | CLANG_WARN_EMPTY_BODY = YES;
326 | CLANG_WARN_ENUM_CONVERSION = YES;
327 | CLANG_WARN_INFINITE_RECURSION = YES;
328 | CLANG_WARN_INT_CONVERSION = YES;
329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
334 | CLANG_WARN_STRICT_PROTOTYPES = YES;
335 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
336 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
337 | CLANG_WARN_UNREACHABLE_CODE = YES;
338 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
339 | CODE_SIGN_IDENTITY = "iPhone Developer";
340 | COPY_PHASE_STRIP = NO;
341 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
342 | ENABLE_NS_ASSERTIONS = NO;
343 | ENABLE_STRICT_OBJC_MSGSEND = YES;
344 | GCC_C_LANGUAGE_STANDARD = gnu11;
345 | GCC_NO_COMMON_BLOCKS = YES;
346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
348 | GCC_WARN_UNDECLARED_SELECTOR = YES;
349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
350 | GCC_WARN_UNUSED_FUNCTION = YES;
351 | GCC_WARN_UNUSED_VARIABLE = YES;
352 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
353 | MTL_ENABLE_DEBUG_INFO = NO;
354 | SDKROOT = iphoneos;
355 | SWIFT_COMPILATION_MODE = wholemodule;
356 | SWIFT_OPTIMIZATION_LEVEL = "-O";
357 | VALIDATE_PRODUCT = YES;
358 | };
359 | name = Release;
360 | };
361 | 8D8A727320824F850021D344 /* Debug */ = {
362 | isa = XCBuildConfiguration;
363 | buildSettings = {
364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
365 | CODE_SIGN_STYLE = Automatic;
366 | DEVELOPMENT_TEAM = C6N3X97ZLM;
367 | INFOPLIST_FILE = Snaptake/Info.plist;
368 | LD_RUNPATH_SEARCH_PATHS = (
369 | "$(inherited)",
370 | "@executable_path/Frameworks",
371 | );
372 | PRODUCT_BUNDLE_IDENTIFIER = de.lausbert.Snaptake;
373 | PRODUCT_NAME = "$(TARGET_NAME)";
374 | SWIFT_VERSION = 4.0;
375 | TARGETED_DEVICE_FAMILY = "1,2";
376 | };
377 | name = Debug;
378 | };
379 | 8D8A727420824F850021D344 /* Release */ = {
380 | isa = XCBuildConfiguration;
381 | buildSettings = {
382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
383 | CODE_SIGN_STYLE = Automatic;
384 | DEVELOPMENT_TEAM = C6N3X97ZLM;
385 | INFOPLIST_FILE = Snaptake/Info.plist;
386 | LD_RUNPATH_SEARCH_PATHS = (
387 | "$(inherited)",
388 | "@executable_path/Frameworks",
389 | );
390 | PRODUCT_BUNDLE_IDENTIFIER = de.lausbert.Snaptake;
391 | PRODUCT_NAME = "$(TARGET_NAME)";
392 | SWIFT_VERSION = 4.0;
393 | TARGETED_DEVICE_FAMILY = "1,2";
394 | };
395 | name = Release;
396 | };
397 | 8D8A7290208252340021D344 /* Debug */ = {
398 | isa = XCBuildConfiguration;
399 | buildSettings = {
400 | CODE_SIGN_STYLE = Automatic;
401 | DEVELOPMENT_TEAM = C6N3X97ZLM;
402 | INFOPLIST_FILE = SnaptakeUITests/Info.plist;
403 | LD_RUNPATH_SEARCH_PATHS = (
404 | "$(inherited)",
405 | "@executable_path/Frameworks",
406 | "@loader_path/Frameworks",
407 | );
408 | PRODUCT_BUNDLE_IDENTIFIER = de.lausbert.SnaptakeUITests;
409 | PRODUCT_NAME = "$(TARGET_NAME)";
410 | SWIFT_VERSION = 4.0;
411 | TARGETED_DEVICE_FAMILY = "1,2";
412 | TEST_TARGET_NAME = Snaptake;
413 | };
414 | name = Debug;
415 | };
416 | 8D8A7291208252340021D344 /* Release */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | CODE_SIGN_STYLE = Automatic;
420 | DEVELOPMENT_TEAM = C6N3X97ZLM;
421 | INFOPLIST_FILE = SnaptakeUITests/Info.plist;
422 | LD_RUNPATH_SEARCH_PATHS = (
423 | "$(inherited)",
424 | "@executable_path/Frameworks",
425 | "@loader_path/Frameworks",
426 | );
427 | PRODUCT_BUNDLE_IDENTIFIER = de.lausbert.SnaptakeUITests;
428 | PRODUCT_NAME = "$(TARGET_NAME)";
429 | SWIFT_VERSION = 4.0;
430 | TARGETED_DEVICE_FAMILY = "1,2";
431 | TEST_TARGET_NAME = Snaptake;
432 | };
433 | name = Release;
434 | };
435 | /* End XCBuildConfiguration section */
436 |
437 | /* Begin XCConfigurationList section */
438 | 8D8A725B20824F840021D344 /* Build configuration list for PBXProject "Snaptake" */ = {
439 | isa = XCConfigurationList;
440 | buildConfigurations = (
441 | 8D8A727020824F850021D344 /* Debug */,
442 | 8D8A727120824F850021D344 /* Release */,
443 | );
444 | defaultConfigurationIsVisible = 0;
445 | defaultConfigurationName = Release;
446 | };
447 | 8D8A727220824F850021D344 /* Build configuration list for PBXNativeTarget "Snaptake" */ = {
448 | isa = XCConfigurationList;
449 | buildConfigurations = (
450 | 8D8A727320824F850021D344 /* Debug */,
451 | 8D8A727420824F850021D344 /* Release */,
452 | );
453 | defaultConfigurationIsVisible = 0;
454 | defaultConfigurationName = Release;
455 | };
456 | 8D8A7292208252340021D344 /* Build configuration list for PBXNativeTarget "SnaptakeUITests" */ = {
457 | isa = XCConfigurationList;
458 | buildConfigurations = (
459 | 8D8A7290208252340021D344 /* Debug */,
460 | 8D8A7291208252340021D344 /* Release */,
461 | );
462 | defaultConfigurationIsVisible = 0;
463 | defaultConfigurationName = Release;
464 | };
465 | /* End XCConfigurationList section */
466 | };
467 | rootObject = 8D8A725820824F840021D344 /* Project object */;
468 | }
469 |
--------------------------------------------------------------------------------