How to run UITests with map view in iOS

Issue #45

Mock a location

You should mock a location to ensure reliable test

Create the gpx file

Go to Xcode -> File -> New -> GPX File

gpx

It looks like

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
<wpt lat="59.913590" lon="10.733750">
<name>Oslo S</name>
<time>2017-05-31T14:55:37Z</time>
</wpt>
<wpt lat="59.913590" lon="10.733750">
<name>Oslo S</name>
<time>2017-05-31T14:55:40Z</time>
</wpt>
</gpx>

The gpx file is very powerful, as it allows you to specify a route with different movement speed.

Provide one or more waypoints containing a latitude/longitude pair. If you provide one
waypoint, Xcode will simulate that specific location. If you provide multiple waypoints,
Xcode will simulate a route visiting each waypoint.

Optionally provide a time element for each waypoint. Xcode will interpolate movement
at a rate of speed based on the time elapsed between each waypoint. If you do not provide
a time element, then Xcode will use a fixed rate of speed. Waypoints must be sorted by time in ascending order.

Use the gpx file

  • Declare the gpx file in app target, not UITests target. Go to your app scheme -> Run -> Options

location

  • Go to Simulator -> Debug -> Location -> Custom Location and select that same location, just to make sure. It does not need to be the same, but I see that without Custom Location, it does not work in UITests

Test that you’re near the initial location

1
2
3
4
5
6
7
let map = app.maps.element(boundBy: 0)
let predicate = NSPredicate(format: "label CONTAINS 'City Hall'")
let cityHall = map.otherElements.matching(predicate).element(boundBy: 0)

// wait for the map to finish loading and zooming
wait(for: cityHall, timeout: 2)
XCTAssertTrue(cityHall.exists)

The wait function is from https://github.com/onmyway133/blog/issues/44

Test that you can interact with your custom pin

You need to specify accessibilityIdentifier, like

1
2
3
4
5
6
7
class MyPin: MKAnnotationView {
override func didMoveToSuperview() {
super.didMoveToSuperview()

accessibilityIdentifier = "myPin"
}
}

and then query for that pin. Not that it is not inside map, it is inside app

1
2
let pin = app.otherElements.matching(identifier: "myPin").element(boundBy: 0)
XCTAssertTrue(pin.exists)

You should use accessibilityIdentifier

accessibilityIdentifier is from UIAccessibilityIdentification protocol. You should not use accessibilityLabel, see https://github.com/kif-framework/KIF/issues/243

Given that accessibilityLabel is an outwardly-facing string that is actually used by accessibility screen readers (and should be localized to the device user’s language), Apple now provides an alternate property (iOS 5+) that is specifically intended for UI Automation purposes

How to run UI Test with Facebook login

Issue #44

Today I’m trying to run some UITest on my app, which uses Facebook login. And here are some of my notes on it.

Challenges

  • The challenges with Facebook is it uses Safari controller, we we deal mostly with web view for now. Starting from iOS 9+, Facebook decided to use safari instead of native facebook app to avoid app switching. You can read the detail here Building the Best Facebook Login Experience for People on iOS 9
  • It does not have wanted accessibilityIdentifier or accessibilityLabel
  • The webview content may change in the future 😸

Create a Facebook test user

Luckily, you don’t have to create your own Facebook user to test. Facebook supports test users that you can manage permissions and friends, very handy

test user

When creating the test user, you have the option to select language. That will be the displayed language in Safari web view. I choose Norwegian 🇳🇴 for now

language

Click the login button and show Facebook login

Here we use the default FBSDKLoginButton

1
2
3
var showFacebookLoginFormButton: XCUIElement {
return buttons["Continue with Facebook"]
}

And then tap it

1
app.showFacebookLoginFormButton.tap()

Check login status

When going to safari Facebook form, user may have already logged in or not. So we need to handle these 2 cases. When user has logged in, Facebook will say something like “you have already logged in” or the OK button.

The advice here is to put breakpoint and po app.staticTexts, po app.buttons to see which UI elements are at a certain point.

You can check for the static text, or simply just the OK button

1
2
3
var isAlreadyLoggedInSafari: Bool {
return buttons["OK"].exists || staticTexts["Du har allerede godkjent Blue Sea."].exists
}

Wait and refresh

But Facebook form is a webview, so its content is a bit dynamic. And UITest seems to cache content for fast query, so before checking staticTexts, we need to wait and refresh the cache

1
app.clearCachedStaticTexts()

This is the wait function

1
2
3
4
5
6
7
8
9
10
11
12
13
extension XCTestCase {
func wait(for duration: TimeInterval) {
let waitExpectation = expectation(description: "Waiting")

let when = DispatchTime.now() + duration
DispatchQueue.main.asyncAfter(deadline: when) {
waitExpectation.fulfill()
}

// We use a buffer here to avoid flakiness with Timer on CI
waitForExpectations(timeout: duration + 0.5)
}
}

Wait for element to appear

But a more solid approach would be to wait for element to appear. For Facebook login form, they should display a Facebook label after loading. So we should wait for this element

1
2
3
4
5
6
7
8
9
10
11
12
extension XCTestCase {
/// Wait for element to appear
func wait(for element: XCUIElement, timeout duration: TimeInterval) {
let predicate = NSPredicate(format: "exists == true")
let _ = expectation(for: predicate, evaluatedWith: element, handler: nil)

// Here we don't need to call `waitExpectation.fulfill()`

// We use a buffer here to avoid flakiness with Timer on CI
waitForExpectations(timeout: duration + 0.5)
}
}

And call this before you do any further inspection on elements in Facebook login form

1
wait(for: app.staticTexts["Facebook"], timeout: 5)

If user is logged in

After login, my app shows the main controller with a map view inside. So a basic test would be to check the existence of that map

1
2
3
4
5
6
7
if app.isAlreadyLoggedInSafari {
app.okButton.tap()

handleLocationPermission()
// Check for the map
XCTAssertTrue(app.maps.element(boundBy: 0).exists)
}

Handle interruption

You know that when showing the map with location, Core Location will ask for permission. So we need to handle that interruption as well. You need to ensure to call it early before the alert happens

1
2
3
4
5
6
fileprivate func handleLocationPermission() {
addUIInterruptionMonitor(withDescription: "Location permission", handler: { alert in
alert.buttons.element(boundBy: 1).tap()
return true
})
}

There is another problem, this monitor won’t be called. So the workaround is to call app.tap() again when the alert will happen. In my case, I call app.tap() when my map has been shown for 1,2 seconds, just to make sure app.tap() is called after alert is shown

For a more detailed guide, please read https://github.com/onmyway133/blog/issues/48

If user is not logged in

In this case, we need to fill in email and password. You can take a look at the The full source code section below. When things don’t work or po does not show you the elements you needed, it’s probably because of caching or you need to wait until dynamic content finishes rendering.

You need to wait for element to appear

Tap on the text field

You may get Neither element nor any descendant has keyboard focus, here are the workaround

  • If you test on Simulator, make sure Simulator -> Hardware -> Keyboard -> Connect Hardware Keyboard is not checked
  • wait a bit after tap
1
app.emailTextField.tap()

Clear all the text

The idea is to move the caret to the end of the textField, then apply each delete key for each character, then type the next text

1
2
3
4
5
6
7
8
9
10
11
12
13
extension XCUIElement {
func deleteAllText() {
guard let string = value as? String else {
return
}

let lowerRightCorner = coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9))
lowerRightCorner.tap()

let deletes = string.characters.map({ _ in XCUIKeyboardKeyDelete }).joined(separator: "")
typeText(deletes)
}
}

Change language

For my case, I want to test in Norwegian, so we need to find the Norwegian option and tap on that. It is identified as static text by UI Test

