Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building 5 Calls for iOS

Building 5 Calls for iOS

A talk I gave in Barcelona. About 1/3 political, 1/3 technical, and 1/3 tactical.

Ben Scheirman

April 27, 2017
Tweet

More Decks by Ben Scheirman

Other Decks in Programming

Transcript

  1. Building

    View Slide

  2. Ben Scheirman
    benscheirman.com
    @subdigital

    View Slide

  3. ficklebits.com

    View Slide

  4. 260+ screencasts on iOS development
    nsscreencast.com

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. What do we do?

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. What do we do?

    View Slide

  13. What can we do?

    View Slide

  14. What can I do?

    View Slide

  15. View Slide

  16. View Slide

  17. United States Congress
    House of
    Representatives
    Senate
    435 Members 100 Members

    View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. 5calls.org

    View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. Launch ASAP

    View Slide

  26. View Slide

  27. Wanted it to be
    a GOOD iOS app

    View Slide

  28. View Slide

  29. github.com/5calls/ios

    View Slide

  30. View Slide

  31. Let people help you!

    View Slide

  32. View Slide

  33. Built on NSOperation
    Storyboards
    UITableView
    CocoaPods
    Vibrancy / Blur
    Dynamic Type
    Custom UIViewController
    Containment
    Local Push Notifications
    Fastlane
    Pantry
    Swift 3
    Universal app

    (split view)
    Fabric / Crashlytics
    R.swift
    Buddybuild

    View Slide

  34. NSOperations
    class BaseOperation : Operation {
    override var isAsynchronous: Bool {
    return true
    }

    private var _executing = false {
    willSet {
    willChangeValue(forKey: "isExecuting")
    }
    didSet {
    didChangeValue(forKey: "isExecuting")
    }
    }
    override var isExecuting: Bool {
    return _executing
    }
    private var _finished = false {
    willSet {
    willChangeValue(forKey: "isFinished")
    }
    didSet {
    didChangeValue(forKey: "isFinished")
    }
    }
    override var isFinished: Bool {
    return _finished
    }
    class BaseOperation : Operation {
    func execute() {
    fatalError("You must override this")
    }
    func finish() {
    _executing = false
    _finished = true
    }
    }

    View Slide

  35. NSOperations
    class FetchIssuesOperation : BaseOperation {
    let location: UserLocation?
    init(location: UserLocation?) {
    self.location = location
    }


    ...
    }

    View Slide

  36. NSOperations
    override func execute() {
    let task = session.dataTask(with: url) { 

    (data, response, error) in
    if let e = error {
    print("Error fetching issues: \
    (e.localizedDescription)”)
    } else {
    self.handleResponse(data: data,
    response: response)
    }
    self.finish()
    }
    task.resume()
    }

    View Slide

  37. NSOperations
    self.issuesList = IssuesList(dictionary: json)

    View Slide

  38. NSOperations
    func fetchIssues(completion: @escaping (IssuesLoadResult) -> Void) {
    let operation = FetchIssuesOperation(location: userLocation)
    operation.completionBlock = { [weak self, weak operation] in
    if let issuesList = operation?.issuesList {
    self?.issuesList = issuesList
    DispatchQueue.main.async {
    completion(.success)
    }
    } else {

    // …

    }


    }
    OperationQueue.main.addOperation(operation)
    }

    View Slide

  39. NSOperations
    FetchIssuesOperation
    FetchStatsOperation
    ReportOutcomeOperation

    View Slide

  40. NSOperations
    Composable
    Dependencies
    Cancellable
    Control Concurrency
    Quality of Service

    View Slide

  41. NSOperations
    https://developer.apple.com/videos/play/wwdc2015/226/
    WWDC Session 226 - Advanced NSOperations
    http://nsscreencast.com/
    NSScreencast Episodes 175-177, 180

    View Slide

  42. Dynamic Type
    Dynamic Type
    Dynamic Type

    View Slide

  43. Using a custom font
    made this more difficult

    View Slide

  44. View Slide

  45. View Slide

  46. Getting Dynamic Type to work
    with UITableViewCells
    Define height
    with constraints

    View Slide

  47. Getting Dynamic Type to work
    with UITableViewCells

    View Slide

  48. Respond to Dynamic Type
    Changes
    UIContentSizeCategoryDidChangeNotification
    adjustsFontsForContentSizeCategory
    iOS 10.0

    View Slide

  49. View Slide

  50. Getting dynamic fonts in code
    headline.font = UIFont.preferredFont(
    forTextStyle: UIFontTextStyleHeadline)
    subhead.font = UIFont.preferredFont(
    forTextStyle: UIFontTextStyleSubheadline)
    body.font = UIFont.preferredFont(
    forTextStyle: UIFontTextStyleBody)

    View Slide

  51. https://github.com/nickoneill/Pantry
    if let available: Bool = Pantry.unpack("promptAvailable") {
    completion(available: available)
    } else {
    anExpensiveOperationToDetermineAvailability({ (available) -> () in
    Pantry.pack(available, key: "promptAvailable",
    expires: .Seconds(60 * 10))
    completion(available: available)
    })
    }

    View Slide

  52. var autopersist: String? {
    set {
    if let newValue = newValue {
    Pantry.pack(newValue, key: "autopersist")
    }
    }
    get {
    return Pantry.unpack("autopersist")
    }
    }
    ...later...
    autopersist = "Hello!"
    // restart app, reboot phone, etc
    print(autopersist) // Hello!

    View Slide

  53. SIMPLE MODEL OBJECT…

    View Slide

  54. CONFORM TO STORABLE

    View Slide

  55. USAGE IS REALLY SIMPLE:

    View Slide

  56. CAREFUL YOU DON’T STORE
    USER DATA IN CACHES

    View Slide

  57. R.swift

    View Slide

  58. R.swift
    let icon = UIImage(named: "settings-icon")
    let icon = R.image.settingsIcon()
    becomes…

    View Slide

  59. R.swift
    let viewController = CustomViewController(nibName: "CustomView",
    bundle: nil)
    let viewController = CustomViewController(nib: R.nib.customView)
    becomes…

    View Slide

  60. R.swift
    let string = String(format:
    NSLocalizedString("welcome.withName", comment: ""), 

    locale: NSLocale.current, "Arthur Dent")
    let string = R.string.localizable.welcomeWithName("Arthur Dent")
    becomes…

    View Slide

  61. R.swift

    View Slide

  62. View Slide

  63. View Slide

  64. # Fastfile

    desc "Runs all the tests"
    lane :test do
    scan(workspace: workspace, scheme: scheme)
    end

    View Slide

  65. $ fastlane test

    View Slide

  66. desc "Increments build number"
    lane :increment_build do
    increment_build_number(xcodeproj: xcodeproj)
    end

    View Slide

  67. View Slide

  68. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  69. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  70. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  71. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  72. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  73. def prompt_for_release_notes
    `open changelog.txt -W`
    File.read('changelog.txt')
    end

    View Slide

  74. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  75. desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
    ensure_git_status_clean
    increment_build
    commit_version_bump
    badge(shield: "Version-#{app_version}-red", shield_no_resize: true)
    gym(workspace: workspace, scheme: scheme)
    changelog = prompt_for_release_notes
    pilot(changelog: changelog)
    git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')
    reset_git_repo(files: app_icon_files)
    add_git_tag
    push_git_tags
    end

    View Slide

  76. Automatic Screenshots
    # Snapfile
    devices([

    "iPhone 7"

    "iPhone 7 Plus",

    "iPhone SE",

    "iPad Pro (12.9 inch)"

    ])
    languages(["en-US"])


    workspace "./FiveCalls/FiveCalls.xcworkspace"

    scheme "FiveCallsUITests"
    output_directory "./screenshots"

    clear_previous_screenshots true

    launch_arguments(["-hasShownWelcomeScreen false"])

    View Slide

  77. Automatic Screenshots

    View Slide

  78. Deterministic Data?
    How do we ALWAYS
    choose
    the same item, despite
    the server changing?

    View Slide

  79. Stubs!
    {

    “issues”: [

    … ] 

    }
    issues.json
    INTERNET
    GET /issues
    UI
    TESTING?
    YES
    NO

    View Slide

  80. View Slide

  81. View Slide

  82. SeededURLSession
    SeededDataTask
    dataTask(with:completion:)

    View Slide

  83. View Slide

  84. View Slide

  85. HOST
    APP
    TESTS
    ENV

    View Slide

  86. TESTS

    View Slide

  87. View Slide

  88. View Slide

  89. View Slide

  90. The Stats

    View Slide

  91. Week 1
    6,300 downloads

    View Slide

  92. View Slide

  93. View Slide

  94. Month 1
    76,916 downloads

    View Slide

  95. View Slide

  96. All Time (4/21/2017)
    105,000 downloads

    View Slide

  97. 44.2k Monthly Active Users
    2.4k Daily Active Users

    View Slide

  98. View Slide

  99. Over 1 million calls to congress

    View Slide

  100. Final Thoughts

    View Slide

  101. !

    View Slide

  102. Track Useful Analytics
    *but don’t be scummy

    View Slide

  103. View Slide

  104. View Slide

  105. View Slide

  106. REDMAP

    View Slide

  107. View Slide

  108. Software Development
    is an incredibly valuable skill.

    View Slide

  109. Software Development
    is an incredibly valuable skill.
    Use your powers for good, not evil.

    View Slide

  110. Ben Scheirman
    benscheirman.com
    @subdigital
    nsscreencast.com
    ¡Gracias!

    View Slide