BUILD FEATURES,
NOT APPS
@NATASHATHEROBOT
SWIFT ROBOT
▸ NatashaTheRobot.com
▸ This Week in Swift
▸ Swift Jobs
▸ @NatashaTheNomad
▸ try! Swift
Most smartphone users download 0 apps per month
An average app loses up to 95% of users within the first month
!
!"
FEATURE:
NOTIFICATIONS
FEATURE:
SPEECH
RECOGNITION
"As speech recognition accuracy goes from say 95% to 99%, all of us in
the room will from barely using it today to using it all the time. Most
people underestimate the difference between 95% and 99% accuracy -
99% is a game changer" - Andrew NG, Chief Scientist at Baidu
SIRI INTENTS:
▸ Audio or video calling
▸ Messaging
▸ Payments
▸ Searching photos
▸ Workouts
▸ Ride booking
FEATURE:
EXTENSIONS
"It took Line Messenger almost four
months to find its first two million
users ...
… but after stickers were launched, it
took only two days to find the next
million...
The company now makes over $270M a
year just from selling stickers."
THE FUTURE?
! -> "
ARCHITECTING
FOR FEATURES
▸ Frameworks all the things!
▸ Decouple sign in
▸ NSUserActivity FTW
!
OPEN VS PUBLIC
let rootURL = FileManager.default().
containerURLForSecurityApplicationGroupIdentifier("group.com.NatashaTheRobot.MyFavoriteGelato")
let defaults = UserDefaults(suiteName: "group.com.NatashaTheRobot.MyFavoriteGelato")
! + ⌚ + #
MULTIPLATFORM, SINGLE-SCHEME XCODE PROJECTS
by Max Howell on PromiseKit.org
FRAMEWORKS
▸ DRY
▸ App Groups
▸ iCloud Key-Value Storage
▸ Cross-Platform
SIGN IN
!
▸ Decouple Sign In
▸ Profit !
NSUSERACTIVITY FTW
// AppDelegate
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
{
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let webpageURL = userActivity.webpageURL
{
// separate webpageURL using NSURLComponents
// present the correct View Controller if valid
// otherwise, open link in Safari
}
return false
}
CREATING AN ACTIVITY
// GelatoDetailViewController
override func viewDidLoad() {
super.viewDidLoad()
// other config here
let activity = NSUserActivity(activityType: "com.natashatherobot.GelatoFinder.gelato")
// will show up as this in Spotlight Search Results
activity.title = gelato.name
// Other keywords to search by
activity.keywords = Set([gelato.name, "gelato"])
// should be handed off to another device?
activity.isEligibleForHandoff = false
// should be indexed in App History?
activity.isEligibleForSearch = true
// should be eligible for indexing for any user of this application?
activity.isEligibleForPublicIndexing = true
// Avoid deallocating before indexing,
// global variable declared in UIResponder class
userActivity = activity
// don't forget to activate!
userActivity!.becomeCurrent()
}
SAVE UNIQUE INFO
// GelatoDetailViewController
override func viewDidLoad() {
// create activity and other config here
activity.delegate = self
activity.needsSave = true
// assign to userActivity, etc
}
extension GelatoDetailViewController: NSUserActivityDelegate {
func userActivityWillSave(_ userActivity: NSUserActivity) {
// info needed to recreate activity!
userActivity.userInfo = ["index": gelatoIndex]
}
}
RESTORE ACTIVITY
// AppDelegate
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
{
let mainController = (window!.rootViewController! as! UINavigationController).viewControllers.first
mainController?.restoreUserActivityState(userActivity)
return true
}
RESTORE ACTIVITY
// GelatoListTableViewController
override func restoreUserActivityState(_ activity: NSUserActivity) {
if let index = activity.userInfo?["index"] as? Int {
searchedGelatoIdentifier = index
performSegue(withIdentifier: "showGelato", sender: self)
}
}
RESTORE ACTIVITY
// GelatoListTableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let gelatoDetailVC = segue.destination as? GelatoDetailViewController {
let index = tableView.indexPathForSelectedRow?.row ?? searchedGelatoIdentifier ?? 0
gelatoDetailVC.gelato = gelatoFlavors[index]
gelatoDetailVC.gelatoIndex = index
}
}
NSUSERACTIVITY
▸ Handoff
▸ Universal Links
▸ Search
▸ Location
▸ Contextual Reminders
▸ Contact Interactions
▸ Frameworks all the things!
▸ Decouple sign In
▸ NSUserActivity FTW
BUILD FEATURES,
NOT APPS
@NATASHATHEROBOT