1
2
3
var norwegianText: XCUIElement {
return staticTexts["Norsk (bokmål)"]
}
1
2
wait(for: app.norwegianText, timeout: 1)
app.norwegianText.tap()

The email text field

Luckily, email text field is detected by UI Test as text field element, so we can query for that. This uses predicate

1
2
3
4
var emailTextField: XCUIElement {
let predicate = NSPredicate(format: "placeholderValue == %@", "E-post eller mobil")
return textFields.element(matching: predicate)
}

The password text field

UI Test can’t seem to identify the password text field, so we need to search for it by coordinate

1
2
3
4
var passwordCoordinate: XCUICoordinate {
let vector = CGVector(dx: 1, dy: 1.5)
return emailTextField.coordinate(withNormalizedOffset: vector)
}

This is the document for func coordinate(withNormalizedOffset normalizedOffset: CGVector) -> XCUICoordinate

Creates and returns a new coordinate with a normalized offset.
The coordinate’s screen point is computed by adding normalizedOffset multiplied by the size of the element’s frame to the origin of the element’s frame.

Then type the password

1
2
app.passwordCoordinate.tap()
app.typeText("My password")

We should not use app.passwordCoordinate.referencedElement because it will point to email text field ❗️ 😢

Run that test again

Go to Xcode -> Product -> Perform Actions -> Test Again to run the previous test again

again

The full source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import XCTest

class LoginTests: XCTestCase {
var app: XCUIApplication!

func testLogin() {
continueAfterFailure = false
app = XCUIApplication()
app.launch()

passLogin()
}
}

extension LoginTests {
func passLogin() {
// Tap login
app.showFacebookLoginFormButton.tap()
wait(for: app.staticTexts["Facebook"], timeout: 5) // This requires a high timeout

// There may be location permission popup when showing map
handleLocationPermission()

if app.isAlreadyLoggedInSafari {
app.okButton.tap()

// Show map
let map = app.maps.element(boundBy: 0)
wait(for: map, timeout: 2)
XCTAssertTrue(map.exists)

// Need to interact with the app for interruption monitor to work
app.tap()
} else {
// Choose norsk
wait(for: app.norwegianText, timeout: 1)
app.norwegianText.tap()

app.emailTextField.tap()
app.emailTextField.deleteAllText()
app.emailTextField.typeText("mujyhwhbby_1496155833@tfbnw.net")

app.passwordCoordinate.tap()
app.typeText("Bob Alageaiecghfb Sharpeman")

// login
app.facebookLoginButton.tap()

// press OK
app.okButton.tap()

// Show map
let map = app.maps.element(boundBy: 0)
wait(for: map, timeout: 2)
XCTAssertTrue(map.exists)

// Need to interact with the app for interruption monitor to work
app.tap()
}
}

fileprivate func handleLocationPermission() {
addUIInterruptionMonitor(withDescription: "Location permission", handler: { alert in
alert.buttons.element(boundBy: 1).tap()
return true
})
}
}

fileprivate extension XCUIApplication {
var showFacebookLoginFormButton: XCUIElement {
return buttons["Continue with Facebook"]
}

var isAlreadyLoggedInSafari: Bool {
return buttons["OK"].exists || staticTexts["Du har allerede godkjent Blue Sea."].exists
}

var okButton: XCUIElement {
return buttons["OK"]
}

var norwegianText: XCUIElement {
return staticTexts["Norsk (bokmål)"]
}

var emailTextField: XCUIElement {
let predicate = NSPredicate(format: "placeholderValue == %@", "E-post eller mobil")
return textFields.element(matching: predicate)
}

var passwordCoordinate: XCUICoordinate {
let vector = CGVector(dx: 1, dy: 1.5)
return emailTextField.coordinate(withNormalizedOffset: vector)
}

var facebookLoginButton: XCUIElement {
return buttons["Logg inn"]
}
}

extension XCTestCase {
func wait(for duration: TimeInterval) {
let waitExpectation = expectation(description: "Waiting")

let when = DispatchTime.now() + duration
DispatchQueue.main.asyncAfter(deadline: when) {
waitExpectation.fulfill()
}

// We use a buffer here to avoid flakiness with Timer on CI
waitForExpectations(timeout: duration + 0.5)
}

/// Wait for element to appear
func wait(for element: XCUIElement, timeout duration: TimeInterval) {
let predicate = NSPredicate(format: "exists == true")
let _ = expectation(for: predicate, evaluatedWith: element, handler: nil)

// We use a buffer here to avoid flakiness with Timer on CI
waitForExpectations(timeout: duration + 0.5)
}
}

extension XCUIApplication {
// Because of "Use cached accessibility hierarchy"
func clearCachedStaticTexts() {
let _ = staticTexts.count
}

func clearCachedTextFields() {
let _ = textFields.count
}

func clearCachedTextViews() {
let _ = textViews.count
}
}

extension XCUIElement {
func deleteAllText() {
guard let string = value as? String else {
return
}

let lowerRightCorner = coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9))
lowerRightCorner.tap()

let deletes = string.characters.map({ _ in XCUIKeyboardKeyDelete }).joined(separator: "")
typeText(deletes)
}
}

Read more

I found these guides to cover many aspects of UITests, worth taking a look

How to use assertionFailure and Optimization Level in iOS

Issue #39

We used to use assertionFailure to mark programmer error or something that shouldn’t happen.

From assertionFailure

Indicates that an internal sanity check failed.

Use this function to stop the program, without impacting the performance of shipping code, when control flow is not expected to reach the call—for example, in the default case of a switch where you have knowledge that one of the other cases must be satisfied. To protect code from invalid usage in Release builds, see preconditionFailure(_:file:line:).

  • In playgrounds and -Onone builds (the default for Xcode’s Debug configuration), stop program execution in a debuggable state after printing message.
  • In -O builds, has no effect.
  • In -Ounchecked builds, the optimizer may assume that this function is never called. Failure to satisfy that assumption is a serious programming error.

So go to your target settings, and check Optimization Level, make sure it is not -Onone for release configuration.

The difference between debug and release is this SWIFT_OPTIMIZATION_LEVEL. If -Onone then your configuration is considered debug, and assertionFailure will crash your apps

Read more

How to perform platform check with typealias and @available

Issue #38

The other day, I was building Anchors which needs to support iOS and macOS. What’s clever way to not use #if os(iOS) || os(tvOS) in all files? Use typealias

This is the first version. I’m trying to support iOS 8, macOS 10.10

1
2
3
4
5
6
7
8
9
10
#if os(iOS) || os(tvOS)
import UIKit
public typealias View = UIView
public typealias LayoutGuide = UILayoutGuide
public typealias EdgeInsets = UIEdgeInsets
#elseif os(OSX)
import AppKit
public typealias View = NSView
public typealias LayoutGuide = NSLayoutGuide
#endif

But then because of LayoutGuide, I need to bump deployment target to iOS 9, macOS 10.11. Which is not what I want. @available to the rescue, but it will affect everything below it. The solution is to split the platform check, the first as normal, the second with @available check

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#if os(iOS) || os(tvOS)
import UIKit
public typealias View = UIView
public typealias EdgeInsets = UIEdgeInsets
#elseif os(OSX)
import AppKit
public typealias View = NSView
#endif

#if os(iOS) || os(tvOS)
import UIKit
@available(iOS 9.0, *)
public typealias LayoutGuide = UILayoutGuide
#elseif os(OSX)
import AppKit
@available(macOS 10.11, *)
public typealias LayoutGuide = NSLayoutGuide
#endif

How to use Controller and View in iOS

Issue #37

I like to write UI in code, and with Auto Layout, it is an easy task. However that leaves ViewController with a lots of code. One way we can do is to separate V from C in MVC, by using a dedicated view

