├── .gitattributes ├── .gitignore ├── 9781484253076.jpg ├── Contributing.md ├── DevelopingAccessibleApps ├── .swiftlint.yml ├── DevelopingAccessibleApps.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── DevelopingAccessibleApps.xcscheme ├── DevelopingAccessibleApps │ ├── AccessibilityExampleListViewController.swift │ ├── AccessibilityExampleListViewController.xib │ ├── AccessibilityExampleTableViewCell.swift │ ├── AccessibilityExampleTableViewCell.xib │ ├── AccessibilityExamplesDataSource.swift │ ├── AppDelegate.swift │ ├── Assets │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── icon_120.png │ │ │ │ ├── icon_152.png │ │ │ │ ├── icon_167.png │ │ │ │ ├── icon_180.png │ │ │ │ ├── icon_40.png │ │ │ │ ├── icon_60.png │ │ │ │ └── icon_76.png │ │ │ ├── Contents.json │ │ │ └── Photos │ │ │ │ ├── Contents.json │ │ │ │ └── Yosemite.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── yosemite.jpg │ │ ├── Localizable.strings │ │ ├── dynamic-type-example.html │ │ └── styles.css │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── ExamplesViewControllers │ │ ├── AccessibilityElementsViewController.swift │ │ ├── AccessibilityElementsViewController.xib │ │ ├── AccessibilityLabelsViewController.swift │ │ ├── AccessibilityLabelsViewController.xib │ │ ├── AccessibilityNotificationsViewController.swift │ │ ├── AccessibilityNotificationsViewController.xib │ │ ├── AccessibilityValuesViewController.swift │ │ ├── AccessibilityValuesViewController.xib │ │ ├── CustomActionsViewController.swift │ │ ├── CustomActionsViewController.xib │ │ ├── DynamicTypesAdaptiveUIViewController.swift │ │ ├── DynamicTypesAdaptiveUIViewController.xib │ │ ├── DynamicTypesAlternativeUIViewController.swift │ │ ├── DynamicTypesAlternativeUIViewController.xib │ │ ├── LargeContentViewerViewController.swift │ │ ├── LargeContentViewerViewController.xib │ │ ├── PerformEscapeViewController.swift │ │ ├── PerformEscapeViewController.xib │ │ ├── SmartInvertedColorsViewController.swift │ │ ├── SmartInvertedColorsViewController.xib │ │ ├── SwitchTableViewController.swift │ │ ├── SwitchTableViewController.xib │ │ ├── View │ │ │ ├── AlternativeImageTableViewCell.swift │ │ │ ├── AlternativeImageTableViewCell.xib │ │ │ ├── CustomModalView.swift │ │ │ ├── CustomModalView.xib │ │ │ ├── CustomToastView.swift │ │ │ ├── CustomToastView.xib │ │ │ ├── ImageCodeConstraintsTableViewCell.swift │ │ │ ├── ImageTableViewCell.swift │ │ │ ├── ImageTableViewCell.xib │ │ │ ├── RaterView.swift │ │ │ ├── SettingsSwitchTableViewCell.swift │ │ │ ├── SettingsSwitchTableViewCell.xib │ │ │ ├── SimpleSwitchTableViewCell.swift │ │ │ ├── SimpleSwitchTableViewCell.xib │ │ │ ├── SocialNetworkTableViewCell.swift │ │ │ ├── SocialNetworkTableViewCell.xib │ │ │ └── UserStatsView.swift │ │ ├── WebViewController.swift │ │ └── WebViewController.xib │ ├── Info.plist │ └── Util │ │ └── Extensions │ │ ├── NibLoadable.swift │ │ └── String+LoremIpsum.swift ├── DevelopingAccessibleAppsTests │ ├── .swiftlint.yml │ ├── DevelopingAccessibleAppsTests.swift │ └── Info.plist └── DevelopingAccessibleAppsUITests │ ├── .swiftlint.yml │ ├── DevelopingAccessibleAppsUITests.swift │ └── Info.plist ├── LICENSE.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /9781484253076.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/9781484253076.jpg -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /DevelopingAccessibleApps/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - cyclomatic_complexity 3 | - for_where 4 | - force_cast 5 | - force_unwrapping 6 | - identifier_name 7 | - todo 8 | opt_in_rules: 9 | - anyobject_protocol 10 | - array_init 11 | # - attributes 12 | - closure_body_length 13 | - closure_end_indentation 14 | - closure_spacing 15 | - collection_alignment 16 | # - conditional_returns_on_newline 17 | - contains_over_first_not_nil 18 | - convenience_type 19 | - discouraged_object_literal 20 | - discouraged_optional_boolean 21 | - discouraged_optional_collection 22 | - empty_count 23 | - empty_string 24 | # - empty_xctest_method 25 | # - explicit_acl 26 | # - explicit_enum_raw_value 27 | - explicit_init 28 | - explicit_self 29 | # - explicit_top_level_acl 30 | # - explicit_type_interface 31 | # - extension_access_modifier 32 | - fallthrough 33 | - fatal_error_message 34 | # - file_header 35 | - file_name 36 | # - file_types_order 37 | - first_where 38 | - force_unwrapping 39 | - function_default_parameter_at_end 40 | - identical_operands 41 | - implicit_return 42 | - implicitly_unwrapped_optional 43 | - joined_default_parameter 44 | - last_where 45 | - legacy_multiple 46 | - legacy_random 47 | - let_var_whitespace 48 | - literal_expression_end_indentation 49 | - lower_acl_than_parent 50 | - missing_docs 51 | - modifier_order 52 | - multiline_arguments 53 | - multiline_arguments_brackets 54 | - multiline_function_chains 55 | - multiline_literal_brackets 56 | - multiline_parameters 57 | - multiline_parameters_brackets 58 | - nimble_operator 59 | - no_extension_access_modifier 60 | # - no_grouping_extension 61 | - nslocalizedstring_key 62 | - nslocalizedstring_require_bundle 63 | - number_separator 64 | # - object_literal 65 | - operator_usage_whitespace 66 | - overridden_super_call 67 | - override_in_extension 68 | - pattern_matching_keywords 69 | - prefixed_toplevel_constant 70 | - private_action 71 | # - private_outlet 72 | # - prohibited_interface_builder 73 | - prohibited_super_call 74 | - quick_discouraged_call 75 | - quick_discouraged_focused_test 76 | - quick_discouraged_pending_test 77 | - reduce_into 78 | - redundant_nil_coalescing 79 | - redundant_type_annotation 80 | # - required_deinit 81 | - required_enum_case 82 | - single_test_class 83 | - sorted_first_last 84 | - sorted_imports 85 | - static_operator 86 | - strict_fileprivate 87 | # - strong_iboutlet 88 | # - switch_case_on_newline 89 | - toggle_bool 90 | - trailing_closure 91 | # - type_contents_order 92 | - unavailable_function 93 | - unneeded_parentheses_in_closure_argument 94 | - unowned_variable_capture 95 | - untyped_error_in_catch 96 | - unused_declaration 97 | - unused_import 98 | - vertical_parameter_alignment_on_call 99 | - vertical_whitespace_between_cases 100 | - vertical_whitespace_closing_braces 101 | - vertical_whitespace_opening_braces 102 | - xct_specific_matcher 103 | - yoda_condition 104 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps.xcodeproj/xcshareddata/xcschemes/DevelopingAccessibleApps.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 52 | 58 | 59 | 60 | 61 | 62 | 72 | 74 | 80 | 81 | 82 | 83 | 89 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AccessibilityExampleListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityExampleListViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol AccessibilityExampleListViewControllerDelegate: AnyObject { 12 | func selectedAccessibilityExample(_ accessibilityExample: AccessibilityExamplesDataSource) 13 | } 14 | 15 | class AccessibilityExampleListViewController: UIViewController { 16 | @IBOutlet weak var tableView: UITableView! 17 | 18 | weak var delegate: AccessibilityExampleListViewControllerDelegate? 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | title = NSLocalizedString("accessibilityExamples", bundle: .main, comment: "") 24 | 25 | tableView.delegate = self 26 | tableView.dataSource = self 27 | tableView.estimatedRowHeight = view.frame.size.height / 10.0 28 | tableView.rowHeight = UITableView.automaticDimension 29 | tableView.register( 30 | UINib(nibName: AccessibilityExampleTableViewCell.identifier, bundle: nil), 31 | forCellReuseIdentifier: AccessibilityExampleTableViewCell.identifier 32 | ) 33 | } 34 | } 35 | 36 | extension AccessibilityExampleListViewController: UITableViewDelegate { 37 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 38 | tableView.deselectRow(at: indexPath, animated: true) 39 | guard let selectedAccessibilityExample = AccessibilityExamplesDataSource(rawValue: indexPath.row) else { 40 | return 41 | } 42 | 43 | delegate?.selectedAccessibilityExample(selectedAccessibilityExample) 44 | } 45 | } 46 | 47 | extension AccessibilityExampleListViewController: UITableViewDataSource { 48 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 49 | AccessibilityExamplesDataSource.allCases.count 50 | } 51 | 52 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 53 | guard let cell = tableView.dequeueReusableCell( 54 | withIdentifier: AccessibilityExampleTableViewCell.identifier, for: indexPath 55 | ) as? AccessibilityExampleTableViewCell else { 56 | fatalError("Couldn't load a valid cell") 57 | } 58 | cell.exampleTitleLabel?.text = AccessibilityExamplesDataSource(rawValue: indexPath.row)?.title() 59 | return cell 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AccessibilityExampleListViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AccessibilityExampleTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityExampleTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AccessibilityExampleTableViewCell: UITableViewCell { 12 | static let identifier = String(describing: AccessibilityExampleTableViewCell.self) 13 | 14 | @IBOutlet weak var exampleTitleLabel: UILabel! 15 | } 16 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AccessibilityExampleTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AccessibilityExamplesDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityExamplesDataSource.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum AccessibilityExamplesDataSource: Int, CaseIterable { 12 | case accessibilityLabels 13 | case accessibilityValues 14 | case accessibilityElements 15 | case customActions 16 | case performEscape 17 | case notifications 18 | case dynamicTypeAdaptiveUI 19 | case dynamicTypeAlternativeUI 20 | case smartInvertColours 21 | case largeContentViewer 22 | case switchVoiceOver 23 | case web 24 | 25 | func viewController() -> UIViewController { 26 | let viewController: UIViewController 27 | 28 | switch self { 29 | case .accessibilityLabels: viewController = AccessibilityLabelsViewController() 30 | case .accessibilityValues: viewController = AccessibilityValuesViewController() 31 | case .accessibilityElements: viewController = AccessibilityElementsViewController() 32 | case .customActions: viewController = CustomActionsViewController() 33 | case .performEscape: viewController = PerformEscapeViewController() 34 | case .notifications: viewController = AccessibilityNotificationsViewController() 35 | case .dynamicTypeAdaptiveUI: viewController = DynamicTypesAdaptiveUIViewController() 36 | case .dynamicTypeAlternativeUI: viewController = DynamicTypesAlternativeUIViewController() 37 | case .smartInvertColours: viewController = SmartInvertedColorsViewController() 38 | case .largeContentViewer: viewController = LargeContentViewerViewController() 39 | case .switchVoiceOver: viewController = SwitchTableViewController() 40 | case .web: viewController = WebViewController() 41 | } 42 | 43 | viewController.title = title() 44 | 45 | return viewController 46 | } 47 | 48 | func title() -> String { 49 | switch self { 50 | case .accessibilityLabels: return NSLocalizedString("accessibilityLabels", bundle: .main, comment: "") 51 | case .accessibilityValues: return NSLocalizedString("adjustTraitValues", bundle: .main, comment: "") 52 | case .accessibilityElements: return NSLocalizedString("accessibilityElements", bundle: .main, comment: "") 53 | case .customActions: return NSLocalizedString("customActions", bundle: .main, comment: "") 54 | case .performEscape: return NSLocalizedString("performZEscapeGesture", bundle: .main, comment: "") 55 | case .notifications: return NSLocalizedString("accessibilityNotifications", bundle: .main, comment: "") 56 | case .dynamicTypeAdaptiveUI: return NSLocalizedString("dynamicTypeAdaptiveUI", bundle: .main, comment: "") 57 | case .dynamicTypeAlternativeUI: return NSLocalizedString("dynamicTypeAlternativeUI", bundle: .main, comment: "") 58 | case .smartInvertColours: return NSLocalizedString("smartInvertColors", bundle: .main, comment: "") 59 | case .largeContentViewer: return NSLocalizedString("largeContentViewer", bundle: .main, comment: "") 60 | case .switchVoiceOver: return NSLocalizedString("uiSwitch", bundle: .main, comment: "") 61 | case .web: return NSLocalizedString("dynamicTypeInWebViews", bundle: .main, comment: "") 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | private let accessibilityListViewController = AccessibilityExampleListViewController() 16 | 17 | lazy var navigationController = UINavigationController(rootViewController: accessibilityListViewController) 18 | 19 | func application( 20 | _ application: UIApplication, 21 | // (since this is Apple-defined, ignore the lint warning...) 22 | // swiftlint:disable:next discouraged_optional_collection 23 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 24 | ) -> Bool { 25 | accessibilityListViewController.delegate = self 26 | 27 | navigationController.navigationBar.prefersLargeTitles = true 28 | 29 | window = UIWindow(frame: UIScreen.main.bounds) 30 | window?.rootViewController = navigationController 31 | window?.makeKeyAndVisible() 32 | 33 | return true 34 | } 35 | } 36 | 37 | extension AppDelegate: AccessibilityExampleListViewControllerDelegate { 38 | func selectedAccessibilityExample(_ accessibilityExample: AccessibilityExamplesDataSource) { 39 | navigationController.pushViewController(accessibilityExample.viewController(), animated: true) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon_40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon_60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "size" : "29x29", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "idiom" : "iphone", 22 | "size" : "29x29", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "idiom" : "iphone", 27 | "size" : "40x40", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "idiom" : "iphone", 32 | "size" : "40x40", 33 | "scale" : "3x" 34 | }, 35 | { 36 | "size" : "60x60", 37 | "idiom" : "iphone", 38 | "filename" : "icon_120.png", 39 | "scale" : "2x" 40 | }, 41 | { 42 | "size" : "60x60", 43 | "idiom" : "iphone", 44 | "filename" : "icon_180.png", 45 | "scale" : "3x" 46 | }, 47 | { 48 | "idiom" : "ipad", 49 | "size" : "20x20", 50 | "scale" : "1x" 51 | }, 52 | { 53 | "idiom" : "ipad", 54 | "size" : "20x20", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "29x29", 60 | "scale" : "1x" 61 | }, 62 | { 63 | "idiom" : "ipad", 64 | "size" : "29x29", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "40x40", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "40x40", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "size" : "76x76", 79 | "idiom" : "ipad", 80 | "filename" : "icon_76.png", 81 | "scale" : "1x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "icon_152.png", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "size" : "83.5x83.5", 91 | "idiom" : "ipad", 92 | "filename" : "icon_167.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "idiom" : "ios-marketing", 97 | "size" : "1024x1024", 98 | "scale" : "1x" 99 | } 100 | ], 101 | "info" : { 102 | "version" : 1, 103 | "author" : "xcode" 104 | } 105 | } -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_120.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_152.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_167.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_180.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_40.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_60.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/AppIcon.appiconset/icon_76.png -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/Photos/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/Photos/Yosemite.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "yosemite.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/Photos/Yosemite.imageset/yosemite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/developing-accessible-iOS-apps/de76e02efc7bee341c1d620f07a5b694a09ff2c7/DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Assets.xcassets/Photos/Yosemite.imageset/yosemite.jpg -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | DevelopingAccessibleApps 4 | 5 | Created by Daniel Devesa Derksen-Staats on 05/01/2020. 6 | Copyright © 2020 Desfici Ltd. All rights reserved. 7 | */ 8 | 9 | "accessibilityExamples" = "Accessibility Examples"; 10 | 11 | "accessibilityLabels" = "Accessibility Labels"; 12 | "adjustTraitValues" = "Adjustable Trait & Values"; 13 | "accessibilityElements" = "Accessibility Elements"; 14 | "customActions" = "Custom Actions"; 15 | "performZEscapeGesture" = "Perform Escape 'Z' Gesture"; 16 | "accessibilityNotifications" = "Accessibility Notifications"; 17 | "dynamicTypeAdaptiveUI" = "Dynamic Type Adaptive UI"; 18 | "dynamicTypeAlternativeUI" = "Dynamic Type Alternative UI"; 19 | "smartInvertColors" = "Smart Invert Colors"; 20 | "largeContentViewer" = "Large Content Viewer"; 21 | "uiSwitch" = "UISwitch"; 22 | "dynamicTypeInWebViews" = "Dynamic Type in web views"; 23 | 24 | "timeExample" = "Time Example"; 25 | "mostReadExample" = "Most Read Example"; 26 | "accessibilityMostReadExample" = "Most Red Example"; 27 | "mostRead" = "Most read"; 28 | "accessibilityMostRead" = "Most Red"; 29 | "languageExample" = "Language Example"; 30 | 31 | "vs" = "vs."; 32 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/dynamic-type-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

