- Create a shared strings file for Twine, with all strings and translations, for all your projects
- Generate per project and platform strings files to support all languages, such as Localizable.strings files on iOS
- On iOS, generate strongly typed code based on Localizable.strings files to use in your UI using R.swift
- In your Xcode project(s), set up Run Script build phase steps to handle Localizable.strings and R.swift code generation
Inconsistencies in copy are a drag. Working on mobile apps across iOS and Android, it seems inevitable that things are going to get out of sync, whether it’s different capitalization on a title Um, the Android app says “Hello, world!” but iOS says “Hello, World!”? or confirmation button copy Did we mean to use “OK” or “Okay”? I see both. Twine is an open source command line tool that aims to solve this problem, by ensuring we conform to the DRY principle and only maintain a single source of string truth!
For the purposes of this article, we’ll be starting with an app that lists phrases in English only, to a fully localized app that supports multiple languages, getting all translations from a Twine data file that could be shared with an Android app!
Additionally, we’ll take it a step further and use R.swift to generate strongly typed localized string references from the
- Short circuit your app launch when unit testing. This leaves the application window free to use to test UI components.
- Test your screens as you develop using XCTestCase! By temporarily adding pauses in test cases, can manually test and interact with views in isolation.
- When manual testing is complete, use snapshot testing to record and verify view appearance - with a bonus of having visual documentation of your screens
Apple provides the XCTest framework for testing iOS and MacOS apps, and it is a powerful, flexible framework for asserting our apps function properly. In regards to unit tests, testing isolated views and view controllers is possible, though arguably a little clunky. For example, when testing view controllers, you need to remember to load the view. The generally accepted answer is to access the view, as the getter will trigger the load:
let viewController = MyViewController()
_ = viewController.view // load the view for testing
However, this doesn’t actually make the view visible, or add it to a window or view hierarchy. Instead, we can create a visible window in the running application when running tests (either simulator or physical device) and add our view controller’s view, or isolated view, to test there.
By default, running tests with XCTest launches and runs the app alongside the tests, as they run in the same process. For unit tests, this is generally undesirable as a) it slows down unit tests and b) unit tests are about testing isolated components, not the complete application. With a few lines of code, you can “short circuit” your app launch when running unit tests (safely keeping the code out of production). This also has the added benefit of freeing up the screen for Unit Testing View Controllers and Views.
- Avoid modifying the views of the presenting or presented view controllers during the animation. UIView snapshots are your friend!
- Custom transition animations don’t require
modalPresentationStyle = .custom unless the presentation itself needs to be customized, not just the transition. (Just remember to add the
to view controller’s view to the transition context)
- Be sure to set the frame of the presented view controller to the bounds of the container view, otherwise you may run into subtle bugs, such as layout issues when the in-call status bar is presented.
TL;DR: clean out
Apple has been recommending automatic code signing for some time, and once Xcode 8 was released I am happy to say that most of its aspirations have been realized. My projects tend to have somewhat complex Build Configurations to support Integration, Staging, and Production builds of each app, so having most of the provisioning and cert madness automated is a big relief. Prior to Xcode 8, I was manually managing provisioning profiles for each configuration which can be quite a burden if frequent provisioning profile updates are made.