We can do that with generic, that initialises a view and replace the view, let’s call it root

1
2
3
4
5
6
7
8
9
import UIKit

class BaseController<T: UIView>: UIViewController {
let root = T()

override func loadView() {
view = root
}
}

Now we can have a UIView subclass, like LoginView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
final class LoginView: UIView {
lazy var textField: UITextField = UITextField().then {
$0.textAlignment = .center
$0.borderStyle = .roundedRect
$0.keyboardType = .phonePad
}

lazy var button: UIButton = UIButton().then {
$0.setTitleColor(.black, for: .normal)
$0.backgroundColor = .lightGray
}

override init(frame: CGRect) {
super.init(frame: frame)

addSubviews(
textField,
button
)

Constraint.on(
textField.centerXAnchor.constraint(equalTo: textField.superview!.centerXAnchor),
textField.centerYAnchor.constraint(equalTo: textField.superview!.centerYAnchor),
textField.widthAnchor.constraint(equalTo: textField.superview!.widthAnchor, constant: -20),

button.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 20),
button.centerXAnchor.constraint(equalTo: button.superview!.centerXAnchor),
button.widthAnchor.constraint(equalTo: textField.widthAnchor, multiplier: 0.8),
button.heightAnchor.constraint(equalToConstant: 44)
)
}

required init?(coder aDecoder: NSCoder) {
fatalError()
}
}

And then the LoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final class LoginController: BaseController<LoginView> {
override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .white

let gr = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
root.addGestureRecognizer(gr)

root.button.setTitle("Login", for: .normal)
root.button.addTarget(self, action: #selector(loginButtonTouched), for: .touchUpInside)
root.button.isEnabled = false
root.button.showsTouchWhenHighlighted = true

root.textField.placeholder = "Phone number"
root.textField.delegate = self
root.textField.text = dependencyContainer.phoneService.prefix
root.textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
}

And this is how we declare the LoginController

1
2
let loginController = LoginController()
navigationController.viewControllers = [loginController]

How to use MainController in iOS

Issue #36

Usually in an app, we have these flows: onboarding, login, main. And we usually set OnboardingController, LoginController and MainController as the root view controller respectively depending on the state.

I find it useful to have the MainController as the container for main flow. It can be a tab controller, swipe menu controller or contains just 1 child view controller. The screens are provided by child view controllers, but the MainController does the following jobs

  • Status bar style

We usually need to call preferredStatusBarStyle on the parent controller. See https://stackoverflow.com/questions/19022210/preferredstatusbarstyle-isnt-called

  • App did become active

Usually when app is brought to foreground, we need to fetch logged in user profile to see if there’s changes. We do this by listening to app did become active in MainController.

  • Mock to open

This can be anti pattern. But in UI Tests, for laziness, we can just use some launch arguments and check to present some specific screens to test, because MainController is the root for main flow.

  • Logout

Because things originate from MainController, things can terminate in MainController. We can handle logout, clear states, and tell MainController to tell AppDelegate to switch to another root controller

How to handle Auto Layout with different screen sizes

Issue #35

Auto Layout is awesome. Just declare the constraints and the views are resized accordingly to their parent ‘s bounds changes. But sometimes it does not look good, because we have fixed values for padding, width, height, and even fixed font size.

Read more How to make Auto Layout more convenient in iOS

This can be solved by some degree using Size Class. The idea of size class is that we have many sets of constraints, and based on the device traits, we enabled some of them. This is more convenient to do in Storyboard (although very hard to reason about), but if we’re doing in code (my prefer way), then it is a lot of code. And a lot of code means a lot of bugs.

If you take a look at iOSRes, we see the ratio 16:9 (height:width)

  • iPhone SE (320 x 568): 1.775
  • iPhone 6 (375 x 667): 1.778
  • iPhone 6+ (414 x 736): 1.778

They mostly have the same ratio. So we can have a simple approach, that scale elements based on ratio. Given the fact that the designer usually designs for iPhone 6 size, we can make that a base.

In this approach, the content will scale up or down depending on its ratio. You may argue that the idea of bigger phone is to display more, not to show the same content bigger. You may be right, in that case you need to create different constraints and different UIs. But if you want simple solutions that work, this is one of them

This is the technique I used when doing Windows Phone development, but it applies to many platforms as well

Calculate the ratio

1
2
3
4
5
6
7
8
class Device {
// Base width in point, use iPhone 6
static let base: CGFloat = 375

static var ratio: CGFloat {
return UIScreen.main.bounds.width / base
}
}

Extension to make it convenient

We can have a computed property called adjusted that adjusts the size based on the ratio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension CGFloat {

var adjusted: CGFloat {
return self * Device.ratio
}
}

extension Double {

var adjusted: CGFloat {
return CGFloat(self) * Device.ratio
}
}

extension Int {

var adjusted: CGFloat {
return CGFloat(self) * Device.ratio
}
}

Use the ratio

You can adjust as much as you want

1
2
3
4
5
6
label.font = UIFont.systemFont(ofSize: 23.adjusted)

phoneTextField.leftAnchor.constraint(equalTo: container.leftAnchor, constant: 30.adjusted),
phoneTextField.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -30.adjusted),

imageView.widthAnchor.constraint(equalToConstant: 80.adjusted), imageView.heightAnchor.constraint(equalToConstant: 90.adjusted),

NSApplicationDelegate and notification

Issue #34

In an iOS project, we often see this in AppDelegate

1
2
3
4
5
6
7
8
9
10
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

return true
}
}

But in a Cocoa project, we see this instead

1
2
3
4
5
6
7
8
9
10
11
12
13
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {



func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}

func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}

In this case the param is of type NSNotification

Delegate and notification

Reading Cocoa Core Competencies - Delegation

The delegate of most Cocoa framework classes is automatically registered as an observer of notifications posted by the delegating object. The delegate need only implement a notification method declared by the framework class to receive a particular notification message. Following the example above, a window object posts an NSWindowWillCloseNotification to observers but sends a windowShouldClose: message to its delegate.

So the pattern is that the delegate should strip the NS and Notification, like NSWindowWillCloseNotification to windowShouldClose:

How to define SDK and Deployment Target in iOS

Issue #33

I see that my answer to the question What’s the meaning of Base SDK, iOS deployment target, Target, and Project in xcode gets lots of views, so I think I need to elaborate more about it

Good read

base

Base SDK

  • We can’t configure this anymore, as Xcode will use the latest SDK. For Xcode 7, the SDK is iOS 9
  • If we upgrade Xcode, it will use the newer version of the SDK. Like Xcode 7.2, the SDK is iOS 9.1
  • Choosing the latest SDK for your project lets you use the new APIs introduced in the OS update that corresponds to that SDK. When new functionality is added as part of a system update, the system update itself does not typically contain updated header files reflecting the change. The SDKs, however, do contain updated header files.

Deployment Target

  • We can set in Xcode -> Target -> Deployment Info -> Deployment Target
  • State that we support this iOS version

What does it mean

So, a modern App might use iOS 9 as the Target SDK, and iOS 7 as the deployment target. This means that you can run on iOS 7, iOS 8 and iOS 9, and that you have available to you any iOS 9 calls when actually running on iOS 9.

.

Each .sdk directory resembles the directory hierarchy of the operating system release it represents: It has usr, System, and Developer directories at its top level. OS X .sdk directories also contain a Library directory. Each of these directories in turn contains subdirectories with the headers and libraries that are present in the corresponding version of the operating system with Xcode installed.

.

The libraries in an iOS or OS X SDK are stubs for linking only; they do not contain executable code but just the exported symbols. SDK support works only with native build targets.