This is an H1 header

9 |

Lorem ipsum dolor amet twee distillery narwhal id pork belly chartreuse fixie aliqua hella vexillologist schlitz letterpress. Reprehenderit af disrupt fixie air plant non. Street art ipsum nostrud nulla. Ethical waistcoat green juice franzen.

10 |

Fam non iPhone keytar plaid pabst 8-bit edison bulb cornhole et ad tattooed tilde ex. Biodiesel asymmetrical try-hard semiotics chillwave schlitz normcore raw denim next level umami officia hexagon. Organic trust fund ethical veniam, aute culpa elit in ullamco offal edison bulb retro palo santo iceland lyft. Selvage wayfarers venmo pabst locavore. Keytar semiotics raclette, dolore letterpress cred reprehenderit.

11 |
This is how a footnote looks like
12 | 13 | 14 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Assets/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: -apple-system-body; 3 | } 4 | 5 | h1 { 6 | font: -apple-system-headline; 7 | color: darkblue; 8 | } 9 | 10 | .footnote { 11 | font: -apple-system-footnote; 12 | color: gray; 13 | } -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityElementsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityElementsViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 20/07/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AccessibilityElementsViewController: UIViewController { 12 | @IBOutlet weak var userProfileImage: UIImageView! 13 | @IBOutlet weak var usernameLabel: UILabel! 14 | 15 | override func viewWillAppear(_ animated: Bool) { 16 | super.viewWillAppear(animated) 17 | userProfileImage.layer.cornerRadius = userProfileImage.frame.size.height / 2 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityElementsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityLabelsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityLabelsViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AccessibilityLabelsViewController: UIViewController { 12 | @IBOutlet weak var timeExampleLabel: UILabel! 13 | @IBOutlet weak var nonReadableTimeLabel: UILabel! 14 | @IBOutlet weak var timeExampleVsLabel: UILabel! 15 | @IBOutlet weak var readableTimeLabel: UILabel! 16 | @IBOutlet weak var mostReadExampleLabel: UILabel! 17 | @IBOutlet weak var incorrectMostReadLabel: UILabel! 18 | @IBOutlet weak var mostReadVsLabel: UILabel! 19 | @IBOutlet weak var correctMostReadLabel: UILabel! 20 | @IBOutlet weak var languageExampleLabel: UILabel! 21 | @IBOutlet weak var incorrectLanguageLabel: UILabel! 22 | @IBOutlet weak var languageExampleVsLabel: UILabel! 23 | @IBOutlet weak var correctLanguageLabel: UILabel! 24 | @IBOutlet weak var ipaExampleLabel: UILabel! 25 | @IBOutlet weak var nonIpaLabel: UILabel! 26 | @IBOutlet weak var ipaVsLabel: UILabel! 27 | @IBOutlet weak var ipaLabel: UILabel! 28 | @IBOutlet weak var punctuationExampleLabel: UILabel! 29 | @IBOutlet weak var withoutPuntuationLabel: UILabel! 30 | @IBOutlet weak var punctuationVSLabel: UILabel! 31 | @IBOutlet weak var withPunctuationLabel: UILabel! 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | configureTimeExample() 36 | configureMostReadExample() 37 | configureLanguageExample() 38 | configureIpaExample() 39 | configurePunctuationExample() 40 | } 41 | 42 | private func configureTimeExample() { 43 | let time = "02:48" 44 | let dateComponentsFormatter = DateComponentsFormatter() 45 | let timeFormatter = DateFormatter() 46 | 47 | timeFormatter.dateFormat = "mm:ss" 48 | dateComponentsFormatter.allowedUnits = [.minute, .second] 49 | dateComponentsFormatter.unitsStyle = .short 50 | 51 | timeExampleLabel.text = NSLocalizedString("timeExample", bundle: .main, comment: "") 52 | timeExampleLabel.accessibilityTraits = UIAccessibilityTraits.header 53 | nonReadableTimeLabel.text = time 54 | timeExampleVsLabel.text = NSLocalizedString("vs", bundle: .main, comment: "") 55 | readableTimeLabel.text = time 56 | 57 | if let date = timeFormatter.date(from: time) { 58 | let components = Calendar.current.dateComponents([.minute, .second], from: date) 59 | readableTimeLabel.accessibilityLabel = dateComponentsFormatter.string(from: components) 60 | } 61 | } 62 | 63 | private func configureMostReadExample() { 64 | mostReadExampleLabel.text = NSLocalizedString("mostReadExample", bundle: .main, comment: "") 65 | mostReadExampleLabel.accessibilityLabel = NSLocalizedString( 66 | "accessibilityMostReadExample", bundle: .main, comment: "" 67 | ) 68 | mostReadExampleLabel.accessibilityTraits = UIAccessibilityTraits.header 69 | incorrectMostReadLabel.text = NSLocalizedString("mostRead", bundle: .main, comment: "") 70 | mostReadVsLabel.text = NSLocalizedString("vs", bundle: .main, comment: "") 71 | correctMostReadLabel.text = NSLocalizedString("mostRead", bundle: .main, comment: "") 72 | correctMostReadLabel.accessibilityLabel = NSLocalizedString("accessibilityMostRead", bundle: .main, comment: "") 73 | } 74 | 75 | private func configureLanguageExample() { 76 | languageExampleLabel.text = NSLocalizedString("languageExample", bundle: .main, comment: "") 77 | languageExampleLabel.accessibilityTraits = UIAccessibilityTraits.header 78 | incorrectLanguageLabel.text = "Paella" 79 | languageExampleVsLabel.text = NSLocalizedString("vs", bundle: .main, comment: "") 80 | correctLanguageLabel.text = "Paella" 81 | correctLanguageLabel.accessibilityAttributedLabel = NSAttributedString( 82 | string: "Paella", attributes: [.accessibilitySpeechLanguage: "es-ES"] 83 | ) 84 | } 85 | 86 | private func configureIpaExample() { 87 | let text = "Paella is a Valencian rice dish" 88 | ipaExampleLabel.text = NSLocalizedString("IPA Notation Example", bundle: .main, comment: "") 89 | ipaExampleLabel.accessibilityTraits = UIAccessibilityTraits.header 90 | nonIpaLabel.text = text 91 | ipaVsLabel.text = NSLocalizedString("vs", bundle: .main, comment: "") 92 | let ipaAttributedText = NSMutableAttributedString(string: "Paella is a Valencian rice dish") 93 | let ipaRange = ipaAttributedText.string.range(of: "Paella")! 94 | ipaAttributedText.addAttributes( 95 | [.accessibilitySpeechIPANotation: "paˈeʎa"], range: NSRange(ipaRange, in: ipaAttributedText.string) 96 | ) 97 | ipaLabel.text = text 98 | ipaLabel.accessibilityAttributedLabel = ipaAttributedText 99 | } 100 | 101 | private func configurePunctuationExample() { 102 | let text = "Text within ‘ and ’ or \" denote either speech or a quotation" 103 | punctuationExampleLabel.text = NSLocalizedString("Punctuation Example", bundle: .main, comment: "") 104 | punctuationExampleLabel.accessibilityTraits = UIAccessibilityTraits.header 105 | withoutPuntuationLabel.text = text 106 | punctuationVSLabel.text = NSLocalizedString("vs", bundle: .main, comment: "") 107 | withPunctuationLabel.text = text 108 | withPunctuationLabel.accessibilityAttributedLabel = NSAttributedString( 109 | string: text, attributes: [.accessibilitySpeechPunctuation: true] 110 | ) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityNotificationsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityNotificationsViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 05/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AccessibilityNotificationsViewController: UIViewController { 12 | @IBOutlet weak var customModalButton: UIButton! 13 | @IBOutlet weak var customToastButton: UIButton! 14 | 15 | private var customToastView: CustomToastView? 16 | private var customModalView: CustomModalView? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | customModalButton.setTitle( 22 | NSLocalizedString("Present Custom Modal (.screenChanged)", bundle: .main, comment: ""), for: .normal 23 | ) 24 | customToastButton.setTitle( 25 | NSLocalizedString("Present Custom Toast (.announcement)", bundle: .main, comment: ""), for: .normal 26 | ) 27 | 28 | NotificationCenter.default.addObserver( 29 | self, 30 | selector: #selector(handleReduceTransparency), 31 | name: UIAccessibility.reduceTransparencyStatusDidChangeNotification, 32 | object: nil 33 | ) 34 | 35 | if let customModalView = CustomModalView.loadFromNib() { 36 | customModalView.accessibilityViewIsModal = true 37 | customModalView.dismissViewActioned = { self.dismissModalSheetView() } 38 | self.customModalView = customModalView 39 | view.addSubview(customModalView) 40 | hideModalSheetView() 41 | } 42 | 43 | if let customToastView = CustomToastView.loadFromNib() { 44 | customToastView.isAccessibilityElement = false 45 | customToastView.configureWithTitle(NSLocalizedString("Custom Toast Shown!", bundle: .main, comment: "")) 46 | customToastView.alpha = 0.0 47 | self.customToastView = customToastView 48 | view.addSubview(customToastView) 49 | } 50 | } 51 | 52 | @objc func handleReduceTransparency(_ sender: Any) { 53 | // Reduce transparency in your app if necessary 54 | } 55 | 56 | private func hideModalSheetView() { 57 | guard let customModalView = customModalView else { return } 58 | customModalView.frame = CGRect.zero 59 | customModalView.center = view.center 60 | customModalView.alpha = 0.0 61 | } 62 | 63 | private func showModalSheetViewToSize(_ size: CGSize) { 64 | guard let customModalView = customModalView else { return } 65 | customModalView.frame.size = size 66 | customModalView.center = self.view.center 67 | customModalView.alpha = 1.0 68 | } 69 | 70 | private func presentModalSheetview() { 71 | guard let customModalView = customModalView else { return } 72 | 73 | UIView.animate( 74 | withDuration: 0.5, 75 | delay: 0.0, 76 | usingSpringWithDamping: 0.5, 77 | initialSpringVelocity: 0.25, 78 | options: .curveEaseIn, 79 | animations: { self.showModalSheetViewToSize(CGSize(width: 300.0, height: 450.0)) }, 80 | completion: { _ in 81 | UIAccessibility.post(notification: .screenChanged, argument: customModalView) 82 | } 83 | ) 84 | } 85 | 86 | private func dismissModalSheetView() { 87 | UIView.animate( 88 | withDuration: 0.5, 89 | delay: 0.0, 90 | usingSpringWithDamping: 0.5, 91 | initialSpringVelocity: 0.25, 92 | options: .curveEaseOut, 93 | animations: { self.hideModalSheetView() }, 94 | completion: { _ in 95 | UIAccessibility.post(notification: .screenChanged, argument: self.customModalButton) 96 | } 97 | ) 98 | } 99 | 100 | private func presentToastView() { 101 | guard let customToastView = customToastView else { return } 102 | customToastView.isHidden = false 103 | customToastView.translatesAutoresizingMaskIntoConstraints = false 104 | 105 | NSLayoutConstraint.activate( 106 | [ 107 | customToastView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0), 108 | customToastView.centerXAnchor.constraint(equalTo: view.centerXAnchor) 109 | ] 110 | ) 111 | 112 | UIView.animate( 113 | withDuration: 0.2, 114 | delay: 3.0, 115 | options: .curveEaseIn, 116 | animations: { customToastView.alpha = 1.0 }, 117 | completion: { _ in 118 | let queueAnnouncementAttributes = [NSAttributedString.Key.accessibilitySpeechQueueAnnouncement: true] 119 | let announcementAttributedString = NSAttributedString( 120 | string: customToastView.accessibilityLabel!, attributes: queueAnnouncementAttributes 121 | ) 122 | 123 | UIAccessibility.post(notification: .announcement, argument: announcementAttributedString) 124 | UIView.animate( 125 | withDuration: 0.2, 126 | delay: 2.0, 127 | options: .curveEaseOut, 128 | animations: { customToastView.alpha = 0.0 }, 129 | completion: { _ in customToastView.isHidden = true } 130 | ) 131 | } 132 | ) 133 | } 134 | 135 | @IBAction private func customModalButtonPressed(_ sender: Any) { 136 | presentModalSheetview() 137 | } 138 | 139 | @IBAction private func customToastButtonPressed(_ sender: Any) { 140 | presentToastView() 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityNotificationsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityValuesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityValuesViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 05/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AccessibilityValuesViewController: UIViewController { 12 | @IBOutlet weak var sliderTitleLabel: UILabel! 13 | @IBOutlet weak var slider: AccessibleSlider! 14 | @IBOutlet weak var valueLabel: UILabel! 15 | @IBOutlet weak var ratingStackView: UIStackView! 16 | 17 | let stepSize: Float = 50_000.0 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | let numberFormatter = NumberFormatter() 23 | numberFormatter.numberStyle = .currency 24 | 25 | slider.configure( 26 | label: NSLocalizedString("Price", bundle: .main, comment: ""), 27 | minValue: 100_000.0, 28 | maxValue: 2_000_000.0, 29 | stepSize: 50_000.0, 30 | numberFormatter: numberFormatter 31 | ) 32 | 33 | sliderTitleLabel.text = NSLocalizedString("Price: ", bundle: .main, comment: "") 34 | sliderTitleLabel.isAccessibilityElement = false 35 | 36 | valueLabel.text = slider?.readableValue() 37 | valueLabel.isAccessibilityElement = false 38 | } 39 | 40 | @IBAction private func sliderValueChanged(_ sender: Any) { 41 | valueLabel.text = slider?.readableValue() 42 | } 43 | } 44 | 45 | class AccessibleSlider: UISlider { 46 | private var stepSize: Float = 10 47 | private var numberFormatter: NumberFormatter? 48 | 49 | func configure(label: String, minValue: Float, maxValue: Float, stepSize: Float, numberFormatter: NumberFormatter) { 50 | self.stepSize = stepSize 51 | self.numberFormatter = numberFormatter 52 | 53 | accessibilityLabel = label 54 | minimumValue = minValue 55 | maximumValue = maxValue 56 | 57 | accessibilityValue = readableValue() 58 | } 59 | 60 | func readableValue() -> String { 61 | let sliderValueString = numberFormatter?.string(from: NSNumber(value: value)) 62 | return sliderValueString ?? "" 63 | } 64 | 65 | override func accessibilityIncrement() { 66 | value += stepSize 67 | accessibilityValue = readableValue() 68 | sendActions(for: .valueChanged) 69 | } 70 | 71 | override func accessibilityDecrement() { 72 | value -= stepSize 73 | accessibilityValue = readableValue() 74 | sendActions(for: .valueChanged) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/AccessibilityValuesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/CustomActionsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomActionsViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 05/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomActionsViewController: UITableViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | tableView.register( 15 | UINib(nibName: SocialNetworkTableViewCell.identifier, bundle: nil), 16 | forCellReuseIdentifier: SocialNetworkTableViewCell.identifier 17 | ) 18 | tableView.estimatedRowHeight = 100.0 19 | tableView.rowHeight = UITableView.automaticDimension 20 | } 21 | 22 | // MARK: - Table view data source 23 | 24 | override func numberOfSections(in tableView: UITableView) -> Int { 25 | 1 26 | } 27 | 28 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 29 | 20 30 | } 31 | 32 | // MARK: - Table view delegate 33 | 34 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 35 | guard let cell = tableView.dequeueReusableCell( 36 | withIdentifier: SocialNetworkTableViewCell.identifier, for: indexPath 37 | ) as? SocialNetworkTableViewCell else { fatalError("Error creating the cell") } 38 | let viewModel = SocialNetworkViewModel(userName: "@a11yUser", postText: String.loremImpsum(withLength: .long)) 39 | cell.configureWithViewModel(viewModel) 40 | return cell 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 44 | tableView.deselectRow(at: indexPath, animated: true) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/CustomActionsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/DynamicTypesAdaptiveUIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicTypesAdaptiveUIViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 02/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DynamicTypesAdaptiveUIViewController: UIViewController { 12 | @IBOutlet weak var iconImageView: UIImageView! 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var descriptionLabel: UILabel! 15 | @IBOutlet weak var callToActionButton: UIButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | iconImageView.adjustsImageSizeForAccessibilityContentSizeCategory = true 20 | titleLabel.text = NSLocalizedString("Something went wrong!", bundle: .main, comment: "") 21 | descriptionLabel.text = NSLocalizedString( 22 | "It looks like we lost you there for a second. Please check your connection and try again", 23 | bundle: .main, 24 | comment: "" 25 | ) 26 | callToActionButton.setTitle(NSLocalizedString("Retry", bundle: .main, comment: ""), for: .normal) 27 | 28 | let iconConfiguration = UIImage.SymbolConfiguration(textStyle: .largeTitle) 29 | 30 | iconImageView.image = UIImage(systemName: "xmark.octagon", withConfiguration: iconConfiguration) 31 | iconImageView.tintColor = .label 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/DynamicTypesAdaptiveUIViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/DynamicTypesAlternativeUIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicTypesAlternativeUIViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 12/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DynamicTypesAlternativeUIViewController: UIViewController { 12 | @IBOutlet weak var tableView: UITableView! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | let imageCellNib = UINib(nibName: ImageTableViewCell.identifier, bundle: nil) 17 | let alternativeCellNib = UINib(nibName: AlternativeImageTableViewCell.identifier, bundle: nil) 18 | 19 | tableView.register(imageCellNib, forCellReuseIdentifier: ImageTableViewCell.identifier) 20 | tableView.register(alternativeCellNib, forCellReuseIdentifier: AlternativeImageTableViewCell.identifier) 21 | tableView.register( 22 | ImageCodeConstraintsTableViewCell.self, forCellReuseIdentifier: ImageCodeConstraintsTableViewCell.identifier 23 | ) 24 | 25 | tableView.estimatedRowHeight = 100.0 26 | tableView.rowHeight = UITableView.automaticDimension 27 | tableView?.delegate = self 28 | tableView?.dataSource = self 29 | } 30 | } 31 | 32 | extension DynamicTypesAlternativeUIViewController: UITableViewDelegate { 33 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | let cell: UITableViewCell 35 | 36 | if indexPath.row.isMultiple(of: 2) { 37 | // https://developer.apple.com/documentation/uikit/uicontentsizecategory 38 | if traitCollection.preferredContentSizeCategory >= .accessibilityExtraExtraExtraLarge { 39 | cell = tableView.dequeueReusableCell( 40 | withIdentifier: AlternativeImageTableViewCell.identifier, for: indexPath 41 | ) 42 | } else { 43 | cell = tableView.dequeueReusableCell(withIdentifier: ImageTableViewCell.identifier, for: indexPath) 44 | } 45 | } else { 46 | cell = tableView.dequeueReusableCell( 47 | withIdentifier: ImageCodeConstraintsTableViewCell.identifier, for: indexPath 48 | ) 49 | } 50 | 51 | return cell 52 | } 53 | 54 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 55 | tableView.deselectRow(at: indexPath, animated: true) 56 | } 57 | } 58 | 59 | extension DynamicTypesAlternativeUIViewController: UITableViewDataSource { 60 | func numberOfSections(in tableView: UITableView) -> Int { 61 | 1 62 | } 63 | 64 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 65 | 20 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/DynamicTypesAlternativeUIViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/LargeContentViewerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LargeContentViewerViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 16/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LargeContentViewerViewController: UIViewController { 12 | @IBOutlet weak var customBarView: UIView! 13 | @IBOutlet weak var customBarButton: UIButton! 14 | @IBOutlet weak var customBarTabView: UIView! 15 | @IBOutlet weak var customBarTabImage: UIImageView! 16 | @IBOutlet weak var customBarTabLabel: UILabel! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | let videosTitle = NSLocalizedString("Videos", bundle: .main, comment: "") 22 | let videosImage = UIImage( 23 | systemName: "play", withConfiguration: UIImage.SymbolConfiguration(textStyle: .largeTitle, scale: .large) 24 | ) 25 | let largeContentViewerInteraction = UILargeContentViewerInteraction() 26 | 27 | customBarTabImage.image = videosImage 28 | customBarTabLabel.text = videosTitle 29 | customBarView.addInteraction(largeContentViewerInteraction) 30 | customBarButton.setImage(videosImage, for: .normal) 31 | customBarButton.showsLargeContentViewer = true 32 | customBarTabView.showsLargeContentViewer = true 33 | customBarTabView.largeContentImage = videosImage 34 | customBarTabView.largeContentTitle = videosTitle 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/LargeContentViewerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/PerformEscapeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerformEscapeViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 04/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Example for: 12 | /// Chapter 4. VoicOver – Up to 11 13 | /// -> Advanced VoiceOver gestures 14 | /// -> Perform Escape 15 | class PerformEscapeViewController: UIViewController { 16 | @IBOutlet weak var presentWithPerformEscapeButton: UIButton! 17 | @IBOutlet weak var presentWithoutPerformEscapeButton: UIButton! 18 | 19 | let withPerformEscapeNavigationController = UINavigationController() 20 | let withoutPerformEscapeNavigationController = UINavigationController() 21 | let withPerformEscapeViewController = WithPerformEscapeViewController() 22 | let withoutPerformEscapeViewController = WithoutPerformEscapeViewController() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | let closeButton = UIBarButtonItem( 28 | title: NSLocalizedString("Close", bundle: .main, comment: ""), 29 | style: .plain, 30 | target: self, 31 | action: #selector(dismissModalView) 32 | ) 33 | 34 | presentWithPerformEscapeButton.setTitle( 35 | NSLocalizedString("Present Modal with Perform Escape", bundle: .main, comment: ""), for: .normal 36 | ) 37 | presentWithoutPerformEscapeButton.setTitle( 38 | NSLocalizedString("Present Modal without Perform Escape", bundle: .main, comment: ""), for: .normal 39 | ) 40 | 41 | withPerformEscapeViewController.navigationItem.rightBarButtonItem = closeButton 42 | withoutPerformEscapeViewController.navigationItem.rightBarButtonItem = closeButton 43 | 44 | // provide context for the user about whether the Perform Escape action should work or not 45 | withPerformEscapeViewController.navigationItem.title = "with Perform Escape" 46 | withoutPerformEscapeViewController.navigationItem.title = "without Perform Escape" 47 | 48 | withPerformEscapeNavigationController.viewControllers = [withPerformEscapeViewController] 49 | withoutPerformEscapeNavigationController.viewControllers = [withoutPerformEscapeViewController] 50 | } 51 | 52 | @objc 53 | private func dismissModalView() { 54 | dismiss(animated: true, completion: nil) 55 | } 56 | 57 | @IBAction private func presentWithPerformEscapeButtonPressed(_ sender: Any) { 58 | present(withPerformEscapeNavigationController, animated: true, completion: nil) 59 | } 60 | 61 | @IBAction private func presentWithoutPerformEscapeButtonPressed(_ sender: Any) { 62 | present(withoutPerformEscapeNavigationController, animated: true, completion: nil) 63 | } 64 | } 65 | 66 | class WithPerformEscapeViewController: UIViewController { 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 | view.backgroundColor = .systemGreen 70 | } 71 | 72 | override func accessibilityPerformEscape() -> Bool { 73 | super.accessibilityPerformEscape() 74 | dismiss(animated: true, completion: nil) 75 | return true 76 | } 77 | } 78 | 79 | class WithoutPerformEscapeViewController: UIViewController { 80 | override func viewDidLoad() { 81 | super.viewDidLoad() 82 | view.backgroundColor = .lightGray 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/PerformEscapeViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/SmartInvertedColorsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmartInvertedColorsViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SmartInvertedColorsViewController: UIViewController { 12 | @IBOutlet weak var noSmartInvertColorLabel: UILabel! 13 | @IBOutlet weak var smartInvertColorLabel: UILabel! 14 | @IBOutlet weak var noSmartInvertColorImage: UIImageView! 15 | @IBOutlet weak var smartInvertColorImage: UIImageView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | noSmartInvertColorLabel.text = NSLocalizedString("Without Smart Invert Colors", bundle: .main, comment: "") 21 | smartInvertColorLabel.text = NSLocalizedString("With Smart Invert Colors", bundle: .main, comment: "") 22 | 23 | noSmartInvertColorImage.image = UIImage(named: "Yosemite") 24 | smartInvertColorImage.image = UIImage(named: "Yosemite") 25 | 26 | configureAccessibility() 27 | } 28 | 29 | func configureAccessibility() { 30 | let accessibilityLabel = NSLocalizedString("Half Dome view from the Galcier Point", bundle: .main, comment: "") 31 | 32 | smartInvertColorImage.accessibilityIgnoresInvertColors = true 33 | 34 | noSmartInvertColorImage.isAccessibilityElement = true 35 | smartInvertColorImage.isAccessibilityElement = true 36 | 37 | noSmartInvertColorImage.accessibilityLabel = accessibilityLabel 38 | smartInvertColorImage.accessibilityLabel = accessibilityLabel 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/SmartInvertedColorsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/SwitchTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchTableViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 22/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SwitchTableViewController: UITableViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | let simpleSwitchCellNib = UINib(nibName: SimpleSwitchTableViewCell.identifier, bundle: nil) 15 | let settingsSwitchCellNib = UINib(nibName: SettingsSwitchTableViewCell.identifier, bundle: nil) 16 | 17 | tableView.register(simpleSwitchCellNib, forCellReuseIdentifier: SimpleSwitchTableViewCell.identifier) 18 | tableView.register(settingsSwitchCellNib, forCellReuseIdentifier: SettingsSwitchTableViewCell.identifier) 19 | tableView.estimatedRowHeight = 100.0 20 | tableView.rowHeight = UITableView.automaticDimension 21 | } 22 | 23 | override func numberOfSections(in tableView: UITableView) -> Int { 24 | 1 25 | } 26 | 27 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 28 | 5 29 | } 30 | 31 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 32 | let cell: UITableViewCell 33 | if indexPath.row.isMultiple(of: 2) { 34 | cell = tableView.dequeueReusableCell(withIdentifier: SettingsSwitchTableViewCell.identifier, for: indexPath) 35 | } else { 36 | cell = tableView.dequeueReusableCell(withIdentifier: SimpleSwitchTableViewCell.identifier, for: indexPath) 37 | } 38 | 39 | return cell 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/SwitchTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/AlternativeImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlternativeImageTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 12/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlternativeImageTableViewCell: UITableViewCell { 12 | @IBOutlet weak var pictureImageView: UIImageView! 13 | @IBOutlet weak var titleLabel: UILabel! 14 | 15 | static let identifier = String(describing: AlternativeImageTableViewCell.self) 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | pictureImageView.image = UIImage(named: "Yosemite") 20 | pictureImageView.accessibilityIgnoresInvertColors = true 21 | titleLabel.text = String.loremImpsum() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/AlternativeImageTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/CustomModalView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomModalView.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 04/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CustomModalView: UIView, NibLoadable { 12 | @IBOutlet weak var closeButton: UIButton! 13 | @IBOutlet weak var bodyTextLabel: UILabel! 14 | 15 | var dismissViewActioned: (() -> Void)? 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | bodyTextLabel.text = NSLocalizedString("Hello custom modal view!", bundle: .main, comment: "") 20 | closeButton.setTitle(NSLocalizedString("Close", bundle: .main, comment: ""), for: .normal) 21 | layer.cornerRadius = 10.0 22 | } 23 | 24 | override func accessibilityPerformEscape() -> Bool { 25 | super.accessibilityPerformEscape() 26 | dismissViewActioned?() 27 | return true 28 | } 29 | 30 | @IBAction private func closeButtonPressed(_ sender: Any) { 31 | dismissViewActioned?() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/CustomModalView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/CustomToastView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomToastView.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 05/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CustomToastView: UIView, NibLoadable { 12 | @IBOutlet weak var toastTitleLabel: UILabel! 13 | @IBOutlet weak var backgroundView: UIView! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | translatesAutoresizingMaskIntoConstraints = false 18 | backgroundView.layer.cornerRadius = 10.0 19 | } 20 | 21 | func configureWithTitle(_ title: String) { 22 | toastTitleLabel.text = title 23 | accessibilityLabel = toastTitleLabel.accessibilityLabel 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/CustomToastView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/ImageCodeConstraintsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCodeConstraintsTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 30/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageCodeConstraintsTableViewCell: UITableViewCell { 12 | static let identifier = String(describing: ImageCodeConstraintsTableViewCell.self) 13 | 14 | private var pictureImageView: UIImageView? 15 | private var titleLabel: UILabel? 16 | private var commonConstraints: [NSLayoutConstraint] = [] 17 | private var defaultConstraints: [NSLayoutConstraint] = [] 18 | private var alternativeConstraints: [NSLayoutConstraint] = [] 19 | 20 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 21 | super.init(style: style, reuseIdentifier: reuseIdentifier) 22 | configureLayout() 23 | } 24 | 25 | @available(*, unavailable) 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 31 | let isAccessibilityCategory = traitCollection.preferredContentSizeCategory.isAccessibilityCategory 32 | if isAccessibilityCategory != previousTraitCollection?.preferredContentSizeCategory.isAccessibilityCategory { 33 | updateLayoutConstraints() 34 | } 35 | } 36 | 37 | private func configureLayout() { 38 | titleLabel = UILabel(frame: CGRect.zero) 39 | titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) 40 | titleLabel?.adjustsFontForContentSizeCategory = true 41 | titleLabel?.numberOfLines = 0 42 | titleLabel?.text = String.loremImpsum() 43 | pictureImageView = UIImageView(image: UIImage(named: "Yosemite")) 44 | pictureImageView?.contentMode = .scaleAspectFill 45 | pictureImageView?.clipsToBounds = true 46 | pictureImageView?.accessibilityIgnoresInvertColors = true 47 | 48 | backgroundColor = .lightGray 49 | 50 | if let titleLabel = titleLabel, 51 | let pictureImageView = pictureImageView { 52 | contentView.addSubview(pictureImageView) 53 | contentView.addSubview(titleLabel) 54 | setupLayoutConstraints() 55 | updateLayoutConstraints() 56 | } 57 | } 58 | 59 | private func setupLayoutConstraints() { 60 | guard let titleLabel = titleLabel else { return } 61 | guard let pictureImageView = pictureImageView else { return } 62 | 63 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 64 | titleLabel.setContentCompressionResistancePriority(.init(250.0), for: .vertical) 65 | pictureImageView.translatesAutoresizingMaskIntoConstraints = false 66 | pictureImageView.setContentCompressionResistancePriority(.required, for: .vertical) 67 | 68 | commonConstraints = [ 69 | NSLayoutConstraint( 70 | item: pictureImageView, 71 | attribute: .width, 72 | relatedBy: .equal, 73 | toItem: pictureImageView, 74 | attribute: .height, 75 | multiplier: 16.0 / 9.0, 76 | constant: 0 77 | ), 78 | pictureImageView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), 79 | pictureImageView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), 80 | titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: contentView.layoutMarginsGuide.trailingAnchor), 81 | titleLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.layoutMarginsGuide.bottomAnchor) 82 | ] 83 | 84 | defaultConstraints = [ 85 | pictureImageView.heightAnchor.constraint(equalToConstant: 100), 86 | pictureImageView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), 87 | pictureImageView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -10.0), 88 | titleLabel.topAnchor.constraint(equalTo: pictureImageView.topAnchor) 89 | ] 90 | 91 | alternativeConstraints = [ 92 | pictureImageView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), 93 | pictureImageView.bottomAnchor.constraint(equalTo: titleLabel.topAnchor, constant: -8.0), 94 | titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor) 95 | ] 96 | } 97 | 98 | private func updateLayoutConstraints() { 99 | NSLayoutConstraint.activate(commonConstraints) 100 | if traitCollection.preferredContentSizeCategory.isAccessibilityCategory { 101 | NSLayoutConstraint.deactivate(defaultConstraints) 102 | NSLayoutConstraint.activate(alternativeConstraints) 103 | } else { 104 | NSLayoutConstraint.deactivate(alternativeConstraints) 105 | NSLayoutConstraint.activate(defaultConstraints) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/ImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 12/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageTableViewCell: UITableViewCell { 12 | @IBOutlet weak var pictureImageView: UIImageView! 13 | @IBOutlet weak var titleLabel: UILabel! 14 | 15 | static let identifier = String(describing: ImageTableViewCell.self) 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | pictureImageView.image = UIImage(named: "Yosemite") 20 | // pictureImageView.accessibilityIgnoresInvertColors = true 21 | titleLabel.text = String.loremImpsum() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/ImageTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/RaterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RaterView.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 20/07/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RaterButton: UIButton { 12 | func toggle(_ isOn: Bool) { 13 | alpha = isOn ? 1.0 : 0.5 14 | } 15 | } 16 | 17 | class RaterView: UIView { 18 | private let stackView = UIStackView() 19 | private var icon = UIImage(systemName: "hand.thumbsup") 20 | private var maxScore: UInt = 5 21 | 22 | var rating: UInt = 0 { 23 | didSet { 24 | updateAccessibilityValue() 25 | } 26 | } 27 | 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | setUp() 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | setUp() 36 | } 37 | 38 | private func setUp() { 39 | stackView.axis = .horizontal 40 | stackView.distribution = .fillEqually 41 | stackView.translatesAutoresizingMaskIntoConstraints = false 42 | self.addSubview(stackView) 43 | NSLayoutConstraint.activate( 44 | [ 45 | stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 46 | stackView.topAnchor.constraint(equalTo: self.topAnchor), 47 | stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 48 | stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor) 49 | ] 50 | ) 51 | 52 | for _ in 0.. 0 else { return } 97 | pressButton(at: rating - 1) 98 | } 99 | 100 | private func pressButton(at index: UInt) { 101 | guard let button = button(at: index) else { return } 102 | ratingButtonPressed(button) 103 | } 104 | 105 | private func button(at index: UInt) -> RaterButton? { 106 | var button: RaterButton? 107 | 108 | for (subviewIndex, arrangedSubview) in stackView.arrangedSubviews.enumerated() { 109 | guard let arrangedSubview = arrangedSubview as? RaterButton else { break } 110 | 111 | if index == subviewIndex { 112 | button = arrangedSubview 113 | break 114 | } 115 | } 116 | 117 | return button 118 | } 119 | 120 | private func updateAccessibilityValue() { 121 | accessibilityValue = "\(rating + 1)" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SettingsSwitchTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsSwitchTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 30/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | // Ignore the empty `set {}` declarations in this file? 10 | // swiftlint:disable unused_setter_value 11 | 12 | import UIKit 13 | 14 | class SettingsSwitchTableViewCell: UITableViewCell { 15 | static let identifier = String(describing: SettingsSwitchTableViewCell.self) 16 | 17 | @IBOutlet weak var settingTitleLabel: UILabel! 18 | @IBOutlet weak var settingSubtitleLabel: UILabel! 19 | @IBOutlet weak var settingSwitch: UISwitch! 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | settingTitleLabel.text = NSLocalizedString("Title for a setting option", bundle: .main, comment: "") 24 | settingSubtitleLabel.text = NSLocalizedString( 25 | "Explanation of what this setting option does", bundle: .main, comment: "" 26 | ) 27 | isAccessibilityElement = true 28 | } 29 | 30 | override var accessibilityLabel: String? { 31 | get { "\(settingTitleLabel.accessibilityLabel ?? ""). \(settingSubtitleLabel.accessibilityLabel ?? "")" } 32 | set {} 33 | } 34 | 35 | override var accessibilityTraits: UIAccessibilityTraits { 36 | get { settingSwitch.accessibilityTraits } 37 | set {} 38 | } 39 | 40 | override var accessibilityValue: String? { 41 | get { settingSwitch.accessibilityValue } 42 | set {} 43 | } 44 | 45 | override func accessibilityActivate() -> Bool { 46 | settingSwitch.isOn.toggle() 47 | return true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SettingsSwitchTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SimpleSwitchTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleSwitchTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 22/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SimpleSwitchTableViewCell: UITableViewCell { 12 | static let identifier = String(describing: SimpleSwitchTableViewCell.self) 13 | 14 | @IBOutlet weak var settingTitleLabel: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | settingTitleLabel.text = NSLocalizedString("Not a good exaple", bundle: .main, comment: "") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SimpleSwitchTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SocialNetworkTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocialNetworkTableViewCell.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 05/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SocialNetworkTableViewCell: UITableViewCell { 12 | @IBOutlet weak var userButton: UIButton! 13 | @IBOutlet weak var postLabel: UILabel! 14 | @IBOutlet weak var repostButton: UIButton! 15 | @IBOutlet weak var likeButton: UIButton! 16 | @IBOutlet weak var shareButton: UIButton! 17 | 18 | static let identifier = String(describing: SocialNetworkTableViewCell.self) 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | let repostTitle = NSLocalizedString("Repost", bundle: .main, comment: "") 23 | let likeTitle = NSLocalizedString("Like", bundle: .main, comment: "") 24 | let shareTitle = NSLocalizedString("Share", bundle: .main, comment: "") 25 | 26 | repostButton.setTitle(repostTitle, for: .normal) 27 | likeButton.setTitle(likeTitle, for: .normal) 28 | shareButton.setTitle(shareTitle, for: .normal) 29 | 30 | postLabel.font = UIFont.preferredFont(forTextStyle: .body) 31 | 32 | accessibilityCustomActions = [ 33 | UIAccessibilityCustomAction(name: repostTitle, target: self, selector: #selector(userButtonPressed(_:))), 34 | UIAccessibilityCustomAction(name: likeTitle, target: self, selector: #selector(likeButtonPressed(_:))), 35 | UIAccessibilityCustomAction(name: shareTitle, target: self, selector: #selector(shareButtonPressed(_:))) 36 | ] 37 | } 38 | 39 | func configureWithViewModel(_ viewModel: SocialNetworkViewModel) { 40 | self.userButton.setTitle(viewModel.userName, for: .normal) 41 | self.postLabel.text = viewModel.postText 42 | 43 | if UIAccessibility.isVoiceOverRunning, 44 | let userAccessibilityLabel = userButton.accessibilityLabel, 45 | let postAccessibilityLabel = postLabel.accessibilityLabel { 46 | accessibilityLabel = "\(String(describing: userAccessibilityLabel)). " + 47 | "\(String(describing: postAccessibilityLabel))" 48 | accessibilityCustomActions?.append( 49 | UIAccessibilityCustomAction( 50 | name: viewModel.userName, target: self, selector: #selector(userButtonPressed(_:)) 51 | ) 52 | ) 53 | } 54 | } 55 | 56 | // TODO: Create custom action selectors properly 57 | @objc func handleAction(_ action: UIAccessibilityCustomAction) -> Bool { true } 58 | 59 | @IBAction private func userButtonPressed(_ sender: Any) { 60 | print("User button presed") 61 | } 62 | 63 | @IBAction private func repostButtonPressed(_ sender: Any) { 64 | print("Repost button presed") 65 | } 66 | 67 | @IBAction private func likeButtonPressed(_ sender: Any) { 68 | print("Like button presed") 69 | } 70 | 71 | @IBAction private func shareButtonPressed(_ sender: Any) { 72 | print("Share button presed") 73 | } 74 | } 75 | 76 | struct SocialNetworkViewModel { 77 | let userName: String 78 | let postText: String 79 | } 80 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/SocialNetworkTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 35 | 43 | 51 | 57 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/View/UserStatsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserStatsView.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 20/07/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UserStatsView: UIStackView { 12 | @IBOutlet weak var followersHeadingLabel: UILabel! 13 | @IBOutlet weak var followersNumberLabel: UILabel! 14 | @IBOutlet weak var followingHeadingLabel: UILabel! 15 | @IBOutlet weak var followingNumberLabel: UILabel! 16 | @IBOutlet weak var postsHeadingLabel: UILabel! 17 | @IBOutlet weak var postsNumberLabel: UILabel! 18 | @IBOutlet weak var followersStackView: UIStackView! 19 | @IBOutlet weak var followingStackView: UIStackView! 20 | @IBOutlet weak var postsStackView: UIStackView! 21 | 22 | // SwiftLint doesn’t normally like types like `[Any]?`. 23 | // But, since we’re inheriting here, we need to use it. 24 | // swiftlint:disable discouraged_optional_collection 25 | 26 | private var _accessibilityElements: [Any]? 27 | 28 | override var accessibilityElements: [Any]? { 29 | set { 30 | _accessibilityElements = newValue 31 | } 32 | 33 | get { 34 | if let _accessibilityElements = _accessibilityElements { 35 | return _accessibilityElements 36 | } 37 | 38 | var elements = [UIAccessibilityElement]() 39 | let followersAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self) 40 | let followingAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self) 41 | let postsAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self) 42 | 43 | followersAccessibilityElement.accessibilityLabel = "\(followersHeadingLabel.accessibilityLabel!), " + 44 | "\(followersNumberLabel.accessibilityLabel!)" 45 | followersAccessibilityElement.accessibilityFrameInContainerSpace = followersStackView 46 | .convert(followersHeadingLabel.frame, to: self) 47 | .union(followersStackView.convert(followersNumberLabel.frame, to: self)) 48 | elements.append(followersAccessibilityElement) 49 | 50 | followingAccessibilityElement.accessibilityLabel = "\(followingHeadingLabel.accessibilityLabel!), " + 51 | "\(followingNumberLabel.accessibilityLabel!)" 52 | followingAccessibilityElement.accessibilityFrameInContainerSpace = followingStackView 53 | .convert(followingHeadingLabel.frame, to: self) 54 | .union(followingStackView.convert(followingNumberLabel.frame, to: self)) 55 | elements.append(followingAccessibilityElement) 56 | 57 | postsAccessibilityElement.accessibilityLabel = "\(postsHeadingLabel.accessibilityLabel!), " + 58 | "\(postsNumberLabel.accessibilityLabel!)" 59 | postsAccessibilityElement.accessibilityFrameInContainerSpace = postsStackView 60 | .convert(postsHeadingLabel.frame, to: self) 61 | .union(postsStackView.convert(postsNumberLabel.frame, to: self)) 62 | elements.append(postsAccessibilityElement) 63 | 64 | _accessibilityElements = elements 65 | 66 | followersHeadingLabel.numberOfLines = 0 67 | followingHeadingLabel.numberOfLines = 0 68 | postsHeadingLabel.numberOfLines = 0 69 | 70 | return _accessibilityElements 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 30/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class WebViewController: UIViewController { 13 | @IBOutlet weak var webView: WKWebView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | if let baseURL = Bundle.main.resourceURL { 18 | let fileURL = baseURL.appendingPathComponent("dynamic-type-example.html") 19 | webView?.loadFileURL(fileURL, allowingReadAccessTo: fileURL) 20 | } 21 | 22 | NotificationCenter.default.addObserver( 23 | self, 24 | selector: #selector(contentSizeCategoryDidChange(_:)), 25 | name: UIContentSizeCategory.didChangeNotification, 26 | object: nil 27 | ) 28 | } 29 | 30 | @objc private func contentSizeCategoryDidChange(_ notification: Notification) { 31 | webView?.reload() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/ExamplesViewControllers/WebViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | A11y->Up to 11 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIAppFonts 26 | 27 | PatrickHand-Regular.ttf 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Util/Extensions/NibLoadable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NibLoadable].swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 04/05/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol NibLoadable: AnyObject { 12 | static func loadFromNib() -> Self? 13 | static func loadFromNib(withName name: String) -> Self? 14 | } 15 | 16 | extension NibLoadable where Self: UIView { 17 | static func loadFromNib() -> Self? { 18 | let viewClassName = String(describing: self) 19 | 20 | return loadFromNib(withName: viewClassName) 21 | } 22 | 23 | static func loadFromNib(withName name: String) -> Self? { 24 | let bundle = Bundle(for: self) 25 | let views = bundle.loadNibNamed(name, owner: nil, options: nil) 26 | let firstView = views?.first 27 | 28 | return firstView as? Self 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleApps/Util/Extensions/String+LoremIpsum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+LoremIpsum.swift 3 | // DevelopingAccessibleApps 4 | // 5 | // Created by Dani Devesa on 30/06/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | enum PlaceholderStringLength { 13 | case short 14 | case long 15 | } 16 | 17 | static func loremImpsum(withLength length: PlaceholderStringLength = .short) -> String { 18 | let loremIpsum = "Lorem ipsum dolor amet dreamcatcher occupy esse occaecat snackwave. " + 19 | "Eiusmod raw denim put a bird on it yuccie fam yr cold-pressed palo santo aliquip " + 20 | "you probably haven't heard of them meh truffaut." 21 | 22 | switch length { 23 | case .short: return String(loremIpsum.prefix(67)) 24 | case .long: return loremIpsum 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsTests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - explicit_acl 3 | - explicit_top_level_acl 4 | - explicit_type_interface 5 | - file_length 6 | - trailing_closure 7 | - type_body_length 8 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsTests/DevelopingAccessibleAppsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DevelopingAccessibleAppsTests.swift 3 | // DevelopingAccessibleAppsTests 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | @testable import DevelopingAccessibleApps 10 | import XCTest 11 | 12 | class DevelopingAccessibleAppsTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | super.tearDown() 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsUITests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - explicit_acl 3 | - explicit_top_level_acl 4 | - explicit_type_interface 5 | - file_length 6 | - trailing_closure 7 | - type_body_length 8 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsUITests/DevelopingAccessibleAppsUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DevelopingAccessibleAppsUITests.swift 3 | // DevelopingAccessibleAppsUITests 4 | // 5 | // Created by Daniel Devesa Derksen-Staats on 07/04/2019. 6 | // Copyright © 2019 Desfici Ltd. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class DevelopingAccessibleAppsUITests: XCTestCase { 12 | override func setUp() { 13 | super.setUp() 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. 20 | // Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - 24 | // required for your tests before they run. The setUp method is a good place to do this. 25 | } 26 | 27 | override func tearDown() { 28 | super.tearDown() 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | } 31 | 32 | func testExample() { 33 | // Use recording to get started writing UI tests. 34 | // Use XCTAssert and related functions to verify your tests produce the correct results. 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DevelopingAccessibleApps/DevelopingAccessibleAppsUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Freeware License, some rights reserved 2 | 3 | Copyright (c) 2019 Daniel Devesa Derksen-Staats 4 | 5 | Permission is hereby granted, free of charge, to anyone obtaining a copy 6 | of this software and associated documentation files (the "Software"), 7 | to work with the Software within the limits of freeware distribution and fair use. 8 | This includes the rights to use, copy, and modify the Software for personal use. 9 | Users are also allowed and encouraged to submit corrections and modifications 10 | to the Software for the benefit of other users. 11 | 12 | It is not allowed to reuse, modify, or redistribute the Software for 13 | commercial use in any way, or for a user’s educational materials such as books 14 | or blog articles without prior permission from the copyright holder. 15 | 16 | The above copyright notice and this permission notice need to be included 17 | in all copies or substantial portions of the software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Developing Accessible iOS Apps: Support VoiceOver, Dynamic Type, and More*](https://www.apress.com/9781484253076) by [Daniel Devesa Derksen-Staats](https://twitter.com/dadederk) (Apress, 2019). 4 | 5 | [comment]: #cover 6 | ![Cover image](9781484253076.jpg) 7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ## What you'll learn 11 | * Accessibility labels: configure language, IPA (International phonetic alphabet), get VoiceOver to read puntuation marks, etc. 12 | * Improve the accessibility of UIKit controls in the context of your app: UISlider, UISwitch, etc. 13 | * Create custom actions for VoiceIOver and Switch Control users 14 | * Advanced VoiceOver gestures: Perform Escape, Magic Tap, etc. 15 | * Accessibility notifications: Notify your user that the scree/layout changed and posting announcements. 16 | * Support Dynamic Type. 17 | * Create an alternative layout when using large text sizes. 18 | * Support Smart Invert Colors. 19 | * Support Dynamic Type also in your web content. 20 | * Implement Large Content Viewer. 21 | 22 | ## Releases 23 | 24 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 25 | 26 | ## Contributions 27 | 28 | See the file Contributing.md for more information on how you can contribute to this repository. 29 | 30 | ## Setup 31 | 32 | ### SwiftLint (optional) 33 | 34 | [SwiftLint](https://github.com/realm/SwiftLint) analyses Swift code files for formatting style 35 | and coding conventions enforcement. Any warnings it generates are reported within Xcode. 36 | 37 | It is automatically run as part of the build process in our Xcode project. 38 | 39 | Installation using [Homebrew](https://brew.sh/): 40 | 41 | brew install swiftlint 42 | 43 | Configuration is done in `.swiftlint.yml` files. The main one is in the root of the project directory. 44 | There can be additional ones in sub-directories, such as `DevelopingAccessibleAppsTests/.swiftlint.yml`, 45 | which overrides some of the configuration for the unit test files. 46 | 47 | The [SwiftLint rules](https://realm.github.io/SwiftLint/rule-directory.html) are available in a list 48 | with explanations and code examples. 49 | 50 | --------------------------------------------------------------------------------