1673078940
Here's list of tips and tricks with all additional sources (playgrounds, images) that I would like to share. Also you can find them on Twitter @szubyak, where you can ask questions and respond with feedback. I will really glad to have you there! 😀
UIStoryboard
usage saferBelow I want to show you a few simple ways how to make usage of string literals connected with UIStoryboard
much more safer.
Global constant string literals
At first it sounds like a good idea. You only need to define the constant once and it will be available everywhere. Then if you want to change its value, there is only one place in code to change for which the effects will cascade throughout the project. But global constants have some disadvantages. Not as much as global variables but still enought to stop using them. It's not the best practice. I will cover the topic of andvatages and disadavntages of global constants/variables usage in my next posts.
Relatable storyboard names
Your storyboards should be named after the sections of which they cover. It's a general rule. If you have a storyboard which houses view controllers are related to Profile, then the name of that storyboard’s file should be Profile.storyboard
.
Uniform storyboard identifiers
When you want to use Storyboard Identifiers
on your view controllers, usage of the class names as identifiers will be a good practice. For example, “ProfileViewController” would be the identifier for ProfuleViewController. Adding this to your naming convention is a good idea.
Enum
Try to consider enums as uniform, centralized global string literal identifiers for 'UIStoryboard'. You can create UIStoryboard class extension which defines all the storyboard files you have in your project. You can also add the convenience initializer or class function to add more syntactic sugar.
extension UIStoryboard {
enum Storyboard: String {
case profile
case login
var name: String {
return rawValue.capitalized
}
}
convenience init(storyboard: Storyboard, bundle: Bundle? = nil) {
self.init(name: storyboard.name, bundle: bundle)
}
// let storyboard = UIStoryboard(storyboard: .profile)
class func storyboard(storyboard: Storyboard, bundle: Bundle? = nil) -> UIStoryboard {
return UIStoryboard(name: storyboard.name, bundle: bundle)
}
// let storyboard = UIStoryboard.storyboard(.profile)
}
I hope provided solutions will really help you to maintain your storyboards.
I'm prety sure that almost every one has heard about "main" MVC problem known as Massive View Controller and how fashionable architectures (MVVM, VIPER, CleanArchitecture) valiantly fight it. But I want to pay attation to not less common problem - massive storyboard. Because of this many developers prefer to make layouts in code insted of storyboard. I've to admit that developers despise storyboards not without reason.
Let's list main storyboard problems:
Inspite of the issues, I'm convinced there is a way to make storyboard great again 🇺🇸, resolving most of the above.
"A storyboard is meant to explain a story, not a saga."
Storyboard can be easily splitted into multiple storyboards, with each one representing an individual story. Yes, it's OK not to have one big, fat, slowloading storyboard file but have 30+ devided storyboards.
Also don't forget about Storyboard References introduced in iOS 9.
But splitting storyboard will not help with one serious problem. Storyboard usage has strong coupling with one silent killer known as String Literals
. Look for the solution in 63 StoryboardIdentifiable
protocol
XCTUnwrap
assertion functionIn Xcode 11 new assertion function has been added for use in Swift tests. XCTUnwrap
asserts that an Optional variable’s value is not nil
, returning the unwrapped value of expression for subsequent use in the test. It protects you from dealing with conditional chaining for the rest of the test. Also it removes the need to use XCTAssertNotNil(_:_:file:line:)
with either unwrapping the value. It’s common to unwrap optional before checking it for a particular value so that’s really where XCTUnwrap()
will come into its own.
struct Note {
var id: Int
var title: String
var body: String
}
class NotesViewModel {
static func all() -> [Note] {
return [
Note(id: 0, title: "first_note_title", body: "first_note_body"),
Note(id: 1, title: "second_note_title", body: "second_note_body"),
]
}
}
func testGetFirstNote() throws {
let notes = NotesViewModel.all()
let firstNote = try XCTUnwrap(notes.first)
XCTAssertEqual(firstNote.title, "first_note_title")
}
This approach is cleaner than what we might have written previously:
func testGetFirstNote() {
let notes = NotesViewModel.all()
if let firstNote = notes.first {
XCTAssertEqual(firstNote.title, "first_note_title")
} else {
XCTFail("Failed to get first note.")
}
}
UITableViewCell
identifierTo register or dequeue UITableViewCell
object we need to provide its string type identifier
. Typing string by hand is wasting time and could couse you typos. In this case I would recomend to use extension which declares static identifier
property inside UITableViewCell
to avoid these problems.
extension UITableViewCell {
static var identifier: String {
return String(describing: self)
}
}
let cell = tableView.dequeueReusableCell(withIdentifier: ExampleTableViewCell.identifier)
tableView.register(ExampleTableViewCell.self, forCellReuseIdentifier: ExampleTableViewCell.identifier)
print(ExampleTableViewCell.identifier) //ExampleTableViewCell
AlertPresentable
protocolIn my current project I work on I present alerts almost on every view controller. To reduce lines of codes and time spent on duplicate code I created AlertPresentable
layer and want to share it with you. Any ViewController
which implements AlertPresentable
protocol receive opportunity to present any type of alerts discribed in this layer just by one line of code.
protocol AlertPresentable {
func presentErrorAlert(with message: String)
func presentSuccessAlert(with message: String, action: ((UIAlertAction) -> Void)?)
func presentConfirmationAlert(with message: String, action: ((UIAlertAction) -> Void)?)
}
extension AlertPresentable where Self: UIViewController {
func presentErrorAlert(with message: String) {
presentAlert(message: message, actions: [UIAlertAction(title: "OK", style: .cancel, handler: nil)])
}
func presentSuccessAlert(with message: String, action: ((UIAlertAction) -> Void)?) {
presentAlert(message: message, actions: [UIAlertAction(title: "OK", style: .cancel, handler: action)])
}
func presentConfirmationAlert(with message: String, action: ((UIAlertAction) -> Void)?) {
presentAlert(message: message, actions: [UIAlertAction(title: "Yes", style: .default, handler: action), UIAlertAction(title: "No", style: .cancel, handler: nil)])
}
private func presentAlert(title: String? = "", message: String? = nil, actions: [UIAlertAction] = []) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach { (action) in
alertController.addAction(action)
}
present(alertController, animated: true, completion: nil)
}
}
Usage:
class ViewController: UIViewController, AlertPresentable {
override func viewDidLoad() {
super.viewDidLoad()
presentErrorAlert(with: "User not found")
presentSuccessAlert(with: "File downloaded") { _ in
// use downloaded file
}
presentConfirmationAlert(with: "Are you sure you would like to sign out?") { _ in
// sign out user
}
}
}
Implementation of grid CollectionView layout is a commont task. But I found that calculation of cell width when you dont know how many cells can fit in one row of CollectionView is not a common task.
Here is my extension for calculation width of cell in grid CollectionView to make your layot adaptive.
extension UICollectionView {
func flexibleCellWidth(minCellWidth: CGFloat, minimumInteritemSpacing: CGFloat) -> CGFloat {
let contentWidth = frame.size.width - contentInset.left - contentInset.right
let numberOfItemsInRow = Int((contentWidth + minimumInteritemSpacing) / (minCellWidth + minimumInteritemSpacing))
let spacesWidth = CGFloat(numberOfItemsInRow - 1) * minimumInteritemSpacing
let availableContentWidth = contentWidth - spacesWidth
return availableContentWidth / CGFloat(numberOfItemsInRow)
}
}
Based on minimal cell width and minimal interitem spacing it autocalculates number of cells in row and return cell width for the best placement.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = collectionView.flexibleCellWidth(minCellWidth: 72, minimumInteritemSpacing: 10)
return CGSize(width: cellWidth, height: cellWidth)
}
UILabel
You can render HTML strings within a UILabel
using a special initializer of NSAttributedString
and passing in NSAttributedString.DocumentType.html
for .documentType
. But in most cases it is not enough to display it as is. If we want to add custom font or color we need to use CSS (add CSS header to our HTML string).
extension String {
func htmlAttributedString(with fontName: String, fontSize: Int, colorHex: String) -> NSAttributedString? {
do {
let cssPrefix = "<style>* { font-family: \(fontName); color: #\(colorHex); font-size: \(fontSize); }</style>"
let html = cssPrefix + self
guard let data = html.data(using: String.Encoding.utf8) else { return nil }
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return nil
}
}
}
Usage example
Error
by adopting LocalizedError
protocolCustom errors are an integral parts of you work. Swift-defined error types can provide localized error descriptions by adopting the new LocalizedError
protocol.
enum UserError: Error {
case credentialsNotMatch
case invalidEmail
case invalidName
}
extension UserError: LocalizedError {
public var errorDescription: String? {
switch self {
case .credentialsNotMatch:
return NSLocalizedString("Your username and/or password do not match", comment: "Credentials do not match")
case .invalidEmail:
return NSLocalizedString("Please enter email address in format: yourname@example.com", comment: "Invalid email format")
case .invalidName:
return NSLocalizedString("Please enter you name", comment: "Name field is blank")
}
}
}
Example:
func validate(email: String?, password: String?) throws {
throw UserError.credentialsNotMatch
}
do {
try validate(email: "email", password: "password")
} catch UserError.credentialsNotMatch {
print(UserError.credentialsNotMatch.localizedDescription)
}
You can provide even more information. It's common decency to show user except error description also failure reasons and recovery suggestions. Sorry for ambiguous error messages :)
enum ProfileError: Error {
case invalidSettings
}
extension ProfileError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("Your profile settings are incorrect", comment: "")
}
}
public var failureReason: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("I don't know why", comment: "")
}
}
public var recoverySuggestion: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("Please provide correct profile settings", comment: "")
}
}
}
let error = ProfileError.invalidSettings
print(error.errorDescription)
print(error.failureReason)
print(error.recoverySuggestion)
Result
type without value to provideResult
type usage is really popular nowadays.
enum Result<T> {
case success(result: T)
case failure(error: Error)
}
func login(with credentials: Credentials, handler: @escaping (_ result: Result<User>) -> Void) {
// Two possible options:
handler(Result.success(result: user))
handler(Result.failure(error: UserError.notFound))
}
login(with:)
operation has user
value to provide and default Result
type fits perfectly here. But let’s imagine that your operation hasn’t got value to provide or you don’t care about it. Default Result
type makes you to provide the result value any way.
To fix this inconvenience you need to add extension and instantiate a generic with an associated value of type Void
.
func login(with credentials: Credentials, handler: @escaping (_ result: Result<Void>) -> Void)
extension Result where T == Void {
static var success: Result {
return .success(result: ())
}
}
Now we can change our func login(with:)
a bit, to ignore result success value if we don’t care about it.
func login(with credentials: Credentials, handler: @escaping (_ result: Result<Void>) -> Void) {
// Two possible options:
handler(Result.success)
handler(Result.failure(error: UserError.notFound))
}
In unit testing terms, there are three phases to a unit test:
func testSum() {
// Given
let firstValue = 4
let secondValue = 3
// When
let sum = sut.sum(firstValue: firstValue, secondValue: secondValue)
// Then
XCTAssertEqual(sum, 7)
}
Note: When comparing two values using XCTAssertEqual
you should always put the actual value first before the expected value.
sut
and test lifecycleThe subject under test is always named sut
. It refers to a subject that is being tested for correct operation. It is short for "whatever thing we are testing" and is always defined from the perspective of the test. So when you look at your tests, there is no ambiguity as to what is being tested. All other objects are named after their types. Clearly identifying the subject under test in the sut
variable makes it stand out from the rest.
The test lifecycle for each test is surrounded by a pair of functions - setUp()
and tearDown()
. The setUp()
function is called before the execution of each test function in the test class. The tearDown()
function is called after the execution of each test function in the test class. They provide you a place to prepare the sut
ready before the test starts, and clean up the sut
and any states after the test finishes. It’s important that each test starts with the desired initial state.
import XCTest
class YourClassTests: XCTestCase {
// MARK: - Subject under test
var sut: YourClass!
// MARK: - Test lifecycle
override func setUp() {
super.setUp()
sut = YourClass()
}
override func tearDown() {
sut = nil
super.tearDown()
}
}
Could be useful solution for positioning nodes in your game or views in ordinary business application on the perimeter of circle.
func pointOnCircle(radius: Float, center: CGPoint) -> CGPoint {
// Random angle between 0 and 2*pi
let theta = Float(arc4random_uniform(UInt32.max)) / Float(UInt32.max-1) * .pi * 2.0
// Convert polar to cartesian
let x = radius * cos(theta)
let y = radius * sin(theta)
return CGPoint(x: CGFloat(x) + center.x, y: CGFloat(y) + center.y)
}
zip()
functionLet’s imagine you have an array of LOTR heroes, and an array of weapons that match those heros. How could I create a list with the heros and weapons combined at each index? zip()
function is designed to combine two sequences into a single sequence of tuples.
Note: I would recomend to wrap the output from zip()
into array to make its output easier to read.
One of the useful features of zip()
is that if your two arrays arent equal in size it will choose the shorter one. Yes, it will not crash your application, just ignore element which doesn’t have a match in second sequence.
StackView
custom spacingStackView
, introduced in iOS 9, made Auto Layout usage much easier, because of reducing the amount of constraints needed to create manually for common layouts. But we faced with the problem, property spacing
applies equally to the arranged subviews of StackView
. Before iOS 11, there were 2 ways how to overcome this problem. One way is to create views with desired height and use them as spacer views or we could do it by nesting stack views but these two ways always seemed to be an unnecessary complication.
In iOS 11 you can create stack views with custom spacing between views. But there is no way to specify this custom spacing in Storyboard, only in code.
stackView.setCustomSpacing(32.0, after: label)
Also, in iOS 11 Apple introduced default and system spacing properties on the UIStackView
class. You can use these to set or reset the custom spacing after a view.
stackview.setCustomSpacing(UIStackView.spacingUseSystem, after: label)
XCode 9.0 gives us opportunity to create named colors. We can do it directly inside assets catalog and use it in code and storyboards. Named colors contain 3 parts: name ("FerrariRed"), color specified as range and device compatibility.
Created named color you can use like this:
view.backgroundColor = UIColor(named: "FerrariRed")
Inside storyboards you can use created named color by selecting it from the color dropdown menu.
Result
error handlingError handling is realy common functionality. But if you want to handle errors across asynchronous boundaries or store value/error results for later processing, then ordinary Swift error handling won’t help. The best alternative is a common pattern called a Result
type: it stores a value/error “sum” type (either one or the other) and can be used between any two arbitrary execution contexts.
Example of Result
type:
enum Result<T> {
case success(result: T)
case failure(error: String)
}
Usage of Result
type:
protocol TaskStoreProtocol {
func fetchTasks(handler: @escaping TaskStoreFetchTasksResult)
}
typealias TaskStoreFetchTasksResult = (_ result: Result<[Task]>) -> Void
class TasksStore: TasksStoreProtocol {
func fetchTasks(handler: @escaping TaskStoreFetchTasksResult) {
// Some useful code, network request or something like this goes here
if success {
handler(Result.success(result: fetchedTasks))
} else {
handler(Result.failure(error: error))
}
}
}
Here I want to introduce you with general info about generic type parameters and their naming style. Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of angle brackets (such as <T>
). Once you specify a type parameter, you can use it to define the type of a function’s parameters, or as the function’s return type, or as a type annotation within the body of the function. In each case, the type parameter is replaced with an actual type whenever the function is called.
You can provide more than one type parameter by writing multiple names within the angle brackets, separated by commas.
In most cases, type parameters have descriptive names, such as Key
and Value
in Dictionary<Key, Value>
and Element
in Array<Element>
, which tells about the relationship between the type parameter and the generic type or function it’s used in. However, when there isn’t a meaningful relationship between them, it’s traditional to name them using single letters such as T
, U
, and V
.
You can find usage of generics in several of my tips. So here I want to show you some basics of generics. They are one of the most powerful features of Swift. It gives you opportinity to write flexible, reusable functions. Types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted way.
At first, here is nongeneric function which swaps two Int
values:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
As you can see, this func is useful but a bit limited, because you can swap only Int
values. If you want to swap two String
values, or two Double
values, you have to write more functions.
Generic functions can work with any type. Here’s a generic representation of our function.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
The body of our two functions is the same. But the first line of is slightly different. The generic version of the function uses a placeholder type name (called T
) instead of an actual type name (such as Int
, String
, or Double
). The placeholder type name doesn’t say anything about what T
must be, but it does say that both a
and b
must be of the same type T
. The actual type to use in place of T
is determined each time the function is called.
As with all dependencies, we should reduce UserDefaults
to an abstract protocol, inject it into the classes that require it, and thereby decouple ourselves from implementation details and increase the testability of our code by introducing seams. But I'll be honest, the UserDefaults API
is so simple and pervasive that standard dependency management feels like overkill. If so, we've probably encountered the same problem: without the ability to inject mocks, testing code that makes use of UserDefaults
can be a pain. Any time we use our test device (including running tests on it!) we potentially change the state of its persisted settings.
Here is extension UserDefaults
that gives your test method an opportunity to run in clean state and not jinx real persisted settings.
extension UserDefaults {
static func blankDefaultsWhile(handler:()->Void){
guard let name = Bundle.main.bundleIdentifier else {
fatalError("Couldn't find bundle ID.")
}
let old = UserDefaults.standard.persistentDomain(forName: name)
defer {
UserDefaults.standard.setPersistentDomain( old ?? [:], forName: name)
}
UserDefaults.standard.removePersistentDomain(forName: name)
handler()
}
}
Usage:
class MyTests: XCTestCase {
func testSomething() {
// Defaults can be full of junk.
UserDefaults.blankDefaultsWhile {
// Some tests that expect clean defaults.
// They can also scribble all over defaults with test-specific values.
}
// Defaults are back to their pre-test state.
}
}
Here is one more trick how to make protocol method to be optional. Let’s remember why we would like to declare a protocol method as optional? It is because we we don’t want to write the implementation if that method will not be used. So Swift has a feature called extension that allow us to provide a default implementation for those methods that we want to be optional.
protocol CarEngineStatusDelegate {
func engineWillStop()
func engineDidStop()
}
extension CarEngineStatusDelegate {
func engineWillStop() {}
}
This way the class/struct that will use our protocol will only need to implement func engineDidStop()
.
How to find the view controller that is responsible for a particular view? This is as easy as walking the responder chain looking for the first UIViewController
you find.
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
You should only use it when you really need it – if you’re able to call methods directly using a delegate or indirectly by posting notifications then do it in first place.
Lets imagine full of texfields scene (e.g. UserProfile scene). Moving between textfields using Next/Return on on-screen keyboard is integral functionality.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
If you need to force the first responder to resign itself and aren’t sure which textfield is in control, it’s easier to use view.endEditing(true)
.
allCases
property for your enum
(Swift 4.2)Retrieving all cases of enum
is really common task, but it's implementation is not so clean. We were forced to create static
variable of array with all cases of our enum
manually.
enum Cars {
case BMW
case Audi
case Volkswagen
case Mercedes
static let allCases = [BMW, Audi, Volkswagen, Mercedes]
}
In Swift 4.2 we got really useful property allCases
, which is autogenerated (all you need to do is make your enum
conform to the CaseIterable
protocol), so it releases us from additional steps.
enum Cars: CaseIterable {
case BMW
case Audi
case Volkswagen
case Mercedes
}
for car in Cars.allCases {
print(car)
}
One more thing left to say about protocols. Sometimes protocols should be adopted only by classes. To achieve this behaviour you should define you protocol with class
keyword or by inheriting from AnyObject
. This is commonly happens because you have a delegate
property that needs to use weak
storage to avoid the risk of a strong reference cycle
(formerly known as a retain cycle
).
weak var delegate: CarDelegate?
The weak
keyword can't be used with structs and enums, because they are value types.
In previous tip you can observe how to split your protocol to smaller pieces. Here I will show you how to combine your protocols to make bigger one using inheritance and composition. To merge protocols together use &
for protocol composition.
typealias Codable = Decodable & Encodable
You can also use protocol inheritance to build larger protocols.
protocol CarEngineStatusDelegate { }
protocol CarMovingStatusDelegate { }
protocol CarDelegate: CarMovingStatusDelegate, CarEngineStatusDelegate { }
If you implement a protocol in Swift you must implement all its requirements. Optional methods are not allowed.
Example:
protocol CarDelegate {
func engineDidStart()
func carShouldStartMoving() -> Bool
func carDidStartMoving()
func engineWillStop()
func engineDidStop()
}
But there are a few tricks how to make some methods to be optionals:
protocol CarEngineStatusDelegate { func engineDidStart() func engineWillStop() func engineDidStop() }
* Use the `@objc` attribute on a Swift protocol to receive opportunity to mark methods as being optional, but you may no longer use Swift structs and enums with that protocol, and you may no longer use protocol extensions.
```swift
@objc protocol CarDelegate {
@objc optional func engineDidStart()
func carShouldStartMoving() -> Bool
func carDidStartMoving()
@objc optional func engineWillStop()
@objc optional func engineDidStop()
}
Here is the optional method usage:
delegate?.engineWillStop?()
The delegate
property is optional because there might not be a delegate assigned. And in the CarDelegate
protocol we made engineWillStop()
an optional requirement, so even if a delegate is present it might not implement that method. As a result, we need to use optional chaining.
If we are talking about naming the protocol itself:
Collection
).able
, ible
, or ing
(e.g. Equatable
, ProgressReporting
).Briefly talking about methods names inside protocol, you can use whatever names you want. But, if your protocol is created to represent delegates please look: #32 Delegate naming
Little thing that surprisingly became a discovery for me, because it's rare case in practice, so I'm not ashamed of it 🤣. Property observers
, getter/setter
and lazy
cant be used together, they are mutually exclusive. So you should choose what pattern makes most profit to you in particaulr situation.
Methods are functions associated with a type. Mock the type, and you can intercept the method. In Swift, we prefer to do this with protocols. But a standalone function lives on its own, without any associated data. In other words, it has no self
. During unit-testing we want to intercept these calls, for two main reasons:
In popular networking 3party library Almofire all request
, upload
or download
functions implemented as standalone functions. So, we have dificulties with intercepting during unit testing. We can't mock associated type, because there is no type. Also responseJSON
function is implemented in extension, so we can't make any changes or override it.
I defined a new class with 2 methods (request
and responseJSON
) with the same method signature as Alamofire's request
and responseJSON
methods. I called the Alamofire version of these methods inside. And then used this class as a dependency in my sut
.
import Alamofire
class AlamofireWrapper {
@discardableResult
public func request(_ url: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil) -> DataRequest {
return Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
}
public func responseJSON(request: DataRequest, queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: @escaping (DataResponse<Any>) -> Void) {
request.responseJSON(queue: queue, options: options, completionHandler: completionHandler)
}
}
My request part starts looking like this:
var alamofireWrapper = AlamofireWrapper()
let request = alamofireWrapper.request(url, parameters: params, encoding: URLEncoding(destination: .queryString))
alamofireWrapper.responseJSON(request: request) { response in
if response.result.isSuccess {
success(response.result.value)
} else if let error = response.result.error {
failure(error)
}
}
And here is my test double finaly:
// MARK: - Test doubles
class AlamofireWrapperSpy: AlamofireWrapper {
enum RequestStatus {
case success
case failure
}
var status: RequestStatus = .success
// MARK: Method call expectations
var requestCalled = false
var responseJSONCalled = false
// MARK: Spied methods
override func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters?, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequest {
requestCalled = true
return SessionManager.default.request("dummy_url")
}
override func responseJSON(request: DataRequest, queue: DispatchQueue?, options: JSONSerialization.ReadingOptions, completionHandler: @escaping (DataResponse<Any>) -> Void) {
responseJSONCalled = true
switch status {
case .success:
let value = ["result": "best_response_ever"]
completionHandler(DataResponse(request: nil, response: nil, data: nil, result: Result.success(value)))
case .failure:
let error = NSError(domain: "", code: 500, userInfo: nil)
completionHandler(DataResponse(request: nil, response: nil, data: nil, result: Result.failure(error)))
}
}
}
Few days ago I faced a sorting task. I needed to sort array of object by more then one criteria that is also optional. My object was Place
with rating
and distance
properties. rating
was main criteria for sorting and distance
was secondary. So, here is my workouts.
struct Place {
var rating: Int?
var distance: Double?
}
func sortPlacesByRatingAndDistance(_ places: [Place]) -> [Place] {
return places.sorted { t1, t2 in
if t1.rating == t2.rating {
guard let distance1 = t1.distance, let distance2 = t2.distance else {
return false
}
return distance1 < distance2
}
guard let rating1 = t1.rating, let rating2 = t2.rating else {
return false
}
return rating1 > rating2
}
}
let places = [Place(rating: 3, distance: 127), Place(rating: 4, distance: 423), Place(rating: 5, distance: nil), Place(rating: nil, distance: 100), Place(rating: nil, distance: 34), Place(rating: nil, distance: nil)]
let sortedPlaces = sortPlacesByRatingAndDistance(places) // [{rating 5, nil}, {rating 4, distance 423}, {rating 3, distance 127}, {nil, distance 34}, {nil, distance 100}, {nil, nil}]
Super lightweight extension 🎈. How to remove particular object from array. Remove first collection element that is equal to the given object
.
extension Array where Element: Equatable {
mutating func remove(_ object: Element) {
if let index = index(of: object) {
remove(at: index)
}
}
}
When creating custom delegate methods take into consideration the following list items:
delegate
, if it is just one of them, or put delegate
at the end, when it is more than one. Here is the example, WKWebView
has uiDelegate
and navigationDelegate
properties that can point to two different objects.
will
, did
, and should
.func tableView(_ tableView: UITableView,...
. It helps in situations where there is more than one object that can send a message .As a result, Apple delegates have a very specific naming convention:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Asynchronous work in Playground.
Tell the playground it should continue running forever, otherwise it will terminate before the asynchronous work has time to hppen.
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchGroup
usageLet’s say you’ve got several long running tasks to perform. After all of them have finished, you’d like to run some further logic. You could run each task in a sequential fashion, but that isn’t so efficient - you’d really like the former tasks to run concurrently. DispatchGroup
enables you to do exactly this.
let dispatchGroup = DispatchGroup()
for i in 1...5 {
dispatchGroup.enter()
Alamofire.request(url, parameters: params).responseJSON { response in
//work with response
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
print("All requests complete")
}
In the above, all long running functions will perform concurrently, followed by the print statement, which will execute on the main thread.
Each call to enter()
must be matched later on with a call to leave()
, after which the group will call the closure provided to notify()
.
DispatchGroup
has a few other tricks:
notify()
, we can call wait()
. This blocks the current thread until the group’s tasks have completed.wait(timeout:)
. This blocks the current thread, but after the timeout specified, continues anyway. To create a timeout object of type DispatchTime
, the syntax .now() + 1
will create a timeout one second from now.wait(timeout:)
returns an enum that can be used to determine whether the group completed, or timed out.Clear way 🛣️ to return the unique list of objects based on a given key 🔑. It has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination.
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set: Set<T> = []
var arrayOrdered: [Element] = []
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
Using View Debugging
you’re now able to inspect an entire view hierarchy visually – right from within Xcode, instead of printing frames to the console and trying to visualize layouts in your head.
You can invoke the view debugger by choosing View UI Hierarchy
from the process view options menu in the debug navigator, or by choosing Debug
> View Debugging
> Capture View Hierarchy
.
You'll see a 3D representation of your view, which means you can look behind the layers to see what else is there. The hierarchy automatically puts some depth between each of its views, so they appear to pop off the canvas as you rotate them. If you have a complicated view layout, View Debugging
> Show View Frames
option will draw lines around all your views so you can see exactly where they are.
More about view debugging here
A breakpoint
is a debugging tool that allows you to pause the execution of your program up to a certain moment. Creating pause
points in your code can help you investigate your code. While your app is paused, light green arrow that shows your current execution position can be moved. Just click and drag it somewhere else to have execution pick up from there – although Xcode will warn you that it might have unexpected results, so tread carefully!
Right-click on the breakpoint (the blue arrow marker) and choose Edit Breakpoint.
In the popup that appears you can set the condition
. Execution will now pause only when your condition is true. You can use conditional breakpoints to execute debugger commands automatically – the Automatically continue
checkbox is perfect for making your program continue uninterrupted while breakpoints silently trigger actions. Also you can set Ignore times before stoping
and actions like Debuger command
, Log message
, Sound
, etc.
Shortcuts:
F6
- Step Over. Ctrl+Cmd+Y
- Continue (continue executing my program until you hit another breakpoint)
In Xcode debug console you can use po
to print what you need during pause.
assert()
is debug-only check that will force your app to crash if specific condition is false.
assert(4 == 4, "Maths error") //OK
assert(3 == 2, "Maths error") //Crash
As you can see assert()
takes two parameters:
If the check evaluates to false, your app will be forced to crash because you know it's not in a safe state, and you'll see the error message in the debug console. If you don’t have a condition to evaluate, or don’t need to evaluate one, you can use assertionFailure()
function.
precondition()
is not debug-only check. It will crash your app even in release mode.
precondition(4 == 4, "Maths error") //OK
precondition(3 == 2, "Maths error") //Crash
preconditionFailure()
works the same as assertionFailure()
. With the same difference as above, it works for release builds.
fatalError()
, like assertionFailure()
and preconditionFailure()
works for all optimisation levels in all build configurations.
fatalError("ERROR")
More about asserts and optimisation levels you can find here
Debuging 👨🔧 is one of the most importent aspects of programing👨💻. It should be in your skillbox anyway. It contains log functions, asserts, breakpoints and view debuging. Let's observe everyone of them just one by one. And here are log functions in the crosshairs 🔍.
We know print()
as a variadic function. Function that accepts any number of parameters.
print("one", "two", "three", "four") //one two three four
But it's variadic nature becomes much more useful when you use separator
and terminator
, its optional extra parameters. separator
gives you opportunity to provide a string that should be placed between every item. It's "space" by default.
print("one", "two", "three", "four", separator: "-") //one-two-three-four
Meanwhile terminator
is what should be placed after the last item. It’s \n
by default, which means "line break".
print("one", "two", "three", "four", terminator: " five") //one two three four five
UIView
content with animationReally lightweight way 🎈 How to add content changing animation to UIView and it subclasses.
extension UIView {
func fadeTransition(_ duration: CFTimeInterval) {
let animation = CATransition()
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.type = kCATransitionFade
animation.duration = duration
layer.add(animation, forKey: kCATransitionFade)
}
}
Just invoke 🧙♂️ fadeTransition(_ duration: CFTimeInterval)
by your view before you will apply a change.
label.fadeTransition(1)
label.text = "Updated test content with animation"
Next code snippet 📃 I use to keep eye on changes that take place in the managed object context. Useful thing to know what's going on, what was added, updated ( what specific values were changed ) or deleted 📥📝📤
func changeNotification(_ notification: Notification) {
guard let userInfo = notification.userInfo else { return }
if let inserts = userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>, inserts.count > 0 {
print("--- INSERTS ---")
print(inserts)
print("+++++++++++++++")
}
if let updates = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>, updates.count > 0 {
print("--- UPDATES ---")
for update in updates {
print(update.changedValues())
}
print("+++++++++++++++")
}
if let deletes = userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>, deletes.count > 0 {
print("--- DELETES ---")
print(deletes)
print("+++++++++++++++")
}
}
NotificationCenter.default.addObserver(self, selector: #selector(self.changeNotification(_:)), name: .NSManagedObjectContextObjectsDidChange, object: moc)
String
into wordsDefault ways of splitting ✂️ String
don't work perfect sometimes, because of punctuation characters and other "wreckers" 🐛. Here is extension for splitting ✂️ String
into words 💻🧐👌.
extension String {
var words: [String] {
return components(separatedBy: .punctuationCharacters)
.joined()
.components(separatedBy: .whitespaces)
.filter{!$0.isEmpty}
}
}
I discovered strange behavior of tuples during comparing 🤪. Comparison cares only about types and ignores labels 😦. So result can be unexpected. Be careful ⚠️.
let car = (model: "Tesla", producer: "USA")
let company = (name: "Tesla", country: "USA")
if car == company {
print("Equal")
} else {
print("Not equal")
}
Printed result will be: Equal
Painless way ( NO to timers from now ⛔️ ) how to detect that user stop typing text in text field ⌨️ Could be usefull for lifetime search 🔍
class TestViewController: UIViewController {
@objc func searchBarDidEndTyping(_ textField: UISearchBar) {
print("User finsihed typing text in search bar")
}
@objc func textFieldDidEndTyping(_ textField: UITextField) {
print("User finished typing text in text field")
}
}
extension TestViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchBarDidEndTyping), object: searchBar)
self.perform(#selector(searchBarDidEndTyping), with: searchBar, afterDelay: 0.5)
return true
}
}
extension TestViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(textFieldDidEndTyping), object: textField)
self.perform(#selector(textFieldDidEndTyping), with: textField, afterDelay: 0.5)
return true
}
}
UITextField
Clear way of adding left\right text offset inside UItextField
🔨🧐💻 Also, because of @IBInspectable
it could be easily editable in Interface Builder’s inspector panel.
@IBDesignable
extension UITextField {
@IBInspectable var leftPaddingWidth: CGFloat {
get {
return leftView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
leftView = paddingView
leftViewMode = .always
}
}
@IBInspectable var rigthPaddingWidth: CGFloat {
get {
return rightView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
rightView = paddingView
rightViewMode = .always
}
}
}
I'm not huge fan of custom operators 😐 because they are intuitively obvious only to their authors, but I've created one which gives you opportunity to get common elements in two arrays whos elements implement Equatable
protocol 🔨🧐💻
infix operator &
func &<T : Equatable>(lhs: [T], rhs: [T]) -> [T] {
return lhs.filter { rhs.contains($0) }
}
Gradient 🏳️🌈 on Navigation Bar is really good looking, but not very easy to implement 🧐🔨👨💻 Works with iOS 11 largeTitle navigation bar too 👌
struct GradientComponents {
var colors: [CGColor]
var locations: [NSNumber]
var startPoint: CGPoint
var endPoint: CGPoint
}
extension UINavigationBar {
func applyNavigationBarGradient(with components: GradientComponents) {
let size = CGSize(width: UIScreen.main.bounds.size.width, height: 1)
let gradient = CAGradientLayer()
gradient.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
gradient.colors = components.colors
gradient.locations = components.locations
gradient.startPoint = components.startPoint
gradient.endPoint = components.endPoint
UIGraphicsBeginImageContext(gradient.bounds.size)
gradient.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.barTintColor = UIColor(patternImage: image!)
}
}
Easy way how to get next element of array
extension Array where Element: Hashable {
func after(item: Element) -> Element? {
if let index = self.index(of: item), index + 1 < self.count {
return self[index + 1]
}
return nil
}
}
Great extension to split array by chunks of given size
extension Array {
func chunk(_ chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map({ (startIndex) -> [Element] in
let endIndex = (startIndex.advanced(by: chunkSize) > self.count) ? self.count-startIndex : chunkSize
return Array(self[startIndex..<startIndex.advanced(by: endIndex)])
})
}
}
Scene with UIImageView
on top looks stylish if navigation bar is transparent. Easy way how to make navigation bar transparent or opaque.
func transparentNavigationBar() {
self.setBackgroundImage(UIImage(), for: .default)
self.shadowImage = UIImage()
}
func opaqueNavigationBar() {
self.shadowImage = nil
self.setBackgroundImage(nil, for: .default)
}
One more useful extension 🔨💻 Gives you opportunity to group objects by property 👨💻🧐
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Usage:
struct Person {
var name: String
var age: Int
}
let mike = Person(name: "Mike", age: 18)
let john = Person(name: "John", age: 18)
let bob = Person(name: "Bob", age: 56)
let jake = Person(name: "Jake", age: 56)
let roman = Person(name: "Roman", age: 25)
let persons = [mike, john, bob, jake, roman]
let groupedPersons = persons.group { $0.age }
for persons in groupedPersons {
print(persons.map { $0.name })
}
Result:
["Mike", "John"]
["Bob", "Jake"]
["Roman"]
Also in-box alternative
Do you need semicolons in Swift ? Short answer is NO, but you can use them and it will give you interesting opportunity. Semicolons enable you to join related components into a single line.
func sum(a: Int, b: Int) -> Int {
let sum = a + b; return sum
}
Unit testing shouldn’t have any side effects. While running tests, Xcode firstly launches app and thus having the side effect of executing any code we may have in our App Delegate and initial View Controller. Fake AppDelegate in your main.swift
to prevent it.
You can find main.swift
file here
didSet
when property’s value is set inside init
contextApple's docs specify that: "Property observers are only called when the property’s value is set outside of initialization context."
defer
can change situation 😊
class AA {
var propertyAA: String! {
didSet {
print("Function: \(#function)")
}
}
init(propertyAA: String) {
self.propertyAA = propertyAA
}
}
class BB {
var propertyBB: String! {
didSet {
print("Function: \(#function)")
}
}
init(propertyBB: String) {
defer {
self.propertyBB = propertyBB
}
}
}
let aa = AA(propertyAA: "aa")
let bb = BB(propertyBB: "bb")
Result:
Function: propertyBB
Two ways of changing type of items in array and obvious difference between them 🧐👨💻
let numbers = ["1", "2", "3", "4", "notInt"]
let mapNumbers = numbers.map { Int($0) } // [Optional(1), Optional(2), Optional(3), Optional(4), nil]
let compactNumbers = numbers.compactMap { Int($0) } // [1, 2, 3, 4]
forEach
and map
execution order differenceExecution order is interesting difference between forEach
and map
: forEach
is guaranteed to go through array elements in its sequence, while map
is free to go in any order.
Common Types of Error Messages
Do you know that using map
gives profit to the compiler: it's now clear we want to apply some code to every item in an array, then like in for
loop we could have break
on halfway through.
compactMap
func is effectively the combination of using map
and joined
in a single call, in that order. It maps items in array A into array B using a func you provide, then joins the results using concatenation.
Functions min
and max
could be also combinations of sorted.first
and sorted.last
in single call.
let colors = ["red", "blue", "black", "white"]
let min = colors.min() // black
let first = colors.sorted().first // black
let max = colors.max() // white
let last = colors.sorted().last // white
Use enumerated
when you iterate over the collection to return a sequence of pairs (n, c)
, where n
- index for each element and c
- its value 👨💻💻
for (n, c) in "Swift".enumerated() {
print("\(n): \(c)")
}
Result:
0: S
1: w
2: i
3: f
4: t
Also be careful with this tricky thing, enumerated
on collection will not provide actual indices, but monotonically increasing integer, which happens to be the same as the index for Array but not for anything else, especially slices.
Ever faced the problem that u can't hide status bar because of prefersStatusBarHidden
is get-only
? The simplest solution is to override
it 🧐👨💻
let vc = UIViewController()
vc.prefersStatusBarHidden = true // error
print("statusBarHidded \(vc.prefersStatusBarHidden)") // false
class TestViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
}
let testVC = TestViewController()
print("statusBarHidded \(testVC.prefersStatusBarHidden)") // true
You can extend collections to return the element at the specified index if it is within bounds, otherwise nil.
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let cars = ["Lexus", "Ford", "Volvo", "Toyota", "Opel"]
let selectedCar1 = cars[safe: 3] // Toyota
let selectedCar2 = cars[safe: 6] // not crash, but nil
Author: Luur
Source Code: https://github.com/Luur/SwiftEchoes-Tips
License: MIT license
1673078940
Here's list of tips and tricks with all additional sources (playgrounds, images) that I would like to share. Also you can find them on Twitter @szubyak, where you can ask questions and respond with feedback. I will really glad to have you there! 😀
UIStoryboard
usage saferBelow I want to show you a few simple ways how to make usage of string literals connected with UIStoryboard
much more safer.
Global constant string literals
At first it sounds like a good idea. You only need to define the constant once and it will be available everywhere. Then if you want to change its value, there is only one place in code to change for which the effects will cascade throughout the project. But global constants have some disadvantages. Not as much as global variables but still enought to stop using them. It's not the best practice. I will cover the topic of andvatages and disadavntages of global constants/variables usage in my next posts.
Relatable storyboard names
Your storyboards should be named after the sections of which they cover. It's a general rule. If you have a storyboard which houses view controllers are related to Profile, then the name of that storyboard’s file should be Profile.storyboard
.
Uniform storyboard identifiers
When you want to use Storyboard Identifiers
on your view controllers, usage of the class names as identifiers will be a good practice. For example, “ProfileViewController” would be the identifier for ProfuleViewController. Adding this to your naming convention is a good idea.
Enum
Try to consider enums as uniform, centralized global string literal identifiers for 'UIStoryboard'. You can create UIStoryboard class extension which defines all the storyboard files you have in your project. You can also add the convenience initializer or class function to add more syntactic sugar.
extension UIStoryboard {
enum Storyboard: String {
case profile
case login
var name: String {
return rawValue.capitalized
}
}
convenience init(storyboard: Storyboard, bundle: Bundle? = nil) {
self.init(name: storyboard.name, bundle: bundle)
}
// let storyboard = UIStoryboard(storyboard: .profile)
class func storyboard(storyboard: Storyboard, bundle: Bundle? = nil) -> UIStoryboard {
return UIStoryboard(name: storyboard.name, bundle: bundle)
}
// let storyboard = UIStoryboard.storyboard(.profile)
}
I hope provided solutions will really help you to maintain your storyboards.
I'm prety sure that almost every one has heard about "main" MVC problem known as Massive View Controller and how fashionable architectures (MVVM, VIPER, CleanArchitecture) valiantly fight it. But I want to pay attation to not less common problem - massive storyboard. Because of this many developers prefer to make layouts in code insted of storyboard. I've to admit that developers despise storyboards not without reason.
Let's list main storyboard problems:
Inspite of the issues, I'm convinced there is a way to make storyboard great again 🇺🇸, resolving most of the above.
"A storyboard is meant to explain a story, not a saga."
Storyboard can be easily splitted into multiple storyboards, with each one representing an individual story. Yes, it's OK not to have one big, fat, slowloading storyboard file but have 30+ devided storyboards.
Also don't forget about Storyboard References introduced in iOS 9.
But splitting storyboard will not help with one serious problem. Storyboard usage has strong coupling with one silent killer known as String Literals
. Look for the solution in 63 StoryboardIdentifiable
protocol
XCTUnwrap
assertion functionIn Xcode 11 new assertion function has been added for use in Swift tests. XCTUnwrap
asserts that an Optional variable’s value is not nil
, returning the unwrapped value of expression for subsequent use in the test. It protects you from dealing with conditional chaining for the rest of the test. Also it removes the need to use XCTAssertNotNil(_:_:file:line:)
with either unwrapping the value. It’s common to unwrap optional before checking it for a particular value so that’s really where XCTUnwrap()
will come into its own.
struct Note {
var id: Int
var title: String
var body: String
}
class NotesViewModel {
static func all() -> [Note] {
return [
Note(id: 0, title: "first_note_title", body: "first_note_body"),
Note(id: 1, title: "second_note_title", body: "second_note_body"),
]
}
}
func testGetFirstNote() throws {
let notes = NotesViewModel.all()
let firstNote = try XCTUnwrap(notes.first)
XCTAssertEqual(firstNote.title, "first_note_title")
}
This approach is cleaner than what we might have written previously:
func testGetFirstNote() {
let notes = NotesViewModel.all()
if let firstNote = notes.first {
XCTAssertEqual(firstNote.title, "first_note_title")
} else {
XCTFail("Failed to get first note.")
}
}
UITableViewCell
identifierTo register or dequeue UITableViewCell
object we need to provide its string type identifier
. Typing string by hand is wasting time and could couse you typos. In this case I would recomend to use extension which declares static identifier
property inside UITableViewCell
to avoid these problems.
extension UITableViewCell {
static var identifier: String {
return String(describing: self)
}
}
let cell = tableView.dequeueReusableCell(withIdentifier: ExampleTableViewCell.identifier)
tableView.register(ExampleTableViewCell.self, forCellReuseIdentifier: ExampleTableViewCell.identifier)
print(ExampleTableViewCell.identifier) //ExampleTableViewCell
AlertPresentable
protocolIn my current project I work on I present alerts almost on every view controller. To reduce lines of codes and time spent on duplicate code I created AlertPresentable
layer and want to share it with you. Any ViewController
which implements AlertPresentable
protocol receive opportunity to present any type of alerts discribed in this layer just by one line of code.
protocol AlertPresentable {
func presentErrorAlert(with message: String)
func presentSuccessAlert(with message: String, action: ((UIAlertAction) -> Void)?)
func presentConfirmationAlert(with message: String, action: ((UIAlertAction) -> Void)?)
}
extension AlertPresentable where Self: UIViewController {
func presentErrorAlert(with message: String) {
presentAlert(message: message, actions: [UIAlertAction(title: "OK", style: .cancel, handler: nil)])
}
func presentSuccessAlert(with message: String, action: ((UIAlertAction) -> Void)?) {
presentAlert(message: message, actions: [UIAlertAction(title: "OK", style: .cancel, handler: action)])
}
func presentConfirmationAlert(with message: String, action: ((UIAlertAction) -> Void)?) {
presentAlert(message: message, actions: [UIAlertAction(title: "Yes", style: .default, handler: action), UIAlertAction(title: "No", style: .cancel, handler: nil)])
}
private func presentAlert(title: String? = "", message: String? = nil, actions: [UIAlertAction] = []) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach { (action) in
alertController.addAction(action)
}
present(alertController, animated: true, completion: nil)
}
}
Usage:
class ViewController: UIViewController, AlertPresentable {
override func viewDidLoad() {
super.viewDidLoad()
presentErrorAlert(with: "User not found")
presentSuccessAlert(with: "File downloaded") { _ in
// use downloaded file
}
presentConfirmationAlert(with: "Are you sure you would like to sign out?") { _ in
// sign out user
}
}
}
Implementation of grid CollectionView layout is a commont task. But I found that calculation of cell width when you dont know how many cells can fit in one row of CollectionView is not a common task.
Here is my extension for calculation width of cell in grid CollectionView to make your layot adaptive.
extension UICollectionView {
func flexibleCellWidth(minCellWidth: CGFloat, minimumInteritemSpacing: CGFloat) -> CGFloat {
let contentWidth = frame.size.width - contentInset.left - contentInset.right
let numberOfItemsInRow = Int((contentWidth + minimumInteritemSpacing) / (minCellWidth + minimumInteritemSpacing))
let spacesWidth = CGFloat(numberOfItemsInRow - 1) * minimumInteritemSpacing
let availableContentWidth = contentWidth - spacesWidth
return availableContentWidth / CGFloat(numberOfItemsInRow)
}
}
Based on minimal cell width and minimal interitem spacing it autocalculates number of cells in row and return cell width for the best placement.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellWidth = collectionView.flexibleCellWidth(minCellWidth: 72, minimumInteritemSpacing: 10)
return CGSize(width: cellWidth, height: cellWidth)
}
UILabel
You can render HTML strings within a UILabel
using a special initializer of NSAttributedString
and passing in NSAttributedString.DocumentType.html
for .documentType
. But in most cases it is not enough to display it as is. If we want to add custom font or color we need to use CSS (add CSS header to our HTML string).
extension String {
func htmlAttributedString(with fontName: String, fontSize: Int, colorHex: String) -> NSAttributedString? {
do {
let cssPrefix = "<style>* { font-family: \(fontName); color: #\(colorHex); font-size: \(fontSize); }</style>"
let html = cssPrefix + self
guard let data = html.data(using: String.Encoding.utf8) else { return nil }
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return nil
}
}
}
Usage example
Error
by adopting LocalizedError
protocolCustom errors are an integral parts of you work. Swift-defined error types can provide localized error descriptions by adopting the new LocalizedError
protocol.
enum UserError: Error {
case credentialsNotMatch
case invalidEmail
case invalidName
}
extension UserError: LocalizedError {
public var errorDescription: String? {
switch self {
case .credentialsNotMatch:
return NSLocalizedString("Your username and/or password do not match", comment: "Credentials do not match")
case .invalidEmail:
return NSLocalizedString("Please enter email address in format: yourname@example.com", comment: "Invalid email format")
case .invalidName:
return NSLocalizedString("Please enter you name", comment: "Name field is blank")
}
}
}
Example:
func validate(email: String?, password: String?) throws {
throw UserError.credentialsNotMatch
}
do {
try validate(email: "email", password: "password")
} catch UserError.credentialsNotMatch {
print(UserError.credentialsNotMatch.localizedDescription)
}
You can provide even more information. It's common decency to show user except error description also failure reasons and recovery suggestions. Sorry for ambiguous error messages :)
enum ProfileError: Error {
case invalidSettings
}
extension ProfileError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("Your profile settings are incorrect", comment: "")
}
}
public var failureReason: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("I don't know why", comment: "")
}
}
public var recoverySuggestion: String? {
switch self {
case .invalidSettings:
return NSLocalizedString("Please provide correct profile settings", comment: "")
}
}
}
let error = ProfileError.invalidSettings
print(error.errorDescription)
print(error.failureReason)
print(error.recoverySuggestion)
Result
type without value to provideResult
type usage is really popular nowadays.
enum Result<T> {
case success(result: T)
case failure(error: Error)
}
func login(with credentials: Credentials, handler: @escaping (_ result: Result<User>) -> Void) {
// Two possible options:
handler(Result.success(result: user))
handler(Result.failure(error: UserError.notFound))
}
login(with:)
operation has user
value to provide and default Result
type fits perfectly here. But let’s imagine that your operation hasn’t got value to provide or you don’t care about it. Default Result
type makes you to provide the result value any way.
To fix this inconvenience you need to add extension and instantiate a generic with an associated value of type Void
.
func login(with credentials: Credentials, handler: @escaping (_ result: Result<Void>) -> Void)
extension Result where T == Void {
static var success: Result {
return .success(result: ())
}
}
Now we can change our func login(with:)
a bit, to ignore result success value if we don’t care about it.
func login(with credentials: Credentials, handler: @escaping (_ result: Result<Void>) -> Void) {
// Two possible options:
handler(Result.success)
handler(Result.failure(error: UserError.notFound))
}
In unit testing terms, there are three phases to a unit test:
func testSum() {
// Given
let firstValue = 4
let secondValue = 3
// When
let sum = sut.sum(firstValue: firstValue, secondValue: secondValue)
// Then
XCTAssertEqual(sum, 7)
}
Note: When comparing two values using XCTAssertEqual
you should always put the actual value first before the expected value.
sut
and test lifecycleThe subject under test is always named sut
. It refers to a subject that is being tested for correct operation. It is short for "whatever thing we are testing" and is always defined from the perspective of the test. So when you look at your tests, there is no ambiguity as to what is being tested. All other objects are named after their types. Clearly identifying the subject under test in the sut
variable makes it stand out from the rest.
The test lifecycle for each test is surrounded by a pair of functions - setUp()
and tearDown()
. The setUp()
function is called before the execution of each test function in the test class. The tearDown()
function is called after the execution of each test function in the test class. They provide you a place to prepare the sut
ready before the test starts, and clean up the sut
and any states after the test finishes. It’s important that each test starts with the desired initial state.
import XCTest
class YourClassTests: XCTestCase {
// MARK: - Subject under test
var sut: YourClass!
// MARK: - Test lifecycle
override func setUp() {
super.setUp()
sut = YourClass()
}
override func tearDown() {
sut = nil
super.tearDown()
}
}
Could be useful solution for positioning nodes in your game or views in ordinary business application on the perimeter of circle.
func pointOnCircle(radius: Float, center: CGPoint) -> CGPoint {
// Random angle between 0 and 2*pi
let theta = Float(arc4random_uniform(UInt32.max)) / Float(UInt32.max-1) * .pi * 2.0
// Convert polar to cartesian
let x = radius * cos(theta)
let y = radius * sin(theta)
return CGPoint(x: CGFloat(x) + center.x, y: CGFloat(y) + center.y)
}
zip()
functionLet’s imagine you have an array of LOTR heroes, and an array of weapons that match those heros. How could I create a list with the heros and weapons combined at each index? zip()
function is designed to combine two sequences into a single sequence of tuples.
Note: I would recomend to wrap the output from zip()
into array to make its output easier to read.
One of the useful features of zip()
is that if your two arrays arent equal in size it will choose the shorter one. Yes, it will not crash your application, just ignore element which doesn’t have a match in second sequence.
StackView
custom spacingStackView
, introduced in iOS 9, made Auto Layout usage much easier, because of reducing the amount of constraints needed to create manually for common layouts. But we faced with the problem, property spacing
applies equally to the arranged subviews of StackView
. Before iOS 11, there were 2 ways how to overcome this problem. One way is to create views with desired height and use them as spacer views or we could do it by nesting stack views but these two ways always seemed to be an unnecessary complication.
In iOS 11 you can create stack views with custom spacing between views. But there is no way to specify this custom spacing in Storyboard, only in code.
stackView.setCustomSpacing(32.0, after: label)
Also, in iOS 11 Apple introduced default and system spacing properties on the UIStackView
class. You can use these to set or reset the custom spacing after a view.
stackview.setCustomSpacing(UIStackView.spacingUseSystem, after: label)
XCode 9.0 gives us opportunity to create named colors. We can do it directly inside assets catalog and use it in code and storyboards. Named colors contain 3 parts: name ("FerrariRed"), color specified as range and device compatibility.
Created named color you can use like this:
view.backgroundColor = UIColor(named: "FerrariRed")
Inside storyboards you can use created named color by selecting it from the color dropdown menu.
Result
error handlingError handling is realy common functionality. But if you want to handle errors across asynchronous boundaries or store value/error results for later processing, then ordinary Swift error handling won’t help. The best alternative is a common pattern called a Result
type: it stores a value/error “sum” type (either one or the other) and can be used between any two arbitrary execution contexts.
Example of Result
type:
enum Result<T> {
case success(result: T)
case failure(error: String)
}
Usage of Result
type:
protocol TaskStoreProtocol {
func fetchTasks(handler: @escaping TaskStoreFetchTasksResult)
}
typealias TaskStoreFetchTasksResult = (_ result: Result<[Task]>) -> Void
class TasksStore: TasksStoreProtocol {
func fetchTasks(handler: @escaping TaskStoreFetchTasksResult) {
// Some useful code, network request or something like this goes here
if success {
handler(Result.success(result: fetchedTasks))
} else {
handler(Result.failure(error: error))
}
}
}
Here I want to introduce you with general info about generic type parameters and their naming style. Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of angle brackets (such as <T>
). Once you specify a type parameter, you can use it to define the type of a function’s parameters, or as the function’s return type, or as a type annotation within the body of the function. In each case, the type parameter is replaced with an actual type whenever the function is called.
You can provide more than one type parameter by writing multiple names within the angle brackets, separated by commas.
In most cases, type parameters have descriptive names, such as Key
and Value
in Dictionary<Key, Value>
and Element
in Array<Element>
, which tells about the relationship between the type parameter and the generic type or function it’s used in. However, when there isn’t a meaningful relationship between them, it’s traditional to name them using single letters such as T
, U
, and V
.
You can find usage of generics in several of my tips. So here I want to show you some basics of generics. They are one of the most powerful features of Swift. It gives you opportinity to write flexible, reusable functions. Types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted way.
At first, here is nongeneric function which swaps two Int
values:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
As you can see, this func is useful but a bit limited, because you can swap only Int
values. If you want to swap two String
values, or two Double
values, you have to write more functions.
Generic functions can work with any type. Here’s a generic representation of our function.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
The body of our two functions is the same. But the first line of is slightly different. The generic version of the function uses a placeholder type name (called T
) instead of an actual type name (such as Int
, String
, or Double
). The placeholder type name doesn’t say anything about what T
must be, but it does say that both a
and b
must be of the same type T
. The actual type to use in place of T
is determined each time the function is called.
As with all dependencies, we should reduce UserDefaults
to an abstract protocol, inject it into the classes that require it, and thereby decouple ourselves from implementation details and increase the testability of our code by introducing seams. But I'll be honest, the UserDefaults API
is so simple and pervasive that standard dependency management feels like overkill. If so, we've probably encountered the same problem: without the ability to inject mocks, testing code that makes use of UserDefaults
can be a pain. Any time we use our test device (including running tests on it!) we potentially change the state of its persisted settings.
Here is extension UserDefaults
that gives your test method an opportunity to run in clean state and not jinx real persisted settings.
extension UserDefaults {
static func blankDefaultsWhile(handler:()->Void){
guard let name = Bundle.main.bundleIdentifier else {
fatalError("Couldn't find bundle ID.")
}
let old = UserDefaults.standard.persistentDomain(forName: name)
defer {
UserDefaults.standard.setPersistentDomain( old ?? [:], forName: name)
}
UserDefaults.standard.removePersistentDomain(forName: name)
handler()
}
}
Usage:
class MyTests: XCTestCase {
func testSomething() {
// Defaults can be full of junk.
UserDefaults.blankDefaultsWhile {
// Some tests that expect clean defaults.
// They can also scribble all over defaults with test-specific values.
}
// Defaults are back to their pre-test state.
}
}
Here is one more trick how to make protocol method to be optional. Let’s remember why we would like to declare a protocol method as optional? It is because we we don’t want to write the implementation if that method will not be used. So Swift has a feature called extension that allow us to provide a default implementation for those methods that we want to be optional.
protocol CarEngineStatusDelegate {
func engineWillStop()
func engineDidStop()
}
extension CarEngineStatusDelegate {
func engineWillStop() {}
}
This way the class/struct that will use our protocol will only need to implement func engineDidStop()
.
How to find the view controller that is responsible for a particular view? This is as easy as walking the responder chain looking for the first UIViewController
you find.
extension UIView {
func findViewController() -> UIViewController? {
if let nextResponder = self.next as? UIViewController {
return nextResponder
} else if let nextResponder = self.next as? UIView {
return nextResponder.findViewController()
} else {
return nil
}
}
}
You should only use it when you really need it – if you’re able to call methods directly using a delegate or indirectly by posting notifications then do it in first place.
Lets imagine full of texfields scene (e.g. UserProfile scene). Moving between textfields using Next/Return on on-screen keyboard is integral functionality.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
If you need to force the first responder to resign itself and aren’t sure which textfield is in control, it’s easier to use view.endEditing(true)
.
allCases
property for your enum
(Swift 4.2)Retrieving all cases of enum
is really common task, but it's implementation is not so clean. We were forced to create static
variable of array with all cases of our enum
manually.
enum Cars {
case BMW
case Audi
case Volkswagen
case Mercedes
static let allCases = [BMW, Audi, Volkswagen, Mercedes]
}
In Swift 4.2 we got really useful property allCases
, which is autogenerated (all you need to do is make your enum
conform to the CaseIterable
protocol), so it releases us from additional steps.
enum Cars: CaseIterable {
case BMW
case Audi
case Volkswagen
case Mercedes
}
for car in Cars.allCases {
print(car)
}
One more thing left to say about protocols. Sometimes protocols should be adopted only by classes. To achieve this behaviour you should define you protocol with class
keyword or by inheriting from AnyObject
. This is commonly happens because you have a delegate
property that needs to use weak
storage to avoid the risk of a strong reference cycle
(formerly known as a retain cycle
).
weak var delegate: CarDelegate?
The weak
keyword can't be used with structs and enums, because they are value types.
In previous tip you can observe how to split your protocol to smaller pieces. Here I will show you how to combine your protocols to make bigger one using inheritance and composition. To merge protocols together use &
for protocol composition.
typealias Codable = Decodable & Encodable
You can also use protocol inheritance to build larger protocols.
protocol CarEngineStatusDelegate { }
protocol CarMovingStatusDelegate { }
protocol CarDelegate: CarMovingStatusDelegate, CarEngineStatusDelegate { }
If you implement a protocol in Swift you must implement all its requirements. Optional methods are not allowed.
Example:
protocol CarDelegate {
func engineDidStart()
func carShouldStartMoving() -> Bool
func carDidStartMoving()
func engineWillStop()
func engineDidStop()
}
But there are a few tricks how to make some methods to be optionals:
protocol CarEngineStatusDelegate { func engineDidStart() func engineWillStop() func engineDidStop() }
* Use the `@objc` attribute on a Swift protocol to receive opportunity to mark methods as being optional, but you may no longer use Swift structs and enums with that protocol, and you may no longer use protocol extensions.
```swift
@objc protocol CarDelegate {
@objc optional func engineDidStart()
func carShouldStartMoving() -> Bool
func carDidStartMoving()
@objc optional func engineWillStop()
@objc optional func engineDidStop()
}
Here is the optional method usage:
delegate?.engineWillStop?()
The delegate
property is optional because there might not be a delegate assigned. And in the CarDelegate
protocol we made engineWillStop()
an optional requirement, so even if a delegate is present it might not implement that method. As a result, we need to use optional chaining.
If we are talking about naming the protocol itself:
Collection
).able
, ible
, or ing
(e.g. Equatable
, ProgressReporting
).Briefly talking about methods names inside protocol, you can use whatever names you want. But, if your protocol is created to represent delegates please look: #32 Delegate naming
Little thing that surprisingly became a discovery for me, because it's rare case in practice, so I'm not ashamed of it 🤣. Property observers
, getter/setter
and lazy
cant be used together, they are mutually exclusive. So you should choose what pattern makes most profit to you in particaulr situation.
Methods are functions associated with a type. Mock the type, and you can intercept the method. In Swift, we prefer to do this with protocols. But a standalone function lives on its own, without any associated data. In other words, it has no self
. During unit-testing we want to intercept these calls, for two main reasons:
In popular networking 3party library Almofire all request
, upload
or download
functions implemented as standalone functions. So, we have dificulties with intercepting during unit testing. We can't mock associated type, because there is no type. Also responseJSON
function is implemented in extension, so we can't make any changes or override it.
I defined a new class with 2 methods (request
and responseJSON
) with the same method signature as Alamofire's request
and responseJSON
methods. I called the Alamofire version of these methods inside. And then used this class as a dependency in my sut
.
import Alamofire
class AlamofireWrapper {
@discardableResult
public func request(_ url: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil) -> DataRequest {
return Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
}
public func responseJSON(request: DataRequest, queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: @escaping (DataResponse<Any>) -> Void) {
request.responseJSON(queue: queue, options: options, completionHandler: completionHandler)
}
}
My request part starts looking like this:
var alamofireWrapper = AlamofireWrapper()
let request = alamofireWrapper.request(url, parameters: params, encoding: URLEncoding(destination: .queryString))
alamofireWrapper.responseJSON(request: request) { response in
if response.result.isSuccess {
success(response.result.value)
} else if let error = response.result.error {
failure(error)
}
}
And here is my test double finaly:
// MARK: - Test doubles
class AlamofireWrapperSpy: AlamofireWrapper {
enum RequestStatus {
case success
case failure
}
var status: RequestStatus = .success
// MARK: Method call expectations
var requestCalled = false
var responseJSONCalled = false
// MARK: Spied methods
override func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters?, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequest {
requestCalled = true
return SessionManager.default.request("dummy_url")
}
override func responseJSON(request: DataRequest, queue: DispatchQueue?, options: JSONSerialization.ReadingOptions, completionHandler: @escaping (DataResponse<Any>) -> Void) {
responseJSONCalled = true
switch status {
case .success:
let value = ["result": "best_response_ever"]
completionHandler(DataResponse(request: nil, response: nil, data: nil, result: Result.success(value)))
case .failure:
let error = NSError(domain: "", code: 500, userInfo: nil)
completionHandler(DataResponse(request: nil, response: nil, data: nil, result: Result.failure(error)))
}
}
}
Few days ago I faced a sorting task. I needed to sort array of object by more then one criteria that is also optional. My object was Place
with rating
and distance
properties. rating
was main criteria for sorting and distance
was secondary. So, here is my workouts.
struct Place {
var rating: Int?
var distance: Double?
}
func sortPlacesByRatingAndDistance(_ places: [Place]) -> [Place] {
return places.sorted { t1, t2 in
if t1.rating == t2.rating {
guard let distance1 = t1.distance, let distance2 = t2.distance else {
return false
}
return distance1 < distance2
}
guard let rating1 = t1.rating, let rating2 = t2.rating else {
return false
}
return rating1 > rating2
}
}
let places = [Place(rating: 3, distance: 127), Place(rating: 4, distance: 423), Place(rating: 5, distance: nil), Place(rating: nil, distance: 100), Place(rating: nil, distance: 34), Place(rating: nil, distance: nil)]
let sortedPlaces = sortPlacesByRatingAndDistance(places) // [{rating 5, nil}, {rating 4, distance 423}, {rating 3, distance 127}, {nil, distance 34}, {nil, distance 100}, {nil, nil}]
Super lightweight extension 🎈. How to remove particular object from array. Remove first collection element that is equal to the given object
.
extension Array where Element: Equatable {
mutating func remove(_ object: Element) {
if let index = index(of: object) {
remove(at: index)
}
}
}
When creating custom delegate methods take into consideration the following list items:
delegate
, if it is just one of them, or put delegate
at the end, when it is more than one. Here is the example, WKWebView
has uiDelegate
and navigationDelegate
properties that can point to two different objects.
will
, did
, and should
.func tableView(_ tableView: UITableView,...
. It helps in situations where there is more than one object that can send a message .As a result, Apple delegates have a very specific naming convention:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Asynchronous work in Playground.
Tell the playground it should continue running forever, otherwise it will terminate before the asynchronous work has time to hppen.
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchGroup
usageLet’s say you’ve got several long running tasks to perform. After all of them have finished, you’d like to run some further logic. You could run each task in a sequential fashion, but that isn’t so efficient - you’d really like the former tasks to run concurrently. DispatchGroup
enables you to do exactly this.
let dispatchGroup = DispatchGroup()
for i in 1...5 {
dispatchGroup.enter()
Alamofire.request(url, parameters: params).responseJSON { response in
//work with response
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
print("All requests complete")
}
In the above, all long running functions will perform concurrently, followed by the print statement, which will execute on the main thread.
Each call to enter()
must be matched later on with a call to leave()
, after which the group will call the closure provided to notify()
.
DispatchGroup
has a few other tricks:
notify()
, we can call wait()
. This blocks the current thread until the group’s tasks have completed.wait(timeout:)
. This blocks the current thread, but after the timeout specified, continues anyway. To create a timeout object of type DispatchTime
, the syntax .now() + 1
will create a timeout one second from now.wait(timeout:)
returns an enum that can be used to determine whether the group completed, or timed out.Clear way 🛣️ to return the unique list of objects based on a given key 🔑. It has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination.
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set: Set<T> = []
var arrayOrdered: [Element] = []
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
Using View Debugging
you’re now able to inspect an entire view hierarchy visually – right from within Xcode, instead of printing frames to the console and trying to visualize layouts in your head.
You can invoke the view debugger by choosing View UI Hierarchy
from the process view options menu in the debug navigator, or by choosing Debug
> View Debugging
> Capture View Hierarchy
.
You'll see a 3D representation of your view, which means you can look behind the layers to see what else is there. The hierarchy automatically puts some depth between each of its views, so they appear to pop off the canvas as you rotate them. If you have a complicated view layout, View Debugging
> Show View Frames
option will draw lines around all your views so you can see exactly where they are.
More about view debugging here
A breakpoint
is a debugging tool that allows you to pause the execution of your program up to a certain moment. Creating pause
points in your code can help you investigate your code. While your app is paused, light green arrow that shows your current execution position can be moved. Just click and drag it somewhere else to have execution pick up from there – although Xcode will warn you that it might have unexpected results, so tread carefully!
Right-click on the breakpoint (the blue arrow marker) and choose Edit Breakpoint.
In the popup that appears you can set the condition
. Execution will now pause only when your condition is true. You can use conditional breakpoints to execute debugger commands automatically – the Automatically continue
checkbox is perfect for making your program continue uninterrupted while breakpoints silently trigger actions. Also you can set Ignore times before stoping
and actions like Debuger command
, Log message
, Sound
, etc.
Shortcuts:
F6
- Step Over. Ctrl+Cmd+Y
- Continue (continue executing my program until you hit another breakpoint)
In Xcode debug console you can use po
to print what you need during pause.
assert()
is debug-only check that will force your app to crash if specific condition is false.
assert(4 == 4, "Maths error") //OK
assert(3 == 2, "Maths error") //Crash
As you can see assert()
takes two parameters:
If the check evaluates to false, your app will be forced to crash because you know it's not in a safe state, and you'll see the error message in the debug console. If you don’t have a condition to evaluate, or don’t need to evaluate one, you can use assertionFailure()
function.
precondition()
is not debug-only check. It will crash your app even in release mode.
precondition(4 == 4, "Maths error") //OK
precondition(3 == 2, "Maths error") //Crash
preconditionFailure()
works the same as assertionFailure()
. With the same difference as above, it works for release builds.
fatalError()
, like assertionFailure()
and preconditionFailure()
works for all optimisation levels in all build configurations.
fatalError("ERROR")
More about asserts and optimisation levels you can find here
Debuging 👨🔧 is one of the most importent aspects of programing👨💻. It should be in your skillbox anyway. It contains log functions, asserts, breakpoints and view debuging. Let's observe everyone of them just one by one. And here are log functions in the crosshairs 🔍.
We know print()
as a variadic function. Function that accepts any number of parameters.
print("one", "two", "three", "four") //one two three four
But it's variadic nature becomes much more useful when you use separator
and terminator
, its optional extra parameters. separator
gives you opportunity to provide a string that should be placed between every item. It's "space" by default.
print("one", "two", "three", "four", separator: "-") //one-two-three-four
Meanwhile terminator
is what should be placed after the last item. It’s \n
by default, which means "line break".
print("one", "two", "three", "four", terminator: " five") //one two three four five
UIView
content with animationReally lightweight way 🎈 How to add content changing animation to UIView and it subclasses.
extension UIView {
func fadeTransition(_ duration: CFTimeInterval) {
let animation = CATransition()
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.type = kCATransitionFade
animation.duration = duration
layer.add(animation, forKey: kCATransitionFade)
}
}
Just invoke 🧙♂️ fadeTransition(_ duration: CFTimeInterval)
by your view before you will apply a change.
label.fadeTransition(1)
label.text = "Updated test content with animation"
Next code snippet 📃 I use to keep eye on changes that take place in the managed object context. Useful thing to know what's going on, what was added, updated ( what specific values were changed ) or deleted 📥📝📤
func changeNotification(_ notification: Notification) {
guard let userInfo = notification.userInfo else { return }
if let inserts = userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>, inserts.count > 0 {
print("--- INSERTS ---")
print(inserts)
print("+++++++++++++++")
}
if let updates = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>, updates.count > 0 {
print("--- UPDATES ---")
for update in updates {
print(update.changedValues())
}
print("+++++++++++++++")
}
if let deletes = userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>, deletes.count > 0 {
print("--- DELETES ---")
print(deletes)
print("+++++++++++++++")
}
}
NotificationCenter.default.addObserver(self, selector: #selector(self.changeNotification(_:)), name: .NSManagedObjectContextObjectsDidChange, object: moc)
String
into wordsDefault ways of splitting ✂️ String
don't work perfect sometimes, because of punctuation characters and other "wreckers" 🐛. Here is extension for splitting ✂️ String
into words 💻🧐👌.
extension String {
var words: [String] {
return components(separatedBy: .punctuationCharacters)
.joined()
.components(separatedBy: .whitespaces)
.filter{!$0.isEmpty}
}
}
I discovered strange behavior of tuples during comparing 🤪. Comparison cares only about types and ignores labels 😦. So result can be unexpected. Be careful ⚠️.
let car = (model: "Tesla", producer: "USA")
let company = (name: "Tesla", country: "USA")
if car == company {
print("Equal")
} else {
print("Not equal")
}
Printed result will be: Equal
Painless way ( NO to timers from now ⛔️ ) how to detect that user stop typing text in text field ⌨️ Could be usefull for lifetime search 🔍
class TestViewController: UIViewController {
@objc func searchBarDidEndTyping(_ textField: UISearchBar) {
print("User finsihed typing text in search bar")
}
@objc func textFieldDidEndTyping(_ textField: UITextField) {
print("User finished typing text in text field")
}
}
extension TestViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(searchBarDidEndTyping), object: searchBar)
self.perform(#selector(searchBarDidEndTyping), with: searchBar, afterDelay: 0.5)
return true
}
}
extension TestViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(textFieldDidEndTyping), object: textField)
self.perform(#selector(textFieldDidEndTyping), with: textField, afterDelay: 0.5)
return true
}
}
UITextField
Clear way of adding left\right text offset inside UItextField
🔨🧐💻 Also, because of @IBInspectable
it could be easily editable in Interface Builder’s inspector panel.
@IBDesignable
extension UITextField {
@IBInspectable var leftPaddingWidth: CGFloat {
get {
return leftView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
leftView = paddingView
leftViewMode = .always
}
}
@IBInspectable var rigthPaddingWidth: CGFloat {
get {
return rightView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
rightView = paddingView
rightViewMode = .always
}
}
}
I'm not huge fan of custom operators 😐 because they are intuitively obvious only to their authors, but I've created one which gives you opportunity to get common elements in two arrays whos elements implement Equatable
protocol 🔨🧐💻
infix operator &
func &<T : Equatable>(lhs: [T], rhs: [T]) -> [T] {
return lhs.filter { rhs.contains($0) }
}
Gradient 🏳️🌈 on Navigation Bar is really good looking, but not very easy to implement 🧐🔨👨💻 Works with iOS 11 largeTitle navigation bar too 👌
struct GradientComponents {
var colors: [CGColor]
var locations: [NSNumber]
var startPoint: CGPoint
var endPoint: CGPoint
}
extension UINavigationBar {
func applyNavigationBarGradient(with components: GradientComponents) {
let size = CGSize(width: UIScreen.main.bounds.size.width, height: 1)
let gradient = CAGradientLayer()
gradient.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
gradient.colors = components.colors
gradient.locations = components.locations
gradient.startPoint = components.startPoint
gradient.endPoint = components.endPoint
UIGraphicsBeginImageContext(gradient.bounds.size)
gradient.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.barTintColor = UIColor(patternImage: image!)
}
}
Easy way how to get next element of array
extension Array where Element: Hashable {
func after(item: Element) -> Element? {
if let index = self.index(of: item), index + 1 < self.count {
return self[index + 1]
}
return nil
}
}
Great extension to split array by chunks of given size
extension Array {
func chunk(_ chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map({ (startIndex) -> [Element] in
let endIndex = (startIndex.advanced(by: chunkSize) > self.count) ? self.count-startIndex : chunkSize
return Array(self[startIndex..<startIndex.advanced(by: endIndex)])
})
}
}
Scene with UIImageView
on top looks stylish if navigation bar is transparent. Easy way how to make navigation bar transparent or opaque.
func transparentNavigationBar() {
self.setBackgroundImage(UIImage(), for: .default)
self.shadowImage = UIImage()
}
func opaqueNavigationBar() {
self.shadowImage = nil
self.setBackgroundImage(nil, for: .default)
}
One more useful extension 🔨💻 Gives you opportunity to group objects by property 👨💻🧐
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Usage:
struct Person {
var name: String
var age: Int
}
let mike = Person(name: "Mike", age: 18)
let john = Person(name: "John", age: 18)
let bob = Person(name: "Bob", age: 56)
let jake = Person(name: "Jake", age: 56)
let roman = Person(name: "Roman", age: 25)
let persons = [mike, john, bob, jake, roman]
let groupedPersons = persons.group { $0.age }
for persons in groupedPersons {
print(persons.map { $0.name })
}
Result:
["Mike", "John"]
["Bob", "Jake"]
["Roman"]
Also in-box alternative
Do you need semicolons in Swift ? Short answer is NO, but you can use them and it will give you interesting opportunity. Semicolons enable you to join related components into a single line.
func sum(a: Int, b: Int) -> Int {
let sum = a + b; return sum
}
Unit testing shouldn’t have any side effects. While running tests, Xcode firstly launches app and thus having the side effect of executing any code we may have in our App Delegate and initial View Controller. Fake AppDelegate in your main.swift
to prevent it.
You can find main.swift
file here
didSet
when property’s value is set inside init
contextApple's docs specify that: "Property observers are only called when the property’s value is set outside of initialization context."
defer
can change situation 😊
class AA {
var propertyAA: String! {
didSet {
print("Function: \(#function)")
}
}
init(propertyAA: String) {
self.propertyAA = propertyAA
}
}
class BB {
var propertyBB: String! {
didSet {
print("Function: \(#function)")
}
}
init(propertyBB: String) {
defer {
self.propertyBB = propertyBB
}
}
}
let aa = AA(propertyAA: "aa")
let bb = BB(propertyBB: "bb")
Result:
Function: propertyBB
Two ways of changing type of items in array and obvious difference between them 🧐👨💻
let numbers = ["1", "2", "3", "4", "notInt"]
let mapNumbers = numbers.map { Int($0) } // [Optional(1), Optional(2), Optional(3), Optional(4), nil]
let compactNumbers = numbers.compactMap { Int($0) } // [1, 2, 3, 4]
forEach
and map
execution order differenceExecution order is interesting difference between forEach
and map
: forEach
is guaranteed to go through array elements in its sequence, while map
is free to go in any order.
Common Types of Error Messages
Do you know that using map
gives profit to the compiler: it's now clear we want to apply some code to every item in an array, then like in for
loop we could have break
on halfway through.
compactMap
func is effectively the combination of using map
and joined
in a single call, in that order. It maps items in array A into array B using a func you provide, then joins the results using concatenation.
Functions min
and max
could be also combinations of sorted.first
and sorted.last
in single call.
let colors = ["red", "blue", "black", "white"]
let min = colors.min() // black
let first = colors.sorted().first // black
let max = colors.max() // white
let last = colors.sorted().last // white
Use enumerated
when you iterate over the collection to return a sequence of pairs (n, c)
, where n
- index for each element and c
- its value 👨💻💻
for (n, c) in "Swift".enumerated() {
print("\(n): \(c)")
}
Result:
0: S
1: w
2: i
3: f
4: t
Also be careful with this tricky thing, enumerated
on collection will not provide actual indices, but monotonically increasing integer, which happens to be the same as the index for Array but not for anything else, especially slices.
Ever faced the problem that u can't hide status bar because of prefersStatusBarHidden
is get-only
? The simplest solution is to override
it 🧐👨💻
let vc = UIViewController()
vc.prefersStatusBarHidden = true // error
print("statusBarHidded \(vc.prefersStatusBarHidden)") // false
class TestViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return true
}
}
let testVC = TestViewController()
print("statusBarHidded \(testVC.prefersStatusBarHidden)") // true
You can extend collections to return the element at the specified index if it is within bounds, otherwise nil.
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let cars = ["Lexus", "Ford", "Volvo", "Toyota", "Opel"]
let selectedCar1 = cars[safe: 3] // Toyota
let selectedCar2 = cars[safe: 6] // not crash, but nil
Author: Luur
Source Code: https://github.com/Luur/SwiftEchoes-Tips
License: MIT license
1600430400
Swift is a fast and efficient general-purpose programming language that provides real-time feedback and can be seamlessly incorporated into existing Objective-C code. This is why developers are able to write safer, more reliable code while saving time. It aims to be the best language that can be used for various purposes ranging from systems programming to mobile as well as desktop apps and scaling up to cloud services.
Below here, we list down the 10 best online resources to learn Swift language.
(The list is in no particular order)
#developers corner #free online resources to learn swift language #learn swift #learn swift free #learn swift online free #resources to learn swift #swift language #swift programming
1609999986
A thoroughly researched list of top Swift developers with ratings & reviews to help find the best Swift development companies around the world.
#swift development service providers #best swift development companies #top swift development companies #swift development solutions #top swift developers #swift
1594193714
Want to create a native iOS application for your Startup?
Hire Dedicated Swift Developers for end-to-end services like development, migration, upgrade, testing, and support & maintenance. Trust HourlyDeveloper.io our Swift development team for iOS device apps that are high on performance and security.
Consult with experts:- https://bit.ly/2C5M6cz
#hire dedicated swift developers #swift developers #swift development company #swift development services #swift development #swift
1666245660
One of the things I really love about Swift is how I keep finding interesting ways to use it in various situations, and when I do - I usually share them on Twitter. Here's a collection of all the tips & tricks that I've shared so far. Each entry has a link to the original tweet, if you want to respond with some feedback or question, which is always super welcome! 🚀
⚠️ This list is no longer being updated. For my latest Swift tips, checkout the "Tips" section on Swift by Sundell.
Also make sure to check out all of my other Swift content:
🚀 Here are some quick tips to make async tests faster & more stable:
// BEFORE:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
detector.detectMentions(in: string) { mentions in
XCTAssertEqual(mentions, ["johnsundell"])
}
sleep(2)
}
}
// AFTER:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
var mentions: [String]?
let expectation = self.expectation(description: #function)
detector.detectMentions(in: string) {
mentions = $0
expectation.fulfill()
}
waitForExpectations(timeout: 10)
XCTAssertEqual(mentions, ["johnsundell"])
}
}
For more on async testing, check out "Unit testing asynchronous Swift code".
✍️ Adding support for the new Apple Pencil double-tap feature is super easy! All you have to do is to create a UIPencilInteraction
, add it to a view, and implement one delegate method. Hopefully all pencil-compatible apps will soon adopt this.
let interaction = UIPencilInteraction()
interaction.delegate = self
view.addInteraction(interaction)
extension ViewController: UIPencilInteractionDelegate {
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
// Handle pencil double-tap
}
}
For more on using this and other iPad Pro features, check out "Building iPad Pro features in Swift".
😎 Here's a cool function that combines a value with a function to return a closure that captures that value, so that it can be called without any arguments. Super useful when working with closure-based APIs and we want to use some of our properties without having to capture self
.
func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
return { closure(value) }
}
// BEFORE:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = { [weak self] in
guard let self = self else {
return
}
self.productManager.startCheckout(for: self.product)
}
}
}
// AFTER:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = combine(product, with: productManager.startCheckout)
}
}
💉 When I'm only using a single function from a dependency, I love to inject that function as a closure, instead of having to create a protocol and inject the whole object. Makes dependency injection & testing super simple.
final class ArticleLoader {
typealias Networking = (Endpoint) -> Future<Data>
private let networking: Networking
init(networking: @escaping Networking = URLSession.shared.load) {
self.networking = networking
}
func loadLatest() -> Future<[Article]> {
return networking(.latestArticles).decode()
}
}
For more on this technique, check out "Simple Swift dependency injection with functions".
💥 It's cool that you can easily assign a closure as a custom NSException
handler. This is super useful when building things in Playgrounds - since you can't use breakpoints - so instead of just signal SIGABRT
, you'll get the full exception description if something goes wrong.
NSSetUncaughtExceptionHandler { exception in
print(exception)
}
❤️ I love that in Swift, we can use the type system to make our code so much more self-documenting - one way of doing so is to use type aliases to give the primitive types that we use a more semantic meaning.
extension List.Item {
// Using type aliases, we can give semantic meaning to the
// primitive types that we use, without having to introduce
// wrapper types.
typealias Index = Int
}
extension List {
enum Mutation {
// Our enum cases now become a lot more self-documenting,
// without having to add additional parameter labels to
// explain them.
case add(Item, Item.Index)
case update(Item, Item.Index)
case remove(Item.Index)
}
}
For more on self-documenting code, check out "Writing self-documenting Swift code".
🤯 A little late night prototyping session reveals that protocol constraints can not only be applied to extensions - they can also be added to protocol definitions!
This is awesome, since it lets us easily define specialized protocols based on more generic ones.
protocol Component {
associatedtype Container
func add(to container: Container)
}
// Protocols that inherit from other protocols can include
// constraints to further specialize them.
protocol ViewComponent: Component where Container == UIView {
associatedtype View: UIView
var view: View { get }
}
extension ViewComponent {
func add(to container: UIView) {
container.addSubview(view)
}
}
For more on specializing protocols, check out "Specializing protocols in Swift".
📦 Here's a super handy extension on Swift's Optional
type, which gives us a really nice API for easily unwrapping an optional, or throwing an error in case the value turned out to be nil
:
extension Optional {
func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
throw errorExpression()
}
}
}
let file = try loadFile(at: path).orThrow(MissingFileError())
For more ways that optionals can be extended, check out "Extending optionals in Swift".
👩🔬 Testing code that uses static APIs can be really tricky, but there's a way that it can often be done - using Swift's first class function capabilities!
Instead of accessing that static API directly, we can inject the function we want to use, which enables us to mock it!
// BEFORE
class FriendsLoader {
func loadFriends(then handler: @escaping (Result<[Friend]>) -> Void) {
Networking.loadData(from: .friends) { result in
...
}
}
}
// AFTER
class FriendsLoader {
typealias Handler<T> = (Result<T>) -> Void
typealias DataLoadingFunction = (Endpoint, @escaping Handler<Data>) -> Void
func loadFriends(using dataLoading: DataLoadingFunction = Networking.loadData,
then handler: @escaping Handler<[Friend]>) {
dataLoading(.friends) { result in
...
}
}
}
// MOCKING IN TESTS
let dataLoading: FriendsLoader.DataLoadingFunction = { _, handler in
handler(.success(mockData))
}
friendsLoader.loadFriends(using: dataLoading) { result in
...
}
🐾 Swift's pattern matching capabilities are so powerful! Two enum cases with associated values can even be matched and handled by the same switch case - which is super useful when handling state changes with similar data.
enum DownloadState {
case inProgress(progress: Double)
case paused(progress: Double)
case cancelled
case finished(Data)
}
func downloadStateDidChange(to state: DownloadState) {
switch state {
case .inProgress(let progress), .paused(let progress):
updateProgressView(with: progress)
case .cancelled:
showCancelledMessage()
case .finished(let data):
process(data)
}
}
🅰 One really nice benefit of Swift multiline string literals - even for single lines of text - is that they don't require quotes to be escaped. Perfect when working with things like HTML, or creating a custom description for an object.
let html = highlighter.highlight("Array<String>")
XCTAssertEqual(html, """
<span class="type">Array</span><<span class="type">String</span>>
""")
💎 While it's very common in functional programming, the reduce
function might be a bit of a hidden gem in Swift. It provides a super useful way to transform a sequence into a single value.
extension Sequence where Element: Equatable {
func numberOfOccurrences(of target: Element) -> Int {
return reduce(0) { result, element in
guard element == target else {
return result
}
return result + 1
}
}
}
You can read more about transforming collections in "Transforming collections in Swift".
📦 When I use Codable in Swift, I want to avoid manual implementations as much as possible, even when there's a mismatch between my code structure and the JSON I'm decoding.
One way that can often be achieved is to use private data containers combined with computed properties.
struct User: Codable {
let name: String
let age: Int
var homeTown: String { return originPlace.name }
private let originPlace: Place
}
private extension User {
struct Place: Codable {
let name: String
}
}
extension User {
struct Container: Codable {
let user: User
}
}
🚢 Instead of using feature branches, I merge almost all of my code directly into master - and then I use feature flags to conditionally enable features when they're ready. That way I can avoid merge conflicts and keep shipping!
extension ListViewController {
func addSearchIfNeeded() {
// Rather than having to keep maintaining a separate
// feature branch for a new feature, we can use a flag
// to conditionally turn it on.
guard FeatureFlags.searchEnabled else {
return
}
let resultsVC = SearchResultsViewController()
let searchVC = UISearchController(
searchResultsController: resultsVC
)
searchVC.searchResultsUpdater = resultsVC
navigationItem.searchController = searchVC
}
}
You can read more about feature flags in "Feature flags in Swift".
💾 Here I'm using tuples to create a lightweight hierarchy for my data, giving me a nice structure without having to introduce any additional types.
struct CodeSegment {
var tokens: (
previous: String?,
current: String
)
var delimiters: (
previous: Character?
next: Character?
)
}
handle(segment.tokens.current)
You can read more about tuples in "Using tuples as lightweight types in Swift"
3️⃣ Whenever I have 3 properties or local variables that share the same prefix, I usually try to extract them into their own method or type. That way I can avoid massive types & methods, and also increase readability, without falling into a "premature optimization" trap.
Before
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articleFolder = try contentFolder.subfolder(named: "posts")
let articleProcessor = ContentProcessor(folder: articleFolder)
let articles = try articleProcessor.process()
...
}
After
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articles = try processArticles(in: contentFolder)
...
}
private func processArticles(in folder: Folder) throws -> [ContentItem] {
let folder = try folder.subfolder(named: "posts")
let processor = ContentProcessor(folder: folder)
return try processor.process()
}
👨🔧 Here's two extensions that I always add to the Encodable
& Decodable
protocols, which for me really make the Codable API nicer to use. By using type inference for decoding, a lot of boilerplate can be removed when the compiler is already able to infer the resulting type.
extension Encodable {
func encoded() throws -> Data {
return try JSONEncoder().encode(self)
}
}
extension Data {
func decoded<T: Decodable>() throws -> T {
return try JSONDecoder().decode(T.self, from: self)
}
}
let data = try user.encoded()
// By using a generic type in the decoded() method, the
// compiler can often infer the type we want to decode
// from the current context.
try userDidLogin(data.decoded())
// And if not, we can always supply the type, still making
// the call site read very nicely.
let otherUser = try data.decoded() as User
📦 UserDefaults
is a lot more powerful than what it first might seem like. Not only can it store more complex values (like dates & dictionaries) and parse command line arguments - it also enables easy sharing of settings & lightweight data between apps in the same App Group.
let sharedDefaults = UserDefaults(suiteName: "my-app-group")!
let useDarkMode = sharedDefaults.bool(forKey: "dark-mode")
// This value is put into the shared suite.
sharedDefaults.set(true, forKey: "dark-mode")
// If you want to treat the shared settings as read-only (and add
// local overrides on top of them), you can simply add the shared
// suite to the standard UserDefaults.
let combinedDefaults = UserDefaults.standard
combinedDefaults.addSuite(named: "my-app-group")
// This value is a local override, not added to the shared suite.
combinedDefaults.set(true, forKey: "app-specific-override")
🎨 By overriding layerClass
you can tell UIKit what CALayer
class to use for a UIView
's backing layer. That way you can reduce the amount of layers, and don't have to do any manual layout.
final class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var colors: (start: UIColor, end: UIColor)? {
didSet { updateLayer() }
}
private func updateLayer() {
let layer = self.layer as! CAGradientLayer
layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
}
}
✅ That the compiler now automatically synthesizes Equatable conformances is such a huge upgrade for Swift! And the cool thing is that it works for all kinds of types - even for enums with associated values! Especially useful when using enums for verification in unit tests.
struct Article: Equatable {
let title: String
let text: String
}
struct User: Equatable {
let name: String
let age: Int
}
extension Navigator {
enum Destination: Equatable {
case profile(User)
case article(Article)
}
}
func testNavigatingToArticle() {
let article = Article(title: "Title", text: "Text")
controller.select(article)
XCTAssertEqual(navigator.destinations, [.article(article)])
}
🤝 Associated types can have defaults in Swift - which is super useful for types that are not easily inferred (for example when they're not used for a specific instance method or property).
protocol Identifiable {
associatedtype RawIdentifier: Codable = String
var id: Identifier<Self> { get }
}
struct User: Identifiable {
let id: Identifier<User>
let name: String
}
struct Group: Identifiable {
typealias RawIdentifier = Int
let id: Identifier<Group>
let name: String
}
🆔 If you want to avoid using plain strings as identifiers (which can increase both type safety & readability), it's really easy to create a custom Identifier type that feels just like a native Swift type, thanks to protocols!
More on this topic in "Type-safe identifiers in Swift".
struct Identifier: Hashable {
let string: String
}
extension Identifier: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
string = value
}
}
extension Identifier: CustomStringConvertible {
var description: String {
return string
}
}
extension Identifier: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
struct Article: Codable {
let id: Identifier
let title: String
}
let article = Article(id: "my-article", title: "Hello world!")
🙌 A really cool thing about using tuples to model the internal state of a Swift type, is that you can unwrap an optional tuple's members directly into local variables.
Very useful in order to group multiple optional values together for easy unwrapping & handling.
class ImageTransformer {
private var queue = [(image: UIImage, transform: Transform)]()
private func processNext() {
// When unwrapping an optional tuple, you can assign the members
// directly to local variables.
guard let (image, transform) = queue.first else {
return
}
let context = Context()
context.draw(image)
context.apply(transform)
...
}
}
❤️ I love to structure my code using extensions in Swift. One big benefit of doing so when it comes to struct initializers, is that defining a convenience initializer doesn't remove the default one the compiler generates - best of both worlds!
struct Article {
let date: Date
var title: String
var text: String
var comments: [Comment]
}
extension Article {
init(title: String, text: String) {
self.init(date: Date(), title: title, text: text, comments: [])
}
}
let articleA = Article(title: "Best Cupcake Recipe", text: "...")
let articleB = Article(
date: Date(),
title: "Best Cupcake Recipe",
text: "...",
comments: [
Comment(user: currentUser, text: "Yep, can confirm!")
]
)
🏈 A big benefit of using throwing functions for synchronous Swift APIs is that the caller can decide whether they want to treat the return value as optional (try?
) or required (try
).
func loadFile(named name: String) throws -> File {
guard let url = urlForFile(named: name) else {
throw File.Error.missing
}
do {
let data = try Data(contentsOf: url)
return File(url: url, data: data)
} catch {
throw File.Error.invalidData(error)
}
}
let requiredFile = try loadFile(named: "AppConfig.json")
let optionalFile = try? loadFile(named: "UserSettings.json")
🐝 Types that are nested in generics automatically inherit their parent's generic types - which is super useful when defining accessory types (for things like states or outcomes).
struct Task<Input, Output> {
typealias Closure = (Input) throws -> Output
let closure: Closure
}
extension Task {
enum Result {
case success(Output)
case failure(Error)
}
}
🤖 Now that the Swift compiler automatically synthesizes Equatable & Hashable conformances for value types, it's easier than ever to setup model structures with nested types that are all Equatable
/Hashable
!
typealias Value = Hashable & Codable
struct User: Value {
var name: String
var age: Int
var lastLoginDate: Date?
var settings: Settings
}
extension User {
struct Settings: Value {
var itemsPerPage: Int
var theme: Theme
}
}
extension User.Settings {
enum Theme: String, Value {
case light
case dark
}
}
You can read more about using nested types in Swift here.
🎉 Swift 4.1 is here! One of the key features it brings is conditional conformances, which lets you have a type only conform to a protocol under certain constraints.
protocol UnboxTransformable {
associatedtype RawValue
static func transform(_ value: RawValue) throws -> Self?
}
extension Array: UnboxTransformable where Element: UnboxTransformable {
typealias RawValue = [Element.RawValue]
static func transform(_ value: RawValue) throws -> [Element]? {
return try value.compactMap(Element.transform)
}
}
I also have an article with lots of more info on conditional conformances here. Paul Hudson also has a great overview of all Swift 4.1 features here.
🕵️♀️ A cool thing about Swift type aliases is that they can be generic! Combine that with tuples and you can easily define simple generic types.
typealias Pair<T> = (T, T)
extension Game {
func calculateScore(for players: Pair<Player>) -> Int {
...
}
}
You can read more about using tuples as lightweight types here.
☑️ A really cool "hidden" feature of UserDefaults is that it contains any arguments that were passed to the app at launch!
Super useful both in Swift command line tools & scripts, but also to temporarily override a value when debugging iOS apps.
let defaults = UserDefaults.standard
let query = defaults.string(forKey: "query")
let resultCount = defaults.integer(forKey: "results")
👏 Swift's &
operator is awesome! Not only can you use it to compose protocols, you can compose other types too! Very useful if you want to hide concrete types & implementation details.
protocol LoadableFromURL {
func load(from url: URL)
}
class ContentViewController: UIViewController, LoadableFromURL {
func load(from url: URL) {
...
}
}
class ViewControllerFactory {
func makeContentViewController() -> UIViewController & LoadableFromURL {
return ContentViewController()
}
}
🤗 When capturing values in mocks, using an array (instead of just a single value) makes it easy to verify that only a certain number of values were passed.
Perfect for protecting against "over-calling" something.
class UserManagerTests: XCTestCase {
func testObserversCalledWhenUserFirstLogsIn() {
let manager = UserManager()
let observer = ObserverMock()
manager.addObserver(observer)
// First login, observers should be notified
let user = User(id: 123, name: "John")
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
// If the same user logs in again, observers shouldn't be notified
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
}
}
private extension UserManagerTests {
class ObserverMock: UserManagerObserver {
private(set) var users = [User]()
func userDidChange(to user: User) {
users.append(user)
}
}
}
👋 When writing tests, you don't always need to create mocks - you can create stubs using real instances of things like errors, URLs & UserDefaults.
Here's how to do that for some common tasks/object types in Swift:
// Create errors using NSError (#function can be used to reference the name of the test)
let error = NSError(domain: #function, code: 1, userInfo: nil)
// Create non-optional URLs using file paths
let url = URL(fileURLWithPath: "Some/URL")
// Reference the test bundle using Bundle(for:)
let bundle = Bundle(for: type(of: self))
// Create an explicit UserDefaults object (instead of having to use a mock)
let userDefaults = UserDefaults(suiteName: #function)
// Create queues to control/await concurrent operations
let queue = DispatchQueue(label: #function)
For when you actually do need mocking, check out "Mocking in Swift".
⏱ I've started using "then" as an external parameter label for completion handlers. Makes the call site read really nicely (Because I do ❤️ conversational API design) regardless of whether trailing closure syntax is used or not.
protocol DataLoader {
// Adding type aliases to protocols can be a great way to
// reduce verbosity for parameter types.
typealias Handler = (Result<Data>) -> Void
associatedtype Endpoint
func loadData(from endpoint: Endpoint, then handler: @escaping Handler)
}
loader.loadData(from: .messages) { result in
...
}
loader.loadData(from: .messages, then: { result in
...
})
😴 Combining lazily evaluated sequences with builder pattern-like properties can lead to some pretty sweet APIs for configurable sequences in Swift.
Also useful for queries & other things you "build up" and then execute.
// Extension adding builder pattern-like properties that return
// a new sequence value with the given configuration applied
extension FileSequence {
var recursive: FileSequence {
var sequence = self
sequence.isRecursive = true
return sequence
}
var includingHidden: FileSequence {
var sequence = self
sequence.includeHidden = true
return sequence
}
}
// BEFORE
let files = folder.makeFileSequence(recursive: true, includeHidden: true)
// AFTER
let files = folder.files.recursive.includingHidden
Want an intro to lazy sequences? Check out "Swift sequences: The art of being lazy".
My top 3 tips for faster & more stable UI tests:
📱 Reset the app's state at the beginning of every test.
🆔 Use accessibility identifiers instead of UI strings.
⏱ Use expectations instead of waiting time.
func testOpeningArticle() {
// Launch the app with an argument that tells it to reset its state
let app = XCUIApplication()
app.launchArguments.append("--uitesting")
app.launch()
// Check that the app is displaying an activity indicator
let activityIndicator = app.activityIndicator.element
XCTAssertTrue(activityIndicator.exists)
// Wait for the loading indicator to disappear = content is ready
expectation(for: NSPredicate(format: "exists == 0"),
evaluatedWith: activityIndicator)
// Use a generous timeout in case the network is slow
waitForExpectations(timeout: 10)
// Tap the cell for the first article
app.tables.cells["Article.0"].tap()
// Assert that a label with the accessibility identifier "Article.Title" exists
let label = app.staticTexts["Article.Title"]
XCTAssertTrue(label.exists)
}
📋 It's super easy to access the contents of the clipboard from a Swift script. A big benefit of Swift scripting is being able to use Cocoa's powerful APIs for Mac apps.
import Cocoa
let clipboard = NSPasteboard.general.string(forType: .string)
🎯 Using Swift tuples for view state can be a super nice way to group multiple properties together and render them reactively using the layout system.
By using a tuple we don't have to either introduce a new type or make our view model-aware.
class TextView: UIView {
var state: (title: String?, text: String?) {
// By telling UIKit that our view needs layout and binding our
// state in layoutSubviews, we can react to state changes without
// doing unnecessary layout work.
didSet { setNeedsLayout() }
}
private let titleLabel = UILabel()
private let textLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.text = state.title
textLabel.text = state.text
...
}
}
⚾️ Swift tests can throw, which is super useful in order to avoid complicated logic or force unwrapping. By making errors conform to LocalizedError
, you can also get a nice error message in Xcode if there's a failure.
class ImageCacheTests: XCTestCase {
func testCachingAndLoadingImage() throws {
let bundle = Bundle(for: type(of: self))
let cache = ImageCache(bundle: bundle)
// Bonus tip: You can easily load images from your test
// bundle using this UIImage initializer
let image = try require(UIImage(named: "sample", in: bundle, compatibleWith: nil))
try cache.cache(image, forKey: "key")
let cachedImage = try cache.image(forKey: "key")
XCTAssertEqual(image, cachedImage)
}
}
enum ImageCacheError {
case emptyKey
case dataConversionFailed
}
// When using throwing tests, making your errors conform to
// LocalizedError will render a much nicer error message in
// Xcode (per default only the error code is shown).
extension ImageCacheError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyKey:
return "An empty key was given"
case .dataConversionFailed:
return "Failed to convert the given image to Data"
}
}
}
For more information, and the implementation of the require
method used above, check out "Avoiding force unwrapping in Swift unit tests".
✍️ Unlike static
properties, class
properties can be overridden by subclasses (however, they can't be stored, only computed).
class TableViewCell: UITableViewCell {
class var preferredHeight: CGFloat { return 60 }
}
class TallTableViewCell: TableViewCell {
override class var preferredHeight: CGFloat { return 100 }
}
👨🎨 Creating extensions with static factory methods can be a great alternative to subclassing in Swift, especially for things like setting up UIViews, CALayers or other kinds of styling.
It also lets you remove a lot of styling & setup from your view controllers.
extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
static func makeForText() -> UILabel {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 0
return label
}
}
class ArticleViewController: UIViewController {
lazy var titleLabel = UILabel.makeForTitle()
lazy var textLabel = UILabel.makeForText()
}
🧒 An awesome thing about child view controllers is that they're automatically resized to match their parent, making them a super nice solution for things like loading & error views.
class ListViewController: UIViewController {
func loadItems() {
let loadingViewController = LoadingViewController()
add(loadingViewController)
dataLoader.loadItems { [weak self] result in
loadingViewController.remove()
self?.handle(result)
}
}
}
For more about child view controller (including the add
and remove
methods used above), check out "Using child view controllers as plugins in Swift".
🤐 Using the zip function in Swift you can easily combine two sequences. Super useful when using two sequences to do some work, since zip takes care of all the bounds-checking.
func render(titles: [String]) {
for (label, text) in zip(titleLabels, titles) {
print(text)
label.text = text
}
}
🎛 The awesome thing about option sets in Swift is that they can automatically either be passed as a single member or as a set. Even cooler is that you can easily define your own option sets as well, perfect for options and other non-exclusive values.
// Option sets are awesome, because you can easily pass them
// both using dot syntax and array literal syntax, like when
// using the UIView animation API:
UIView.animate(withDuration: 0.3,
delay: 0,
options: .allowUserInteraction,
animations: animations)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.allowUserInteraction, .layoutSubviews],
animations: animations)
// The cool thing is that you can easily define your own option
// sets as well, by defining a struct that has an Int rawValue,
// that will be used as a bit mask.
extension Cache {
struct Options: OptionSet {
static let saveToDisk = Options(rawValue: 1)
static let clearOnMemoryWarning = Options(rawValue: 1 << 1)
static let clearDaily = Options(rawValue: 1 << 2)
let rawValue: Int
}
}
// We can now use Cache.Options just like UIViewAnimationOptions:
Cache(options: .saveToDisk)
Cache(options: [.saveToDisk, .clearDaily])
🙌 Using the where
clause when designing protocol-oriented APIs in Swift can let your implementations (or others' if it's open source) have a lot more freedom, especially when it comes to collections.
See "Using generic type constraints in Swift 4" for more info.
public protocol PathFinderMap {
associatedtype Node
// Using the 'where' clause for associated types, we can
// ensure that a type meets certain requirements (in this
// case that it's a sequence with Node elements).
associatedtype NodeSequence: Sequence where NodeSequence.Element == Node
// Instead of using a concrete type (like [Node]) here, we
// give implementors of this protocol more freedom while
// still meeting our requirements. For example, one
// implementation might use Set<Node>.
func neighbors(of node: Node) -> NodeSequence
}
👨🍳 Combine first class functions in Swift with the fact that Dictionary elements are (Key, Value) tuples and you can build yourself some pretty awesome functional chains when iterating over a Dictionary.
func makeActor(at coordinate: Coordinate, for building: Building) -> Actor {
let actor = Actor()
actor.position = coordinate.point
actor.animation = building.animation
return actor
}
func render(_ buildings: [Coordinate : Building]) {
buildings.map(makeActor).forEach(add)
}
😎 In Swift, you can call any instance method as a static function and it will return a closure representing that method. This is how running tests using SPM on Linux works.
More about this topic in my blog post "First class functions in Swift".
// This produces a '() -> Void' closure which is a reference to the
// given view's 'removeFromSuperview' method.
let closure = UIView.removeFromSuperview(view)
// We can now call it just like we would any other closure, and it
// will run 'view.removeFromSuperview()'
closure()
// This is how running tests using the Swift Package Manager on Linux
// works, you return your test functions as closures:
extension UserManagerTests {
static var allTests = [
("testLoggingIn", testLoggingIn),
("testLoggingOut", testLoggingOut),
("testUserPermissions", testUserPermissions)
]
}
👏 One really nice benefit of dropping suffixes from method names (and just using verbs, when possible) is that it becomes super easy to support both single and multiple arguments, and it works really well semantically.
extension UIView {
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
view.add(button)
view.add(label)
// By dropping the "Subview" suffix from the method name, both
// single and multiple arguments work really well semantically.
view.add(button, label)
👽 Using the AnyObject
(or class
) constraint on protocols is not only useful when defining delegates (or other weak references), but also when you always want instances to be mutable without copying.
// By constraining a protocol with 'AnyObject' it can only be adopted
// by classes, which means all instances will always be mutable, and
// that it's the original instance (not a copy) that will be mutated.
protocol DataContainer: AnyObject {
var data: Data? { get set }
}
class UserSettingsManager {
private var settings: Settings
private let dataContainer: DataContainer
// Since DataContainer is a protocol, we an easily mock it in
// tests if we use dependency injection
init(settings: Settings, dataContainer: DataContainer) {
self.settings = settings
self.dataContainer = dataContainer
}
func saveSettings() throws {
let data = try settings.serialize()
// We can now assign properties on an instance of our protocol
// because the compiler knows it's always going to be a class
dataContainer.data = data
}
}
🍣 Even if you define a custom raw value for a string-based enum in Swift, the full case name will be used in string interpolation.
Super useful when using separate raw values for JSON, while still wanting to use the full case name in other contexts.
extension Building {
// This enum has custom raw values that are used when decoding
// a value, for example from JSON.
enum Kind: String {
case castle = "C"
case town = "T"
case barracks = "B"
case goldMine = "G"
case camp = "CA"
case blacksmith = "BL"
}
var animation: Animation {
return Animation(
// When used in string interpolation, the full case name is still used.
// For 'castle' this will be 'buildings/castle'.
name: "buildings/\(kind)",
frameCount: frameCount,
frameDuration: frameDuration
)
}
}
👨🔬 Continuing to experiment with expressive ways of comparing a value with a list of candidates in Swift. Adding an extension on Equatable is probably my favorite approach so far.
extension Equatable {
func isAny(of candidates: Self...) -> Bool {
return candidates.contains(self)
}
}
let isHorizontal = direction.isAny(of: .left, .right)
See tip 35 for my previous experiment.
📐 A really interesting side-effect of a UIView
's bounds
being its rect within its own coordinate system is that transforms don't affect it at all. That's why it's usually a better fit than frame
when doing layout calculations of subviews.
let view = UIView()
view.frame.size = CGSize(width: 100, height: 100)
view.transform = CGAffineTransform(scaleX: 2, y: 2)
print(view.frame) // (-50.0, -50.0, 200.0, 200.0)
print(view.bounds) // (0.0, 0.0, 100.0, 100.0)
👏 It's awesome that many UIKit APIs with completion handlers and other optional parameters import into Swift with default arguments (even though they are written in Objective-C). Getting rid of all those nil arguments is so nice!
// BEFORE: All parameters are specified, just like in Objective-C
viewController.present(modalViewController, animated: true, completion: nil)
modalViewController.dismiss(animated: true, completion: nil)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
options: [],
animations: animations,
completion: nil)
// AFTER: Since many UIKit APIs with completion handlers and other
// optional parameters import into Swift with default arguments,
// we can make our calls shorter
viewController.present(modalViewController, animated: true)
modalViewController.dismiss(animated: true)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
animations: animations)
✂️ Avoiding Massive View Controllers is all about finding the right levels of abstraction and splitting things up.
My personal rule of thumb is that as soon as I have 3 methods or properties that have the same prefix, I break them out into their own type.
// BEFORE
class LoginViewController: UIViewController {
private lazy var signUpLabel = UILabel()
private lazy var signUpImageView = UIImageView()
private lazy var signUpButton = UIButton()
}
// AFTER
class LoginViewController: UIViewController {
private lazy var signUpView = SignUpView()
}
class SignUpView: UIView {
private lazy var label = UILabel()
private lazy var imageView = UIImageView()
private lazy var button = UIButton()
}
❤️ I love the fact that optionals are enums in Swift - it makes it so easy to extend them with convenience APIs for certain types. Especially useful when doing things like data validation on optional values.
func validateTextFields() -> Bool {
guard !usernameTextField.text.isNilOrEmpty else {
return false
}
...
return true
}
// Since all optionals are actual enum values in Swift, we can easily
// extend them for certain types, to add our own convenience APIs
extension Optional where Wrapped == String {
var isNilOrEmpty: Bool {
switch self {
case let string?:
return string.isEmpty
case nil:
return true
}
}
}
// Since strings are now Collections in Swift 4, you can even
// add this property to all optional collections:
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case let collection?:
return collection.isEmpty
case nil:
return true
}
}
}
🗺 Using the where
keyword can be a super nice way to quickly apply a filter in a for
-loop in Swift. You can of course use map
, filter
and forEach
, or guard
, but for simple loops I think this is very expressive and nice.
func archiveMarkedPosts() {
for post in posts where post.isMarked {
archive(post)
}
}
func healAllies() {
for player in players where player.isAllied(to: currentPlayer) {
player.heal()
}
}
👻 Variable shadowing can be super useful in Swift, especially when you want to create a local copy of a parameter value in order to use it as state within a closure.
init(repeatMode: RepeatMode, closure: @escaping () -> UpdateOutcome) {
// Shadow the argument with a local, mutable copy
var repeatMode = repeatMode
self.closure = {
// With shadowing, there's no risk of accidentially
// referring to the immutable version
switch repeatMode {
case .forever:
break
case .times(let count):
guard count > 0 else {
return .finished
}
// We can now capture the mutable version and use
// it for state in a closure
repeatMode = .times(count - 1)
}
return closure()
}
}
✒️ Dot syntax is one of my favorite features of Swift. What's really cool is that it's not only for enums, any static method or property can be used with dot syntax - even initializers! Perfect for convenience APIs and default parameters.
public enum RepeatMode {
case times(Int)
case forever
}
public extension RepeatMode {
static var never: RepeatMode {
return .times(0)
}
static var once: RepeatMode {
return .times(1)
}
}
view.perform(animation, repeated: .once)
// To make default parameters more compact, you can even use init with dot syntax
class ImageLoader {
init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
...
}
}
🚀 One really cool aspect of Swift having first class functions is that you can pass any function (or even initializer) as a closure, and even call it with a tuple containing its parameters!
// This function lets us treat any "normal" function or method as
// a closure and run it with a tuple that contains its parameters
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
return function(input)
}
class ViewFactory {
func makeHeaderView() -> HeaderView {
// We can now pass an initializer as a closure, and a tuple
// containing its parameters
return call(HeaderView.init, with: loadTextStyles())
}
private func loadTextStyles() -> (font: UIFont, color: UIColor) {
return (theme.font, theme.textColor)
}
}
class HeaderView {
init(font: UIFont, textColor: UIColor) {
...
}
}
💉 If you've been struggling to test code that uses static APIs, here's a technique you can use to enable static dependency injection without having to modify any call sites:
// Before: Almost impossible to test due to the use of singletons
class Analytics {
static func log(_ event: Event) {
Database.shared.save(event)
let dictionary = event.serialize()
NetworkManager.shared.post(dictionary, to: eventURL)
}
}
// After: Much easier to test, since we can inject mocks as arguments
class Analytics {
static func log(_ event: Event,
database: Database = .shared,
networkManager: NetworkManager = .shared) {
database.save(event)
let dictionary = event.serialize()
networkManager.post(dictionary, to: eventURL)
}
}
🎉 In Swift 4, type inference works for lazy properties and you don't need to explicitly refer to self
!
// Swift 3
class PurchaseView: UIView {
private lazy var buyButton: UIButton = self.makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
// Swift 4
class PurchaseView: UIView {
private lazy var buyButton = makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
😎 You can turn any Swift Error
into an NSError
, which is super useful when pattern matching with a code 👍. Also, switching on optionals is pretty cool!
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code == NSURLErrorNotConnectedToInternet:
presenter.showOfflineView()
case .some(let error):
presenter.showGenericErrorView()
case .none:
presenter.renderContent(from: data)
}
}
task.resume()
Also make sure to check out Kostas Kremizas' tip about how you can pattern match directly against a member of URLError
.
🖥 Here's an easy way to make iOS model code that uses UIImage
macOS compatible - like me and Gui Rambo discussed on the Swift by Sundell Podcast.
// Either put this in a separate file that you only include in your macOS target or wrap the code in #if os(macOS) / #endif
import Cocoa
// Step 1: Typealias UIImage to NSImage
typealias UIImage = NSImage
// Step 2: You might want to add these APIs that UIImage has but NSImage doesn't.
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
// Step 3: Profit - you can now make your model code that uses UIImage cross-platform!
struct User {
let name: String
let profileImage: UIImage
}
🤖 You can easily define a protocol-oriented API that can only be mutated internally, by using an internal protocol that extends a public one.
// Declare a public protocol that acts as your immutable API
public protocol ModelHolder {
associatedtype Model
var model: Model { get }
}
// Declare an extended, internal protocol that provides a mutable API
internal protocol MutableModelHolder: ModelHolder {
var model: Model { get set }
}
// You can now implement the requirements using 'public internal(set)'
public class UserHolder: MutableModelHolder {
public internal(set) var model: User
internal init(model: User) {
self.model = model
}
}
🎛 You can switch on a set using array literals as cases in Swift! Can be really useful to avoid many if
/else if
statements.
class RoadTile: Tile {
var connectedDirections = Set<Direction>()
func render() {
switch connectedDirections {
case [.up, .down]:
image = UIImage(named: "road-vertical")
case [.left, .right]:
image = UIImage(named: "road-horizontal")
default:
image = UIImage(named: "road")
}
}
}
🌍 When caching localized content in an app, it's a good idea to add the current locale to all keys, to prevent bugs when switching languages.
func cache(_ content: Content, forKey key: String) throws {
let data = try wrap(content) as Data
let key = localize(key: key)
try storage.store(data, forKey: key)
}
func loadCachedContent(forKey key: String) -> Content? {
let key = localize(key: key)
let data = storage.loadData(forKey: key)
return data.flatMap { try? unbox(data: $0) }
}
private func localize(key: String) -> String {
return key + "-" + Bundle.main.preferredLocalizations[0]
}
🚳 Here's an easy way to setup a test to avoid accidental retain cycles with object relationships (like weak delegates & observers) in Swift:
func testDelegateNotRetained() {
// Assign the delegate (weak) and also retain it using a local var
var delegate: Delegate? = DelegateMock()
controller.delegate = delegate
XCTAssertNotNil(controller.delegate)
// Release the local var, which should also release the weak reference
delegate = nil
XCTAssertNil(controller.delegate)
}
👨🔬 Playing around with an expressive way to check if a value matches any of a list of candidates in Swift:
// Instead of multiple conditions like this:
if string == "One" || string == "Two" || string == "Three" {
}
// You can now do:
if string == any(of: "One", "Two", "Three") {
}
You can find a gist with the implementation here.
👪 APIs in a Swift extension automatically inherit its access control level, making it a neat way to organize public, internal & private APIs.
public extension Animation {
init(textureNamed textureName: String) {
frames = [Texture(name: textureName)]
}
init(texturesNamed textureNames: [String], frameDuration: TimeInterval = 1) {
frames = textureNames.map(Texture.init)
self.frameDuration = frameDuration
}
init(image: Image) {
frames = [Texture(image: image)]
}
}
internal extension Animation {
func loadFrameImages() -> [Image] {
return frames.map { $0.loadImageIfNeeded() }
}
}
🗺 Using map
you can transform an optional value into an optional Result
type by simply passing in the enum case.
enum Result<Value> {
case value(Value)
case error(Error)
}
class Promise<Value> {
private var result: Result<Value>?
init(value: Value? = nil) {
result = value.map(Result.value)
}
}
👌 It's so nice that you can assign directly to self
in struct
initializers in Swift. Very useful when adding conformance to protocols.
extension Bool: AnswerConvertible {
public init(input: String) throws {
switch input.lowercased() {
case "y", "yes", "👍":
self = true
default:
self = false
}
}
}
☎️ Defining Swift closures as inline functions enables you to recursively call them, which is super useful in things like custom sequences.
class Database {
func records(matching query: Query) -> AnySequence<Record> {
var recordIterator = loadRecords().makeIterator()
func iterate() -> Record? {
guard let nextRecord = recordIterator.next() else {
return nil
}
guard nextRecord.matches(query) else {
// Since the closure is an inline function, it can be recursively called,
// in this case in order to advance to the next item.
return iterate()
}
return nextRecord
}
// AnySequence/AnyIterator are part of the standard library and provide an easy way
// to define custom sequences using closures.
return AnySequence { AnyIterator(iterate) }
}
}
Rob Napier points out that using the above might cause crashes if used on a large databaset, since Swift has no guaranteed Tail Call Optimization (TCO).
Slava Pestov also points out that another benefit of inline functions vs closures is that they can have their own generic parameter list.
🏖 Using lazy properties in Swift, you can pass self
to required Objective-C dependencies without having to use force-unwrapped optionals.
class DataLoader: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
class Renderer {
lazy var displayLink: CADisplayLink = self.makeDisplayLink()
private func makeDisplayLink() -> CADisplayLink {
return CADisplayLink(target: self, selector: #selector(screenDidRefresh))
}
}
👓 If you have a property in Swift that needs to be weak
or lazy
, you can still make it readonly by using private(set)
.
class Node {
private(set) weak var parent: Node?
private(set) lazy var children = [Node]()
func add(child: Node) {
children.append(child)
child.parent = self
}
}
🌏 Tired of using URL(string: "url")!
for static URLs? Make URL
conform to ExpressibleByStringLiteral
and you can now simply use "url"
instead.
extension URL: ExpressibleByStringLiteral {
// By using 'StaticString' we disable string interpolation, for safety
public init(stringLiteral value: StaticString) {
self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
}
}
// We can now define URLs using static string literals 🎉
let url: URL = "https://www.swiftbysundell.com"
let task = URLSession.shared.dataTask(with: "https://www.swiftbysundell.com")
// In Swift 3 or earlier, you also have to implement 2 additional initializers
extension URL {
public init(extendedGraphemeClusterLiteral value: StaticString) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: StaticString) {
self.init(stringLiteral: value)
}
}
To find the extension that adds the require()
method on Optional
that I use above, check out Require.
✚ I'm always careful with operator overloading, but for manipulating things like sizes, points & frames I find them super useful.
extension CGSize {
static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
button.frame.size = image.size * 2
If you like the above idea, check out CGOperators, which contains math operator overloads for all Core Graphics' vector types.
🔗 You can use closure types in generic constraints in Swift. Enables nice APIs for handling sequences of closures.
extension Sequence where Element == () -> Void {
func callAll() {
forEach { $0() }
}
}
extension Sequence where Element == () -> String {
func joinedResults(separator: String) -> String {
return map { $0() }.joined(separator: separator)
}
}
callbacks.callAll()
let names = nameProviders.joinedResults(separator: ", ")
(If you're using Swift 3, you have to change Element
to Iterator.Element
)
🎉 Using associated enum values is a super nice way to encapsulate mutually exclusive state info (and avoiding state-specific optionals).
// BEFORE: Lots of state-specific, optional properties
class Player {
var isWaitingForMatchMaking: Bool
var invitingUser: User?
var numberOfLives: Int
var playerDefeatedBy: Player?
var roundDefeatedIn: Int?
}
// AFTER: All state-specific information is encapsulated in enum cases
class Player {
enum State {
case waitingForMatchMaking
case waitingForInviteResponse(from: User)
case active(numberOfLives: Int)
case defeated(by: Player, roundNumber: Int)
}
var state: State
}
👍 I really like using enums for all async result types, even boolean ones. Self-documenting, and makes the call site a lot nicer to read too!
protocol PushNotificationService {
// Before
func enablePushNotifications(completionHandler: @escaping (Bool) -> Void)
// After
func enablePushNotifications(completionHandler: @escaping (PushNotificationStatus) -> Void)
}
enum PushNotificationStatus {
case enabled
case disabled
}
service.enablePushNotifications { status in
if status == .enabled {
enableNotificationsButton.removeFromSuperview()
}
}
🏃 Want to work on your async code in a Swift Playground? Just set needsIndefiniteExecution
to true to keep it running:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let greeting = "Hello after 3 seconds"
print(greeting)
}
To stop the playground from executing, simply call PlaygroundPage.current.finishExecution()
.
💦 Avoid memory leaks when accidentially refering to self
in closures by overriding it locally with a weak reference:
Swift >= 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let self = self else {
return
}
self.cache(result)
...
Swift < 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let `self` = self else {
return
}
self.cache(result)
...
Note that the reason the above currently works is because of a compiler bug (which I hope gets turned into a properly supported feature soon).
🕓 Using dispatch work items you can easily cancel a delayed asynchronous GCD task if you no longer need it:
let workItem = DispatchWorkItem {
// Your async code goes in here
}
// Execute the work item after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
➕ While working on a new Swift developer tool (to be open sourced soon 😉), I came up with a pretty neat way of organizing its sequence of operations, by combining their functions into a closure:
internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
rhs: @escaping (B) throws -> C) -> (A) throws -> C {
return { try rhs(lhs($0)) }
}
public func run() throws {
try (determineTarget + build + analyze + output)()
}
If you're familiar with the functional programming world, you might know the above technique as the pipe operator (thanks to Alexey Demedreckiy for pointing this out!)
🗺 Using map()
and flatMap()
on optionals you can chain multiple operations without having to use lengthy if lets
or guards
:
// BEFORE
guard let string = argument(at: 1) else {
return
}
guard let url = URL(string: string) else {
return
}
handle(url)
// AFTER
argument(at: 1).flatMap(URL.init).map(handle)
🚀 Using self-executing closures is a great way to encapsulate lazy property initialization:
class StoreViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
view.delegate = self
view.dataSource = self
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
⚡️ You can speed up your Swift package tests using the --parallel
flag. For Marathon, the tests execute 3 times faster that way!
swift test --parallel
🛠 Struggling with mocking UserDefaults
in a test? The good news is: you don't need mocking - just create a real instance:
class LoginTests: XCTestCase {
private var userDefaults: UserDefaults!
private var manager: LoginManager!
override func setUp() {
super.setup()
userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
manager = LoginManager(userDefaults: userDefaults)
}
}
👍 Using variadic parameters in Swift, you can create some really nice APIs that take a list of objects without having to use an array:
extension Canvas {
func add(_ shapes: Shape...) {
shapes.forEach(add)
}
}
let circle = Circle(center: CGPoint(x: 5, y: 5), radius: 5)
let lineA = Line(start: .zero, end: CGPoint(x: 10, y: 10))
let lineB = Line(start: CGPoint(x: 0, y: 10), end: CGPoint(x: 10, y: 0))
let canvas = Canvas()
canvas.add(circle, lineA, lineB)
canvas.render()
😮 Just like you can refer to a Swift function as a closure, you can do the same thing with enum cases with associated values:
enum UnboxPath {
case key(String)
case keyPath(String)
}
struct UserSchema {
static let name = key("name")
static let age = key("age")
static let posts = key("posts")
private static let key = UnboxPath.key
}
📈 The ===
operator lets you check if two objects are the same instance. Very useful when verifying that an array contains an instance in a test:
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
func testDestroyingEnemy() {
player.attack(enemy)
XCTAssertTrue(player.destroyedEnemies.contains(enemy))
}
😎 Cool thing about Swift initializers: you can call them using dot syntax and pass them as closures! Perfect for mocking dates in tests.
class Logger {
private let storage: LogStorage
private let dateProvider: () -> Date
init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
self.storage = storage
self.dateProvider = dateProvider
}
func log(event: Event) {
storage.store(event: event, date: dateProvider())
}
}
📱 Most of my UI testing logic is now categories on XCUIApplication
. Makes the test cases really easy to read:
func testLoggingInAndOut() {
XCTAssertFalse(app.userIsLoggedIn)
app.launch()
app.login()
XCTAssertTrue(app.userIsLoggedIn)
app.logout()
XCTAssertFalse(app.userIsLoggedIn)
}
func testDisplayingCategories() {
XCTAssertFalse(app.isDisplayingCategories)
app.launch()
app.login()
app.goToCategories()
XCTAssertTrue(app.isDisplayingCategories)
}
🙂 It’s a good idea to avoid “default” cases when switching on Swift enums - it’ll “force you” to update your logic when a new case is added:
enum State {
case loggedIn
case loggedOut
case onboarding
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
💂 It's really cool that you can use Swift's 'guard' statement to exit out of pretty much any scope, not only return from functions:
// You can use the 'guard' statement to...
for string in strings {
// ...continue an iteration
guard shouldProcess(string) else {
continue
}
// ...or break it
guard !shouldBreak(for: string) else {
break
}
// ...or return
guard !shouldReturn(for: string) else {
return
}
// ..or throw an error
guard string.isValid else {
throw StringError.invalid(string)
}
// ...or exit the program
guard !shouldExit(for: string) else {
exit(1)
}
}
❤️ Love how you can pass functions & operators as closures in Swift. For example, it makes the syntax for sorting arrays really nice!
let array = [3, 9, 1, 4, 6, 2]
let sorted = array.sorted(by: <)
🗝 Here's a neat little trick I use to get UserDefault key consistency in Swift (#function expands to the property name in getters/setters). Just remember to write a good suite of tests that'll guard you against bugs when changing property names.
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
📛 Want to use a name already taken by the standard library for a nested type? No problem - just use Swift.
to disambiguate:
extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}
📦 Playing around with using Wrap to implement Equatable
for any type, primarily for testing:
protocol AutoEquatable: Equatable {}
extension AutoEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
let lhsData = try! wrap(lhs) as Data
let rhsData = try! wrap(rhs) as Data
return lhsData == rhsData
}
}
📏 One thing that I find really useful in Swift is to use typealiases to reduce the length of method signatures in generic types:
public class PathFinder<Object: PathFinderObject> {
public typealias Map = Object.Map
public typealias Node = Map.Node
public typealias Path = PathFinderPath<Object>
public static func possiblePaths(for object: Object, at rootNode: Node, on map: Map) -> Path.Sequence {
return .init(object: object, rootNode: rootNode, map: map)
}
}
📖 You can reference either the external or internal parameter label when writing Swift docs - and they get parsed the same:
// EITHER:
class Foo {
/**
* - parameter string: A string
*/
func bar(with string: String) {}
}
// OR:
class Foo {
/**
* - parameter with: A string
*/
func bar(with string: String) {}
}
👍 Finding more and more uses for auto closures in Swift. Can enable some pretty nice APIs:
extension Dictionary {
mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value {
if let value = self[key] {
return value
}
let value = valueClosure()
self[key] = value
return value
}
}
🚀 I’ve started to become a really big fan of nested types in Swift. Love the additional namespacing it gives you!
public struct Map {
public struct Model {
public let size: Size
public let theme: Theme
public var terrain: [Position : Terrain.Model]
public var units: [Position : Unit.Model]
public var buildings: [Position : Building.Model]
}
public enum Direction {
case up
case right
case down
case left
}
public struct Position {
public var x: Int
public var y: Int
}
public enum Size: String {
case small = "S"
case medium = "M"
case large = "L"
case extraLarge = "XL"
}
}
Author: JohnSundell
Source Code: https://github.com/JohnSundell/SwiftTips
License: MIT license