So the SDK is just like stub and header only. It means that we can use certain APIs, but on OS that does not have the real symbols for those APIs, it crashes

available

Swift 2 introduces available construct that guards against failure when trying to use newer APIs.

Note that available is runtime, not compile time. All the code is inside your executable

1
2
3
4
5
if #available(iOS 9, OSX 10.10, *) {
// Code to execute on iOS 9, OS X 10.10
} else {

}

deprecated APIs

Always check to see if you are using deprecated APIs; though still available, deprecated APIs are not guaranteed to be available in the future

Compile time vs Runtime

1
2
3
4
5
#if (arch(i386) || arch(x86_64)) && os(iOS)
// code inside gets inserted into executable when builds for simulator
#else
// code inside gets inserted into executable when builds for device
#endif
1
2
3
4
5
#if os(OSX)
import Cocoa
#elseif os(iOS)
import UIKit
#endif
1
2
3
4
5
6
// All the code gets inserted into executable, but is run depending on the version of the OS
if #available(iOS 9, *) {
// use UIStackView
} else {
// show your manual Auto Layout skill
}

Weakly vs strongly linked

For example, suppose in Xcode you set the deployment target (minimum required version) to “OS X v10.5” and the base SDK (maximum allowed version) to “OS X v10.6”. During compilation, the compiler would weakly link interfaces that were introduced in OS X v10.6 while strongly linking interfaces defined in earlier versions of the OS. This would allow your application to run in OS X v10.5 and take advantage of newer features when available.

.

None of the (platform) frameworks is really “included in the bundle”. Instead, your app has a reference (“link”) to a framework once you add it to the “Link Binary with Library” build phase. The frameworks are pre-installed on the devices. When you run an app, all the app’s framework references are resolved by the dynamic linker (on the device), which means the framework code is loaded so your app can use it.

Reference


Updated at 2020-12-08 06:31:54

App backed by website in iOS 9

Issue #32

iOS 9 introduces new ways for your app to work better, backed by your websites

Smart App Banners

If the app is already installed on a user’s device, the banner intelligently changes its action, and tapping the banner will simply open the app. If the user doesn’t have your app on his device, tapping on the banner will take him to the app’s entry in the App Store

To add a Smart App Banner to your website, include the following meta tag in the head of each page where you’d like the banner to appear:

1
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">

When you support universal links, iOS 9 users can tap a link to your website and get seamlessly redirected to your installed app without going through Safari. If your app isn’t installed, tapping a link to your website opens your website in Safari.

Web Markup

If some or all of your app’s content is also available on your website, you can use web markup to give users access to your content in search results. Using web markup lets the Applebot web crawler index your content in Apple’s server-side index, which makes it available to all iOS users in Spotlight and Safari search results.

Shared Web Credentials

Shared web credentials is a programming interface that enables native iOS apps to share credentials with their website counterparts. For example, a user may log in to a website in Safari, entering a user name and password, and save those credentials using the iCloud Keychain. Later, the user may run a native app from the same developer, and instead of the app requiring the user to reenter a user name and password, shared web credentials gives it access to the credentials that were entered earlier in Safari.

Reference

Disingenuousness

Issue #31

I’m very happy to be on open source movement, and it ‘ll be great to hear about what people have achieved

And @merowing_ also mentioned in Writing Xcode plugin in Swift

Attribution

Writing this was much simpler because I was able to look at other people plugins, mostly those related to console, without them being open sourcing it would be more work to figure this stuff out with hopper.

Open source helps us move forward, learn and share together

The dark side of the Force

Luke: Is the dark side stronger?

Yoda: No, no, no. Quicker, easier, more seductive.

It’s a pain to see plagiarism around

Open source softwares are in fact intellectual properties, and the authors should get acknowledgement for the work that they do.

It’s not fair to take the credit of other’s work and not giving any attribution

By its nature, open source software has a unique relationship with intellectual property rights

One thing that’s not up for debate in most circles is that it’s dishonest and disingenuous to take someone else’s project, modify it slightly, and call it your own.

Further, regardless of whether or not a project crosses that line, it must (by the terms of most open source licenses) acknowledge the original work/author.

And the reaction

It’s always sad to see blatant plagiarism, and I think it really hurts the community more than the author itself. It gives people a good reason to keep the sources private.

Being nice

I often hear people say that

It is easier to find good developer than developer with good attitude

Github also states that

We understand and agree that copying others’ work without permission goes against the spirit of the open source community

Do the right things

Is it MIT ‘s fault? Definitely no

False choice. Giving up freedom does not lead to more security, just less freedom.

Takeaways

  • Don’t take things personally
  • It’s hard to be perfect, but we can learn to do the right things
  • We may do the wrong things, but don’t worry, there ‘ll be help via Issues and Pull Requests

How to do curry in Swift

Issue #30

Haskell is notorious for currying, and Swift has currying, too

I love ReactiveCocoa, RxSwift and I always take time to dig into it. The other day, I was practise making Signal based on this talk UIKonf 2015 - Jens Ravens: Functional Reactive Programming without Black Magic

Take a look at my repo Signal

filter

I was making a filter for a Signal. The idea of filter is that we should update signal if the Event is Next with right filtered value

Signal.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
public func filter(f: T -> Bool) -> Signal<T>{
let signal = Signal<T>()
subscribe { result in
switch(result) {
case let .Success(value):
if f(value) {
signal.update(result)
}
case let .Error(error): signal.update(.Error(error))
}
}
return signal
}

2 params

But having Event as another monad, I think it should be more encapsulated if that switching logic gets moved into the Event. Here the filter takes 2 params

Event.swift

1
2
3
4
5
6
7
8
9
10
func filter(f: T -> Bool, callback: (Event<T> -> Void)) {
switch self {
case let .Next(value) where f(value):
callback(self)
case .Failed:
callback(self)
default:
break
}
}

Signal.swift

1
2
3
4
5
6
7
8
9
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()

subscribe { event in
event.filter(f, callback: signal.update)
}

return signal
}

Currying

With currying, we can make filter a more abstract function, and defer the decision to pass the callback param. It is a little carried away but I find it helpful this way

Now filter accepts 1 param, and it returns a function that takes callback as its param

Event.swift

1
2
3
4
5
6
7
8
9
10
11
12
func filter(f: T -> Bool) -> ((Event<T> -> Void) -> Void) {
return { g in
switch self {
case let .Next(value) where f(value):
g(self)
case .Failed:
g(self)
default:
break
}
}
}

Signal.swift

1
2
3
4
5
6
7
8
9
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()

subscribe { event in
event.filter(f)(signal.update)
}

return signal
}

Curry syntax in Swift 2

Swift 2 supports curry syntax function

1
2
3
4
5
6
7

func sum(a: Int)(b: Int) -> Int {
return a + b
}

let sumWith5 = sum(5)
let result = sumWith5(b: 10)

No more curry syntax in Swift 3

You may want to find out

Reference

How to use push notification in iOS

Issue #29

Here are my notes for working with Push Notification, updated for iOS 9

How to register

  • Register to receive push notification

registerForRemoteNotificationTypes is deprecated in iOS 8+

1
UIApplication.sharedApplication().registerForRemoteNotifications()
  • Register to alert user through UI

If your app displays alerts, play sounds, or badges its icon, you must call this method during your launch cycle to request permission to alert the user in these ways

1
2
3
4
5
let types: UIUserNotificationType = [.Badge, .Sound, .Alert]
let categories = Set<UIUserNotificationCategory>()
let settings = UIUserNotificationSettings(forTypes: types, categories: categories)

UIApplication.sharedApplication().registerUserNotificationSettings(settings)

You don’t need to wait for registerUserNotificationSettings to callback before calling registerForRemoteNotifications

When to register

