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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Ben Scheirman
April 27, 2017
Programming
150
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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
More Decks by Ben Scheirman
See All by Ben Scheirman
A Promise for a Better Future
subdigital
0
200
Bézier Curves
subdigital
1
5.7k
Buckets of Code
subdigital
0
2k
Swift on Linux
subdigital
1
910
tvOS Workshop
subdigital
1
190
Swift Solutions
subdigital
2
500
iOS 8 Networking
subdigital
4
970
iOS 8 App Extensions
subdigital
1
400
Effective Networking with iOS 8 and Swift
subdigital
4
1.7k
Other Decks in Programming
See All in Programming
技術記事、 専門家としてのプログラマ、 言語化
mizchi
14
7.4k
AI駆動開発を妨げる技術的負債の解消アプローチ / ai-refactoring-approach
minodriven
17
8.9k
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.4k
AIエージェントで 変わるAndroid開発環境
takahirom
2
550
フィードバックで育てるAI開発
kotaminato
1
110
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
840
Vite+ Unified Toolchain for the Web
naokihaba
0
740
分散システム、なんですぐ死んでしまうん?耐障害性を高めたいあなたのためのレジリエンスパターン入門
mshibuya
7
5.8k
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
310
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
840
エンジニアにデザインハーネスを 〜デザインプロセスを規定するためのハーネス〜 / Design harness from an engineer's perspective
rkaga
2
1.4k
【やさしく解説 設計編 #1】「ドメイン駆動」と「実装駆動」ってなに? 〜設計の考え方を、たとえ話で学ぼう〜
panda728
PRO
1
110
Featured
See All Featured
Speed Design
sergeychernyshev
33
1.9k
BBQ
matthewcrist
89
10k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Designing for Timeless Needs
cassininazir
1
320
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
760
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
220
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
We Are The Robots
honzajavorek
0
280
My Coaching Mixtape
mlcsv
0
170
A designer walks into a library…
pauljervisheath
211
24k
Producing Creativity
orderedlist
PRO
348
40k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.4k
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!