Build Features Not Apps

  • 1.
  • 2.
    SWIFT ROBOT ▸ NatashaTheRobot.com ▸This Week in Swift ▸ Swift Jobs ▸ @NatashaTheNomad ▸ try! Swift
  • 4.
    Most smartphone usersdownload 0 apps per month
  • 5.
    An average apploses up to 95% of users within the first month
  • 7.
  • 10.
  • 11.
  • 14.
  • 17.
    "As speech recognitionaccuracy goes from say 95% to 99%, all of us in the room will from barely using it today to using it all the time. Most people underestimate the difference between 95% and 99% accuracy - 99% is a game changer" - Andrew NG, Chief Scientist at Baidu
  • 18.
    SIRI INTENTS: ▸ Audioor video calling ▸ Messaging ▸ Payments ▸ Searching photos ▸ Workouts ▸ Ride booking
  • 19.
  • 24.
    "It took LineMessenger almost four months to find its first two million users ...
  • 25.
    … but afterstickers were launched, it took only two days to find the next million...
  • 26.
    The company nowmakes over $270M a year just from selling stickers."
  • 27.
  • 29.
  • 30.
  • 31.
    ▸ Frameworks allthe things! ▸ Decouple sign in ▸ NSUserActivity FTW
  • 34.
  • 37.
  • 39.
    let rootURL =FileManager.default(). containerURLForSecurityApplicationGroupIdentifier("group.com.NatashaTheRobot.MyFavoriteGelato")
  • 40.
    let defaults =UserDefaults(suiteName: "group.com.NatashaTheRobot.MyFavoriteGelato")
  • 42.
  • 46.
    MULTIPLATFORM, SINGLE-SCHEME XCODEPROJECTS by Max Howell on PromiseKit.org
  • 47.
    FRAMEWORKS ▸ DRY ▸ AppGroups ▸ iCloud Key-Value Storage ▸ Cross-Platform
  • 48.
  • 52.
  • 57.
    ▸ Decouple SignIn ▸ Profit !
  • 58.
  • 59.
    // AppDelegate func application(_application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let webpageURL = userActivity.webpageURL { // separate webpageURL using NSURLComponents // present the correct View Controller if valid // otherwise, open link in Safari } return false }
  • 65.
    CREATING AN ACTIVITY //GelatoDetailViewController override func viewDidLoad() { super.viewDidLoad() // other config here let activity = NSUserActivity(activityType: "com.natashatherobot.GelatoFinder.gelato") // will show up as this in Spotlight Search Results activity.title = gelato.name // Other keywords to search by activity.keywords = Set([gelato.name, "gelato"]) // should be handed off to another device? activity.isEligibleForHandoff = false // should be indexed in App History? activity.isEligibleForSearch = true // should be eligible for indexing for any user of this application? activity.isEligibleForPublicIndexing = true // Avoid deallocating before indexing, // global variable declared in UIResponder class userActivity = activity // don't forget to activate! userActivity!.becomeCurrent() }
  • 66.
    SAVE UNIQUE INFO //GelatoDetailViewController override func viewDidLoad() { // create activity and other config here activity.delegate = self activity.needsSave = true // assign to userActivity, etc } extension GelatoDetailViewController: NSUserActivityDelegate { func userActivityWillSave(_ userActivity: NSUserActivity) { // info needed to recreate activity! userActivity.userInfo = ["index": gelatoIndex] } }
  • 67.
    RESTORE ACTIVITY // AppDelegate funcapplication(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { let mainController = (window!.rootViewController! as! UINavigationController).viewControllers.first mainController?.restoreUserActivityState(userActivity) return true }
  • 68.
    RESTORE ACTIVITY // GelatoListTableViewController overridefunc restoreUserActivityState(_ activity: NSUserActivity) { if let index = activity.userInfo?["index"] as? Int { searchedGelatoIdentifier = index performSegue(withIdentifier: "showGelato", sender: self) } }
  • 69.
    RESTORE ACTIVITY // GelatoListTableViewController overridefunc prepare(for segue: UIStoryboardSegue, sender: Any?) { if let gelatoDetailVC = segue.destination as? GelatoDetailViewController { let index = tableView.indexPathForSelectedRow?.row ?? searchedGelatoIdentifier ?? 0 gelatoDetailVC.gelato = gelatoFlavors[index] gelatoDetailVC.gelatoIndex = index } }
  • 71.
    NSUSERACTIVITY ▸ Handoff ▸ UniversalLinks ▸ Search ▸ Location ▸ Contextual Reminders ▸ Contact Interactions
  • 73.
    ▸ Frameworks allthe things! ▸ Decouple sign In ▸ NSUserActivity FTW
  • 74.