Never cache a device token; always get the token from the system whenever you need it. If your app previously registered for remote notifications, calling the registerForRemoteNotifications method again does not incur any additional overhead, and iOS returns the existing device token to your app delegate immediately. In addition, iOS calls your delegate method any time the device token changes, not just in response to your app registering or re-registering

The user can change the notification settings for your app at any time using the Settings app. Because settings can change, always call the registerUserNotificationSettings: at launch time and use the application:didRegisterUserNotificationSettings: method to get the response. If the user disallows specific notification types, avoid using those types when configuring local and remote notifications for your app.

didReceiveRemoteNotification

About application:didReceiveRemoteNotification:

Implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead of this one whenever possible. If your delegate implements both methods, the app object calls the application:didReceiveRemoteNotification:fetchCompletionHandler: method.

If the app is not running when a remote notification arrives, the method launches the app and provides the appropriate information in the launch options dictionary. The app does not call this method to handle that remote notification. Instead, your implementation of the application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method needs to get the remote notification payload data and respond appropriately.

About application:didReceiveRemoteNotification:fetchCompletionHandler:

This is for silent push notification with content-available

Unlike the application:didReceiveRemoteNotification: method, which is called only when your app is running in the foreground, the system calls this method when your app is running in the foreground or background

In addition, if you enabled the remote notifications background mode, the system launches your app (or wakes it from the suspended state) and puts it in the background state when a push notification arrives. However, the system does not automatically launch your app if the user has force-quit it. In that situation, the user must relaunch your app or restart the device before the system attempts to launch your app automatically again.

If the user opens your app from the system-displayed alert, the system may call this method again when your app is about to enter the foreground so that you can update your user interface and display information pertaining to the notification.

How to handle

Usually, the use of push notification is to display a specific article, a specific DetailViewController, … in your app. So the good practices are

  • When the app is in foreground: Gently display some kind of alert view and ask the user whether he would like to go to that specific page or not
  • When user is brought from background to foreground, or from terminated to foreground: Just navigate to that specific page. For example, if you use UINavigationController, you can set that specific page the top most ViewController, if you use UITabBarController, you can set that specific page the selected tab, something like that
1
2
3
4
5
6
7
8
9
10
11
- func handlePushNotification(userInfo: NSDictionary) {
// Check applicationState
if (applicationState == UIApplicationStateActive) {
// Application is running in foreground
showAlertForPushNotification(userInfo)
}
else if (applicationState == UIApplicationStateBackground || applicationState == UIApplicationStateInactive) {
// Application is brought from background or launched after terminated
handlePushNotification(userInfo)
}
}

