├── fastlane ├── Appfile ├── .env ├── README.md └── Fastfile ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── CI.yml ├── state.yml └── CODE_OF_CONDUCT.md ├── Example ├── Sources │ ├── en.lproj │ │ └── Main.strings │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ContentView.swift │ ├── Info.plist │ └── SceneDelegate.swift └── Example.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── xcshareddata │ └── xcschemes │ │ └── Example.xcscheme │ └── project.pbxproj ├── Tests ├── CheckCocoaPodsQualityIndexes.rb ├── LinuxMain.swift └── StepsTests │ ├── XCTestManifests.swift │ ├── Helpers │ └── TestsHelper.swift │ ├── Effects │ ├── ScaleXEffectTests.swift │ └── OffsetEffectTests.swift │ ├── StepTests.swift │ ├── Components │ ├── StepContainerTests.swift │ ├── StepSeparatorTests.swift │ └── StepElementTests.swift │ ├── StepsStateTests.swift │ └── StepsTests.swift ├── Assets ├── logo.png └── example.gif ├── Gemfile ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ ├── StepsTests.xcscheme │ └── Steps.xcscheme ├── .codecov.yml ├── Sources └── Steps │ ├── Helpers │ └── Helpers.swift │ ├── Step.swift │ ├── Effects │ ├── ScaleXEffect.swift │ └── OffsetEffect.swift │ ├── Config.swift │ ├── Components │ ├── Container.swift │ ├── Separator.swift │ └── Item.swift │ ├── StepsState.swift │ └── Steps.swift ├── Package.resolved ├── LICENSE ├── Steps.podspec ├── CHANGELOG.md ├── Package.swift ├── .swiftlint.yml ├── .gitignore ├── CHANGELOG_GUIDELINES.md ├── README.md ├── CONTRIBUTING.md └── Gemfile.lock /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [asam139] 2 | -------------------------------------------------------------------------------- /Example/Sources/en.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Tests/CheckCocoaPodsQualityIndexes.rb: -------------------------------------------------------------------------------- 1 | 404: Not Found 2 | -------------------------------------------------------------------------------- /Assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asam139/Steps/HEAD/Assets/logo.png -------------------------------------------------------------------------------- /Assets/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asam139/Steps/HEAD/Assets/example.gif -------------------------------------------------------------------------------- /Example/Sources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'xcpretty' 4 | gem 'xcpretty-json-formatter' 5 | 6 | gem 'fastlane' 7 | gem 'cocoapods' -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import StepsTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += StepsTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/StepsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(StepsTests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: 1811599e-cdf0-431f-bbca-002c54dc5dbb 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: 70...100 8 | 9 | status: 10 | project: true 11 | patch: true 12 | changes: true 13 | 14 | ignore: 15 | - "Tests/**/*" -------------------------------------------------------------------------------- /Tests/StepsTests/Helpers/TestsHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestsHelper.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 19/04/2020. 6 | // 7 | 8 | import Foundation 9 | import ViewInspector 10 | @testable import Steps 11 | 12 | extension Inspection: InspectionEmissary { } 13 | -------------------------------------------------------------------------------- /fastlane/.env: -------------------------------------------------------------------------------- 1 | NAME = Steps 2 | PODSPEC = Steps.podspec 3 | REPO_URL = https://github.com/asam139/Steps 4 | RELEASE_BRANCH = master 5 | SOURCES_PATH = Sources/* 6 | 7 | #SLACK_URL = https://hooks.slack.com/services/..... 8 | #SLACK_CHANNEL = '#general' -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/StepsTests/Effects/ScaleXEffectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleXEffectTests.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 13/06/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | @testable import Steps 11 | 12 | final class ScaleXEffectTests: XCTestCase { 13 | let scale: CGFloat = 0.5 14 | 15 | func testInit() { 16 | let effect = ScaleXEffect(scaleX: scale) 17 | XCTAssertEqual(effect.scaleX, scale) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Steps/Helpers/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 19/04/2020. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | // MARK: - View Inspection helper 12 | 13 | /// Helper to inspection in the tests 14 | internal final class Inspection where V: View { 15 | let notice = PassthroughSubject() 16 | var callbacks = [UInt: (V) -> Void]() 17 | 18 | func visit(_ view: V, _ line: UInt) { 19 | if let callback = callbacks.removeValue(forKey: line) { 20 | callback(view) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/StepsTests/StepTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepTests.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 19/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | @testable import Steps 11 | 12 | final class StepTests: XCTestCase { 13 | func testInitWithTitleAndImage() { 14 | let title = "Title" 15 | let image = Image("") 16 | 17 | let step = Step(title: title, image: image) 18 | XCTAssertEqual(step.title, title) 19 | XCTAssertEqual(step.image, image) 20 | } 21 | 22 | static var allTests = [ 23 | ("testInitWithTitleAndImage", testInitWithTitleAndImage) 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwifterSwiftUI", 6 | "repositoryURL": "https://github.com/asam139/SwifterSwiftUI.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "e69419dd7cdf3bb7df315978530d621d69c98b88", 10 | "version": "0.5.1" 11 | } 12 | }, 13 | { 14 | "package": "ViewInspector", 15 | "repositoryURL": "https://github.com/nalexn/ViewInspector", 16 | "state": { 17 | "branch": null, 18 | "revision": "e42529aa0b6ff57393ab52540f6006f3a709ee00", 19 | "version": "0.9.6" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Checklist 4 | 5 | 6 | - [ ] I checked the [**Contributing Guidelines**](https://github.com/asam139/Steps/blob/master/CONTRIBUTING.md) before creating this request. 7 | - [ ] New code is written in Swift 5.0. 8 | - [ ] New code support iOS 10.0+ / tvOS 9.0+ / macOS 10.10+, or use `@available` if not. 9 | - [ ] I have added tests for new features, and they passed. 10 | - [ ] I have added a [changelog](https://github.com/asam139/Steps/blob/master/CHANGELOG_GUIDELINES.md) entry describing my changes. -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwifterSwiftUI", 6 | "repositoryURL": "https://github.com/asam139/SwifterSwiftUI.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "6fc8aed7aa029cba6b6a74659eab9d7190028b20", 10 | "version": "0.5.2" 11 | } 12 | }, 13 | { 14 | "package": "ViewInspector", 15 | "repositoryURL": "https://github.com/nalexn/ViewInspector", 16 | "state": { 17 | "branch": null, 18 | "revision": "4effbd9143ab797eb60d2f32d4265c844c980946", 19 | "version": "0.9.5" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Tests/StepsTests/Components/StepContainerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContainerTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 19/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | import ViewInspector 11 | @testable import Steps 12 | 13 | final class ContainerTests: XCTestCase { 14 | let config = Config() 15 | 16 | func testContainer() { 17 | let title = "Title" 18 | 19 | let container = Container(title: title) { 20 | Text(title) 21 | } 22 | XCTAssertEqual(container.title, title) 23 | 24 | let exp = container.inspection.inspect { view in 25 | XCTAssertNoThrow(try view.vStack().vStack(0)) 26 | XCTAssertNoThrow(try view.vStack().text(1)) 27 | } 28 | ViewHosting.host(view: container.environmentObject(config)) 29 | wait(for: [exp], timeout: 0.1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Steps/Step.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Step.swift 3 | // 4 | // 5 | // Created by Saul Moreno Abril on 10/04/2020. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// Represents a new step in the component 11 | public struct Step { 12 | /// Title 13 | public var title: String? 14 | /// Image 15 | public var image: Image? 16 | 17 | /// State in which can be found a step 18 | enum State: Int, CaseIterable { 19 | /// State for uncompleted step 20 | case uncompleted 21 | 22 | /// State for the current step 23 | case current 24 | 25 | /// State for completed step 26 | case completed 27 | } 28 | 29 | /// Index 30 | var index: Int = 0 31 | 32 | /// Initializes a new step. 33 | /// 34 | /// - Parameters: 35 | /// - title: title of the step 36 | /// - config: image of the step 37 | public init(title: String? = nil, image: Image? = nil) { 38 | self.title = title 39 | self.image = image 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Saul Moreno Abril 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Steps.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Steps' 3 | s.version = '0.3.9' 4 | s.summary = 'Steps is a navigation bar that guides users through the steps of a task.' 5 | s.description = <<-DESC 6 | Steps is a navigation bar that guides users through the steps of a task. You need to use it when a given task is complicated or has a certain sequence in the series of subtasks, we can decompose it into several steps to make things easier. 7 | DESC 8 | 9 | s.homepage = 'https://github.com/asam139/Steps' 10 | s.license = { :type => 'MIT', :file => 'LICENSE' } 11 | s.author = { 'asam139' => '93sauu@gmail.com' } 12 | s.screenshot = 'https://raw.githubusercontent.com/asam139/Steps/master/Assets/logo.png' 13 | 14 | s.ios.deployment_target = '13.0' 15 | s.osx.deployment_target = '10.15' 16 | s.tvos.deployment_target = '13.0' 17 | 18 | s.swift_version = '5.1' 19 | s.source = { :git => 'https://github.com/asam139/Steps.git', :tag => s.version.to_s } 20 | s.source_files = 'Sources/Steps/**/*' 21 | 22 | s.frameworks = 'SwiftUI', 'Combine' 23 | s.dependency 'SwifterSwiftUI', '~> 0.5.2' 24 | end 25 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## iOS 17 | 18 | ### ios lint 19 | 20 | ```sh 21 | [bundle exec] fastlane ios lint 22 | ``` 23 | 24 | Validate the project is ready for releasing 25 | 26 | ### ios patch 27 | 28 | ```sh 29 | [bundle exec] fastlane ios patch 30 | ``` 31 | 32 | Release a new version with a `patch` bump_type 33 | 34 | ### ios minor 35 | 36 | ```sh 37 | [bundle exec] fastlane ios minor 38 | ``` 39 | 40 | Release a new version with a `minor` bump_type 41 | 42 | ### ios major 43 | 44 | ```sh 45 | [bundle exec] fastlane ios major 46 | ``` 47 | 48 | Release a new version with a `major` bump_type 49 | 50 | ---- 51 | 52 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 53 | 54 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 55 | 56 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 57 | -------------------------------------------------------------------------------- /Tests/StepsTests/Components/StepSeparatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeparatorTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 20/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | import ViewInspector 11 | @testable import Steps 12 | 13 | final class SeparatorTests: XCTestCase { 14 | let config = Config() 15 | let data = ["First", "Second"] 16 | lazy var state: StepsState = { 17 | return StepsState(data: data) 18 | }() 19 | 20 | let delay = 0.5 21 | 22 | func testSeparator() { 23 | let container = Separator(step: Step()) 24 | 25 | let exp = container.inspection.inspect { _ in 26 | self.state.nextStep() // 1 27 | } 28 | let exp2 = container.inspection.inspect(after: delay * 2) { _ in 29 | self.state.previousStep() // 0 30 | 31 | self.state.nextStep() // 1 32 | self.state.nextStep() // 2 33 | } 34 | let exp3 = container.inspection.inspect(after: delay * 4) { _ in 35 | self.state.previousStep() // 1 36 | self.state.previousStep() // 0 37 | } 38 | ViewHosting.host(view: container 39 | .environmentObject(state) 40 | .environmentObject(config) 41 | ) 42 | wait(for: [exp, exp2, exp3], timeout: 5) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog for **Steps**. Also see the [releases](https://github.com/asam139/Steps/releases) on GitHub. 4 | 5 | ## Upcoming Release 6 | 7 | ### Added 8 | 9 | ### Changed 10 | 11 | ### Deprecated 12 | 13 | ### Removed 14 | 15 | ### Fixed 16 | 17 | ### Security 18 | 19 | --- 20 | ## [v0.3.8](https://github.com/asam139/Steps/releases/tag/0.3.8) 21 | ### Change 22 | - Allow consumer to inject config into Steps initializer 23 | 24 | ## [v0.3.7](https://github.com/asam139/Steps/releases/tag/0.3.7) 25 | ### Added 26 | - Add action to know when a step is selected. 27 | 28 | ## [v0.3.6](https://github.com/asam139/Steps/releases/tag/0.3.6) 29 | ### Fixed 30 | - Fix state for each step 31 | 32 | ## [v0.3.4](https://github.com/asam139/Steps/releases/tag/0.3.4) 33 | ### Added 34 | - New method to change the current step. 35 | ### Change 36 | - Implement component following the builder pattern. 37 | - Improve animations. 38 | 39 | ## [v0.3.3](https://github.com/asam139/Steps/releases/tag/0.3.3) 40 | ### Fixed 41 | - Fix layout for title 42 | 43 | ## [v0.3.2](https://github.com/asam139/Steps/releases/tag/0.3.2) 44 | ### Added 45 | - Update deps 46 | 47 | ## [v0.3.0](https://github.com/asam139/Steps/releases/tag/0.3.0) 48 | ### Added 49 | - Support to modify initial step 50 | - Add documentation 51 | - Implement tests 52 | - Initial implementation 53 | 54 | --- 55 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Steps", 8 | platforms: [ 9 | .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "Steps", 15 | targets: ["Steps"] 16 | ), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | .package(url: "https://github.com/asam139/SwifterSwiftUI.git", .upToNextMajor(from: "0.5.2")), 21 | .package(url: "https://github.com/nalexn/ViewInspector", .upToNextMajor(from: "0.9.5")) 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 26 | .target( 27 | name: "Steps", 28 | dependencies: ["SwifterSwiftUI"] 29 | ), 30 | .testTarget( 31 | name: "StepsTests", 32 | dependencies: ["Steps", "ViewInspector"] 33 | ), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - multiple_closures_with_trailing_closure # by SwiftUI 3 | opt_in_rules: # some rules are only opt-in 4 | - empty_count 5 | # Find all the available rules by running: 6 | # swiftlint rules 7 | included: # paths to include during linting. `--path` is ignored if present. 8 | - ./Sources 9 | - ./Tests 10 | - ./Example 11 | analyzer_rules: # Rules run by `swiftlint analyze` (experimental) 12 | - explicit_self 13 | 14 | # configurable rules can be customized from this configuration file 15 | # binary rules can set their severity level 16 | force_try: 17 | severity: warning # explicitly 18 | # rules that have both warning and error levels, can set just the warning level 19 | # implicitly 20 | line_length: 21 | warning: 120 22 | ignores_comments: true 23 | ignores_urls: true 24 | file_length: 25 | - 1000 # warning 26 | - 1500 # error 27 | type_body_length: 28 | - 500 # warning 29 | - 600 # error 30 | function_body_length: 31 | warning: 200 32 | error: 300 33 | type_name: 34 | min_length: 3 # only warning 35 | max_length: # warning and error 36 | warning: 40 37 | error: 50 38 | excluded: 39 | - K 40 | identifier_name: 41 | min_length: # only min_length 42 | warning: 1 43 | error: 1 # only error 44 | excluded: # excluded via string array 45 | - i 46 | - id 47 | - URL 48 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 49 | -------------------------------------------------------------------------------- /Sources/Steps/Effects/ScaleXEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleXEffect.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 13/06/2020. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // Custom scale x effect to add completion block 11 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 12 | struct ScaleXEffect: AnimatableModifier { 13 | 14 | /// The initial scale in X-axis of the effect 15 | var initialScaleX: CGFloat 16 | 17 | /// The scale in X-axis of the effect 18 | var scaleX: CGFloat 19 | 20 | /// Block when animation is finished 21 | var onCompletion: (() -> Void)? 22 | 23 | /// Initializes a new scale effect 24 | /// 25 | /// - Parameters: 26 | /// - scaleX: scale in the X-axis to be animated 27 | /// - onCompletion: completion block 28 | init(scaleX: CGFloat, onCompletion: (() -> Void)? = nil) { 29 | self.initialScaleX = scaleX 30 | self.scaleX = scaleX 31 | self.onCompletion = onCompletion 32 | } 33 | 34 | func checkIfFinished() { 35 | if let onCompletion = onCompletion, scaleX == initialScaleX { 36 | DispatchQueue.main.async { 37 | onCompletion() 38 | } 39 | } 40 | } 41 | 42 | var animatableData: CGFloat { 43 | get { scaleX } 44 | set { 45 | scaleX = newValue 46 | checkIfFinished() 47 | } 48 | } 49 | 50 | func body(content: Content) -> some View { 51 | content.scaleEffect(x: scaleX, y: 1, anchor: .center) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/StepsTests/Components/StepElementTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 20/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | import ViewInspector 11 | @testable import Steps 12 | 13 | final class ItemTests: XCTestCase { 14 | let config = Config() 15 | let data = ["First", "Second"] 16 | lazy var state: StepsState = { 17 | return StepsState(data: data) 18 | }() 19 | 20 | let delay = 0.5 21 | func testItem() { 22 | let container = Item(step: Step()) 23 | 24 | let exp = container.inspection.inspect { _ in 25 | self.state.nextStep() // 1 26 | } 27 | let exp2 = container.inspection.inspect(after: delay) { _ in 28 | self.state.previousStep() // 0 29 | 30 | self.state.nextStep() // 1 31 | self.state.nextStep() // 2 32 | } 33 | let exp3 = container.inspection.inspect(after: delay * 4) { _ in 34 | self.state.previousStep() // 1 35 | self.state.previousStep() // 0 36 | } 37 | 38 | let exp4 = container.inspection.inspect(after: delay * 6) { _ in 39 | self.state.nextStep() // 1 40 | self.state.nextStep() // 2 41 | self.state.nextStep() // 3 42 | } 43 | ViewHosting.host(view: container 44 | .environmentObject(state) 45 | .environmentObject(config) 46 | ) 47 | wait(for: [exp, exp2, exp3, exp4], timeout: 5) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Steps/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // 4 | // 5 | // Created by Saul Moreno Abril on 11/04/2020. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | /// Object to manage the config of the main component 12 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 13 | public class Config: ObservableObject { 14 | 15 | /// Spacing between elements 16 | @Published var itemSpacing: CGFloat = 5 17 | 18 | /// Size of each step 19 | @Published var size: CGFloat = 14 20 | 21 | /// Line thickness for all lines in the component 22 | @Published var lineThickness: CGFloat = 2 23 | 24 | /// Color for current and completed steps 25 | @Published var primaryColor: Color = Color.blue 26 | 27 | /// Color for text inside step element 28 | @Published var secondaryColor: Color = Color.white 29 | 30 | /// Color for uncompleted steps 31 | @Published var disabledColor: Color = Color.gray 32 | 33 | /// Default image for completed steps 34 | #if os(iOS) || os(watchOS) || os(tvOS) 35 | @Published public var defaultImage: Image? = Image(systemName: "checkmark") 36 | #elseif os(OSX) 37 | @Published public var defaultImage: Image? 38 | #endif 39 | 40 | /// Padding to adjust subviews 41 | var figurePadding: CGFloat { 42 | return size * 0.5 43 | } 44 | 45 | /// Default animation 46 | var animation: Animation { 47 | return Animation.spring(response: 0.5, dampingFraction: 0.95, blendDuration: 0) 48 | } 49 | 50 | /// Initializes a new config. 51 | public init() {} 52 | } 53 | -------------------------------------------------------------------------------- /Example/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS Example 4 | // 5 | // Created by Saul Moreno Abril on Apr 10, 2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application( 15 | _ application: UIApplication, 16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 17 | ) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, 25 | configurationForConnecting connectingSceneSession: UISceneSession, 26 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 30 | } 31 | 32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 35 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/StepsTests/StepsStateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepsStateTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 20/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | import ViewInspector 11 | @testable import Steps 12 | 13 | final class StepsStateTests: XCTestCase { 14 | let data = ["First", "Second"] 15 | 16 | func testStepsTests() { 17 | let state = StepsState(data: data) 18 | 19 | let currentIndex = state.currentIndex 20 | 21 | state.setStep(100) 22 | XCTAssertEqual(state.currentIndex, currentIndex) 23 | state.setStep(currentIndex + 1) 24 | XCTAssertEqual(state.currentIndex, currentIndex + 1) 25 | state.setStep(currentIndex) 26 | 27 | state.nextStep() 28 | XCTAssertEqual(state.currentIndex, currentIndex + 1) 29 | state.nextStep() 30 | XCTAssertEqual(state.currentIndex, currentIndex + 2) 31 | state.nextStep() 32 | XCTAssertEqual(state.currentIndex, data.endIndex + 1) 33 | state.nextStep() 34 | XCTAssertEqual(state.currentIndex, data.endIndex + 1) 35 | XCTAssertFalse(state.hasNext) 36 | 37 | state.previousStep() 38 | XCTAssertEqual(state.currentIndex, currentIndex + 2) 39 | state.previousStep() 40 | XCTAssertEqual(state.currentIndex, currentIndex + 1) 41 | state.previousStep() 42 | XCTAssertEqual(state.currentIndex, currentIndex) 43 | state.previousStep() 44 | XCTAssertEqual(state.currentIndex, currentIndex) 45 | XCTAssertFalse(state.hasPrevious) 46 | } 47 | 48 | static var allTests = [ 49 | ("testStepsTests", testStepsTests) 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /Example/Sources/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 | -------------------------------------------------------------------------------- /Sources/Steps/Components/Container.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Container.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 05/04/2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwifterSwiftUI 11 | 12 | /// Container for each subview of the bar 13 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 14 | struct Container: View where Content: View { 15 | /// The style of the component 16 | @EnvironmentObject var config: Config 17 | 18 | /// The title of the subcomponent 19 | var title: String? 20 | 21 | /// The content of the container 22 | var content: Content 23 | 24 | /// Helper to inspect 25 | let inspection = Inspection() 26 | 27 | /// Initializes a new step container 28 | /// 29 | /// - Parameters: 30 | /// - title: title 31 | /// - content: content of the container 32 | public init(title: String? = nil, @ViewBuilder content: () -> Content) { 33 | self.title = title 34 | self.content = content() 35 | } 36 | 37 | var body: some View { 38 | VStack(spacing: config.size * 0.75) { 39 | VStack { 40 | content 41 | } 42 | .frame(height: config.size + 2 * config.figurePadding) 43 | if let title { 44 | Text(title) 45 | .fixedSize(horizontal: true, vertical: false) 46 | .lineLimit(1) 47 | } 48 | } 49 | .frame(minWidth: 0, maxWidth: .infinity) 50 | .onReceive(inspection.notice) { self.inspection.visit(self, $0) } 51 | } 52 | } 53 | 54 | #if DEBUG 55 | struct Container_Previews: PreviewProvider { 56 | static var previews: some View { 57 | Container { 58 | Text("Testing") 59 | } 60 | } 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /Example/Sources/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 | -------------------------------------------------------------------------------- /Example/Sources/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // iOS Example 4 | // 5 | // Created by Saul Moreno Abril on 10/04/2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Steps 11 | 12 | struct Item { 13 | var title: String 14 | var image: Image? 15 | } 16 | 17 | struct ContentView: View { 18 | @ObservedObject private var stepsState: StepsState 19 | 20 | init() { 21 | let items = [ 22 | Item(title: "First_", image: Image(systemName: "wind")), 23 | Item(title: ""), 24 | Item(title: "Second__", image: Image(systemName: "tornado")), 25 | Item(title: ""), 26 | Item(title: "Fifth_____", image: Image(systemName: "hurricane")) 27 | ] 28 | stepsState = StepsState(data: items) 29 | } 30 | 31 | func onCreateStep(_ item: Item) -> Step { 32 | return Step(title: item.title, image: item.image) 33 | } 34 | 35 | func onSelectStepAtIndex(_ index: Int) { 36 | stepsState.setStep(index) 37 | } 38 | 39 | var body: some View { 40 | VStack(spacing: 12) { 41 | Steps(state: stepsState, onCreateStep: onCreateStep) 42 | .onSelectStepAtIndex(onSelectStepAtIndex) 43 | .itemSpacing(10) 44 | .font(.caption) 45 | .padding() 46 | 47 | Button(action: { 48 | self.stepsState.nextStep() 49 | }) { 50 | Text("Next") 51 | } 52 | .disabled(!stepsState.hasNext) 53 | Button(action: { 54 | self.stepsState.previousStep() 55 | }) { 56 | Text("Previous") 57 | } 58 | .disabled(!stepsState.hasPrevious) 59 | }.padding() 60 | } 61 | } 62 | 63 | struct ContentView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | ContentView() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | fastlane_require 'dotenv' 2 | default_platform(:ios) 3 | 4 | platform :ios do 5 | 6 | desc 'Validate the project is ready for releasing' 7 | lane :lint do 8 | pod_lib_lint( 9 | allow_warnings: true 10 | ) 11 | end 12 | 13 | desc 'Release a new version with a `patch` bump_type' 14 | lane :patch do 15 | release('patch') 16 | end 17 | 18 | desc 'Release a new version with a `minor` bump_type' 19 | lane :minor do 20 | release('minor') 21 | end 22 | 23 | desc 'Release a new version with a `major` bump_type' 24 | lane :major do 25 | release('major') 26 | end 27 | 28 | def release(type) 29 | branch = ENV['RELEASE_BRANCH'] 30 | ensure_git_branch( 31 | branch: branch 32 | ) 33 | new_changes = sh("git log #{last_git_tag}..HEAD | wc -l").strip! 34 | if new_changes == '0' 35 | UI.user_error!("No changes since last release: #{last_git_tag}, please add new features and try again!") 36 | end 37 | 38 | lint 39 | 40 | podspec = ENV['PODSPEC'] 41 | sources_path = ENV['SOURCES_PATH'] 42 | 43 | version = version_bump_podspec( 44 | path: podspec, 45 | bump_type: type 46 | ) 47 | 48 | git_add( 49 | path: [podspec, sources_path], 50 | shell_escape: false 51 | ) 52 | 53 | git_commit( 54 | path: [podspec, sources_path], 55 | message: "release: v#{version}" 56 | ) 57 | 58 | add_git_tag( 59 | tag: "#{version}" 60 | ) 61 | 62 | push_to_git_remote 63 | 64 | pod_push( 65 | path: podspec, 66 | allow_warnings: true 67 | ) 68 | 69 | #author = last_git_commit[:author] 70 | #alert("*#{ENV['NAME']} v#{version} is here 🎉*", { :'Download URL' => ENV['REPO_URL'], :Author => author }) 71 | end 72 | 73 | def alert(message, payload) 74 | payload['Date'] = Time.new.to_s 75 | slack( 76 | slack_url: ENV['SLACK_URL'], 77 | message: message, 78 | channel: ENV['SLACK_CHANNEL'], 79 | payload: payload, 80 | default_payloads: [] 81 | ) 82 | end 83 | 84 | end -------------------------------------------------------------------------------- /Example/Sources/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 | } -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Tests 12 | runs-on: macos-latest 13 | strategy: 14 | matrix: 15 | xcode: ['/Applications/Xcode_14.1.app/Contents/Developer'] 16 | env: 17 | SCHEME: StepsTests 18 | CONFIGURATION: Debug 19 | DERIVED_PATH: .build/derivedData 20 | DEVELOPER_DIR: ${{ matrix.xcode }} 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 2 25 | - name: Swift Build 26 | run: swift build 27 | - name: Bundle Install 28 | run: bundle install 29 | - name: Brew Upgrade 30 | run: | 31 | brew update 32 | brew outdated xctool || brew upgrade xctool 33 | - name: Test iOS 34 | run: | 35 | xcodebuild test -scheme $SCHEME -configuration $CONFIGURATION -destination "$DESTINATION" -derivedDataPath $DERIVED_PATH | XCPRETTY_JSON_FILE_OUTPUT="xcodebuild-ios.json" xcpretty -f `xcpretty-json-formatter` 36 | bash <(curl -s https://codecov.io/bash) -cF ios -J 'Steps' -D $DERIVED_PATH 37 | env: 38 | DESTINATION: platform=iOS Simulator,name=iPhone 11 39 | - name: Test macOS 40 | run: | 41 | xcodebuild test -scheme $SCHEME -configuration $CONFIGURATION -destination "$DESTINATION" -derivedDataPath $DERIVED_PATH | XCPRETTY_JSON_FILE_OUTPUT="xcodebuild-macos.json" xcpretty -f `xcpretty-json-formatter` 42 | bash <(curl -s https://codecov.io/bash) -cF macos -J 'Steps' -D $DERIVED_PATH 43 | env: 44 | DESTINATION: platform=OS X 45 | - name: Test TVOS 46 | run: | 47 | xcodebuild test -scheme $SCHEME -configuration $CONFIGURATION -destination "$DESTINATION" -derivedDataPath $DERIVED_PATH | XCPRETTY_JSON_FILE_OUTPUT="xcodebuild-tvos.json" xcpretty -f `xcpretty-json-formatter` 48 | bash <(curl -s https://codecov.io/bash) -cF tvos -J 'Steps' -D $DERIVED_PATH 49 | env: 50 | DESTINATION: platform=tvOS Simulator,name=Apple TV 4K (at 1080p) 51 | -------------------------------------------------------------------------------- /Example/Sources/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 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Sources/Steps/StepsState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepsState.swift 3 | // 4 | // 5 | // Created by Saul Moreno Abril on 11/04/2020. 6 | // 7 | 8 | import Combine 9 | 10 | public class StepsState: ObservableObject { 11 | /// Array of items that will populate each page 12 | var data: [Element] 13 | 14 | /// Indicate the current step 15 | @Published public private(set) var currentIndex: Int = 0 16 | 17 | /// Indicate if there is a next step 18 | @Published public private(set) var hasNext: Bool = true 19 | 20 | /// Indicate if there is a previous step 21 | @Published public private(set) var hasPrevious: Bool = true 22 | 23 | /// Set to store all cancellables 24 | private var cancellable: AnyCancellable? 25 | 26 | /// Initializes a new state. 27 | /// 28 | /// - Parameters: 29 | /// - steps: array of all steps 30 | public init(data: [Element], initialStep: Int = 0) { 31 | self.data = data 32 | 33 | if initialStep >= data.startIndex && initialStep <= data.endIndex { 34 | currentIndex = initialStep 35 | } 36 | 37 | cancellable = $currentIndex.sink { (index) in 38 | self.hasNext = index < self.data.endIndex 39 | self.hasPrevious = index > self.data.startIndex 40 | } 41 | } 42 | 43 | /// Change current step 44 | public func setStep(_ index: Int) { 45 | if index < data.startIndex || index > data.endIndex + 1 { 46 | return 47 | } 48 | currentIndex = index 49 | } 50 | 51 | /// Move to the next step 52 | public func nextStep() { 53 | if currentIndex > data.endIndex { 54 | return 55 | } 56 | currentIndex += 1 57 | } 58 | 59 | /// Move to the previous step 60 | public func previousStep() { 61 | if currentIndex == data.startIndex { 62 | return 63 | } 64 | currentIndex -= 1 65 | } 66 | } 67 | 68 | // MARK: Helpers 69 | extension StepsState { 70 | /// Get state for step at an index 71 | func stateFor(step: Step) -> Step.State { 72 | if step.index < currentIndex { 73 | return .completed 74 | } else if step.index == currentIndex { 75 | return .current 76 | } 77 | return .uncompleted 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.github/state.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - enhancement 16 | - bug 17 | - confirmed bug 18 | - discussion 19 | - help wanted 20 | 21 | # Set to true to ignore issues in a project (defaults to false) 22 | exemptProjects: false 23 | 24 | # Set to true to ignore issues in a milestone (defaults to false) 25 | exemptMilestones: false 26 | 27 | # Set to true to ignore issues with an assignee (defaults to false) 28 | exemptAssignees: false 29 | 30 | # Label to use when marking as stale 31 | staleLabel: stale 32 | 33 | # Comment to post when marking as stale. Set to `false` to disable 34 | markComment: > 35 | This issue has been automatically marked as stale because it has not had 36 | recent activity. It will be closed if no further activity occurs. Thank you 37 | for your contributions. 38 | # Comment to post when removing the stale label. 39 | # unmarkComment: > 40 | # Your comment here. 41 | 42 | # Comment to post when closing a stale Issue or Pull Request. 43 | closeComment: > 44 | This issue has been automatically closed because it has not had 45 | recent activity. Thank you for your contributions. 46 | # Limit the number of actions per hour, from 1-30. Default is 30 47 | limitPerRun: 30 48 | 49 | # Limit to only `issues` or `pulls` 50 | # only: issues 51 | 52 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 53 | # pulls: 54 | # daysUntilStale: 30 55 | # markComment: > 56 | # This pull request has been automatically marked as stale because it has not had 57 | # recent activity. It will be closed if no further activity occurs. Thank you 58 | # for your contributions. 59 | 60 | # issues: 61 | # exemptLabels: 62 | # - confirmed -------------------------------------------------------------------------------- /Tests/StepsTests/StepsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepsTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 20/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | import ViewInspector 11 | @testable import Steps 12 | 13 | final class StepsTests: XCTestCase { 14 | let config = Config() 15 | let data = ["First", "Second"] 16 | lazy var state: StepsState = { 17 | return StepsState(data: data) 18 | }() 19 | 20 | func testSteps() { 21 | let container = Steps(state: state, onCreateStep: { string in 22 | Step(title: string, image: Image("")) 23 | }) 24 | let exp = container.inspection.inspect { view in 25 | let count = try view.actualView().state.data.count 26 | for i in 0.. 2 | 5 | 8 | 9 | 16 | 17 | 23 | 24 | 25 | 26 | 28 | 34 | 35 | 36 | 37 | 38 | 48 | 49 | 55 | 56 | 58 | 59 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Sources/Steps/Effects/OffsetEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OffsetEffect.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 11/04/2020. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // Custom offset effect to simulate acceleration deforming the view 11 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 12 | struct OffsetEffect: GeometryEffect { 13 | 14 | /// The initial offset of the effect 15 | private(set) var initialOffset: CGFloat 16 | 17 | /// The offset of the effect 18 | private(set) var offset: CGFloat 19 | 20 | /// The percentage between two values of offset. [0, 1] 21 | private(set) var pct: CGFloat 22 | 23 | /// The factor to control the deformation 24 | private(set) var factor: CGFloat 25 | 26 | /// The difference the offset in each update 27 | private var offsetDiff: CGFloat = 0 28 | 29 | /// Block when animation is finished 30 | private var onCompletion: (() -> Void)? 31 | 32 | /// Initializes a new offset effect 33 | /// 34 | /// - Parameters: 35 | /// - offset: offset to be animated 36 | /// - pct: percentage of the animation 37 | /// - factor: factor to deform 38 | /// - onCompletion: completion block 39 | init(offset: CGFloat, pct: CGFloat, factor: CGFloat = 0.1, onCompletion: (() -> Void)? = nil) { 40 | self.initialOffset = offset 41 | self.offset = offset 42 | self.factor = factor 43 | if pct >= 0.0 && pct <= 1.0 { 44 | self.pct = pct 45 | } else { 46 | self.pct = pct < 0.0 ? 0.0 : 1.0 47 | } 48 | self.onCompletion = onCompletion 49 | } 50 | 51 | func checkIfFinished() { 52 | if let onCompletion = onCompletion, initialOffset == offset { 53 | DispatchQueue.main.async { 54 | onCompletion() 55 | } 56 | } 57 | } 58 | 59 | var animatableData: AnimatablePair { 60 | get { return AnimatablePair(offset, pct) } 61 | set { 62 | offsetDiff = (offset - newValue.first) 63 | 64 | offset = newValue.first 65 | pct = newValue.second 66 | 67 | checkIfFinished() 68 | } 69 | } 70 | 71 | func effectValue(size: CGSize) -> ProjectionTransform { 72 | var skew: CGFloat 73 | let direction: CGFloat = offsetDiff > 0 ? -1 : 1 74 | if pct < 0.2 { 75 | skew = (pct * 5) 76 | } else if pct > 0.8 { 77 | skew = ((1 - pct) * 5) 78 | } else { 79 | skew = 1 80 | } 81 | skew *= direction * factor 82 | 83 | return ProjectionTransform(CGAffineTransform(a: 1, b: 0, c: skew, d: 1, tx: offset, ty: 0)) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Tests/StepsTests/Effects/OffsetEffectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OffsetEffectTests.swift 3 | // StepsTests 4 | // 5 | // Created by Saul Moreno Abril on 19/04/2020. 6 | // 7 | 8 | import XCTest 9 | import SwiftUI 10 | @testable import Steps 11 | 12 | final class OffsetEffectTests: XCTestCase { 13 | let offset: CGFloat = 0.5 14 | let pct: CGFloat = 0 15 | let factor: CGFloat = 0.1 16 | 17 | func testInit() { 18 | let effect = OffsetEffect(offset: offset, pct: pct, factor: factor) 19 | XCTAssertEqual(effect.offset, offset) 20 | XCTAssertEqual(effect.pct, pct) 21 | XCTAssertEqual(effect.factor, factor) 22 | 23 | let negativeInvalidPct: CGFloat = -0.1 24 | let nEffect = OffsetEffect(offset: offset, pct: negativeInvalidPct, factor: factor) 25 | XCTAssertEqual(nEffect.pct, 0.0) 26 | 27 | let positiveInvalidPct: CGFloat = 1.1 28 | let pEffect = OffsetEffect(offset: offset, pct: positiveInvalidPct, factor: factor) 29 | XCTAssertEqual(pEffect.pct, 1.0) 30 | } 31 | 32 | func testAnimatableData() { 33 | var effect = OffsetEffect(offset: offset, pct: pct, factor: factor) 34 | XCTAssertEqual(effect.animatableData.first, offset) 35 | XCTAssertEqual(effect.animatableData.second, pct) 36 | 37 | let newOffset: CGFloat = 0.1 38 | let newPct: CGFloat = 0.25 39 | effect.animatableData = AnimatablePair(newOffset, newPct) 40 | XCTAssertEqual(effect.animatableData.first, newOffset) 41 | XCTAssertEqual(effect.animatableData.second, newPct) 42 | } 43 | 44 | func testEffectValue() { 45 | let initialOffset: CGFloat = 0.0 46 | var effect = OffsetEffect(offset: initialOffset, pct: pct, factor: factor) 47 | 48 | let newOffset: CGFloat = 0.1 49 | // Positive offset 50 | effect.animatableData = AnimatablePair(newOffset, 0.0) 51 | _ = effect.effectValue(size: CGSize(width: 10, height: 10)) 52 | XCTAssertEqual(effect.offset, newOffset) 53 | XCTAssertEqual(effect.pct, pct) 54 | 55 | // Negative offset 56 | effect.animatableData = AnimatablePair(-newOffset, 0.0) 57 | _ = effect.effectValue(size: CGSize(width: 10, height: 10)) 58 | XCTAssertEqual(effect.offset, -newOffset) 59 | XCTAssertEqual(effect.pct, pct) 60 | 61 | // Pct 62 | for value in 0...11 { 63 | let newPct: CGFloat = CGFloat(value) * 0.1 64 | effect.animatableData = AnimatablePair(offset, newPct) 65 | _ = effect.effectValue(size: CGSize(width: 10, height: 10)) 66 | XCTAssertEqual(effect.pct, newPct) 67 | } 68 | XCTAssertEqual(effect.offset, offset) 69 | } 70 | 71 | static var allTests = [ 72 | ("testInit", testInit), 73 | ("testAnimatableData", testAnimatableData), 74 | ("testEffectValue", testEffectValue) 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /Example/Sources/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOS Example 4 | // 5 | // Created by Saul Moreno Abril on Apr 10, 2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, 17 | willConnectTo session: UISceneSession, 18 | options connectionOptions: UIScene.ConnectionOptions) { 19 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 20 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 21 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 22 | 23 | // Create the SwiftUI view that provides the window contents. 24 | let contentView = ContentView() 25 | 26 | // Use a UIHostingController as window root view controller. 27 | if let windowScene = scene as? UIWindowScene { 28 | let window = UIWindow(windowScene: windowScene) 29 | window.rootViewController = UIHostingController(rootView: contentView) 30 | self.window = window 31 | window.makeKeyAndVisible() 32 | } 33 | } 34 | 35 | func sceneDidDisconnect(_ scene: UIScene) { 36 | // Called as the scene is being released by the system. 37 | // This occurs shortly after the scene enters the background, or when its session is discarded. 38 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 39 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 40 | } 41 | 42 | func sceneDidBecomeActive(_ scene: UIScene) { 43 | // Called when the scene has moved from an inactive state to an active state. 44 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 45 | } 46 | 47 | func sceneWillResignActive(_ scene: UIScene) { 48 | // Called when the scene will move from an active state to an inactive state. 49 | // This may occur due to temporary interruptions (ex. an incoming phone call). 50 | } 51 | 52 | func sceneWillEnterForeground(_ scene: UIScene) { 53 | // Called as the scene transitions from the background to the foreground. 54 | // Use this method to undo the changes made on entering the background. 55 | } 56 | 57 | func sceneDidEnterBackground(_ scene: UIScene) { 58 | // Called as the scene transitions from the foreground to the background. 59 | // Use this method to save data, release shared resources, and store enough scene-specific state information 60 | // to restore the scene back to its current state. 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CHANGELOG_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Changelog Guidelines 2 | 3 | Here you can find the general guidelines for maintaining the Changelog (or adding new entries). We follow the guidelines from [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) with few additions. 4 | 5 | ## Guiding Principles 6 | 7 | - Changelogs are for humans, not machines. 8 | - There should be an entry for every single version. 9 | - The same types of changes should be grouped. 10 | - Versions and sections should be linkable. 11 | - The latest version comes first. 12 | - The release date of each versions is displayed. 13 | - Mention whether you follow Semantic Versioning. 14 | 15 | ... with the following **Steps** specific additions: 16 | 17 | - Keep an unreleased section at the top. 18 | - Each release title should link to release's page. 19 | - Add PR number and a GitHub tag at the end of each entry. 20 | - Each breaking change entry should have **Breaking Change** label at the beginning of this entry. 21 | - **Breaking Change** entries should be placed at the top of the section it's in. 22 | - Entries under each category should be grouped by the type they extend. 23 | 24 | ## Types of changes 25 | 26 | - **Added** for new features. 27 | - **Changed** for changes in existing functionality. 28 | - **Deprecated** for soon-to-be removed features. 29 | - **Removed** for now removed features. 30 | - **Fixed** for any bug fixes. 31 | - **Security** in case of vulnerabilities. 32 | 33 | --- 34 | 35 | ## Example 36 | 37 | ## [v0.0.0](https://github.com/asam139/Steps/releases/tag/0.0.0) 38 | 39 | ### Added 40 | 41 | - **UIDatePicker** 42 | - Added `textColor` property to set and get text color of UIDatePicker. [#335](https://github.com/asam139/Steps/pull/335) by [asam139](https://github.com/asam139). 43 | 44 | - **Continuous Integration** 45 | - Added **Danger** to continuous integration. [#252](https://github.com/asam139/Steps/pull/252) by [SD10](https://github.com/SD10). 46 | 47 | ### Changed 48 | 49 | - **Date** 50 | - **Breaking Change** The property `weekday` is now a get-only property. [#313](https://github.com/asam139/Steps/pull/313) by [asam139](https://github.com/asam139). 51 | 52 | - **Array** 53 | - `shuffle` and `shuffled` are no more constrained to Equatable. [#327](https://github.com/asam139/Steps/pull/327) by [asam139](https://github.com/asam139). 54 | 55 | ### Deprecated 56 | 57 | - **String** 58 | - `reversed() -> String` is deprecated in favor of Swift 4 new `reversed() -> ReversedCollection`. [#305](https://github.com/asam139/Steps/pull/305) by [asam139](https://github.com/asam139). 59 | - **Date** 60 | - `isInThisWeek` has been renamed to `isInCurrentWeek`. 61 | 62 | ### Removed 63 | 64 | - **UIViewController** 65 | - **Breaking Change** Removed `navigationBar` that was causing iOS apps to crash, thanks to drewpitchford for reporting in [#243](https://github.com/asam139/Steps/issues/243). by [drewpitchford](https://github.com/drewpitchford) 66 | 67 | ### Fixed 68 | 69 | - **Tests** 70 | - Fixed a bug where `XCTAssertNotNil` could not handle optionals. [#188](https://github.com/asam139/Steps/pull/188). by [asam139](https://github.com/asam139) -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Sources/Steps/Components/Separator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Separator.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 09/04/2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwifterSwiftUI 11 | 12 | /// Item to represent each separator between steps 13 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 14 | struct Separator: View { 15 | /// Get step object for the set index 16 | var step: Step 17 | 18 | /// The main state 19 | @EnvironmentObject var state: StepsState 20 | 21 | /// The style of the component 22 | @EnvironmentObject var config: Config 23 | 24 | /// Previous index 25 | @State private var previousIndex: Int = 0 26 | 27 | /// Current scale to be animated 28 | @State private var scaleX: CGFloat = 1 29 | 30 | /// Min scale in the axis X 31 | private let minScaleX: CGFloat = 0.25 32 | 33 | /// Helper to inspect 34 | let inspection = Inspection() 35 | 36 | private var stepState: Step.State { 37 | return state.stateFor(step: step) 38 | } 39 | 40 | /// Get foreground color for the current step 41 | private var foregroundColor: Color { 42 | switch stepState { 43 | case .uncompleted, 44 | .current: 45 | return config.disabledColor 46 | default: 47 | return config.primaryColor 48 | } 49 | } 50 | 51 | /// Update the scale to animate according the next index 52 | private func updateScale(nextIndex: Int) { 53 | let diff = nextIndex - previousIndex 54 | if abs(diff) != 1 { 55 | scaleX = 1 56 | return 57 | } 58 | 59 | if previousIndex == step.index && diff > 0 { 60 | scaleX = minScaleX 61 | } else if nextIndex == step.index && diff < 0 { 62 | scaleX = minScaleX 63 | } else { 64 | scaleX = 1 65 | } 66 | } 67 | 68 | private func onCompletionEffect() { 69 | if self.scaleX != 1 { 70 | scaleX = 1 71 | } 72 | } 73 | 74 | var body: some View { 75 | Container { 76 | Rectangle() 77 | .frame(height: config.lineThickness) 78 | } 79 | .foregroundColor(foregroundColor) 80 | .modifier( 81 | ScaleXEffect( 82 | scaleX: scaleX, 83 | onCompletion: onCompletionEffect 84 | ) 85 | ) 86 | .animation(config.animation) 87 | .onReceive(state.$currentIndex, perform: { (nextIndex) in 88 | self.updateScale(nextIndex: nextIndex) 89 | self.previousIndex = nextIndex 90 | }) 91 | .onReceive(inspection.notice) { self.inspection.visit(self, $0) } 92 | } 93 | } 94 | 95 | #if DEBUG 96 | struct Separator_Previews: PreviewProvider { 97 | static var previews: some View { 98 | let steps = ["First", "", ""] 99 | let state = StepsState(data: steps) 100 | return ( 101 | Steps(state: state, onCreateStep: { element in 102 | Step(title: element) 103 | }).padding() 104 | ) 105 | } 106 | } 107 | #endif 108 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 93sauu@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/Steps.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Sources/Steps/Components/Item.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Item.swift 3 | // Steps 4 | // 5 | // Created by Saul Moreno Abril on 09/04/2020. 6 | // Copyright © 2020 Saul Moreno Abril. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SwifterSwiftUI 11 | 12 | /// Item to represent each step 13 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 14 | struct Item: View { 15 | /// Get step object for the set index 16 | var step: Step 17 | 18 | /// The main state 19 | @EnvironmentObject var state: StepsState 20 | 21 | /// The style of the component 22 | @EnvironmentObject var config: Config 23 | 24 | /// Helper to inspect 25 | let inspection = Inspection() 26 | 27 | /// Previous index 28 | @State private var previousIndex: Int = 0 29 | 30 | /// Current offset to be animated 31 | @State private var offset: CGFloat = 0 32 | 33 | /// Max diff offset to be animated 34 | private var maxOffset: CGFloat { 35 | return config.itemSpacing * 2 36 | } 37 | 38 | private var stepState: Step.State { 39 | return state.stateFor(step: step) 40 | } 41 | 42 | /// Get image for the current step 43 | private var image: Image? { 44 | return stepState != .completed ? step.image : config.defaultImage 45 | } 46 | 47 | /// Get foreground color for the current step 48 | private var foregroundColor: Color { 49 | switch stepState { 50 | case .uncompleted: 51 | return config.disabledColor 52 | default: 53 | return config.primaryColor 54 | } 55 | } 56 | 57 | /// Update the offset to animate according the next index 58 | private func updateOffset(nextIndex: Int) { 59 | let diff = nextIndex - previousIndex 60 | if abs(diff) != 1 { 61 | offset = 0 62 | return 63 | } 64 | 65 | if 66 | (previousIndex == state.data.endIndex && diff > 0) || 67 | (nextIndex == state.data.endIndex && diff < 0) 68 | { 69 | offset = 0 70 | } else if previousIndex == step.index { 71 | offset = CGFloat(diff) * maxOffset 72 | } else if nextIndex == step.index { 73 | offset = -CGFloat(diff) * maxOffset 74 | } else { 75 | offset = 0 76 | } 77 | } 78 | 79 | private func onCompletionEffect() { 80 | if self.offset != 0 { 81 | offset = 0 82 | } 83 | } 84 | 85 | var body: some View { 86 | Container(title: step.title) { 87 | element 88 | .frame(width: config.size, height: config.size) 89 | .padding(config.figurePadding) 90 | .if(step.index == state.currentIndex, then: { 91 | $0.background(config.primaryColor).foregroundColor(config.secondaryColor) 92 | }, else: { 93 | $0.overlay( 94 | Circle().stroke(lineWidth: config.lineThickness) 95 | ) 96 | }) 97 | .cornerRadius(config.size) 98 | } 99 | .foregroundColor(foregroundColor) 100 | .modifier( 101 | OffsetEffect( 102 | offset: offset, 103 | pct: abs(offset) > 0 ? 1 : 0, 104 | onCompletion: onCompletionEffect 105 | ) 106 | ) 107 | .animation(config.animation) 108 | .onReceive(state.$currentIndex, perform: { (nextIndex) in 109 | self.updateOffset(nextIndex: nextIndex) 110 | self.previousIndex = nextIndex 111 | }) 112 | .onReceive(inspection.notice) { self.inspection.visit(self, $0) } 113 | } 114 | 115 | @ViewBuilder 116 | private var element: some View { 117 | if let image { 118 | image.resizable() 119 | } else { 120 | Text("\(step.index + 1)") 121 | .font(.system(size: config.size)) 122 | } 123 | } 124 | } 125 | 126 | #if DEBUG 127 | struct Item_Previews: PreviewProvider { 128 | static var previews: some View { 129 | let steps = ["First", "", ""] 130 | let state = StepsState(data: steps) 131 | return ( 132 | Steps(state: state, onCreateStep: { element in 133 | Step(title: element) 134 | }).padding() 135 | ) 136 | } 137 | } 138 | #endif 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Build Status](https://github.com/asam139/Steps/workflows/Steps/badge.svg?branch=master)](https://github.com/asam139/Steps/actions) 6 | [![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS%20%7C%20macOS-lightgrey.svg)](https://github.com/asam139/Steps) 7 | [![Cocoapods](https://img.shields.io/cocoapods/v/Steps.svg)](https://cocoapods.org/pods/Steps) 8 | [![SPM compatible](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/) 9 | [![codecov](https://codecov.io/gh/asam139/Steps/branch/master/graph/badge.svg)](https://codecov.io/gh/asam139/Steps) 10 | [![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg)](https://swift.org) 11 | [![Xcode](https://img.shields.io/badge/Xcode-11.4-blue.svg)](https://developer.apple.com/xcode) 12 | [![MIT](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT) 13 | 14 | Steps is a navigation bar that guides users through the steps of a task. You need to use it when a given task is complicated or has a certain sequence in the series of subtasks, we can decompose it into several steps to make things easier. 15 | 16 | ## Requirements 17 | 18 | - **iOS** 10.0+ / **tvOS** 9.0+ / **macOS** 10.10+ / **Ubuntu** 14.04+ 19 | - Swift 5.0+ 20 | 21 | ## Installation 22 | 23 |
24 | CocoaPods 25 |

To integrate Steps into your Xcode project using CocoaPods, specify it in your Podfile:

26 | 27 |
pod 'Steps'
28 |
29 | 30 |
31 | Swift Package Manager 32 |

You can use The Swift Package Manager to install Steps by adding the proper description to your Package.swift file:

33 | 34 |
import PackageDescription
 35 | 
 36 | let package = Package(
 37 |     name: "YOUR_PROJECT_NAME",
 38 |     targets: [],
 39 |     dependencies: [
 40 |         .package(url: "https://github.com/asam139/Steps.git", from: "0.2.0")
 41 |     ]
 42 | )
 43 | 
44 | 45 |

Next, add Steps to your targets dependencies like so:

46 |
.target(
 47 |     name: "YOUR_TARGET_NAME",
 48 |     dependencies: [
 49 |         "Steps",
 50 |     ]
 51 | ),
52 |

Then run swift package update.

53 |
54 | 55 | 56 | 57 |
58 | Manually 59 |

Add the Steps project to your Xcode project

60 |
61 | 62 | ## Example 63 | 64 |

65 | 66 |

67 | 68 |
struct Item {
 69 |     var title: String
 70 |     var image: Image?
 71 | }
 72 | 
 73 | struct ContentView: View {
 74 |     @ObservedObject private var stepsState: StepsState
 75 | 
 76 |     init() {
 77 |         let items = [
 78 |             Item(title: "First_", image: Image(systemName: "wind")),
 79 |             Item(title: ""),
 80 |             Item(title: "Second__", image: Image(systemName: "tornado")),
 81 |             Item(title: ""),
 82 |             Item(title: "Fifth_____", image: Image(systemName: "hurricane"))
 83 |         ]
 84 |         stepsState = StepsState(data: items)
 85 |     }
 86 | 
 87 |     func onCreateStep(_ item: Item) -> Step {
 88 |         return Step(title: item.title, image: item.image)
 89 |     }
 90 | 
 91 |     var body: some View {
 92 |         VStack(spacing: 12) {
 93 |             Steps(state: stepsState, onCreateStep:onCreateStep)
 94 |                 .itemSpacing(10)
 95 |                 .font(.caption)
 96 |                 .padding()
 97 | 
 98 |             Button(action: {
 99 |                 self.stepsState.nextStep()
100 |             }) {
101 |                 Text("Next")
102 |             }
103 |             .disabled(!stepsState.hasNext)
104 |             Button(action: {
105 |                 self.stepsState.previousStep()
106 |             }) {
107 |                 Text("Previous")
108 |             }
109 |             .disabled(!stepsState.hasPrevious)
110 |         }.padding()
111 |     }
112 | }
113 | 114 | 115 | ## Get involved 116 | 117 | We want your feedback. 118 | Please refer to [contributing guidelines](https://github.com/asam139/Steps/tree/master/CONTRIBUTING.md) before participating. 119 | 120 | ## Thanks 121 | 122 | Special thanks to: 123 | 124 | - Hoping new contributors 125 | 126 | ## License 127 | 128 | Steps is released under the MIT license. See [LICENSE](https://github.com/asam139/Steps/blob/master/LICENSE) for more information. 129 | -------------------------------------------------------------------------------- /Sources/Steps/Steps.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | import SwifterSwiftUI 4 | 5 | /// 🏄‍♂️ A navigation bar that guides users through the steps of a task. 6 | public struct Steps: View { 7 | 8 | /// The main state of the component 9 | @ObservedObject private(set) var state: StepsState 10 | 11 | /// Block to create each step 12 | let onCreateStep: (Element) -> Step 13 | 14 | /// Action when a step is selected by the user 15 | var onSelectStepAtIndex: ((Int) -> Void)? 16 | 17 | /// The style of the component 18 | let config: Config 19 | 20 | /// Helper to inspect 21 | let inspection = Inspection() 22 | 23 | /// Initializes a new `Steps`. 24 | /// 25 | /// - Parameter state: State to manage component 26 | /// - Parameter onCreateStep: Block to create each step 27 | public init(state: StepsState, config: Config = Config(), onCreateStep: @escaping (Element) -> Step) { 28 | self.state = state 29 | self.config = config 30 | self.onCreateStep = onCreateStep 31 | } 32 | 33 | /// Initializes a new separator 34 | /// 35 | /// - Parameters: 36 | /// - index: index of the step 37 | private func renderIndex(_ index: Int) -> some View { 38 | let element = state.data[index] 39 | var step = onCreateStep(element) 40 | step.index = index 41 | 42 | #if os(iOS) || os(watchOS) || os(macOS) 43 | let first = Item(step: step) 44 | .if(onSelectStepAtIndex != nil) { item in 45 | item.onTapGesture { 46 | self.onSelectStepAtIndex?(index) 47 | }.eraseToAnyView() 48 | } 49 | .eraseToAnyView() 50 | #elseif os(tvOS) 51 | let first = Item(step: step).eraseToAnyView() 52 | #endif 53 | 54 | let second: AnyView? = index < state.data.endIndex - 1 ? 55 | Separator(step: step).eraseToAnyView() : nil 56 | return ViewBuilder.buildBlock(first, second) 57 | } 58 | 59 | public var body: some View { 60 | HStack(alignment: .top, spacing: config.itemSpacing) { 61 | ForEach(state.data.indices, id: \.self) { index in 62 | self.renderIndex(index) 63 | } 64 | } 65 | .environmentObject(state) 66 | .environmentObject(config) 67 | .onReceive(inspection.notice) { self.inspection.visit(self, $0) } 68 | } 69 | } 70 | 71 | #if DEBUG 72 | struct Steps_Previews: PreviewProvider { 73 | static var previews: some View { 74 | let steps = ["First", "", ""] 75 | let state = StepsState(data: steps) 76 | return ( 77 | Steps(state: state, onCreateStep: { element in 78 | Step(title: element) 79 | }).padding() 80 | ) 81 | } 82 | } 83 | #endif 84 | 85 | // MARK: Builders 86 | extension Steps: Mutable { 87 | /// Set action when a step is selected by the user 88 | /// 89 | /// - Parameter action: new action to call when a step is selected 90 | @available(iOS 13.0, OSX 10.15, watchOS 6.0, *) 91 | @available(tvOS, unavailable) 92 | public func onSelectStepAtIndex(_ action: ((Int) -> Void)?) -> Self { 93 | return mutating(keyPath: \.onSelectStepAtIndex, value: action) 94 | } 95 | 96 | /// Adds space between each page 97 | /// 98 | /// - Parameter value: Spacing between elements 99 | public func itemSpacing(_ value: CGFloat) -> Self { 100 | config.itemSpacing = value 101 | return self 102 | } 103 | 104 | /// Modify size of each step element 105 | /// 106 | /// - Parameter value: Size of each step element 107 | public func size(_ value: CGFloat) -> Self { 108 | config.size = value 109 | return self 110 | } 111 | 112 | /// Modify the line thickness of all elements 113 | /// 114 | /// - Parameter value: Line thickness of all elements 115 | public func lineThickness(_ value: CGFloat) -> Self { 116 | config.lineThickness = value 117 | return self 118 | } 119 | 120 | /// Modify color for current and completed steps 121 | /// 122 | /// - Parameter value: Color for current and completed steps 123 | public func primaryColor(_ value: Color) -> Self { 124 | config.primaryColor = value 125 | return self 126 | } 127 | 128 | /// Modify color for text inside step element 129 | /// 130 | /// - Parameter value: Color for text inside step element 131 | public func secondaryColor(_ value: Color) -> Self { 132 | config.secondaryColor = value 133 | return self 134 | } 135 | 136 | /// Modify color for text inside step element 137 | /// 138 | /// - Parameter value: Color for uncompleted steps 139 | public func disabledColor(_ value: Color) -> Self { 140 | config.disabledColor = value 141 | return self 142 | } 143 | 144 | /// Modify color for text inside step element 145 | /// 146 | /// - Parameter value: Color for uncompleted steps 147 | public func defaultImage(_ value: Image) -> Self { 148 | config.defaultImage = value 149 | return self 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | This document contains information and guidelines about contributing to this project. Please read it before you start participating. 4 | 5 | **Topics** 6 | 7 | - [Asking Questions](#asking-questions) 8 | - [Ways to Contribute](#ways-to-contribute) 9 | - [Adding new code](#adding-new-code) 10 | - [Adding Tests](#adding-tests) 11 | - [Adding documentation](#adding-documentation) 12 | - [Adding changelog entries](#adding-changelog-entries) 13 | - [Reporting Issues](#reporting-issues) 14 | 15 | --- 16 | 17 | ## Asking Questions 18 | 19 | We don't use GitHub as a support forum. 20 | For any usage questions that are not specific to the project itself, please ask on [Stack Overflow](https://stackoverflow.com) instead with the tag Steps. 21 | By doing so, you'll be more likely to quickly solve your problem, and you'll allow anyone else with the same question to find the answer. 22 | This also allows us to focus on improving the project for others. 23 | 24 | --- 25 | 26 | ## Ways to Contribute 27 | 28 | You can contribute to the project in a variety of ways: 29 | 30 | - Improve documentation 🙏 31 | - Add more extensions 👍 32 | - Add missing unit tests 😅 33 | - Fixing or reporting bugs 😱 34 | 35 | If you're new to Open Source or Swift the Steps community is a great place to get involved. 36 | 37 | **Your contribution is always welcomed, no contribution is too small.** 38 | 39 | --- 40 | 41 | ## Adding new code 42 | 43 | Please refer to the following rules before submitting a pull request with your new extensions: 44 | 45 | - Make sure no similar extension already exist in Steps. 46 | - Add your contributions to [**master branch**](https://github.com/asam139/Steps/tree/master): 47 | - by doing this we can merge new pull-requests into **master** branch as soon as they are accepted, and add them to the next releases once they are fully tested. 48 | - Mention the original source of extension source (if possible) as a comment inside extension: 49 | 50 | ```swift 51 | public extension SomeType { 52 | 53 | public name: SomeType { 54 | // https://stackoverflow.com/somepage 55 | // .. code 56 | } 57 | 58 | } 59 | ``` 60 | 61 | - All methods should be well documented. see [Adding documentation](#adding-documentation) 62 | - All methods should be tested. See [Adding Tests](#adding-tests) to know more. 63 | - Classes and tests are ordered inside files in the following order: 64 | 65 | ```swift 66 | // MARK: - enums 67 | public enum { 68 | // ... 69 | } 70 | 71 | // MARK: - Properties 72 | public extension SomeType {} 73 | 74 | // MARK: - Methods 75 | public extension SomeType {} 76 | 77 | // MARK: - Initializers 78 | public extension SomeType {} 79 | ``` 80 | 81 | --- 82 | 83 | ## Adding Tests 84 | 85 | Please follow these guidelines before submitting a pull request with new tests: 86 | 87 | - Every extended Steps type should have one specific subclass of XCTestCase. 88 | - There should be a one to one relationship between methods/properties and their backing tests. 89 | - The subclass should be marked as final. 90 | - All extensions files and test files have a one to one relationship. 91 | - (example: all tests for "**StringExtensions.swift**" are found in the "**StringExtensionsTests.swift**" file) 92 | - Steps source files should not be added to the test target directly, but you should rather import Steps into the test target by using: @testable import Steps 93 | - Tests are ordered inside files in the same order as classes. 94 | 95 | --- 96 | 97 | ## Adding documentation 98 | 99 | Use the following template to add documentation for extensions 100 | > Replace placeholders inside <> 101 | 102 | > Remove any extra lines, eg. if method does not return any value, delete the `- Returns:` line 103 | 104 | ### Documentation template for units with single parameter 105 | 106 | ```swift 107 | /// Steps: . 108 | /// 109 | /// 110 | /// 111 | /// - Parameter : . 112 | /// - Throws: 113 | /// - Returns: 114 | ``` 115 | 116 | ### Documentation template for units with multiple parameters 117 | 118 | ```swift 119 | /// Steps: . 120 | /// 121 | /// 122 | /// 123 | /// - Parameters: 124 | /// - : . 125 | /// - : . 126 | /// - Throws: 127 | /// - Returns: 128 | ``` 129 | 130 | ### Documentation template for enums 131 | 132 | ```swift 133 | /// Steps: . 134 | /// 135 | /// - : 136 | /// - : 137 | /// - : 138 | /// - : 139 | ``` 140 | 141 | ### Documentation Examples 142 | 143 | ```swift 144 | 145 | /// Steps: Sum of all elements in array. 146 | /// 147 | /// [1, 2, 3, 4, 5].sum() -> 15 148 | /// 149 | /// - Returns: Sum of the array's elements. 150 | func sum() -> Element { 151 | // ... 152 | } 153 | 154 | /// Steps: Date by changing value of calendar component. 155 | /// 156 | /// - Parameters: 157 | /// - component: component type. 158 | /// - value: new value of component to change. 159 | /// - Returns: original date after changing given component to given value. 160 | func changing(_ component: Calendar.Component, value: Int) -> Date? { 161 | // ... 162 | } 163 | 164 | ``` 165 | 166 | ### Power Tip 167 | 168 | In Xcode select a method and press `command` + `alt` + `/` to create a documentation template! 169 | 170 | --- 171 | 172 | ## Adding changelog entries 173 | 174 | The [Changelog](https://github.com/asam139/Steps/blob/master/CHANGELOG.md) is a file which contains a curated, chronologically ordered list of notable changes for each version of a project. Please make sure to add a changelog entry describing your contribution to it every time there is a notable change. 175 | 176 | The [Changelog Guidelines](https://github.com/asam139/Steps/blob/master/CHANGELOG_GUIDELINES.md) contains instructions for maintaining (or adding new entries) to the Changelog. 177 | 178 | --- 179 | 180 | ## Reporting Issues 181 | 182 | A great way to contribute to the project is to send a detailed issue when you encounter a problem. 183 | We always appreciate a well-written, thorough bug report. 184 | 185 | Check that the project [issues page](https://github.com/asam139/Steps/issues) doesn't already include that problem or suggestion before submitting an issue. 186 | If you find a match, add a quick "**+1**" or "**I have this problem too**". 187 | Doing this helps prioritize the most common problems and requests. 188 | 189 | **When reporting issues, please include the following:** 190 | 191 | - What did you do? 192 | - What did you expect to happen? 193 | - What happened instead? 194 | - Steps version 195 | - Xcode version 196 | - macOS version running Xcode 197 | - Swift version 198 | - Platform(s) running Steps 199 | - Demo Project (if available) 200 | 201 | This information will help us review and fix your issue faster. 202 | 203 | --- 204 | 205 | ## [No Brown M&M's](http://en.wikipedia.org/wiki/Van_Halen#Contract_riders) 206 | 207 | If you made it all the way to the end, bravo dear user, we love you. You can include the 🚀 emoji in the top of your ticket to signal to us that you did in fact read this file and are trying to conform to it as best as possible: `:rocket:`. -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (6.1.7.3) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.4) 13 | public_suffix (>= 2.0.2, < 6.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | artifactory (3.0.15) 18 | atomos (0.1.3) 19 | aws-eventstream (1.2.0) 20 | aws-partitions (1.756.0) 21 | aws-sdk-core (3.171.0) 22 | aws-eventstream (~> 1, >= 1.0.2) 23 | aws-partitions (~> 1, >= 1.651.0) 24 | aws-sigv4 (~> 1.5) 25 | jmespath (~> 1, >= 1.6.1) 26 | aws-sdk-kms (1.63.0) 27 | aws-sdk-core (~> 3, >= 3.165.0) 28 | aws-sigv4 (~> 1.1) 29 | aws-sdk-s3 (1.121.0) 30 | aws-sdk-core (~> 3, >= 3.165.0) 31 | aws-sdk-kms (~> 1) 32 | aws-sigv4 (~> 1.4) 33 | aws-sigv4 (1.5.2) 34 | aws-eventstream (~> 1, >= 1.0.2) 35 | babosa (1.0.4) 36 | claide (1.1.0) 37 | cocoapods (1.12.1) 38 | addressable (~> 2.8) 39 | claide (>= 1.0.2, < 2.0) 40 | cocoapods-core (= 1.12.1) 41 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 42 | cocoapods-downloader (>= 1.6.0, < 2.0) 43 | cocoapods-plugins (>= 1.0.0, < 2.0) 44 | cocoapods-search (>= 1.0.0, < 2.0) 45 | cocoapods-trunk (>= 1.6.0, < 2.0) 46 | cocoapods-try (>= 1.1.0, < 2.0) 47 | colored2 (~> 3.1) 48 | escape (~> 0.0.4) 49 | fourflusher (>= 2.3.0, < 3.0) 50 | gh_inspector (~> 1.0) 51 | molinillo (~> 0.8.0) 52 | nap (~> 1.0) 53 | ruby-macho (>= 2.3.0, < 3.0) 54 | xcodeproj (>= 1.21.0, < 2.0) 55 | cocoapods-core (1.12.1) 56 | activesupport (>= 5.0, < 8) 57 | addressable (~> 2.8) 58 | algoliasearch (~> 1.0) 59 | concurrent-ruby (~> 1.1) 60 | fuzzy_match (~> 2.0.4) 61 | nap (~> 1.0) 62 | netrc (~> 0.11) 63 | public_suffix (~> 4.0) 64 | typhoeus (~> 1.0) 65 | cocoapods-deintegrate (1.0.5) 66 | cocoapods-downloader (1.6.3) 67 | cocoapods-plugins (1.0.0) 68 | nap 69 | cocoapods-search (1.0.1) 70 | cocoapods-trunk (1.6.0) 71 | nap (>= 0.8, < 2.0) 72 | netrc (~> 0.11) 73 | cocoapods-try (1.2.0) 74 | colored (1.2) 75 | colored2 (3.1.2) 76 | commander (4.6.0) 77 | highline (~> 2.0.0) 78 | concurrent-ruby (1.2.2) 79 | declarative (0.0.20) 80 | digest-crc (0.6.4) 81 | rake (>= 12.0.0, < 14.0.0) 82 | domain_name (0.5.20190701) 83 | unf (>= 0.0.5, < 1.0.0) 84 | dotenv (2.8.1) 85 | emoji_regex (3.2.3) 86 | escape (0.0.4) 87 | ethon (0.16.0) 88 | ffi (>= 1.15.0) 89 | excon (0.99.0) 90 | faraday (1.10.3) 91 | faraday-em_http (~> 1.0) 92 | faraday-em_synchrony (~> 1.0) 93 | faraday-excon (~> 1.1) 94 | faraday-httpclient (~> 1.0) 95 | faraday-multipart (~> 1.0) 96 | faraday-net_http (~> 1.0) 97 | faraday-net_http_persistent (~> 1.0) 98 | faraday-patron (~> 1.0) 99 | faraday-rack (~> 1.0) 100 | faraday-retry (~> 1.0) 101 | ruby2_keywords (>= 0.0.4) 102 | faraday-cookie_jar (0.0.7) 103 | faraday (>= 0.8.0) 104 | http-cookie (~> 1.0.0) 105 | faraday-em_http (1.0.0) 106 | faraday-em_synchrony (1.0.0) 107 | faraday-excon (1.1.0) 108 | faraday-httpclient (1.0.1) 109 | faraday-multipart (1.0.4) 110 | multipart-post (~> 2) 111 | faraday-net_http (1.0.1) 112 | faraday-net_http_persistent (1.2.0) 113 | faraday-patron (1.0.0) 114 | faraday-rack (1.0.0) 115 | faraday-retry (1.0.3) 116 | faraday_middleware (1.2.0) 117 | faraday (~> 1.0) 118 | fastimage (2.2.6) 119 | fastlane (2.212.2) 120 | CFPropertyList (>= 2.3, < 4.0.0) 121 | addressable (>= 2.8, < 3.0.0) 122 | artifactory (~> 3.0) 123 | aws-sdk-s3 (~> 1.0) 124 | babosa (>= 1.0.3, < 2.0.0) 125 | bundler (>= 1.12.0, < 3.0.0) 126 | colored 127 | commander (~> 4.6) 128 | dotenv (>= 2.1.1, < 3.0.0) 129 | emoji_regex (>= 0.1, < 4.0) 130 | excon (>= 0.71.0, < 1.0.0) 131 | faraday (~> 1.0) 132 | faraday-cookie_jar (~> 0.0.6) 133 | faraday_middleware (~> 1.0) 134 | fastimage (>= 2.1.0, < 3.0.0) 135 | gh_inspector (>= 1.1.2, < 2.0.0) 136 | google-apis-androidpublisher_v3 (~> 0.3) 137 | google-apis-playcustomapp_v1 (~> 0.1) 138 | google-cloud-storage (~> 1.31) 139 | highline (~> 2.0) 140 | json (< 3.0.0) 141 | jwt (>= 2.1.0, < 3) 142 | mini_magick (>= 4.9.4, < 5.0.0) 143 | multipart-post (~> 2.0.0) 144 | naturally (~> 2.2) 145 | optparse (~> 0.1.1) 146 | plist (>= 3.1.0, < 4.0.0) 147 | rubyzip (>= 2.0.0, < 3.0.0) 148 | security (= 0.1.3) 149 | simctl (~> 1.6.3) 150 | terminal-notifier (>= 2.0.0, < 3.0.0) 151 | terminal-table (>= 1.4.5, < 2.0.0) 152 | tty-screen (>= 0.6.3, < 1.0.0) 153 | tty-spinner (>= 0.8.0, < 1.0.0) 154 | word_wrap (~> 1.0.0) 155 | xcodeproj (>= 1.13.0, < 2.0.0) 156 | xcpretty (~> 0.3.0) 157 | xcpretty-travis-formatter (>= 0.0.3) 158 | ffi (1.15.5) 159 | fourflusher (2.3.1) 160 | fuzzy_match (2.0.4) 161 | gh_inspector (1.1.3) 162 | google-apis-androidpublisher_v3 (0.40.0) 163 | google-apis-core (>= 0.11.0, < 2.a) 164 | google-apis-core (0.11.0) 165 | addressable (~> 2.5, >= 2.5.1) 166 | googleauth (>= 0.16.2, < 2.a) 167 | httpclient (>= 2.8.1, < 3.a) 168 | mini_mime (~> 1.0) 169 | representable (~> 3.0) 170 | retriable (>= 2.0, < 4.a) 171 | rexml 172 | webrick 173 | google-apis-iamcredentials_v1 (0.17.0) 174 | google-apis-core (>= 0.11.0, < 2.a) 175 | google-apis-playcustomapp_v1 (0.13.0) 176 | google-apis-core (>= 0.11.0, < 2.a) 177 | google-apis-storage_v1 (0.19.0) 178 | google-apis-core (>= 0.9.0, < 2.a) 179 | google-cloud-core (1.6.0) 180 | google-cloud-env (~> 1.0) 181 | google-cloud-errors (~> 1.0) 182 | google-cloud-env (1.6.0) 183 | faraday (>= 0.17.3, < 3.0) 184 | google-cloud-errors (1.3.1) 185 | google-cloud-storage (1.44.0) 186 | addressable (~> 2.8) 187 | digest-crc (~> 0.4) 188 | google-apis-iamcredentials_v1 (~> 0.1) 189 | google-apis-storage_v1 (~> 0.19.0) 190 | google-cloud-core (~> 1.6) 191 | googleauth (>= 0.16.2, < 2.a) 192 | mini_mime (~> 1.0) 193 | googleauth (1.5.2) 194 | faraday (>= 0.17.3, < 3.a) 195 | jwt (>= 1.4, < 3.0) 196 | memoist (~> 0.16) 197 | multi_json (~> 1.11) 198 | os (>= 0.9, < 2.0) 199 | signet (>= 0.16, < 2.a) 200 | highline (2.0.3) 201 | http-cookie (1.0.5) 202 | domain_name (~> 0.5) 203 | httpclient (2.8.3) 204 | i18n (1.13.0) 205 | concurrent-ruby (~> 1.0) 206 | jmespath (1.6.2) 207 | json (2.6.3) 208 | jwt (2.7.0) 209 | memoist (0.16.2) 210 | mini_magick (4.12.0) 211 | mini_mime (1.1.2) 212 | minitest (5.18.0) 213 | molinillo (0.8.0) 214 | multi_json (1.15.0) 215 | multipart-post (2.0.0) 216 | nanaimo (0.3.0) 217 | nap (1.1.0) 218 | naturally (2.2.1) 219 | netrc (0.11.0) 220 | optparse (0.1.1) 221 | os (1.1.4) 222 | plist (3.7.0) 223 | public_suffix (4.0.7) 224 | rake (13.0.6) 225 | representable (3.2.0) 226 | declarative (< 0.1.0) 227 | trailblazer-option (>= 0.1.1, < 0.2.0) 228 | uber (< 0.2.0) 229 | retriable (3.1.2) 230 | rexml (3.2.5) 231 | rouge (2.0.7) 232 | ruby-macho (2.5.1) 233 | ruby2_keywords (0.0.5) 234 | rubyzip (2.3.2) 235 | security (0.1.3) 236 | signet (0.17.0) 237 | addressable (~> 2.8) 238 | faraday (>= 0.17.5, < 3.a) 239 | jwt (>= 1.5, < 3.0) 240 | multi_json (~> 1.10) 241 | simctl (1.6.10) 242 | CFPropertyList 243 | naturally 244 | terminal-notifier (2.0.0) 245 | terminal-table (1.8.0) 246 | unicode-display_width (~> 1.1, >= 1.1.1) 247 | trailblazer-option (0.1.2) 248 | tty-cursor (0.7.1) 249 | tty-screen (0.8.1) 250 | tty-spinner (0.9.3) 251 | tty-cursor (~> 0.7) 252 | typhoeus (1.4.0) 253 | ethon (>= 0.9.0) 254 | tzinfo (2.0.6) 255 | concurrent-ruby (~> 1.0) 256 | uber (0.1.0) 257 | unf (0.1.4) 258 | unf_ext 259 | unf_ext (0.0.8.2) 260 | unicode-display_width (1.8.0) 261 | webrick (1.8.1) 262 | word_wrap (1.0.0) 263 | xcodeproj (1.22.0) 264 | CFPropertyList (>= 2.3.3, < 4.0) 265 | atomos (~> 0.1.3) 266 | claide (>= 1.0.2, < 2.0) 267 | colored2 (~> 3.1) 268 | nanaimo (~> 0.3.0) 269 | rexml (~> 3.2.4) 270 | xcpretty (0.3.0) 271 | rouge (~> 2.0.7) 272 | xcpretty-json-formatter (0.1.1) 273 | xcpretty (~> 0.2, >= 0.0.7) 274 | xcpretty-travis-formatter (1.0.1) 275 | xcpretty (~> 0.2, >= 0.0.7) 276 | zeitwerk (2.6.8) 277 | 278 | PLATFORMS 279 | ruby 280 | 281 | DEPENDENCIES 282 | cocoapods 283 | fastlane 284 | xcpretty 285 | xcpretty-json-formatter 286 | 287 | BUNDLED WITH 288 | 2.4.12 289 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 97930A7D29FBDC6C0098E0A8 /* Steps in Frameworks */ = {isa = PBXBuildFile; productRef = 97930A7C29FBDC6C0098E0A8 /* Steps */; }; 11 | 9C3CACED2440EFCB0088C114 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3CACEC2440EFCB0088C114 /* ContentView.swift */; }; 12 | D9A12776235699C600D136A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9A12775235699C600D136A5 /* AppDelegate.swift */; }; 13 | D9A12778235699C600D136A5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9A12777235699C600D136A5 /* SceneDelegate.swift */; }; 14 | D9A1277D235699C600D136A5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D9A1277B235699C600D136A5 /* Main.storyboard */; }; 15 | D9A1277F235699C700D136A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D9A1277E235699C700D136A5 /* Assets.xcassets */; }; 16 | D9A12782235699C700D136A5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D9A12780235699C700D136A5 /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9C70629B24422A000061E8D8 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 97930A7B29FBDC3D0098E0A8 /* Steps */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Steps; path = ..; sourceTree = ""; }; 34 | 9C3CACEC2440EFCB0088C114 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 35 | D9A12772235699C600D136A5 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | D9A12775235699C600D136A5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | D9A12777235699C600D136A5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 38 | D9A1277C235699C600D136A5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | D9A1277E235699C700D136A5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | D9A12781235699C700D136A5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | D9A12783235699C700D136A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | D9A1276F235699C600D136A5 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 97930A7D29FBDC6C0098E0A8 /* Steps in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | D9A12769235699C600D136A5 = { 57 | isa = PBXGroup; 58 | children = ( 59 | 97930A7B29FBDC3D0098E0A8 /* Steps */, 60 | D9A12774235699C600D136A5 /* Sources */, 61 | D9A12773235699C600D136A5 /* Products */, 62 | D9A127892356A25E00D136A5 /* Frameworks */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | D9A12773235699C600D136A5 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | D9A12772235699C600D136A5 /* Example.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | D9A12774235699C600D136A5 /* Sources */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | D9A12775235699C600D136A5 /* AppDelegate.swift */, 78 | D9A12777235699C600D136A5 /* SceneDelegate.swift */, 79 | 9C3CACEC2440EFCB0088C114 /* ContentView.swift */, 80 | D9A1277B235699C600D136A5 /* Main.storyboard */, 81 | D9A1277E235699C700D136A5 /* Assets.xcassets */, 82 | D9A12780235699C700D136A5 /* LaunchScreen.storyboard */, 83 | D9A12783235699C700D136A5 /* Info.plist */, 84 | ); 85 | path = Sources; 86 | sourceTree = ""; 87 | }; 88 | D9A127892356A25E00D136A5 /* Frameworks */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | ); 92 | name = Frameworks; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | D9A12771235699C600D136A5 /* Example */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = D9A12786235699C700D136A5 /* Build configuration list for PBXNativeTarget "Example" */; 101 | buildPhases = ( 102 | D9A1276E235699C600D136A5 /* Sources */, 103 | D9A1276F235699C600D136A5 /* Frameworks */, 104 | D9A12770235699C600D136A5 /* Resources */, 105 | 9C70629B24422A000061E8D8 /* Embed Frameworks */, 106 | 9C7F4436244CB076005DC0C8 /* Lint */, 107 | ); 108 | buildRules = ( 109 | ); 110 | dependencies = ( 111 | ); 112 | name = Example; 113 | packageProductDependencies = ( 114 | 97930A7C29FBDC6C0098E0A8 /* Steps */, 115 | ); 116 | productName = "iOS Example"; 117 | productReference = D9A12772235699C600D136A5 /* Example.app */; 118 | productType = "com.apple.product-type.application"; 119 | }; 120 | /* End PBXNativeTarget section */ 121 | 122 | /* Begin PBXProject section */ 123 | D9A1276A235699C600D136A5 /* Project object */ = { 124 | isa = PBXProject; 125 | attributes = { 126 | LastSwiftUpdateCheck = 1110; 127 | LastUpgradeCheck = 1410; 128 | ORGANIZATIONNAME = "Saul Moreno Abril"; 129 | TargetAttributes = { 130 | D9A12771235699C600D136A5 = { 131 | CreatedOnToolsVersion = 11.1; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = D9A1276D235699C600D136A5 /* Build configuration list for PBXProject "Example" */; 136 | compatibilityVersion = "Xcode 9.3"; 137 | developmentRegion = en; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = D9A12769235699C600D136A5; 144 | productRefGroup = D9A12773235699C600D136A5 /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | D9A12771235699C600D136A5 /* Example */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | D9A12770235699C600D136A5 /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | D9A12782235699C700D136A5 /* LaunchScreen.storyboard in Resources */, 159 | D9A1277F235699C700D136A5 /* Assets.xcassets in Resources */, 160 | D9A1277D235699C600D136A5 /* Main.storyboard in Resources */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXResourcesBuildPhase section */ 165 | 166 | /* Begin PBXShellScriptBuildPhase section */ 167 | 9C7F4436244CB076005DC0C8 /* Lint */ = { 168 | isa = PBXShellScriptBuildPhase; 169 | alwaysOutOfDate = 1; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | ); 173 | inputFileListPaths = ( 174 | ); 175 | inputPaths = ( 176 | ); 177 | name = Lint; 178 | outputFileListPaths = ( 179 | ); 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "unset SDKROOT\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null && mint which swiftlint; then\n cd ..\n mint run realm/SwiftLint@0.51.0 swiftlint autocorrect --format\nelse\n echo \"warning: Mint not installed, download from https://github.com/yonaskolb/mint\"\nfi\n"; 185 | }; 186 | /* End PBXShellScriptBuildPhase section */ 187 | 188 | /* Begin PBXSourcesBuildPhase section */ 189 | D9A1276E235699C600D136A5 /* Sources */ = { 190 | isa = PBXSourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | D9A12776235699C600D136A5 /* AppDelegate.swift in Sources */, 194 | D9A12778235699C600D136A5 /* SceneDelegate.swift in Sources */, 195 | 9C3CACED2440EFCB0088C114 /* ContentView.swift in Sources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXSourcesBuildPhase section */ 200 | 201 | /* Begin PBXVariantGroup section */ 202 | D9A1277B235699C600D136A5 /* Main.storyboard */ = { 203 | isa = PBXVariantGroup; 204 | children = ( 205 | D9A1277C235699C600D136A5 /* Base */, 206 | ); 207 | name = Main.storyboard; 208 | sourceTree = ""; 209 | }; 210 | D9A12780235699C700D136A5 /* LaunchScreen.storyboard */ = { 211 | isa = PBXVariantGroup; 212 | children = ( 213 | D9A12781235699C700D136A5 /* Base */, 214 | ); 215 | name = LaunchScreen.storyboard; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXVariantGroup section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | D9A12784235699C700D136A5 /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_ANALYZER_NONNULL = YES; 226 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 228 | CLANG_CXX_LIBRARY = "libc++"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_ENABLE_OBJC_WEAK = YES; 232 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_COMMA = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 245 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 249 | CLANG_WARN_STRICT_PROTOTYPES = YES; 250 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 251 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 252 | CLANG_WARN_UNREACHABLE_CODE = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | COPY_PHASE_STRIP = NO; 255 | DEBUG_INFORMATION_FORMAT = dwarf; 256 | ENABLE_STRICT_OBJC_MSGSEND = YES; 257 | ENABLE_TESTABILITY = YES; 258 | GCC_C_LANGUAGE_STANDARD = gnu11; 259 | GCC_DYNAMIC_NO_PIC = NO; 260 | GCC_NO_COMMON_BLOCKS = YES; 261 | GCC_OPTIMIZATION_LEVEL = 0; 262 | GCC_PREPROCESSOR_DEFINITIONS = ( 263 | "DEBUG=1", 264 | "$(inherited)", 265 | ); 266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 268 | GCC_WARN_UNDECLARED_SELECTOR = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 270 | GCC_WARN_UNUSED_FUNCTION = YES; 271 | GCC_WARN_UNUSED_VARIABLE = YES; 272 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 273 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 274 | MTL_FAST_MATH = YES; 275 | ONLY_ACTIVE_ARCH = YES; 276 | SDKROOT = iphoneos; 277 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 278 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 279 | }; 280 | name = Debug; 281 | }; 282 | D9A12785235699C700D136A5 /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_ANALYZER_NONNULL = YES; 287 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 289 | CLANG_CXX_LIBRARY = "libc++"; 290 | CLANG_ENABLE_MODULES = YES; 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | CLANG_ENABLE_OBJC_WEAK = YES; 293 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_COMMA = YES; 296 | CLANG_WARN_CONSTANT_CONVERSION = YES; 297 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 300 | CLANG_WARN_EMPTY_BODY = YES; 301 | CLANG_WARN_ENUM_CONVERSION = YES; 302 | CLANG_WARN_INFINITE_RECURSION = YES; 303 | CLANG_WARN_INT_CONVERSION = YES; 304 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 305 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 306 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 309 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 310 | CLANG_WARN_STRICT_PROTOTYPES = YES; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 317 | ENABLE_NS_ASSERTIONS = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu11; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 328 | MTL_ENABLE_DEBUG_INFO = NO; 329 | MTL_FAST_MATH = YES; 330 | SDKROOT = iphoneos; 331 | SWIFT_COMPILATION_MODE = wholemodule; 332 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 333 | VALIDATE_PRODUCT = YES; 334 | }; 335 | name = Release; 336 | }; 337 | D9A12787235699C700D136A5 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | CODE_SIGN_STYLE = Automatic; 342 | DEVELOPMENT_TEAM = U96GUE2A6L; 343 | INFOPLIST_FILE = Sources/Info.plist; 344 | LD_RUNPATH_SEARCH_PATHS = ( 345 | "$(inherited)", 346 | "@executable_path/Frameworks", 347 | ); 348 | PRODUCT_BUNDLE_IDENTIFIER = "com.saulmorenoabril.steps.iOS-Example"; 349 | PRODUCT_NAME = "$(TARGET_NAME)"; 350 | SWIFT_VERSION = 5.0; 351 | TARGETED_DEVICE_FAMILY = "1,2"; 352 | }; 353 | name = Debug; 354 | }; 355 | D9A12788235699C700D136A5 /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 359 | CODE_SIGN_STYLE = Automatic; 360 | DEVELOPMENT_TEAM = U96GUE2A6L; 361 | INFOPLIST_FILE = Sources/Info.plist; 362 | LD_RUNPATH_SEARCH_PATHS = ( 363 | "$(inherited)", 364 | "@executable_path/Frameworks", 365 | ); 366 | PRODUCT_BUNDLE_IDENTIFIER = "com.saulmorenoabril.steps.iOS-Example"; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | SWIFT_VERSION = 5.0; 369 | TARGETED_DEVICE_FAMILY = "1,2"; 370 | }; 371 | name = Release; 372 | }; 373 | /* End XCBuildConfiguration section */ 374 | 375 | /* Begin XCConfigurationList section */ 376 | D9A1276D235699C600D136A5 /* Build configuration list for PBXProject "Example" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | D9A12784235699C700D136A5 /* Debug */, 380 | D9A12785235699C700D136A5 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | D9A12786235699C700D136A5 /* Build configuration list for PBXNativeTarget "Example" */ = { 386 | isa = XCConfigurationList; 387 | buildConfigurations = ( 388 | D9A12787235699C700D136A5 /* Debug */, 389 | D9A12788235699C700D136A5 /* Release */, 390 | ); 391 | defaultConfigurationIsVisible = 0; 392 | defaultConfigurationName = Release; 393 | }; 394 | /* End XCConfigurationList section */ 395 | 396 | /* Begin XCSwiftPackageProductDependency section */ 397 | 97930A7C29FBDC6C0098E0A8 /* Steps */ = { 398 | isa = XCSwiftPackageProductDependency; 399 | productName = Steps; 400 | }; 401 | /* End XCSwiftPackageProductDependency section */ 402 | }; 403 | rootObject = D9A1276A235699C600D136A5 /* Project object */; 404 | } 405 | --------------------------------------------------------------------------------