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
96
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
130
Bézier Curves
subdigital
1
5k
Buckets of Code
subdigital
0
1.9k
Swift on Linux
subdigital
1
800
tvOS Workshop
subdigital
1
130
Swift Solutions
subdigital
2
440
iOS 8 Networking
subdigital
4
900
iOS 8 App Extensions
subdigital
1
360
Effective Networking with iOS 8 and Swift
subdigital
4
1.6k
Other Decks in Programming
See All in Programming
AWS Lambda Web Adapterを活用する新しいサーバーレスの実装パターン
tmokmss
6
5.3k
Remix × Cloudflare Pages × Sentry 奮闘記 / remix-pages-sentry
nkzn
1
360
The Efficiency Paradox and How to Save Yourself and the World
hollycummins
0
120
Micro Frontends for Java Microservices - dev2next 2024
mraible
PRO
0
170
Perl 5 OOP機構30年史 - Perl 5's OOP Mechanism over the past 30 years
moznion
1
770
WEBアプリケーションにおけるAWS Lambdaを用いた大規模な非同期処理の実践
delhi09
PRO
7
3.5k
Cohesion in Modeling and Design
mploed
3
170
GitHub Copilot Workspace で我々のアプリ開発がどう変わるのか?
shuyakinjo
0
830
いまあるチームにフィットさせる Serverless そして Platform Engineeringへの挑戦 / Serverless Fits the Team You Have and Platform Engineering
seike460
PRO
2
1.2k
Compose Multiplatform과 Ktor로 플랫폼의 경계를 넘어보자
kwakeuijin
0
200
CSC305 Lecture 01
javiergs
PRO
1
140
GraphQLとGigaViewer for Apps
numeroanddev
4
870
Featured
See All Featured
What's in a price? How to price your products and services
michaelherold
243
11k
Fashionably flexible responsive web design (full day workshop)
malarkey
402
65k
Art, The Web, and Tiny UX
lynnandtonic
294
20k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
Writing Fast Ruby
sferik
623
60k
How to Ace a Technical Interview
jacobian
274
23k
From Idea to $5000 a Month in 5 Months
shpigford
380
46k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
41
6.5k
Design by the Numbers
sachag
277
19k
Making Projects Easy
brettharned
114
5.8k
Put a Button on it: Removing Barriers to Going Fast.
kastner
58
3.4k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
354
29k
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!