Here we create another method `handlePushNotification:`` to handle push notification. When you receive push notification, 3 cases can occur

Case 1: Foreground

Loud push

  • No system alert
  • application:didReceiveRemoteNotification:fetchCompletionHandler: called

Silent push

  • No system alert
  • application:didReceiveRemoteNotification:fetchCompletionHandler: called

Case 2: Background

Loud push

  • System alert
  • No method called
  • Tap notification and application:didReceiveRemoteNotification:fetchCompletionHandler: called
  • Tap on App Icon and nothing is called

Silent push

  • System alert
  • application:didReceiveRemoteNotification:fetchCompletionHandler: called. If app is suspended, its state changed to UIApplicationStateBackground
  • Tap notification and application:didReceiveRemoteNotification:fetchCompletionHandler: called
  • Tap on App Icon and nothing is called

Case 3: Terminated

Loud push

  • System alert
  • No method called
  • Tap notification and application:didFinishLaunchingWithOptions: with launchOptions, application:didReceiveRemoteNotification:fetchCompletionHandler: called
  • Tap on App Icon and application:didFinishLaunchingWithOptions: is called with launchOptions set to nil

Silent push

  • System alert
  • application:didReceiveRemoteNotification:fetchCompletionHandler: called. If app was not killed by user, it is woke up and state changed to UIApplicationStateInactive.
  • Tap notification and application:didFinishLaunchingWithOptions: with launchOptions, application:didReceiveRemoteNotification:fetchCompletionHandler: called
  • Tap on App Icon and application:didFinishLaunchingWithOptions: is called with launchOptions set to nil

System alert

System alert only show if the payload contains “alert”

1
2
3
4
5
6
7
8
9
10
11
12
{
"aps" : {
"alert" : {
"title" : "Game Request",
"body" : "Bob wants to play poker",
"action-loc-key" : "PLAY"
},
"badge" : 5
},
"param1" : "bar",
"param2" : [ "bang", "whiz" ]
}

Silent push payload

For now I see that silent push must contain “sound” for application:didReceiveRemoteNotification:fetchCompletionHandler: to be called when app is in background

1
2
3
4
5
6
7
8
9
{
"aps": {
"content-available": 1,
"alert": "hello" // include this if we want to show alert
"sound": "" // this does the trick
},
"param1": 1,
"param2": "text"
}

Reference

Understanding push and pull signal in reactive paradigm

Issue #28

The idea of Signal may originate from Elm Reactivity, and it has now been widely adopted in iOS

I once asked What are examples of hot and cold signal in ReactiveCocoa?

Whether it is hot vs cold, Signal vs Signal Producer, Observable vs Enumerable, … it’s good to understand how it gets implemented, so that to have a good sense of how they work

Monad

Basically, Signal and its Result are just monads, which are thing that can be mapped and chained.

Signal makes use of deferred execution callback blocks, and push vs pull is just how the Signal updates its value and the order the callbacks are called

Execution callback block is that we pass a function to another function, and it will get called when appropriated

Sync vs Async

Monad can be in either sync or async mode. Sync is easier to understand, but async is somewhat you’re already familiar and used in practice

Basically,

  • Sync: you get the returned value right away via return
  • Aync: you get the returned value via callback block

Here is an example of a simple function

1
2
3
4
5
6
7
8
9
10
11
12
// Sync
func sum(a: Int, b: Int) -> Int {
return a + b
}

// Async
func sum(a: Int, b: Int, completion: Int -> Void) {
// Assumed it is a very long task to get the result
let result = a + b

completion(result)
}

Here is an example of Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Sync
public func map<U>(@noescape f: T -> U) -> Event<U> {
switch self {
case let .Next(value):
return .Next(value: f(value))
case let .Failed(error):
return .Failed(error: error)
}
}

// Async
public func map<U>(f: (T, U -> Void) -> Void) -> ((Event<U> -> Void) -> Void) {
return { g in // g: Event<U> -> Void
switch self {
case let .Next(value):
f(value) { transformedValue in // transformedValue: U
g(.Next(value: transformedValue))
}
case let .Failed(error):
g(.Failed(error: error))
}
}
}

Push Signal

Take a look at my Push Signal, called Signal, it is like how Promise A+ Then works

Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public final class Signal<T> {
var event: Event<T>?
var callbacks: [Event<T> -> Void] = []

func notify() {
guard let event = event else {
return
}

callbacks.forEach { callback in
callback(event)
}
}

func update(event event: Event<T>) {
dispatch_sync(lockQueue) {
self.event = event
}

notify()
}

public func subscribe(f: Event<T> -> Void) -> Signal<T> {
// Callback
if let event = event {
f(event)
}

callbacks.append(f)

return self
}

public func map<U>(f: T -> U) -> Signal<U> {
let signal = Signal<U>()

subscribe { event in
signal.update(event: event.map(f))
}

return signal
}
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
let signal = Signal<String>()

signal.map { value in
return value.characters.count
}.subscribe { event in
if case let .Next(value) = event {
XCTAssert(value == 4)
} else {
XCTAssert(false)
}
}

signal.sendNext("test")

Callbacks

Given a chained signals like this

A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)

  • The idea is we send event to the source signal, and it propagates events through via callbacks.
  • Triggered by sending event to the source signal.
  • We must keep A as it keeps the others around
  • We subscribe the last D
  • We send event to the first A
  • A ‘s callback gets called, it it in turn calls callback of B with the result of A ‘s map, then B ‘s callback calls C ‘s callback with the result of B
    ‘s flatMap, …

Pull Signal

Take a look at my Pull Signal, called Future

Implementation

Here operation is a task, when called and completed, will notify its completion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public struct Future<T> {
let operation: (Event<T> -> Void) -> Void

public init(operation: (Event<T> -> Void) -> Void) {
self.operation = operation
}

public func start(completion: Event<T> -> Void) {
operation() { event in
completion(event)
}
}

public func map<U>(f: T -> U) -> Future<U> {
return Future<U> { completion in
self.start { event in
completion(event.map(f))
}
}
}
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
let _ = Future<String> { completion in
// There is some work here
completion(Event(value: "test"))
}
.map { value in
value.characters.count
}.start { event in
if case let .Next(value) = event {
XCTAssert(value == 4)
} else {
XCTAssert(false)
}
}

Callbacks

Given a chained signals like this

A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)

  • The idea is we subscribe to the final signal D, and it cause the previous signals to action.
  • Triggered by subscribing to the final signal.
  • We must keep D as it keeps the others around
  • We subscribe the last D
  • D ‘s operation actions, and it cause C ‘s operation to action, … then A ‘s operation actions. It is in A that the task is performed (like fetching network, retrieving database, file access, heavy computation, …) to get the result, and A ‘s completion gets called. Then A’s completion calls B ‘s completion with the result mapped by B ‘s map, … all the way to the subscriber ‘s completion block

How to make iOS Stretchy Header with Auto Layout

Issue #27

Stretchy header is cool. People are familiar with changing frames to achieve this, like Design Teardown: Stretchy Headers. But with Auto Layout, we can achieve this with much nicer declarative constraints

The demo project is StretchyHeader

demo

I use SnapKit to make it clear what constraints we need

scrollView

The scrollView should pin its 4 edges to the ViewController 's view

1
2
3
4
5
6
7
8
9
func setupScrollView() {
scrollView = UIScrollView()
scrollView.delegate = self

view.addSubview(scrollView)
scrollView.snp_makeConstraints { make in
make.edges.equalTo(view)
}
}

scrollViewContentView

The scrollViewContentView must pin its 4 edges to the scrollView to help determine scrollView contentSize

The height of scrollViewContentView is determined by its subviews. The subviews inside must pin their top and bottom to the scrollViewContentView

1
2
3
4
5
6
7
8
9
func setupScrollViewContentView() {
scrollViewContentView = UIView()

scrollView.addSubview(scrollViewContentView)
scrollViewContentView.snp_makeConstraints { make in
make.edges.equalTo(scrollView)
make.width.equalTo(view.snp_width)
}
}

The header must pin its top to the scrollView parent, which is the ViewController 's view

Read the title section, you ‘ll see that in order to make header stretchy, it must be pinned top and bottom

But if we scroll up, there will be a constraint conflict between these pinned top and bottom constraints

So we must declare headerTopConstraint priority as 999, and headerLessThanTopConstraint

1
2
3
4
5
6
7
8
9
10
11
12
13
func setupHeader() {
header = UIImageView()
header.image = UIImage(named: "onepiece")!

scrollViewContentView.addSubview(header)
header.snp_makeConstraints { make in
// Pin header to scrollView 's parent, which is now ViewController 's view
// When header is moved up, headerTopConstraint is not enough, so make its priority 999, and add another less than or equal constraint
make.leading.trailing.equalTo(scrollViewContentView)
self.headerTopConstraint = make.top.equalTo(view.snp_top).priority(999).constraint
self.headerLessThanTopConstraint = make.top.lessThanOrEqualTo(view.snp_top).constraint
}
}

title

The title must pin its top to the scrollViewContentView to help determine scrollViewContentView height

The title must also pin its top the header bottom in order to make header stretchy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func setupTitleLabel() {
titleLabel = UILabel()
titleLabel.numberOfLines = 0
titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleTitle1)
titleLabel.text = "One Piece"

scrollViewContentView.addSubview(titleLabel)
titleLabel.snp_makeConstraints { make in
make.leading.equalTo(scrollViewContentView).offset(20)
make.trailing.equalTo(scrollViewContentView).offset(-20)
// Pin to the header to make it stretchy
make.top.equalTo(header.snp_bottom).offset(20)
// Pin to the content view to help determine scrollView contentSize
make.top.equalTo(scrollViewContentView.snp_top).offset(headerHeight)
}
}

scrollViewDidScroll

The header is always pinned to the top, unless you adjust it, here in scrollViewDidScroll

Here I use Constraint, which is a class from SnapKit, but the idea is to change the constant of the NSLayoutConstraint

1
2
3
4
5
6
7
8
9
10
11
12
13
func scrollViewDidScroll(scrollView: UIScrollView) {
guard let headerTopConstraint = headerTopConstraint,
headerLessThanTopConstraint = headerLessThanTopConstraint
else {
return
}

let y = scrollView.contentOffset.y
let offset = y > 0 ? -y : 0

headerLessThanTopConstraint.updateOffset(offset)
headerTopConstraint.updateOffset(offset)
}

By the way, did you just learn the story of One Piece :]

Reference

How to group digits in Swift

Issue #26

When working on Scale I think it’s good to have a way to group the digit so that it is easier to reason

Luckily, Swift already supports this. See The Swift Programming Language - Numeric Literals

Numeric literals can contain extra formatting to make them easier to read. Both integers and floats can be padded with extra zeros and can contain underscores to help with readability. Neither type of formatting affects the underlying value of the literal

1
2
3
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

Talking about grouping digits after the decimal point, it is interesting too Convention of digit grouping after decimal point

So now we have

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum MetricUnit: Double {
case nano = 0.000_000_001
case micro = 0.000_001
case milli = 0.001
case centi = 0.01
case deci = 0.1
case base = 1
case deka = 10
case hecto = 100
case kilo = 1_000
case mega = 1_000_000
case giga = 1_000_000_000
case tera = 1_000_000_000_000
case peta = 1_000_000_000_000_000

static var defaultScale: Double {
return MetricUnit.base.rawValue
}
}

How to make a simple resolver in Swift

Issue #25

The Marvel world

Ant Man

We know Ant Man is Hank Pym

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct AntManSuit {
let name: String
}

struct HankPym {
let suit = AntManSuit(name: "Ant Man ID #101")

func fight() {
print("Fighting with the suit named " + suit.name)
}
}

let hankPym = HankPym()
hankPym.fight()

Everytime HankPym is created, he always uses the Ant Man suit. This time he is so coupled to the role Ant Man

More suits

Well, he does not have to be too dependent on the Ant Man suit. We know Hank Pym is a genius scientist, he has more suits to use. Let’s make it decoupled

Using Dependency Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol Suit {
var name: String { get }
}

struct AntManSuit: Suit {
let name: String
}

struct YellowJacketSuit: Suit {
let name: String
}

struct HankPym {
let suit: Suit

func fight() {
print("Fighting with the suit named " + suit.name)
}
}

let suit = YellowJacketSuit(name: "Yellow Jacket ID #33")
let hankPym = HankPym(suit: suit)
hankPym.fight()

Now Hank Pym can be more flexible on which suit to use.

Dependency Injection

The technique we just saw is called Dependency Injection, in which Hank Pym does not need to create the Suit, it will be provided through constructor or property.

Dependency Inversion Principle

In the first example, Hank Pym is dependent on the concrete implementation of the Suit

In the second example, both Hank Pym and the suits are dependent on the Suit protocol. This way Hank Pym only knows about the Suit protocol, and future suits must be crafted to that it conforms to the Suit protocol

This way the dependency is inverted

High level modules should not depend upon low level modules. Both should depend upon abstractions.

What is the high level policy? It is the abstractions that underlie the application, the
truths that do not vary when the details are changed

Inversion of Control Container

You may ask yourself Why is Inversion of Control named that way?

Framework vs library

People said “the framework calls you but you call the library”

Command line vs GUI

See What is Inversion of Control?

For example, in an old school menu, you might have:

1
2
3
4
5
6
print "enter your name"
read name
print "enter your address"
read address
etc...
store in database

thereby controlling the flow of user interaction.

In a GUI program or some such, instead we say

1
2
3
when the user types in field a, store it in NAME
when the user types in field b, store it in ADDRESS
when the user clicks the save button, call StoreInDatabase

You how have a brief understanding of how IoC means

IoC container

In the 2nd example of the Suit protocol, you can see how there is a inversion of control. What if there is a container that contains all the Suit conformances?

Let’s use my Resolver

1
2
3
4
5
6
7
let resolver = Resolver()
resolver.register {
YellowJacketSuit(name: "YellowJacket ID #404") as Suit
}

let suit = try! resolver.resolve() as Suit
let hankPym = HankPym(suit: suit)

Quite helpful, right? :]

Features

Actually, IoC container helps you more than that.

  • Circular Dependency Injection
  • Auto Injection
  • Object Scope

There are some IoC containers in Swift

Swinject

1
2
3
4
5
let container = Container()
container.register(AnimalType.self) { _ in Cat(name: "Mimi") }
container.register(PersonType.self) { r in
PetOwner(pet: r.resolve(AnimalType.self)!)
}

Swinject requires explicit type declaration. It has SwinjectStoryboard, which helps configuring the dependency for your view controller

Dip

Dip leverages generic and encourage protocols

1
2
container.register { ServiceImp() as Service }
let service = try! container.resolve() as Service

You ‘ll learn a lot just by reading Dip source code, on how factory and factory type are stored and checked using generic

1
2
3
4
5
6
7
8
9
10
11
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }

guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
throw DipError.DefinitionNotFound(key)
}

let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil
return _resolve(tag, key: usingKey, definition: definition, builder: builder)
}

Build your own simple IoC container

You may have discovered, that the idea of all those framework is to use closure as factory method

1
2
3
4
5
let factory = {
YellowJacketSuit(name: "YellowJacket ID #007") as Suit
}

let suit = factory()

All we have to do is to store these factories closure

Take a look at my gist SimpleResolver.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class SimpleResolver {
var factories = [String: Any]()

func factory<T>(factory: () -> T) {
let key = String(T.self)
factories[key] = factory
}

func resolve<T>() -> T {
let key = String(T.self)
if let factory = factories[key] as? () -> T {
return factory()
} else {
fatalError("Registration not found")
}
}
}

let resolver = SimpleResolver()

resolver.factory {
YellowJacketSuit(name: "YellowJacket IS #009") as Suit
}

let suit = resolver.resolve() as Suit
print(suit.name)

Reference

How to make lighter AppDelegate in iOS

Issue #24

There is Lighter View Controllers, and there is Lighter AppDelegate, too

Since working with iOS, I really like the delegate pattern, in which it helps us defer the decision to another party.

The iOS application delegates its event to AppDelegate, which over time will be a big mess. Usually, the AppDelegate is where you put your root view controller setup, crash tracking, push notification, debugging, … and we just somehow violent the Single Responsibility principle. Moreover, it makes us hard to reason about code in AppDelegate

Service

I like to think of each task in AppDelegate as a service. And the AppDelegate distributes the events into each service via ServiceDispatcher. Simple plain old composition and looping

I tend to have RootService as a place to setup root view controllers

It looks like this

ServiceDispatcher.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class ServiceDispatcher : NSObject, UIApplicationDelegate {
let services: [UIApplicationDelegate]

init(services: [UIApplicationDelegate]) {
self.services = services
}

func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {

services.forEach { service in
service.application?(application, didFinishLaunchingWithOptions: launchOptions)
}

return true
}

func applicationDidBecomeActive(application: UIApplication) {
services.forEach { service in
service.applicationDidBecomeActive?(application)
}
}

func applicationWillResignActive(application: UIApplication) {
services.forEach { service in
service.applicationWillResignActive?(application)
}
}

func applicationWillEnterForeground(application: UIApplication) {
services.forEach { service in
service.applicationWillEnterForeground?(application)
}
}

func applicationDidEnterBackground(application: UIApplication) {
services.forEach { service in
service.applicationDidEnterBackground?(application)
}
}
}

RootService.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class RootService : NSObject, UIApplicationDelegate {
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {

appDelegate().window = UIWindow(frame: UIScreen.mainScreen().bounds)
showHome()
appDelegate().window?.makeKeyAndVisible()

return true
}
}

extension RootService {
func showHome() {
let home = HomeWireframe().makeHome()
let navC = UINavigationController(rootViewController: home!)
appDelegate().window?.rootViewController = navC
}
}

extension RootService {
func appDelegate() -> AppDelegate {
return UIApplication.sharedApplication().delegate as! AppDelegate
}
}

AppDelegate.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
let serviceDispatcher = ServiceDispatcher(services: [RootService()])


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

serviceDispatcher.application(application, didFinishLaunchingWithOptions: launchOptions)

return true
}

func applicationWillResignActive(application: UIApplication) {
serviceDispatcher.applicationWillResignActive(application)
}

func applicationDidEnterBackground(application: UIApplication) {
serviceDispatcher.applicationDidEnterBackground(application)
}

func applicationWillEnterForeground(application: UIApplication) {
serviceDispatcher.applicationWillEnterForeground(application)
}

func applicationDidBecomeActive(application: UIApplication) {
serviceDispatcher.applicationDidBecomeActive(application)
}
}

I have more services like DebugService, PushNotificationService, CrashTrackingService, …

The downside to this approach is that in real life, there will be dependencies between those services, like that UserService must be called before RootService? In this case, I have to use comment to explain why I have that decision, which is hard for newcomers to understand at first. Take a look at How to Move Bootstrapping Code Out of AppDelegate for how dependencies are managed

JSDecoupledAppDelegate comes with another approach, in which service events are named according to the functions, like appStateDelegate, appDefaultOrientationDelegate, watchInteractionDelegate, …

But for me, Service and ServiceDispatcher suit my need

Reference

How to debug Auto Layout

Issue #23

hasAmbiguousLayout

Returns whether the constraints impacting the layout of the view incompletely specify the location of the view.

exerciseAmbiguityInLayout

This method randomly changes the frame of a view with an ambiguous layout between its different valid values, causing the view to move in the interface. This makes it easy to visually identify what the valid frames are and may enable the developer to discern what constraints need to be added to the layout to fully specify a location for the view.

_autolayoutTrace

This returns a string describing the whole view tree which tells you when a view has an ambiguous layout.

NSLayoutConstraint identifier

The name that identifies the constraint.

UIViewAlertForUnsatisfiableConstraints

DETECTED_MISSING_CONSTRAINTS

https://forums.developer.apple.com/thread/63811

View Debugger search by address

Read more

How to create a piano using iOS 9 Auto Layout

Issue #22

In the beginning, people use frame and Autoresizing Mask, then they use Auto Layout, then iOS 9 encourages them to use NSLayoutAnchor, UILayoutGuide and UIStackView

For more convenient Auto Layout, check How to make Auto Layout more convenient in iOS and Anchors

NSLayoutAnchor

The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using a fluent API. Use these constraints to programmatically define your layout using Auto Layout.

It has 3 subclasses

NSLayoutDimension

  • func constraintEqualToConstant(_ c: CGFloat) -> NSLayoutConstraint!

NSLayoutXAxisAnchor

  • Allows working with horizontal constraints
  • Prevent these
1
2
// This constraint generates an incompatible pointer type warning
cancelButton.leadingAnchor.constraintEqualToAnchor(saveButton.topAnchor, constant: 8.0).active = true

NSLayoutYAxisAnchor

  • Allows working with vertical constraints
  • Prevent these
1
2
// This constraint generates an incompatible pointer type warning
cancelButton.topAnchor.constraintEqualToAnchor(saveButton.trailingAnchor, constant: 8.0).active = true

UILayoutGuide

Previously, we used dummy views to aid constraints. Now we use UILayoutGuide

Define an equal spacing between a series of views

uilayoutguide_spacing

See full gist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let space1 = UILayoutGuide()
view.addLayoutGuide(space1)

let space2 = UILayoutGuide()
view.addLayoutGuide(space2)

space1.widthAnchor.constraintEqualToAnchor(space2.widthAnchor).active = true

saveButton.trailingAnchor.constraintEqualToAnchor(space1.leadingAnchor).active = true

cancelButton.leadingAnchor.constraintEqualToAnchor(space1.trailingAnchor).active = true
cancelButton.trailingAnchor.constraintEqualToAnchor(space2.leadingAnchor).active = true

clearButton.leadingAnchor.constraintEqualToAnchor(space2.trailingAnchor).active = true

Layout guides can also act as a black box, containing a number of other views and controls

uilayoutguide_container

See the full gist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let container = UILayoutGuide()
view.addLayoutGuide(container)

// Set interior constraints
label.lastBaselineAnchor.constraintEqualToAnchor(textField.lastBaselineAnchor).active = true
label.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor).active = true

textField.leadingAnchor.constraintEqualToAnchor(label.trailingAnchor, constant: 8.0).active = true
textField.trailingAnchor.constraintEqualToAnchor(container.trailingAnchor).active = true

textField.topAnchor.constraintEqualToAnchor(container.topAnchor).active = true
textField.bottomAnchor.constraintEqualToAnchor(container.bottomAnchor).active = true

// Set exterior constraints
// The contents of the container can be treated as a black box
let margins = view.layoutMarginsGuide

container.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor).active = true
container.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor).active = true

// Must use NSLayoutConstraint with the scene's top and bottom layout guides.
NSLayoutConstraint(item: container,
attribute: .Top,
relatedBy: .Equal,
toItem: topLayoutGuide,
attribute: .Bottom,
multiplier: 1.0,
constant: 20.0).active = true

layoutMarginsGuide

Margins are now represented as layoutMarginsGuide, a subclass of UILayoutGuide

topLayoutGuide and bottomLayoutGuide

In the container example, we saw how we must use NSLayoutConstraint with the topLayoutGuide. topLayoutGuide and bottomLayoutGuide are object conforming to UILayoutSupport protocol

layoutFrame

The layout guide defines a rectangular space in its owning view’s coordinate system. This property contains a valid CGRect value by the time its owning view’s layoutSubviews method is called.

In the above container example, the container layout guide frame is

1
(16.0, 40.0, 343.0, 21.0)

Piano

piano

See Piano on Github on how to create a Piano using UILayoutGuide, NSLayoutAnchor and UIStackView

How to handle RefreshControl in iOS

Issue #20

The other day I was doing refresh control, and I saw this Swift Protocols with Default Implementations as UI Mixins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension Refreshable where Self: UIViewController
{
/// Install the refresh control on the table view
func installRefreshControl()
{
let refreshControl = UIRefreshControl()
refreshControl.tintColor = .primaryColor
refreshControl.addTarget(self, action: #selector(handleRefresh(_:)), for: .valueChanged)
self.refreshControl = refreshControl

if #available(iOS 10.0, *)
{
tableView.refreshControl = refreshControl
}
else
{
tableView.backgroundView = refreshControl
}
}
}

Protocol extension is cool but somehow I’m not a fan of it. I always consider composition first, to extract the specific task to one entity that does that well. It looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RefreshHandler: NSObject {
let refresh = PublishSubject<Void>()
let refreshControl = UIRefreshControl()

init(view: UIScrollView) {
super.init()
view.addSubview(refreshControl)
refreshControl.addTarget(self, action: #selector(refreshControlDidRefresh(_: )), for: .valueChanged)
}

// MARK: - Action

func refreshControlDidRefresh(_ control: UIRefreshControl) {
refresh.onNext(())
}

func end() {
refreshControl.endRefreshing()
}
}

It is a bit Rx, we can use block if we like, but the idea is we can declare this RefreshHandler and use it everywhere we want

1
2
3
4
5
6
refreshHandler = RefreshHandler(view: scrollView)

refreshHandler.refresh
.startWith(())
.bindTo(viewModel.input.fetch)
.addDisposableTo(bag)

How to hack iOS apps

Issue #19

We need to care about security nowadays, here are some links I find useful to read more about this matter

Detecting languages and framework

iOS Security

Private frameworks

Hack macOS apps

Private frameworks

Hacking Apple

Hack Android apps


Updated at 2021-02-23 10:56:28

How to deal with singleton in iOS

Issue #18

A single singleton

There are many classes that designed to be used as singleton, like UserDefaults.standard, FileManager.default, NotificationCenter.default or even our own classes like UserManager, Storage, … Singleton is a design patter and has its own use case, sometimes we still need to use it. But if we are to use singleton, we should just use 1, and group all other singleton under this single singleton. Thanks to Vadym for showing this to me

Swift makes it extremely easy to make singleton, let name it App then we have a single point of control for all the singletons

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct App {
static let model = AppModel()
static let realmProvider = RealmProvider()
static let networkingProvider = NetworkingProvider()
static var navigator = Navigator()
static let config = AppConfig()
static let pushNotificationCenter = PushNotificationCenter()
static let lifeCycle = LifeCycle()
}
```

