├── .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 |
20 |
21 |
22 |
23 |
24 |
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 |
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 |
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 |
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 |
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 |
23 |
24 |
25 |
26 |
27 |
28 |
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/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 |
30 |
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 |
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 |
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 |
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 |
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 |
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 |
23 |
24 |
25 |
26 |
27 |
28 |
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/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 |
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 |
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 |
27 |
28 |
29 |
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/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 |
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 |
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 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
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 |
28 |
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/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 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
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 |
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 |
20 |
21 |
22 |
23 |
24 |
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 |
28 |
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 |
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 |
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 | 
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 |
--------------------------------------------------------------------------------