├── ModularArchitecture.md
├── README.md
├── ViperArchitecture.md
├── app-distribution.md
├── apple-developer-accounts.md
├── ci
├── bitrise-app-store-connect-api-key.md
├── bitrise-complete-guide-cloud-signing.md
├── bitrise-issues-readme.md
└── project.yml
├── images
├── ci-cloudSigning
│ ├── 0_1.png
│ ├── 1_1.png
│ ├── 2_1.png
│ ├── 2_2.png
│ ├── 2_3.png
│ ├── 2_4.png
│ ├── 2_5.png
│ ├── 3_1.png
│ ├── 3_2.png
│ ├── 4_1.png
│ ├── 4_2.png
│ ├── 5_1.png
│ ├── 5_2.png
│ └── 5_3.png
├── ci
│ ├── 01-new-app-bitrise.jpg
│ ├── 02-new-app-bitrise.jpg
│ ├── 03-new-app-bitrise.jpg
│ ├── 04-new-app-bitrise.jpg
│ ├── 05-new-app-bitrise.jpg
│ ├── 06-new-app-bitrise.jpg
│ ├── 07-new-app-bitrise.jpg
│ ├── 08-api-key.png
│ ├── 09-api-key.png
│ └── 10-api-key.png
└── nodes-viper-flow.png
├── migration-guide.md
├── styleguide.md
└── upload-checklist.md
/ModularArchitecture.md:
--------------------------------------------------------------------------------
1 |
2 | ## 📖 Modular Project architecture
3 |
4 | With the addition of SPM, we now aim to modularise our projects as much as possible. The [iOS project template](https://github.com/nodes-ios/ios-template) gives a great starting point on how to do this. Our aim is to modularise all code, meaning all code can be set in standalone features and modules that can be run and tested independetly in isolation from the rest of the code base. This approach forces us to build simpler code that is less likely to have hidden implicit dependencies that may complicate features unnecessarily.
5 |
6 | Before developing with this modularised approach, you should watch both part 1 & 2 of the [point free episodes](https://www.pointfree.co/episodes/ep171-modularization-part-1), which explain more on the advantages of this approach and how to do it.
7 |
8 | --
9 |
10 | ### Project structure
11 |
12 | The template project consists of several base modules
13 |
14 | - APIClient Module
15 | - In charge of handling any communication to backend APIs for any required configurations
16 | - Ability to provide mock & preview responses
17 |
18 | - APIClientLive Module
19 | - Holds all the DTO models
20 | - Handles building required URLRequests with authentication
21 | - Handles the decoding of request responses
22 |
23 | - AppFeature Module
24 | - The main entry point module for the application
25 |
26 | - Localizations Module
27 | - Handles localizations across the app, in most cases using NStack
28 | - Provides ObservableLocalizations class allowing for SwiftUI to use Localizations confirming to the `ObservableObject` protocol
29 |
30 | - Model Module
31 | - Contains all models used for managing data
32 |
33 | - NetworkClient Module
34 | - A helper class to observe network changes
35 |
36 | - PersistenceClient Module
37 | - For persisting/caching data throughout the application
38 |
39 | - Style Module
40 | - For managing and accessing Fonts, Colors, Assets
41 | - For core standard common UI such as Alert & ButtonStyles
42 |
43 | The template has example modules of features your app may require;
44 | - LoginFeature Module
45 | - MainFeature Module
46 |
47 | Other more straightforward modules include;
48 | - AppVersion Module
49 | - Helpers Module
50 |
51 | --
52 |
53 | Summary:
54 |
55 | - Separation of concerns and decoupling
56 | - Improved reusability
57 | - Improved testability
58 |
59 | ## Testing
60 |
61 | Using a modular architecture, everything is easy to test since it is based on SRP (Single Responsibility Principle). The template contains great examples on how to add test targets to the package.swift file.
62 |
63 | ```swift
64 | .testTarget(
65 | name: "LoginFeatureTests",
66 | dependencies: [
67 | "LoginFeature", .product(name: "CombineSchedulers", package: "combine-schedulers"),
68 | ]),
69 | ```
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This document is meant for developers. It shows how we work at Monstarlab (IM).
2 |
3 | # Monstarlab (IM) iOS
4 |
5 |
6 | We love what we do and care about the products we create. They are a team effort, together with our designers, backend developers, project managers, and all the other people at Monstarlab.
7 |
8 | We try to write clean code and separate the pieces of code that occur often in our apps into separate libraries that can be reused in other projects. Our open source activity can be found on [GitHub](https://github.com/nodes-ios).
9 |
10 | We try to increase our contribution to the iOS developer community through:
11 |
12 | - abstracting relevant parts of our code and turning them into open source libraries that other developers can use
13 | - encouraging our team members to join iOS related meetups, groups and conferences
14 | - encouraging our team members to speak at iOS events
15 | - contributing back to some of the other open source libraries we use through issues and PRs
16 | - encouraging our team members to write tech articles about any topic they desire on our [engineering blog](https://engineering.nodesagency.com/); if needed, we can also suggest topics for new articles
17 |
18 | We create lots of apps for our clients. We strive to keep all our applications architecture as modern, consistent and familiar to all developers as possible. We should build all new projects using our [iOS project template](https://github.com/nodes-ios/ios-template), making use of the [Modular MVVM pattern](./ModularArchitecture.md) using SwiftUI & SPM. We should assure all code is tested to a high level where possible.
19 |
20 | # Team meetings
21 | Here are the recurring meetings the iOS team takes part in:
22 |
23 | #### iOS Team Meeting
24 | This is the main meeting in the iOS team, where we talk about the latest things happening in our team. The programme normally consists of assessing the general direction of the team, quick updates from each member of the team what they are working on, test of the day (where someone presents a test they have written in the past two weeks) & somebody presenting an approach or some work they have done. It gives the team an opportunity to discuss work and approaches so we can all learn from one another and improve our ways of working. This happens bi-weekly every Thursday morning.
25 |
26 | #### Monthly CTO Update
27 |
28 | Each month there will be an update from the CTO Office covering regional status' from APAC, EMEA and AMER as well as progress updates on initiatives being run by the CTO office.
29 |
30 |
31 | # Your First Day
32 |
33 | The IT department is responsible for setting up accounts and giving you access to the tools you need to do your job. The easiest way to contact the IT department is via [Zendesk](https://monstarlab-internal.zendesk.com/hc/en-us).
34 |
35 | - Meet with the Head of iOS & your Team Manager. They will be your first persons of reference in the company. Use them as much as you need to get settled in, but also be considerate of their own time.
36 | - Join [our private git](https://github.com/nodes-projects). Ask IT if you don't have access.
37 | - Get the cert sign request and private key from [this private repo here](https://github.com/nodes-projects/keyring-ios). Add the private key to your Keychain and from now on, always use this certificate signing request when creating new certificates on the developer portal.
38 | - Install the latest Xcode. Ideally, from the [Apple developer portal](https://developer.apple.com/download/more/) and not from the App Store. If you got it from the App Store, that's ok too.
39 | - Get another iOS developer to invite you to our developer accounts.
40 | - We want you to help us make great open source software. So get another iOS developer to invite you to [our GitHub organisation](https://github.com/nodes-ios). Make sure you enable your 2FA on GitHub. Our client projects are on [our private git](https://github.com/nodes-projects) and our public ones are on [GitHub](https://github.com/nodes-ios).
41 | - Set up your git keys properly, so you can pull and push from both git servers.
42 | - You should have been invited to our Slack and JIRA accounts. Ask IT if you don't have access.
43 | - Make sure to read the [IM Technology Confluence](https://monstarlab.atlassian.net/wiki/spaces/CTO/overview). It will provide some extra information regarding each practices covered in Monstarlab IM.
44 |
45 | # Tools We Use
46 |
47 | For employee management, we use [BambooHR](https://nodesagency.bamboohr.com/home). Ask IT if you don't have access and the Chief People Officer if anything in there is unclear. Use this to request time off, register sick day and manage your personal info and banking info for salary payments.
48 |
49 | Our main communication tool is [**Slack**](https://nodes.slack.com). We use this for daily communication.
50 |
51 | Our main project management tool is [**JIRA**](https://nodesagency.atlassian.net/jira/). Each project has its own JIRA board. Always keep your project's JIRA board up to date to reflect the current status of the project. We work in an agile manner and move fast, but the JIRA board must clearly reflect the project's status. Ask your project manager if you have questions.
52 |
53 | With the same JIRA account, you will also have access to Confluence, where you can find project-related documentation, and also internal documents.
54 |
55 | [**MPlanner**](https://nplanner.io/) is the tool we use to manage allocation, time tracking and project finance. You can use it to see which project you are assigned to and also track your hours spent on the project. Make sure to always track your time on the appropriate project. Ask the PM on which project you should track. We expect you to track 7.5 hours per day (8 in Germany). Lunchtime is not tracked, and it doesn't count towards the daily work hours.
56 |
57 | Use MPlanner to see what project you're assigned to that day. You can also see what other people are assigned to, how long a project should take, when other people are on vacation, etc. Project managers also use MPlanner to see if a project goes over budget (more hours were tracked than allocated). But that's not something you as a developer should care about.
58 |
59 | [**Postman**](https://www.getpostman.com/) is a great tool which helps you see and test web APIs. Our backend team uses Postman to test and document their APIs, so all the web APIs for our apps can be found in Postman. You can use it to see the different endpoints available, read their documentation or make requests to those endpoints. Ask IT if you don't have access.
60 |
61 | To ease the collaboration between designers and developers, we use [**Figma**](https://www.figma.com/). In Figma, you can see each screen in the design, get info about the sizes, padding, fonts, colours and also export assets for the mobile devices. You can also add notes in Zeplin and communicate with your designer directly on the project.
62 |
63 | [**Charles**](https://www.charlesproxy.com/) is an HTTP proxy. You can set your phone / simulator to proxy through Charles and you can see all the API calls it made, you can inspect its requests or responses. It comes in very handy especially if you work with an external, poorly documented API. Ask IT if you need a license.
64 |
65 | For in-house distribution and crash reporting, we use [**Firebase**](firebase.google.com/). Depending on the project, sometimes we only use TestFlight. Talk to your PM or with previous developers on that project.
66 |
67 | Modern apps should use SPM where possible to manage all dependencies. Older projects may use Carthage or CocoaPods but we should try to use SPM if possible as we believe it is the easiest to use, maintain and manage.
68 |
69 | The way we do localization in Monstarlab(IM) is a bit different than in other places. We use [**NStack**](https://nstack.io/), a service we built, which together with the [**NStack SDK**](https://github.com/nstack-io/nstack-ios-sdk) offers dynamic localization for our apps. Go to [nstack.io](https://nstack.io/), log in with your Nodes account, select the app you need (or create a new one), go to "Localize" and add new translations or edit the current ones. A translation consists of a key (by which the string is recognized through the SDK) and one or more values (depending on the number of languages your app is translated to). The translations can be changed in NStack and the changes will reflect in the app without the need for an app update.
70 |
71 |
72 | # Coding Guidelines
73 | Make sure to read our [**Swift Style Guide**](styleguide.md).
74 | We use [**Conventional commits**](https://www.conventionalcommits.org/en/v1.0.0) in our commit messages. Mostly `fix:` and `feat:` but feel free to use others as well. These are used to extract change logs.
75 |
76 | # Continuous Integration (CI)
77 |
78 | As a digital agency, we build hundreds of apps during the day and to manage all these builds we are using Bitrise as a CI tool to speed up our deployment process. The most modern way to setup CI and signing is using cloud signing.
79 |
80 | Please refer to our CI setup guide [here](https://github.com/nodes-ios/Playbook/blob/master/ci/bitrise-complete-guide-cloud-signing.md#app-store-setup).
81 |
82 | We use SonarQube to monitor code quality. We should run Bitrise on PullRequests, that runs and asserts all tests pass and reports code coverage to SonarQube. For more information on how to setup SonarQube with your Bitrise project, see [here](https://monstarlab.atlassian.net/wiki/spaces/CTO/pages/7591755784/SonarQube+Setup).
83 |
84 | ***Troubleshooting***
85 | We also listed the most common issues when Bitrise is trying to make the builds. Please check the [common issues document](./ci/bitrise-issues-readme.md) if you are having some problems.
86 |
87 |
88 | # Working with Remote Colleagues
89 |
90 | We are often required to work in teams remotely. Communication is key, more so than ever when this is the case. Our main means of communication inside the company is Slack. Make sure to rely as much as possible on JIRA.
91 |
92 | It helps a lot to try and change your mindset from synchronous communication to asynchronous communication. This means that you try to foresee any problems you might run into and ask about info for them before you actually run into them. Try not to get into a situation where you need an answer from someone to be able to continue.
93 |
94 | Always have a good overview of what's left to be done and what you need in order to get it done.
95 |
96 | If you need something (an image asset, some input from the PM, etc), make a JIRA card asking for that.
97 |
98 | If there's some feedback from the client but you're not sure what to do about it, because there are multiple options, comment on the JIRA issue presenting what options there are, estimating how much time each of it would take and maybe advising towards one of it and ask the PM to take the decision.
99 |
100 | If in doubt, ask for a video call to discuss things. However, try to write down the notes / conclusions of the meeting, so you have them in writing. This helps you not forget anything and it also helps to align everyone on the team to the same conclusion.
101 |
102 | # Further reading
103 |
104 | * [VIPER architecture](./ViperArchitecture.md)
105 | * [Modular architecture](./ModularArchitecture.md)
106 | * [Code Modes](https://github.com/nodes-projects/readme/blob/master/mobile/ios/code-modes.md) (private document)
107 | * [Securing your app](https://github.com/nodes-projects/readme/tree/master/security) (private document)
108 | * Consider joining one or more of our [squads](https://github.com/nodes-projects/squads) (private document)
109 |
--------------------------------------------------------------------------------
/ViperArchitecture.md:
--------------------------------------------------------------------------------
1 |
2 | ## 📖 Project architecture
3 |
4 | Most of Nodes applications are architected using 🐍 Viper Architecture.
5 | Architecture templates can be [found here](https://github.com/nodes-ios/VIPERCoordinatorsXcodeFileTemplate)
6 |
7 |
8 | ### Project structure
9 |
10 | The project consists in a 5 layer architecture.
11 |
12 | - Coordinator:
13 | - Contains the navigation logic flow and holds the reference of the views to present.
14 | - Also creates the whole scene and injects the dependencies.
15 | - Interactor:
16 | - Where all the business logic goes specific by each use case.
17 | - Presenter:
18 | - It's a bridge between the Interactor(s) and the View Controller.
19 | - Its responsibility is to prepare the data for display.
20 | - ViewController:
21 | - Displays the content and handles the UI events to the presenter.
22 | - Models:
23 | - Provides the model for the interactor.
24 | - It's used to share the data between the layers.
25 |
26 |
27 | Each layer has it's own accessor from outside. We call it `input` and `output`.
28 |
29 | To interact with `input's` and `output's` we use protocols. It means that the objects will be restricted to the actions described in the protocols bringing more reliability and type safety to our project.
30 |
31 | --
32 |
33 | **The coordinator** (or Router) is responsible to instantiate and initilize the views during the app lifecycle when needed.
34 | One coordinator takes care of one or more scenes. It means that we don't have to create one coordinator for each scene.
35 | We can have one coordinator taking care of the same group of views.
36 |
37 | Eg.: Profile create, profile edit and profile display.
38 |
39 | --
40 |
41 | **Interactor** holds the business logic for a specific data model. It has the connection with the Models to manipulate the data and it should be totaly independent from the UI.
42 |
43 | One of it's responsibilities is to make the API call's and parse the received data to the models.
44 | When building Interactors we create one Interactor per data model. For example each application that has an User object will have a single UserInteractor.
45 | All the data is provided trough the input's and output's.
46 |
47 | --
48 |
49 | **Presenter** is responsible to handle the view interactions and drive the UI. It receives all the user actions on the UI and drives the app to the correct path. It's also their responsibility to make the calls for the other layers in order to get the data or to navigate to another flow. It also has its accessors defined by the input and output protocols.
50 |
51 | One Presenter can have multiple Interactors.
52 |
53 | --
54 |
55 | **View Controller** manages the interface for the user. The view never asks the presenter for data. It's passive and waits until the presenter gives the data back to be presented in the view. All the events should be send to the presenter. When the data is ready, the presenter will invoke the output method from the view controller with the result of the request.
56 |
57 | --
58 |
59 | **Models** It's not just data structures. We use the models to share the data between the different layers of the scene.
60 |
61 | Eg.: The interactor makes a API call and receives the json file. The data will be parsed and stored in memory in the models and it can be accessed on presenter layer.
62 |
63 |
64 | ## Flow control
65 |
66 | 
67 |
68 | ## Protocols
69 |
70 | In order to notify the objects through the app, we use protocols.
71 | Be careful when defining your protocols to avoid retain cycles. Make your protocols conform to `class` protocol to its declaration and use `weak` reference when declaring your variables. Keep in mind that not all delegates should be declared as `weak`, you may need some reference to that protocols during the view lifecycle and those objects should not be deallocated.
72 |
73 | Eg.:
74 |
75 | ```
76 | protocol MyCoordinatorInput: class { }
77 | weak var coordinator: MyCoordinatorInput?
78 |
79 |
80 | Example of strong references:
81 |
82 | ViewController >> PresenterInput
83 | PresenterInput >> InteractorInput
84 |
85 | Example of weak references:
86 |
87 | Presenter >> CoordinatorInput
88 | Coordinator >> ViewController
89 | ```
90 |
91 | ## Testing
92 |
93 | Using Viper architecture, everything is easy to test since it's proposal is based on SRP (Single Responsibility Principle).
94 |
95 | For automated tests we suggest you to use Quick and Nimble framework.
96 |
--------------------------------------------------------------------------------
/app-distribution.md:
--------------------------------------------------------------------------------
1 | # Nodes iOS app distribution
2 |
3 | # 📱
4 |
5 | ### Overview
6 | The purpose of this document is to describe in detail the process for distributing iOS apps. All projects will follow this set of guidelines, and any deviations will be concidered exceptions and handled on a case-by-case basis.
7 |
8 | ### Technologies:
9 | ##### Bitrise.io:
10 |
11 | Continuous Integration system that automates the building and deployment process. Developers will configure their project for use with Bitrise and builds will be automatically deployed based on this configuration. Developers will not archive and upload builds locally from their own computer, or using their own accounts.
12 |
13 | ##### TestFlight
14 |
15 | App distribution for all other purposes. Builds for the Monstarlab QA team that require custom entitlements, as well as *all* external and/or builds for Monstarlab clients will be distributed via TestFlight. Note that this requires clients to have at least one Apple ID logged in on a device with the TestFlight app.
16 |
17 | TestFlight builds can be tested by two groups: internal and external testers. Internal testers can test builds as soon as they are available, but must have their Apple ID added to the App Store Connect team. External testing requires a quick one-time review from Apple before being available. Testers can be invited by e-mail alone and do not need to be invited to the App Store Connect team.
18 |
19 |
20 | ##### App Store
21 |
22 | The live app distribution platform managed by Apple. Builds that have been uploaded to iTunes Connect can be submitted for review by Apple. After review, the app is ready for sale in the store (or pending release if that option is enabled). Only apps that have been approved for release by QA will be submitted for review by Apple. Note that any builds uploaded for TestFlight can be submitted for review, so be very careful that *only* release-tested builds are submitted.
23 |
--------------------------------------------------------------------------------
/apple-developer-accounts.md:
--------------------------------------------------------------------------------
1 | # Apple Developer Accounts
2 | # 🌟
3 |
4 | * Create an Apple ID on your Nodes email
5 | * Have Service Desk add you to the project you need by making an Access Request (Internal) request here https://nodes-internal.zendesk.com/
6 | * Remember to ask for access to create certificates and provisioning profiles (there's an extra checkbox they have to check if they add you as Developer)
7 | * Service Desk will monitor certificate expiry (mostly for push notifications)
8 | * QA / Service Desk are responsible for releases. They will create screenshots, what's new in version, etc and take care of the AppStore Connect page
9 | * CI should be setup to upload production builds to Test Flight. You shouldn't have to build and upload manually. If someone asks you to do that, ask for more time to setup CI correctly.
10 |
11 |
--------------------------------------------------------------------------------
/ci/bitrise-app-store-connect-api-key.md:
--------------------------------------------------------------------------------
1 | # App Store Connect API key - Bitrise setup
2 |
3 | To upload builds from Bitrise to App Store Connect, we need to authenticate the connection between Bitrise and App Store. Preferably this is done using the App Store Connect API key.
4 |
5 | The setup has 3 stages:
6 | - [1. Create the App Store Connect API Key](#1.-Create-the-App-Store-Connect-API-Key)
7 | - [2. Upload the App Store Connect API key to Bitrise](#2.-Upload-the-App-Store-Connect-API-key-to-Bitrise)
8 | - [3. Adjust the Bitrise Upload step](#3.-Adjust-the-Bitrise-Upload-step)
9 |
10 | ## 1. Create the App Store Connect API Key
11 |
12 | [Apple documentation.](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api)
13 |
14 | The API key can be created only by the Apple Dev Team Account Holder. You will need to contact this person (probably the client) to do it for you.
15 |
16 | To create the API key, go to:
17 | - https://appstoreconnect.apple.com
18 | - Users and Access
19 | - Keys
20 | - App Store Connect API
21 |
22 | and create a new API key with Admin privileges. Download and save the key (it is a .p8 file and can be downloaded only once).
23 | ❗️DO NOT CHANGE THE FILE NAME, OTHERWISE IT WILL BREAK THE STEP IN THE CI ❗️. The file format should be AuthKey_XXXXXXXX.p8.
24 |
25 | You will also need the issuer ID from this page.
26 |
27 | **Once you have downloaded the key, please send it to Martin Majer at Service Desk or the IT team and tell them to put it in the API key 1password vault - as a Secure note, together with the issuer and key ID.**
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## 2. Upload the App Store Connect API key to Bitrise
35 |
36 | Go to:
37 | - Your app on Bitrise
38 | - Workflow
39 | - Code signing tab
40 |
41 | Upload the .p8 file to the Generic File Storage and give it a name that you will later fill in the Upload step. Also make the file protected so no one can download it.
42 |
43 |
44 |
45 |
46 |
47 | ## 3. Adjust the Bitrise Upload step
48 |
49 | Find the "Deploy to iTunes Connect" step in your Workflow.
50 |
51 | Set these input vars:
52 | - Bitrise App Developer Connection - off
53 | - API Key URL - the variable name of the .p8 file you uploaded to the Generic File Storage in the previous step.
54 | - API Key Issuer ID - can be obtained at App Store Connect ona the webpage with the API key (see above).
55 | - Email and Password - remove these. They will probably cause an error otherwise.
56 |
57 |
58 |
59 |
60 |
61 | All set and done.
62 |
--------------------------------------------------------------------------------
/ci/bitrise-complete-guide-cloud-signing.md:
--------------------------------------------------------------------------------
1 | # Monstarlab iOS Bitrise - Setup Guide with Cloud Code Signing ☁️
2 |
3 | This is a simple Bitrise app setup guide that makes use of Apple cloud signing. This allows us to simplify the setup tremendously.
4 |
5 | ## How does cloud signing work?
6 | Cloud signing means the code signing files are managed on Apple servers. You do not need to set them up locally or use Fastlane Match. The code signing files are fetched from Apple servers and you just need an Apple dev account with sufficient access rights (Admin or Developer with access to certificates - including cloud certificates).
7 |
8 | Cloud code signing makes use of the `xcodebuild` command which is also used internally in the Bitrise archiving step. You need these 3 things to create a code signed .ipa file using this command:
9 | - xarchive of your app - initial archive. Before the proper code signing files are fetched from Apple, this archive is created and signed using a development certificate. Later it is resigned using the proper code signing files.
10 | - exportOptions.plist - just a plist specifying the configuration options like team id or distribution method. Handled by the Bitrise step.
11 | - App Store Connect API key - to authenticate with Apple and download code signing files. You need to create this and upload it to your profile in Bitrise.
12 |
13 |
14 |
15 |
16 |
17 | Once all of these are passed to the command, the right code signing files are fetched from the Apple servers and used to code sign the app. The resulting .ipa file is then uploaded to App Store Connect in the upload Bitrise step.
18 |
19 | More on cloud signing [here](https://developer.apple.com/videos/play/wwdc2021/10204/).
20 |
21 | ## Features of this setup:
22 | - Easy setup in the Bitrise GUI
23 | - Builds two Xcode configurations: Release and Test
24 | - Uses App Store Connect API key to code sign and send builds to Testflight
25 | - Deploys builds to Testflight only
26 | - Builds triggered by commit tags - `*`, `*_release` and `*_test`
27 | - Sends a success message to Slack - does not send anything on failure
28 | - Works with CocoaPods and SPM. Carthage is dead ☠️
29 |
30 | ## Steps:
31 | - [1. App Store setup](#App-Store-setup)
32 | - [2. Xcode setup](#Xcode-setup)
33 | - [3. App setup on Bitrise](#App-setup-on-Bitrise)
34 | - [4. Environment vars setup](#Environment-vars-setup)
35 | - [5. Secrets setup](#Secrets-setup)
36 | - [6. Code signing](#Code-signing)
37 | - [7. App Store Connect Authentication](#App-Store-Connect-Authentication)
38 |
39 | ## App Store setup
40 | Make sure you have all of these set up in App Store:
41 | - 2 apps for both for the release and test environments
42 | - Production certificate and provisioning profiles for all the apps (including the entitlements)
43 |
44 | ❗️Important❗️
45 | If you migrate from using Fastlane Match, it is probably wise to nuke all the certificates and provisioning profiles and remake them from scratch. Using the old ones can result in getting a *This App Cannot be installed because its integrity could not be verified* error when trying to install the app from TestFlight.
46 |
47 | ## Xcode setup
48 |
49 | Make sure your project's schemes are ***shared (including app extensions)***.
50 | Enable ***Automatically manage signing*** for all the configurations you want to build in the CI. If you do not do this, you will get failures in the Archive step.
51 |
52 |
53 |
54 |
55 |
56 |
57 | ## App setup on Bitrise
58 |
59 | 1) Create a new App from the Bitrise dashboard
60 | - Make sure you choose **Monstarlab** account when creating the new app and make it **Private**
61 |
62 |
63 |
64 |
65 |
66 | 2) Connect the repository to Bitrise
67 |
68 |
69 |
70 |
71 |
72 |
73 | 3) When setting up the access make sure to add our SSH key using the private key found in the [ci-resources-ios](https://github.com/nodes-projects/ci-resources-ios/blob/master/privatekey) repository.
74 |
75 |
76 |
77 |
78 |
79 |
80 | 4) Indicate which branch Bitrise should use to make the builds. Usually develop.
81 |
82 | 5) After choosing the branch, Bitrise will validate the source code and then you can select the *export method* you want to use. Go with ***app store***. Bitrise should also autodetect your project file and scheme.
83 |
84 |
85 |
86 |
87 |
88 | 6) The last step is the Webhook setup. This is used to trigger the builds by tags. Tap on **Register a webhook for me**. If the automatic registration does not work, you can [setup the webhook manually](https://devcenter.bitrise.io/en/apps/webhooks/adding-incoming-webhooks.html#adding-incoming-webhooks) now or later. You need to have the admin rights for the Github repository of your app for this though.
89 |
90 |
91 |
92 |
93 |
94 | ## Environment vars setup
95 | In order to make everything work on Bitrise you'll need to copy the template content from [bitrise.yml](https://github.com/nodes-projects/ci-resources-ios/blob/master/bitrise_cloud_signing.yml) into the Bitrise workflow script.
96 |
97 |
98 |
99 |
100 |
101 | Go to **Workflow > Env** Vars tab.
102 | You should see all your environment variables here.
103 | - SLACK_CHANNEL - name of the Slack channel to send a message when the build succeeds
104 | - RELEASE_BUILD_CONFIGURATION and TEST_BUILD_CONFIGURATION - change only if you have different names in your project.
105 | - BITRISE_PROJECT_PATH and BITRISE_SCHEME - change them from placeholders to your project's values.
106 | - RUN_TESTS_BEFORE_BUILDING - change to true if you want to run tests in your build workflows. This increases the build time significantly.
107 | Leave the BUILD_CONFIGURATION empty. This is assigned in the preparation steps during building.
108 |
109 |
110 |
111 |
112 |
113 | ## Secrets setup
114 | Go to the **Workflow > Secrets** tab. Add these secrets:
115 | - BUILD_TRIGGER_API_TOKEN - Can be found in the **Integrations** section of **App Setting** on Bitrise. Look for the Token under Build trigger API. This is needed to trigger the build workflows.
116 | - SLACK_TOKEN - Used to send notifications after successful builds. The token is universal for the Monstarlab Slack Workspace (I think). Can be found [here](https://github.com/nodes-projects/ci-resources-ios/blob/master/slackToken) or copied from another app on Bitrise.
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | ## Code signing
127 | You only need to upload the development signing certificate to Bitrise. This is used to code sign an initial archive before the code signing files are downloaded from Apple and the app is resigned with them. Even this development certificate could be omitted theoretically because the cloud code signing can create a new one if it does not find it locally. This, however, creates multiple new development certificates with every new build so it is better to upload it to Bitrise.
128 |
129 | 1) Create a development certificate. You can do this either using Fastlane Match or manually on the Apple developer portal.
130 |
131 | 2) Find the cert in your Keychain. Make sure to open it from the "My certificates" tab, not the "All items" tab. You might get an "Error getting details of uploaded file" while uploading the file to Bitrise later otherwise. Expand it and right tap on the private key. Select ***Export ***. If you do not see the private key, you downloaded a certificate without it. You can either find the person who created the certificate and get the private key from them or you can create a new certificate.
132 |
133 |
134 |
135 |
136 | 5) Create a password for the exported key. You will need to fill it later into Bitrise.
137 | 6) In Bitrise, go to **Workflow > Code signing** and upload the certificate. Fill in the password and make it protected by clicking on the three dots and selecting **Make protected**.
138 |
139 |
140 |
141 |
142 |
143 |
144 | ## App Store Connect API Authentication
145 | ASC API key is used to authenticate with the ASC to fetch code signing files and send the builds to the App Store. The ASC key configuration is slightly different than the one we used in past.
146 |
147 | In short, you need to create the key in ASC and then upload it to your profile in Bitrise. Once you have it uploaded to your profile, you need to select this connection in your Bitrise app. This is considered your private connection, there is no way to set this up for an entire organization afaik. Once this connection si set in the app, the builds triggered by other members of the team can use it too though.
148 |
149 | #### 1. Create the App Store Connect API Key
150 |
151 | [Apple documentation.](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api)
152 |
153 | The API key authentication can be allowed only by the Apple Dev Team Account Holder. You will need to contact this person (probably the client) to do it for you. Once this kind of authentication is allowed, the account's admins can create the keys themselves.
154 |
155 | To create the API key, go to:
156 | - https://appstoreconnect.apple.com
157 | - Users and Access
158 | - Keys
159 | - App Store Connect API
160 |
161 | and create a new API key with Admin privileges. Download and save the key (it is a .p8 file and can be downloaded only once).
162 | ❗️DO NOT CHANGE THE FILE NAME, OTHERWISE IT WILL BREAK THE STEP IN THE CI ❗️. The file format should be AuthKey_XXXXXXXX.p8.
163 |
164 | You will also need the issuer ID and the key ID from this page.
165 |
166 | **Once you have downloaded the key, please send it to Martin Majer at Service Desk or the IT team and tell them to put it in the API key 1password vault - as a Secure note, together with the issuer and key ID.**
167 |
168 |
169 | #### 2. Upload the App Store Connect API key to Bitrise
170 | Next you need to upload the obtained .p8 key file to the Monstarlab organization on Bitrise and set this connection in your app's Team tab. The key works universally for all apps in one Apple Dev Team and can be therefore reused for multiple apps from the same Apple Dev Team.
171 |
172 | You can find a guide to set the API key connection for the app [here](https://devcenter.bitrise.io/en/accounts/connecting-to-services/connecting-to-an-apple-service-with-api-key.html#adding-api-key-authentication-data-on-bitrise).
173 |
174 | # Tadaaa, you are all done 🎉
175 | Before making a build, also make sure you use the right version of Xcode in the **Stack** tab.
176 |
177 | Now tag a commit and push the tag to your repo. This should trigger a build.
178 |
179 | If you did everything correctly, the build should succeed. No guarantees though, CI can be a b*tch sometimes 😀.
180 |
181 |
--------------------------------------------------------------------------------
/ci/bitrise-issues-readme.md:
--------------------------------------------------------------------------------
1 | # Nodes iOS Bitrise - Common issues 🤯
2 |
3 | If you run into issues this list of things might help you!
4 | In case you find a new error and fixed it please update this doc to help your colleagues! 🙂
5 |
6 | ## Issues:
7 |
8 | ##### error: No signing certificate "iOS Development" found: No "iOS Development" signing certificate matching team ID "id" with a private key was found
9 |
10 | If you see this error using v0.2, Go into your build settings -> Code Signing Identity, and manually select Automatic: iOS Developer or iOS Distribution for your *target* and not the project. This error occurs when the target inherits the code signing identity value from the project.
11 |
12 | --
13 |
14 | ##### Prepare repository fails at checking Hockey version
15 | This happens due to the info plist path in the Xcode project containing a variable, which is usually expanded by Xcode, however, it isn't by the CI script.
16 |
17 | Easiest way to fix this issue is by removing the variable part (usually `$(SRCROOT)`) from the info plist path in your target build settings.
18 |
19 | --
20 |
21 | ##### ITC Providers during Testflight upload
22 | If you get this error:
23 | `Your Apple ID account is attached to other iTunes providers. You will need to specify which provider you intend to submit content to by using the -itc_provider command`
24 |
25 | For some reason, sometimes the team name isn't enough to tell iTunes Connect which team to upload to. You need to add an extra field like this `itc_provider: "ZLCKLX4ACJ"` to that target in the project.yml file. That value is the Team ID from your account membership page in the developer portal. More info here: https://medium.com/@nickmeehan/fastlane-testflight-and-itc-provider-9233a3c4b5e5
26 |
27 | --
28 |
29 | ##### Mutiple teams found
30 |
31 | **Note** This one should be solved. If you still get this error, open an issue so we can see what's going wrong
32 |
33 |
34 | If you see this message when trying to upload to Testflight: `Multiple iTunes Connect Teams found; unable to choose, terminal not ineractive!`
35 |
36 | This means fastlane couldn't figure out which team to upload to. The easiest way to fix this is to add an environment variable with either the team name or team id. In the workflow editor, click Env Vars, and add a new variable under the deploy-testflight workflow. `FASTLANE_ITC_TEAM_ID` = ``, or `FASTLANE_ITC_TEAM_NAME` = ``. The team name and ID can be found in logs from the build that failed (there will be a list of teams that it couldn't choose from). Take a look at the fitness DK project for an example of this.
37 |
38 | --
39 |
40 | ##### Exit 65 - ARCHIVE FAILED error
41 |
42 | Your Xcode archived/build suceeds fine, but CI/fastlane archiving fails with exit code 65? Inspect your Xcode build/archive log for any errors that are outputted - surprisingly Xcode lets you finish the archive while running `xcodebuild` from the commandline (which is what fastlane does) fails horribly.
43 |
44 | For example, in one project a `find` command in a custom build phase was outputting an error where it couldn't find a directory.
45 |
46 | Search for this error in your Xcode log, if you think something similar to the above might be the cause:
47 | `Command /bin/sh emitted errors but did not return a nonzero exit code to indicate failure`
48 |
49 | --
50 |
51 | ##### Codesign error, export failed during Fastlane gym phase
52 |
53 | Make sure you remove the translations model generator from your project. It should exist in the project directory but not referenced or linked in the actual xcodeproj.
54 |
55 | --
56 |
57 | ##### Loading settings from project.yml
58 |
59 | ```
60 | + ruby /var/folders/90/5stft2v13fb_m_gv3c8x9nwc0000gn/T/bitrise034957535/step_src/parse_project_settings.rb
61 | /usr/local/lib/ruby/gems/2.4.0/gems/xcodeproj-1.5.3/lib/xcodeproj/project.rb:217:in `initialize_from_file': [Xcodeproj] Unknown object version. (RuntimeError)
62 | from /usr/local/lib/ruby/gems/2.4.0/gems/xcodeproj-1.5.3/lib/xcodeproj/project.rb:102:in `open'
63 | from /var/folders/90/5stft2v13fb_m_gv3c8x9nwc0000gn/T/bitrise034957535/step_src/parse_project_settings.rb:80:in `'
64 | ```
65 | We've seen this issue being causes by 2 things:
66 | 1. The schemes are not shared
67 | Solution: Share the schemes from xCode -> Product -> Scheme -> Manage schemes -> Make sure "Shared" is checked.
68 | 2. The xCode version you are using does not match the stack on the Bitrise project.
69 | Solution: Change the default stack in Bitrise -> Workflow -> Stack
70 |
--------------------------------------------------------------------------------
/ci/project.yml:
--------------------------------------------------------------------------------
1 | ---
2 | format_version: '11'
3 | default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
4 | project_type: ios
5 | trigger_map:
6 | - pull_request_source_branch: "*"
7 | workflow: pull-request
8 | pull_request_target_branch: develop
9 | - tag: "*_test"
10 | workflow: main-test
11 | - tag: "*_release"
12 | workflow: main-release
13 | - tag: "*"
14 | workflow: main
15 | workflows:
16 | archive:
17 | steps:
18 | - cache-pull@2: {}
19 | - xcode-archive@4:
20 | inputs:
21 | - distribution_method: app-store
22 | - configuration: "$BUILD_CONFIGURATION"
23 | - automatic_code_signing: api-key
24 | - scheme: "$BITRISE_SCHEME"
25 | is_always_run: true
26 | - cache-push@2: {}
27 | build-info:
28 | steps:
29 | - xcode-project-info@2:
30 | inputs:
31 | - xcodeproj_path: "$BITRISE_PROJECT_PATH"
32 | - target: ''
33 | - info_plist_path: "$BITRISE_APP_DIR_PATH/Info.plist"
34 | clone:
35 | steps:
36 | - activate-ssh-key@4:
37 | is_skippable: true
38 | - git-clone@6:
39 | inputs:
40 | - fetch_tags: 'yes'
41 | deploy-testflight:
42 | steps:
43 | - deploy-to-itunesconnect-application-loader@1:
44 | inputs:
45 | - connection: api_key
46 | main:
47 | after_run: []
48 | steps:
49 | - trigger-bitrise-workflow@0:
50 | inputs:
51 | - workflow_id: main-test
52 | - api_token: "$BUILD_TRIGGER_API_TOKEN"
53 | title: Trigger Bitrise Staging workflow
54 | - trigger-bitrise-workflow@0:
55 | title: Trigger Bitrise Release workflow
56 | inputs:
57 | - workflow_id: main-release
58 | - api_token: "$BUILD_TRIGGER_API_TOKEN"
59 | main-release:
60 | after_run:
61 | - clone
62 | - prepare-release
63 | - archive
64 | - deploy-testflight
65 | - notify
66 | main-test:
67 | after_run:
68 | - clone
69 | - prepare-test
70 | - archive
71 | - deploy-testflight
72 | - notify
73 | notify:
74 | after_run: []
75 | steps:
76 | - git::https://github.com/monstar-lab-oss/bitrise-changelog-step.git@main:
77 | inputs:
78 | - ticket_message_format: "%ticket - %message"
79 | - custom_other_name: "\U0001F4BE Other changes"
80 | - slack:
81 | inputs:
82 | - webhook_url: ''
83 | - channel: "$SLACK_CHANNEL"
84 | - pretext: 'Fresh iOS build is out. Made with :heart: by $GIT_CLONE_COMMIT_AUTHOR_NAME'
85 | - message: |-
86 | ```
87 | $COMMIT_CHANGELOG
88 | ```
89 | - emoji: ":applerainbow:"
90 | - message_on_error: 'Ah! Something went wrong :sweat:'
91 | - emoji_on_error: ":applerainbow:"
92 | - title_link: ''
93 | - color: "#f8f825"
94 | - channel_on_error: ''
95 | - text: ''
96 | - api_token: "$SLACK_TOKEN"
97 | - title: 'BUILD CHANGELOG: '
98 | - footer: ''
99 | - author_name: ''
100 | - fields: |
101 | App|${BITRISE_APP_TITLE}
102 | Branch|${BITRISE_GIT_BRANCH}
103 | Configuration|${BUILD_CONFIGURATION}
104 | Version|$XPI_VERSION ($XPI_BUILD)
105 | is_always_run: false
106 | before_run:
107 | - clone
108 | - build-info
109 | prepare-release:
110 | steps:
111 | - set-env-var:
112 | inputs:
113 | - destination_keys: BUILD_CONFIGURATION
114 | - value: "$RELEASE_BUILD_CONFIGURATION"
115 | prepare-test:
116 | steps:
117 | - set-env-var:
118 | inputs:
119 | - destination_keys: BUILD_CONFIGURATION
120 | - value: "$TEST_BUILD_CONFIGURATION"
121 | pull-request:
122 | after_run:
123 | - clone
124 | - prepare-test
125 | - run-sonarqube
126 | run-sonarqube:
127 | steps:
128 | - xcode-test@4: {}
129 | - file-downloader@1:
130 | inputs:
131 | - destination: xccov-to-sonarqube-generic.sh
132 | - file_permission: '755'
133 | - source: https://raw.githubusercontent.com/SonarSource/sonar-scanning-examples/master/swift-coverage/swift-coverage-example/xccov-to-sonarqube-generic.sh
134 | - brew-install@0:
135 | inputs:
136 | - packages: jq
137 | - script@1:
138 | inputs:
139 | - content: |-
140 | #!/usr/bin/env bash
141 | # fail if any commands fails
142 | set -e
143 | # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully
144 | set -o pipefail
145 | # debug log
146 | set -x
147 |
148 | echo "Converting code coverage log to Sonar format"
149 | ./xccov-to-sonarqube-generic.sh $BITRISE_XCRESULT_PATH > cov.xml
150 | - sonarqube-scanner@1:
151 | inputs:
152 | - scanner_properties:
153 | # replace with your project's sonarqube details, see https://monstarlab.atlassian.net/wiki/spaces/CTO/pages/7591755784/SonarQube+Setup#Set-up-Bitrise-Workflow-for-Pull-Request
154 | "sonar.projectKey=nodes-projects_test-project-ios_AYPqTLvTjO42SgPMhvYd\nsonar.sources=.\nsonar.host.url=https://sonar.monstarlab.io
155 | \nsonar.login=sqp_7e5a2885218659a6db6343fb0926e7923b73fbd9\nsonar.coverageReportPaths=cov.xml\nsonar.c.file.suffixes=-\nsonar.cpp.file.suffixes=-\nsonar.objc.file.suffixes=-"
156 | meta:
157 | bitrise.io:
158 | stack: osx-xcode-14.1.x-ventura
159 | app:
160 | envs:
161 | - opts:
162 | is_expand: false
163 | SLACK_CHANNEL: REPLACE_SLACK_CHANNEL # replace with your project's Slack Channel name
164 | - opts:
165 | is_expand: false
166 | RELEASE_BUILD_CONFIGURATION: Release
167 | - opts:
168 | is_expand: false
169 | TEST_BUILD_CONFIGURATION: Staging
170 | # REPLACE: This is the project config file
171 | - BITRISE_PROJECT_PATH: REPLACE_PROJECT_NAME.xcodeproj
172 | opts:
173 | is_expand: false
174 | - opts:
175 | is_expand: false
176 | # REPLACE: This is the name of the main app scheme in your project
177 | BITRISE_SCHEME: REPLACE_SCHEME_NAME
178 | - opts:
179 | is_expand: false
180 | BITRISE_DISTRIBUTION_METHOD: app-store
181 | - opts:
182 | is_expand: false
183 | BUILD_CONFIGURATION: ''
184 |
185 |
--------------------------------------------------------------------------------
/images/ci-cloudSigning/0_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/0_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/1_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/2_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/2_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/2_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/2_2.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/2_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/2_3.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/2_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/2_4.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/2_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/2_5.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/3_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/3_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/3_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/3_2.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/4_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/4_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/4_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/4_2.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/5_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/5_1.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/5_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/5_2.png
--------------------------------------------------------------------------------
/images/ci-cloudSigning/5_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci-cloudSigning/5_3.png
--------------------------------------------------------------------------------
/images/ci/01-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/01-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/02-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/02-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/03-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/03-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/04-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/04-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/05-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/05-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/06-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/06-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/07-new-app-bitrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/07-new-app-bitrise.jpg
--------------------------------------------------------------------------------
/images/ci/08-api-key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/08-api-key.png
--------------------------------------------------------------------------------
/images/ci/09-api-key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/09-api-key.png
--------------------------------------------------------------------------------
/images/ci/10-api-key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/ci/10-api-key.png
--------------------------------------------------------------------------------
/images/nodes-viper-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-opensource/Playbook/395937ad2dcd0c79b9d0a6c39173869e578283e9/images/nodes-viper-flow.png
--------------------------------------------------------------------------------
/migration-guide.md:
--------------------------------------------------------------------------------
1 | # Swift Migration Guide
2 |
3 | This guide should help you with migrating projects on previous version of Swift to newer versions.
4 |
5 | ### Swift 2 → Swift 3
6 |
7 | **IMPORTANT:** Make granular commits after each step and substep in this guide. Make sure the commit messages follow the proper rules.
8 |
9 | 1. Clone project and pull/checkout all branches ([script here](http://stackoverflow.com/a/21189710/1001803)).
10 | - Clean up all branches and keep only `master` and `develop`
11 | - Make sure `develop` is even or ahead of `master`
12 | 2. From `develop` branch run `git checkout -b feature/swift3-migration`.
13 | 3. *(optional)* Clean project, remove unused files, update file structure.
14 | 4. Update `Cartfile` to point to Swift 3 versions of dependencies. 📦
15 | - Update framework names if used in project
16 | - `NStack` → `NStackSDK`
17 | - `Serializable` → `Serpent`
18 | - Remove existing Carthage folder and run `carthage update --platform ios` (change platform as needed)
19 | 5. *(optional)* Update translations generator.
20 | - Download from latest Swift 3 release [on Github](https://github.com/nodes-ios/nstack-translations-generator/releases), do NOT add to Xcode
21 | - Remove any old translations generator and templates present in project
22 | 6. Open `YourProject.xcodeproj` in Xcode 8 and run Swift migrator.
23 | - Go through suggested changes to double check
24 | 7. Go to Build Phases and modify current run scripts.
25 | - Update translations script as per [README](https://github.com/nodes-ios/nstack-translations-generator)
26 | - Add or update script checking for TODO & FIXME ([gist here](https://gist.github.com/nickskull/2df890a021ada999bda383b8413c9473))
27 | - Add or update Carthage copy frameworks script with new names/frameworks
28 | 8. Update build settings.
29 | - Enable whole module optimizations for release/adhoc
30 | - Enable bitcode if possible
31 | - Remove unused/old user-defined build settings (scroll to the bottom of build settings)
32 | 9. Build and fix errors, rinse & repeat until project compiles. 😬
33 | - **Alamofire** ([official migration guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md))
34 | - Change `Manager` to `SessionManager`
35 | - Update completion type to `DataResponse`
36 | - Rename completion closure paramter to response instead of result
37 | - Requests have to start with the url, followed by method and other parameters
38 | - Switch on `response.result` instead of result
39 | - Update switch cases to `.success(let data)` and `.failure(let error)`
40 | - **Serpent**
41 | - Replace `import Serializable` with `import Serpent`
42 | - Remove `completionHandler:` named parameter in Alamofire requests
43 | - (**important**) Set default unwrapper, or manually specify per request
44 | - **Blobfish**
45 | - Completely replace ErrorHandler or add error handling if not present
46 | - Properly setup blobs for token expired, connection error, unknown error
47 | - **NStackSDK**
48 | - Replace `import NStack` with `import NStackSDK`
49 | - Start function now takes also a `launchOptions:` parameter
50 | - and more *(read documentation of frameworks)*
51 | - *(optional)* Refactor code to newer standards along the way (small changes)
52 | 10. Fix as many (ideally all) warnings in the project, so that it's clean and updated.
53 | 11. *(optional)* If using HockeySDK, add `NSPhotoLibraryUsageDescription` to the `Info.plist`.
54 | 12. Add or udpate README.
55 | - Change Swift / Xcode versions
56 | - Small project description
57 | - Things to watch out for
58 | - Add test users if necessary
59 | 13. *(optional)* Implement CI, if applicable (refer [here](https://github.com/nodes-projects/ci-test-ios)).
60 | 14. After you're done open a pull request to `develop` branch.
61 | - Make sure that CI / other checks pass and someone does code-review
62 | - After it gets merged, delete the feature branch
63 | - If this is a framework, also open a PR to `master`
64 | (don't do this on projects if not ready for release)
65 | 15. Enjoy life and pat yourself on the back for succeeding at this. 🎉
66 |
67 | ### Swift 3 → Swift 4
68 |
69 | Coming late 2017.
70 |
--------------------------------------------------------------------------------
/styleguide.md:
--------------------------------------------------------------------------------
1 | # Swift Style Guide
2 |
3 | We don't enforce a strict style guide. The rule of thumb for the style of our Swift code is common sense.
4 |
5 | This document is a continuous work in progress. When we discover a better way to do thigs, we update our coding style guide.
6 |
7 | We suggest those guidelines because if everyone in the team does things in a similar way, it's much easier for one to work on someone else's project.
8 |
9 | These guidelines will, of course, not cover every aspect of writing Swift code. If you're unsure about something, either refer to the [Ray Wenderlich Swift style guide](https://github.com/raywenderlich/swift-style-guide) or ask in the Slack channel.
10 |
11 | Always adhere to the Swift official [API design guidelines](https://swift.org/documentation/api-design-guidelines/).
12 |
13 | The template should provide most of the style reference you should follow when adopting this architecture.
14 |
15 |
16 | ## Structure
17 |
18 | ### Project
19 |
20 | The following guidelines (most of which will be repeated further down in this document) should give you some tips that will make it easier for you to navigate through the project.
21 |
22 |
23 | * The models will have descriptive, simple names: `User`, `Post`, `NewsItem`, `Bird`, etc.
24 | * Usually, anything that is not a model should have some suffix indicating what it is: `RoundedButton`, `CacheManager`, `DeepLinkManager`. There are a few exceptions. Follow the Swift API design guidelines with regards to this rule.
25 | * All the custom views should end in `View`: `NewsItemView`, `PensionItemView`, etc. This clearly separates them from the model but also indicates what that view does.
26 | * Similarly, all custom buttons end in `Button`, all custom cells end in `Table/CollectionViewCell`, all custom labels end in `Label`, all custom something ends in that something.
27 | * When using UIKit, all view controllers end in `ViewController`.
28 |
29 | ### File
30 |
31 | Use `// MARK: - ` comments to separate the different parts of your code. Some of these are provided automatically by the template.
32 |
33 |
34 | ### Line length
35 |
36 | Some languages use 80 characters as the line length, but we feel that is too short for a verbose framework like UIKit. Our swiftlint.yml file sets the limit at 120 lines. If you set your page guide in Xcode preferences to 100, it will give you a bit of a buffer.
37 |
38 | There are two common ways to add line breaks to long lines. We haven't decided on a final solution, so just be consistent.
39 |
40 |
41 | Examples:
42 |
43 | Xcode default:
44 |
45 | ```swift
46 | tableView.tableHeaderView = UIView(frame: CGRect(x: 0,
47 | y: 0,
48 | width: 0,
49 | height: 0))
50 |
51 | func tableView(_ tableView: UITableView,
52 | cellForRowAt indexPath: IndexPath) -> UITableViewCell {
53 | // ...
54 | }
55 |
56 | ```
57 |
58 | Vapor's line break strategy:
59 |
60 | ```swift
61 | tableView.tableHeaderView = UIView(
62 | frame: CGRect(
63 | x: 0,
64 | y: 0,
65 | width: 0,
66 | height: 0
67 | )
68 | )
69 |
70 | func tableView(
71 | _ tableView: UITableView,
72 | cellForRowAt indexPath: IndexPath
73 | ) -> UITableViewCell {
74 | ...
75 | }
76 |
77 |
78 |
79 | ```
80 |
81 | ## Extensions
82 | When adding a protocol implementation to a class or struct, add a separate extension for the protocol methods and use the `// MARK: - ` comment. This increases the readability of the code. Try not to conform to protocols in the initial class/struct declaration (`Codable` is an exception for example).
83 |
84 | ```swift
85 | class MainViewController: UIViewController {
86 | // ...
87 | }
88 |
89 | // MARK: - UITableViewDataSource
90 | extension MainViewController: UITableViewDataSource {
91 | // table view data source methods
92 | }
93 | ```
94 |
95 | ## Naming
96 | * The classes, structs, protocols, methods, variables, etc. must have descriptive names.
97 | * Method names and variables must start with a lower case letter.
98 | * Classes, structs, type names, enums should be capitalized.
99 | * The name of a variable should be enough to tell another programmer what that variable does. Don't use variable names such as `number`, `a`, `b`, `x`, `button`, `label`, etc.
100 | * To adhere to modern Swift syntax, all enum cases should be lowerCamelCase and not UpperCamelCase as before.
101 | * Don't use snake_case.
102 | * Don't prefix your classes or structs. Swift types are automatically namespaced by the module they're contained in, so there should be no collisions.
103 |
104 | ## Classes and Structs
105 | Where it makes sense, use structs over classes. Unless you really need inheritance or Objective-C compatibility, it's probably advisable to use a struct and not a class. And if you use classes but you don't need inheritance, consider making them `final`.
106 |
107 | ## Access control
108 | Specify `private` functions and variables when applicable. Variables can almost always be `private` and injected via the initializer if needed. Computed properties are potentially an exception. Helper functions should be `private` if they are not called from outside the class.
109 |
110 | The distinction between `open` `public` and `internal` is not as important for our development style at the moment so specifying `internal` is not necessary.
111 |
112 | As of Swift 4, `fileprivate` is rarely required.
113 |
114 | ## Storyboards and nibs
115 | Although SwiftUI is prefered these days, there may be cases on older projects where you may still wish to develop using storyboards and nibs. If this is the case, please don't set the fonts and colors in the storyboard. It's ok to do that to make the view more recognisable in the IB file, but always set the appearance in code too.
116 |
117 | ## Colors and fonts
118 | Our template has a Style target which should be used to maintain all colors & fonts. Colors should ideally be added to the project using the Colors Asset Catalog. Fonts, Assets & Colors then be extended and accessed via the code like the examples in the template.
119 |
120 | ## Other guidelines
121 | * Use `let` and not `var` wherever possible
122 | * Use `guard let` wherever appropriate
123 | * For pure constants, use enums without cases to namespace them. For example
124 |
125 | ```swift
126 | enum Constants {
127 | enum Math {
128 | static let e = 2.71
129 | static let pi = 3.14
130 | }
131 |
132 | enum CacheKeys {
133 | static let feed = "feed"
134 | static let notifications = "notifications"
135 | }
136 | }
137 |
138 | print(Constants.Math.pi)
139 |
140 | ```
141 | * Don't write unnecessary code. That includes declaring types that can be inferred or using shortcut versions of type declarations (`[String]` instead of `Array`).
142 | * Avoid using `self` where it doesn't need to be used.
143 | * Use computed properties over functions if the operation can be done in O(1). For example
144 |
145 | ```swift
146 | var diameter: Float {
147 | return radius * 2
148 | }
149 | ```
150 | Instead of
151 |
152 | ```swift
153 | func diameter() -> Float {
154 | return radius * 2
155 | }
156 | ```
157 |
158 | * Avoid pyramids of doom. Unwrap multiple optionals on the same line and mix it with logical conditions as needed. Prefer `guard` to `if` when possible. Examples with line breaks:
159 |
160 |
161 |
162 | ```swift
163 | // Default
164 | guard let age = age,
165 | let name = name,
166 | !name.isEmpty,
167 | !age.isEmpty else {
168 | //...
169 | }
170 |
171 | guard
172 | let age = age,
173 | let name = name,
174 | !name.isEmpty,
175 | !age.isEmpty
176 | else {
177 | //...
178 | }
179 |
180 | ```
181 | Remember you can also do things like
182 |
183 | ```swift
184 | if !myAutoFailCondition, let foo = expensiveCall() {
185 | //...
186 | }
187 |
188 | ```
189 |
190 | * Comments are for "why" and not just "what". It is often more helpful to explain why you made a complicated decision than to describe step-by-step what is going on.
191 | * Don't introduce new warnings. Sometimes warnings are understandable, if they come from a framework, shown as a //TODO, or were already in the project when you took over. But your code should always have the warnings resolved. A project *can* have 0 warnings.
192 | * Don't leave commented code in. Or if you feel you really need to, also leave a comment saying why that code is there and why it might be needed again. But in general, delete commented out code. We use git, so you can always get the old code from there.
193 | * If you need to check an enum case but you wouldn't want to do a switch for only one item, consider using something like `if case let .button = object.type { }`. It also works with associated values `if case let .button(value) = object.type { }`, and you can use `value` inside the `if`
194 |
195 |
196 |
197 | ## References
198 | * [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)
199 | * [Ray Wenderlich Swift style guide](https://github.com/raywenderlich/swift-style-guide)
200 |
--------------------------------------------------------------------------------
/upload-checklist.md:
--------------------------------------------------------------------------------
1 | # Upload Checklist
2 |
3 | These are the requirements for an app to be submitted to the store. Unless you get an explicit exception from QA and the PM, these requirements must be fulfilled. If some are missing tickets, add them yourself and notify the PM so everyone is aware of the extra time needed before submitting.
4 |
5 | ## Tools/Frameworks
6 | * Hockey crash reporting.
7 | * Google Analytics
8 | * [More info here](https://github.com/nodes-projects/readme/blob/master/mobile/analytics.md)
9 | * If there is no ticket for analytics and/or the client "doesn't care," we will still complete the **Default tracking** listed in the link above.
10 | * NStack
11 | * The App open `NStack.start()` call is required, even if we are not using NStack translations or other features.
12 | * Version control should be tested and working.
13 | * N-meta header must be included in API calls (See the [Nodes framework](https://github.com/nodes-ios/Nodes) for info).
14 | * New relic, if the app communicates with an API that Nodes did not develop.
15 |
16 |
17 | ## UI
18 | * Version number info somewhere in the settings.
19 | * No hardcoded text.
20 | * App functions offline with cached data.
21 | * Similarly, if there is no data, there must be UI for an empty view
22 | * The design team does not always create these views. At a minimum, there should be a label saying there is no data to display.
23 | * Keyboards should not block content. Views with keyboards must either fit the entire UI above the keyboard area, or become scrollable while the keyboard is visible.
24 | * For updates to existing apps: cached data should persist across versions. Users should not need to log in again when they update the app.
25 |
26 |
27 | ## Project
28 | * README.md on github.
29 | * Build runs on the CI.
30 | * Staging and Production environments are separated and handled appropriately (not just by commenting out lines of code).
31 |
32 | ___
33 | # Extra features
34 |
35 | At Nodes, we pride ourselves on creating awesome app experiences. The following checklist items are not *required* for an app upload, but are a reflection of the quality we expect to deliver. QA *will* make tickets for these and flag them as issues, and developers at Nodes are expected to complete these tasks even if there isn't a ticket.
36 |
37 | * Pull to refresh on lists, and pagination where appropriate.
38 | * Native UI behavior works as expected:
39 | * Swipe to go back in a navigation stack
40 | * Buttons show a pressed state (even just the default)
41 | * Table view cells select/deselect
42 | * Form entry views should have keyboards with Next and Done/Continue buttons
43 | * Views transition appropriately and do not immediately flash on screen in one frame. This includes switching from a login to main flow when replacing the window/root VC.
44 | * Loading indicators or skeleton views anytime UI is dependent on an API call.
45 | * Do not block the entire UI with a spinner unless absolutely necessary. Users should still be able to cancel or go back, and the UI should display what it can with the data it has (this is why skeleton views are preferred).
46 | * When uploading data, the app should display the local version of the new data while waiting for the API to return, and not show blank data.
--------------------------------------------------------------------------------