A talk I gave in Barcelona. About 1/3 political, 1/3 technical, and 1/3 tactical.
Building
View Slide
Ben Scheirmanbenscheirman.com@subdigital
ficklebits.com
260+ screencasts on iOS developmentnsscreencast.com
What do we do?
What can we do?
What can I do?
United States CongressHouse ofRepresentativesSenate435 Members 100 Members
5calls.org
Launch ASAP
Wanted it to bea GOOD iOS app
github.com/5calls/ios
Let people help you!
Built on NSOperationStoryboardsUITableViewCocoaPodsVibrancy / BlurDynamic TypeCustom UIViewControllerContainmentLocal Push NotificationsFastlanePantrySwift 3Universal app (split view)Fabric / CrashlyticsR.swiftBuddybuild
NSOperationsclass 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}}
NSOperationsclass FetchIssuesOperation : BaseOperation {let location: UserLocation?init(location: UserLocation?) {self.location = location} ...}
NSOperationsoverride func execute() {let task = session.dataTask(with: url) { (data, response, error) inif let e = error {print("Error fetching issues: \(e.localizedDescription)”)} else {self.handleResponse(data: data,response: response)}self.finish()}task.resume()}
NSOperationsself.issuesList = IssuesList(dictionary: json)
NSOperationsfunc fetchIssues(completion: @escaping (IssuesLoadResult) -> Void) {let operation = FetchIssuesOperation(location: userLocation)operation.completionBlock = { [weak self, weak operation] inif let issuesList = operation?.issuesList {self?.issuesList = issuesListDispatchQueue.main.async {completion(.success)}} else { // … } }OperationQueue.main.addOperation(operation)}
NSOperationsFetchIssuesOperationFetchStatsOperationReportOutcomeOperation
NSOperationsComposableDependenciesCancellableControl ConcurrencyQuality of Service
NSOperationshttps://developer.apple.com/videos/play/wwdc2015/226/WWDC Session 226 - Advanced NSOperationshttp://nsscreencast.com/NSScreencast Episodes 175-177, 180
Dynamic TypeDynamic TypeDynamic Type
Using a custom fontmade this more difficult
Getting Dynamic Type to workwith UITableViewCellsDefine heightwith constraints
Getting Dynamic Type to workwith UITableViewCells
Respond to Dynamic TypeChangesUIContentSizeCategoryDidChangeNotificationadjustsFontsForContentSizeCategoryiOS 10.0
Getting dynamic fonts in codeheadline.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleHeadline)subhead.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleSubheadline)body.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleBody)
https://github.com/nickoneill/Pantryif let available: Bool = Pantry.unpack("promptAvailable") {completion(available: available)} else {anExpensiveOperationToDetermineAvailability({ (available) -> () inPantry.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, etcprint(autopersist) // Hello!
SIMPLE MODEL OBJECT…
CONFORM TO STORABLE
USAGE IS REALLY SIMPLE:
CAREFUL YOU DON’T STOREUSER DATA IN CACHES
R.swift
R.swiftlet icon = UIImage(named: "settings-icon")let icon = R.image.settingsIcon()becomes…
R.swiftlet viewController = CustomViewController(nibName: "CustomView",bundle: nil)let viewController = CustomViewController(nib: R.nib.customView)becomes…
R.swiftlet string = String(format:NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.current, "Arthur Dent")let string = R.string.localizable.welcomeWithName("Arthur Dent")becomes…
# Fastfile desc "Runs all the tests"lane :test doscan(workspace: workspace, scheme: scheme)end
$ fastlane test
desc "Increments build number"lane :increment_build doincrement_build_number(xcodeproj: xcodeproj)end
desc "Submit a new Beta Build to Apple TestFlight"lane :beta doensure_git_status_cleanincrement_buildcommit_version_bumpbadge(shield: "Version-#{app_version}-red", shield_no_resize: true)gym(workspace: workspace, scheme: scheme)changelog = prompt_for_release_notespilot(changelog: changelog)git_commit(path: 'fastlane/changelog.txt', message: 'Updated changelog.txt')reset_git_repo(files: app_icon_files)add_git_tagpush_git_tagsend
def prompt_for_release_notes`open changelog.txt -W`File.read('changelog.txt')end
Automatic Screenshots# Snapfiledevices([ "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 ALWAYSchoosethe same item, despitethe server changing?
Stubs!{ “issues”: [ … ] }issues.jsonINTERNETGET /issuesUITESTING?YESNO
SeededURLSessionSeededDataTaskdataTask(with:completion:)
HOSTAPPTESTSENV
TESTS
The Stats
Week 16,300 downloads
Month 176,916 downloads
All Time (4/21/2017)105,000 downloads
44.2k Monthly Active Users2.4k Daily Active Users
Over 1 million calls to congress
Final Thoughts
!
Track Useful Analytics*but don’t be scummy
REDMAP
Software Developmentis an incredibly valuable skill.
Software Developmentis an incredibly valuable skill.Use your powers for good, not evil.
Ben Scheirmanbenscheirman.com@subdigitalnsscreencast.com¡Gracias!