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
100
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
140
Bézier Curves
subdigital
1
5.2k
Buckets of Code
subdigital
0
1.9k
Swift on Linux
subdigital
1
830
tvOS Workshop
subdigital
1
140
Swift Solutions
subdigital
2
440
iOS 8 Networking
subdigital
4
900
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
一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜
atsumim
0
110
第3回関東Kaggler会_AtCoderはKaggleの役に立つ
chettub
3
890
AWS Organizations で実現する、 マルチ AWS アカウントのルートユーザー管理からの脱却
atpons
0
130
知られざるDMMデータエンジニアの生態 〜かつてツチノコと呼ばれし者〜
takaha4k
4
1.3k
最近のVS Codeで気になるニュース 2025/01
74th
1
250
Formの複雑さに立ち向かう
bmthd
1
720
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
670
昭和の職場からアジャイルの世界へ
kumagoro95
1
350
時計仕掛けのCompose
mkeeda
1
280
Software Architecture
hschwentner
6
2.1k
GAEログのコスト削減
mot_techtalk
0
110
GitHub Actions × RAGでコードレビューの検証の結果
sho_000
0
240
Featured
See All Featured
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
29
2.2k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
193
16k
Code Reviewing Like a Champion
maltzj
521
39k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
20
2.4k
Java REST API Framework Comparison - PWX 2021
mraible
28
8.4k
Building Your Own Lightsaber
phodgson
104
6.2k
Why You Should Never Use an ORM
jnunemaker
PRO
55
9.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
27
1.9k
How STYLIGHT went responsive
nonsquared
98
5.3k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
99
18k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.3k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
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!