These are use cases where a single instance is needed

### AppModel
This is where we store model for an app, that can be
- is onboarding shown
- organization name
- `Session` that encapsulates token, current profile

### LifeCycle
This is where we listen to app life cycle, I use `rx` to make it easy, see https://github.com/onmyway133/blog/issues/12

### RealmProvider
I prefer `Realm` for storing and caching, usually 1 `Realm` is enough. This is where we return the a certain `Realm` instance

```swift
class RealmProvider {
static func realm() -> Realm {
let configuration = Realm.Configuration(schemaVersion: App.config.schemaVersion)
return try! Realm(configuration: configuration)
}
}

AppConfig

This is where we have configurations for staging and production environment, those can be client key, Firebase configuration, analytics keys, …

I use Compass to do central navigation, and there should be 1 Navigator that does the job

Inject a singleton

Sometime we rely on a singleton to do our job, to make dependencies clear and testing easier, we need to inject this singleton, and leverage Swift default parameter, thanks to John for showing this to me

Here is an example of a ViewModel that relies on networking

1
2
3
4
5
6
7
8
9
10
11
12
13
class ProfileViewModel {

let networking: Networking<APIEndpoint>

init(networking: Networking<APIEndpoint> = App.networking) {
self.networking = networking

networking.rxRequest(APIEndpoint.profile)
.bindNext({ profile in
print(profile)
})
}
}

Swift snippets

Issue #17

I always forget how to write correct #available( or #if swift(>=3.0) or just lazy to write required init?(coder aDecoder: NSCoder) every time I make a subclass. That’s why I made SwiftSnippets to save time for these tedious tasks. Installation is easy with script, so you should give it a try.

I can’t recommend this enough, it saves me tons of time