Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Building 5 Calls for iOS
Search
Ben Scheirman
April 27, 2017
Programming
0
110
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
Share
More Decks by Ben Scheirman
See All by Ben Scheirman
A Promise for a Better Future
subdigital
0
150
Bézier Curves
subdigital
1
5.3k
Buckets of Code
subdigital
0
1.9k
Swift on Linux
subdigital
1
850
tvOS Workshop
subdigital
1
150
Swift Solutions
subdigital
2
450
iOS 8 Networking
subdigital
4
910
iOS 8 App Extensions
subdigital
1
370
Effective Networking with iOS 8 and Swift
subdigital
4
1.7k
Other Decks in Programming
See All in Programming
生成AIで日々のエラー調査を進めたい
yuyaabo
0
390
Parallel::Pipesの紹介
skaji
2
900
TypeScript を活かしてデザインシステム MCP を作る / #tskaigi_after_night
izumin5210
5
520
OpenNext + Hono on Cloudflare でイマドキWeb開発スタックを実現する
rokuosan
0
110
少数精鋭エンジニアがフルスタック力を磨く理由 -そしてAI時代へ-
rebase_engineering
0
150
FastMCPでMCPサーバー/クライアントを構築してみる
ttnyt8701
2
130
「ElixirでIoT!!」のこれまでとこれから
takasehideki
0
310
機械学習って何? 5分で解説頑張ってみる
kuroneko2828
0
190
Effect の双対、Coeffect
yukikurage
4
1.2k
イベントストーミングから始めるドメイン駆動設計
jgeem
4
770
Haskell でアルゴリズムを抽象化する / 関数型言語で競技プログラミング
naoya
15
3.2k
セキュリティマネジャー廃止とクラウドネイティブ型サンドボックス活用
kazumura
1
150
Featured
See All Featured
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.5k
Build The Right Thing And Hit Your Dates
maggiecrowley
35
2.7k
Reflections from 52 weeks, 52 projects
jeffersonlam
349
20k
Bash Introduction
62gerente
614
210k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.7k
GitHub's CSS Performance
jonrohan
1031
460k
How to Think Like a Performance Engineer
csswizardry
24
1.7k
Building Adaptive Systems
keathley
42
2.6k
Unsuck your backbone
ammeep
671
58k
Being A Developer After 40
akosma
90
590k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
870
VelocityConf: Rendering Performance Case Studies
addyosmani
329
24k
Transcript
Building
Ben Scheirman benscheirman.com @subdigital
ficklebits.com
260+ screencasts on iOS development nsscreencast.com
None
None
None
What do we do?
None
None
None
What do we do?
What can we do?
What can I do?
None
None
United States Congress House of Representatives Senate 435 Members 100
Members
None
None
None
5calls.org
None
None
None
Launch ASAP
None
Wanted it to be a GOOD iOS app
None
github.com/5calls/ios
None
Let people help you!
None
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
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 } }
NSOperations class FetchIssuesOperation : BaseOperation { let location: UserLocation? init(location:
UserLocation?) { self.location = location } ... }
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() }
NSOperations self.issuesList = IssuesList(dictionary: json)
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) }
NSOperations FetchIssuesOperation FetchStatsOperation ReportOutcomeOperation
NSOperations Composable Dependencies Cancellable Control Concurrency Quality of Service
NSOperations https://developer.apple.com/videos/play/wwdc2015/226/ WWDC Session 226 - Advanced NSOperations http://nsscreencast.com/ NSScreencast
Episodes 175-177, 180
Dynamic Type Dynamic Type Dynamic Type
Using a custom font made this more difficult
None
None
Getting Dynamic Type to work with UITableViewCells Define height with
constraints
Getting Dynamic Type to work with UITableViewCells
Respond to Dynamic Type Changes UIContentSizeCategoryDidChangeNotification adjustsFontsForContentSizeCategory iOS 10.0
None
Getting dynamic fonts in code headline.font = UIFont.preferredFont( forTextStyle: UIFontTextStyleHeadline)
subhead.font = UIFont.preferredFont( forTextStyle: UIFontTextStyleSubheadline) body.font = UIFont.preferredFont( forTextStyle: UIFontTextStyleBody)
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) }) }
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!
SIMPLE MODEL OBJECT…
CONFORM TO STORABLE
USAGE IS REALLY SIMPLE:
CAREFUL YOU DON’T STORE USER DATA IN CACHES
R.swift
R.swift let icon = UIImage(named: "settings-icon") let icon = R.image.settingsIcon()
becomes…
R.swift let viewController = CustomViewController(nibName: "CustomView", bundle: nil) let viewController
= CustomViewController(nib: R.nib.customView) becomes…
R.swift let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale:
NSLocale.current, "Arthur Dent") let string = R.string.localizable.welcomeWithName("Arthur Dent") becomes…
R.swift
None
None
# Fastfile desc "Runs all the tests" lane :test
do scan(workspace: workspace, scheme: scheme) end
$ fastlane test
desc "Increments build number" lane :increment_build do increment_build_number(xcodeproj: xcodeproj) end
None
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
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
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
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
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
def prompt_for_release_notes `open changelog.txt -W` File.read('changelog.txt') end
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
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
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"])
Automatic Screenshots
Deterministic Data? How do we ALWAYS choose the same item,
despite the server changing?
Stubs! { “issues”: [ … ] } issues.json INTERNET
GET /issues UI TESTING? YES NO
None
None
SeededURLSession SeededDataTask dataTask(with:completion:)
None
None
HOST APP TESTS ENV
TESTS
None
None
None
The Stats
Week 1 6,300 downloads
None
None
Month 1 76,916 downloads
None
All Time (4/21/2017) 105,000 downloads
44.2k Monthly Active Users 2.4k Daily Active Users
None
Over 1 million calls to congress
Final Thoughts
!
Track Useful Analytics *but don’t be scummy
None
None
None
REDMAP
None
Software Development is an incredibly valuable skill.
Software Development is an incredibly valuable skill. Use your powers
for good, not evil.
Ben Scheirman benscheirman.com @subdigital nsscreencast.com ¡Gracias!