The other day I was watching , and he mentions Law of Jante

It is pretty much this

  1. You’re not to think you are anything special.
  2. You’re not to think you are as good as we are.
  3. You’re not to think you are smarter than we are.
  4. You’re not to convince yourself that you are better than we are.
  5. You’re not to think you know more than we do.
  6. You’re not to think you are more important than we are.
  7. You’re not to think you are good at anything.
  8. You’re not to laugh at us.
  9. You’re not to think anyone cares about you.
  10. You’re not to think you can teach us anything.

There are many discussions about this

Putting on your black hat, it sounds negative. Putting on your yellow hat, it sounds positive

But what I learn about it is the team work. No one lives alone, everyone lives among the others. It is about to be humble and learn collaboration

There are times we want to run an action just once. It would be nice if we can encapsulate this

dispatch_once

dispatch_once is meant to be used for action that runs once and only once

Once

We can have

1
2
3
4
5
6
7
8
9
10
11
class Once {

var already: Bool = false

func run(@noescape block: () -> Void) {
guard !already else { return }

block()
already = true
}
}

Then we can use it like

1
2
3
4
5
6
7
8
9
10
11
class ViewController: UIViewController {
let once = Once()

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)

once.run {
cameraMan.setup()
}
}
}

Function

Functions in Swift are distinguishable by

  • parameter label
  • parameter type
  • return type

so that these are all valid, and works for subscript as well

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
struct A {

// return type
func get() -> String { return "" }
func get() -> Int { return 1 }

// mix of parameter type and return type
func get(param: String) -> String { return "" }
func get(param: String) -> Int { return 1 }
func get(param: Int) -> Int { return 1 }
func get(param: Int) -> String { return "" }

subscript(param: String) -> String { return "" }
subscript(param: String) -> Int { return 1 }
subscript(param: Int) -> Int { return 1 }
subscript(param: Int) -> String { return "" }

// parameter label
func set(int: Int) {}
func set(string: String) {}

// concrete type from generic
func get(param: Array) -> String { return "" }
func get(param: Array) -> Int { return 1 }

subscript(param: Array<String>) -> String { return "" }
subscript(param: Array<Int>) -> Int { return 1 }
}

When you specialize a generic type, like Array, you’re actually using a concrete type

Unfortunately, this does not work for NSObject subclass

Method ‘get()’ with Objective-C selector ‘get’ conflicts with previous declaration with the same Objective-C selector

1
2
3
4
5
class B: NSObject {

func get() -> String { return "" }
func get() -> Int { return 1 }
}

Generic function

We can overload generic functions as well

1
2
3
4
5
6
7
8
9
10
11
func f(t: T) {
print("T")
}

func f(string: String) {
print("String")
}

func f(int: Int) {
print("Int")
}

This post is like a sum up of sum ways to configure your property

Using a helper method
1
2
3
4
5
6
7
8
9
10
11
12
13
var label: UILabel!

override func viewDidLoad() {
super.viewDidLoad()

configureLabel()
}

func configureLabel() {
label = UILabel()
label.backgroundColor = UIColor.greenColor()
view.addSubview(label)
}
Using anonymous function
1
2
3
4
5
lazy var label: UILabel = { [weak self] in
let label = UILabel()
label.backgroundColor = UIColor.greenColor()
return label
}()

Ah, by the way, did you know that

  • You shouldn’t call access label in ViewController deinit, because it is lazy and we have weak self
  • lazy increases your compile time
@noescape configure Block on init

I first saw it on https://github.com/AliSoftware/Dip/blob/develop/Sources/Dip.swift#L61

1
2
3
public init(@noescape configBlock: (DependencyContainer->()) = { _ in }) {
configBlock(self)
}
configure Block as extension

This https://github.com/devxoul/Then makes it easier to configure your property as an extension to NSObject

1
2
3
4
5
6
7
extension Then where Self: AnyObject {

public func then(@noescape block: Self -> Void) -> Self {
block(self)
return self
}
}

so we have

1
2
3
lazy var label: UILabel = UILabel().then { [weak self] in
$0.backgroundColor = UIColor.greenColor()
}

We have to declare label: UILabel to use `[weak self]

init without extension

I try to avoid extension, after reading this http://nshipster.com/new-years-2016/

1
2
3
4
5
public func Init(value : Type, @noescape block: (object: Type) -> Void) -> Type
{
block(object: value)
return value
}

we can use it like

1
2
3
lazy var label: UILabel = Init(UILabel()) { [weak self] in
$0.backgroundColor = UIColor.greenColor()
}

We have to declare label: UILabel to use `[weak self]

anonymous function again

This https://gist.github.com/erica/4fa60524d9b71bfa9819 makes configuration easier

1
2
3
4
lazy var label: UILabel = { [weak self] in
$0.backgroundColor = UIColor.greenColor()
return $0
}(UILabel())

GitHub is so awesome. It is where people around the world collaborate with each other.

It is more awesome to show more about you in your GitHub profile. How about a badge? a welcome text?

It is doable with organization. GitHub takes time and name of the organiazations you joined to determined how it displays on your profile

This is what shown on my GitHub profile https://github.com/onmyway133

For me, I display the text “Hello World”, so I have to create organizations for “h”, “e”, “l”, “l”, “o”, “w”, “o”, “r”, “l”, “d”

To ensure the order, you can name your organization like “org-h”, “org-he”, “org-hel”, “org-hell”, “org-hello”, … and you must join the organization in the correct order

I create another GitHub account called https://github.com/fantabot to manage my organizations

Your imaginary is your limit. May your code continue to compile :grin:

Swift allows us to define more methods on existing class using extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension UIView {

func shake() {

}

func fade() {

}

func centerIn(anotherView: UIView) {

}
}

If you ‘re afraid of the naming conflict, you can prefix your methods. Or a better way, reverse it :dancer: , like

1
2
3
view.animation.shake()
view.animation.fade()
view.layout.centerIn(anotherView)

This way, no more conflict and we make it clear that shake() and fade() belongs to animation category

Actually, animation and layout are properties in UIView extension. This may cause naming conflict, but the number of them is reduced

This is how it works

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
extension UIView {

struct Animation {
let view: UIView

func shake() {
// Shake the view
}

func fade() {
PowerfulAnimationEngine.fade(view)
}
}

var animation: Animation {
return Animation(view: self)
}

struct Layout {
let view: UIView

func centerIn(anotherView: UIView) {

}
}

var layout: Layout {
return Layout(view: self)
}
}

This is applied in Wave

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

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