motion-accessibility 2.2 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0fda7b42011a96176aceec7228a2e39709678ffb
4
- data.tar.gz: 1ecf5a331e9f478340c90c487b3d4a74ce97c36b
3
+ metadata.gz: 3c821b39f3467d6525a96b0a695bed9b4450ee98
4
+ data.tar.gz: e6abe481288ab71e3b52d18dd9ff8ffc0856dc11
5
5
  SHA512:
6
- metadata.gz: 2500c41291c52741eb0393ca50cf845e5914674fe53ce64a8a66eec443338fabf0239a5fb1e7377f9b986fc19a2cf25c3ca20521ec35cfeadd4d329e26d78b03
7
- data.tar.gz: 1c43feb40d9621ca49f5c5551d4e6b87d396a4f89ad1fb019469717f0f7c7d3a9623ea89df9b136f4cb58e06a52346a306d6100d5bcf85e85ab15a8ffe93239e
6
+ metadata.gz: 92fda9f928f534f81fc2f45bf1d7b834c60927a7ec51880b79f15fcdbcb7775c8ec27d1ad9e5eb37435b196d78dbe5f8858ca4add557ceb2b97777611efbfe14
7
+ data.tar.gz: 9c61a401a1ccb1fcef1b0d02140cc3be343981e9f7f265609e9cd12de1883bc5d93ecc0d7c8cc04aeca2901c19713e68a3c9875700b732f40fc94277e8f59119
data/README.md CHANGED
@@ -4,9 +4,7 @@
4
4
 
5
5
  https://github.com/austinseraphin/motion-accessibility
6
6
 
7
- Motion-Accessibility wraps the UIAccessibility protocols in nice
8
- ruby. I hope that making them easier will encourage developers to use it
9
- more and make their apps accessible. The gem also includes an accessibility inspector and a text console to aide blind iOS developers.
7
+ Motion-accessibility provides the tools needed for sighted and blind iOS RubyMotion developers to make their apps more accessible. It wraps Apple’s UIAccessibility protocols in Ruby, and provides an accessibility inspector. It has a console for blind developers, since the iOS simulator doesn’t work well with VoiceOver. It also has automated accessibility testing for your views, and the accessibility doctor will help diagnose your problems and tell you how to fix them. You can build accessibility testing into your specs, so you will never break VoiceOver compatibility!
10
8
 
11
9
  ## Installation
12
10
 
@@ -38,24 +36,23 @@ The `browse` or `b` command lets you examine the view hierarchy in a speech-frie
38
36
  The following examples come from the sample app included with motion-accessibility.
39
37
 
40
38
  ```
41
- (main)> browse
42
- Browsing UIWindow
43
- 1 UILabel Hello!
44
- 2 Touchable UITextField
45
- 3 Touchable UIRoundedRectButton Update
46
- 4 UINavigationBar
47
- 5 UITabBar with 2 subviews
39
+ (main)> browse
40
+ Browsing UIWindow
41
+ 1 UIView with 3 subviews
42
+ 2 UINavigationBar with 2 subviews
43
+ 3 UITabBar with 3 subviews
48
44
  => nil
49
45
  ```
50
46
 
51
47
  If a view has subviews, you can browse that view.
52
48
 
53
49
  ```
54
- (main)> b 5
55
- Browsing UITabBar
56
- 0 Superview UIWindow
57
- 1 Touchable UITabBarButton Test App
58
- 2 Touchable UITabBarButton Table
50
+ ((main)> b 1
51
+ Browsing UIView
52
+ 0 Superview UIWindow
53
+ 1 UILabel Hello!
54
+ 2 Touchable UITextField
55
+ 3 Touchable UIButton Update
59
56
  => nil
60
57
  ```
61
58
 
@@ -63,71 +60,152 @@ You can refresh the browser by passing the `:refresh` or `:top` keyword.
63
60
 
64
61
  You may pass the `:scroll` keyword to scroll a UIScrollView or descendants, such as a UITableView. This still has some minor issues .
65
62
 
66
- You may pass any view as an argument to browse it. Use the `:refresh` or `:top` keywords to switch back to the running application.
67
-
68
63
  #### `view` or `v`
69
64
 
70
65
  The `view` or `v` command simply returns the current view. If you have just browsed a view, it will return that. Otherwise, you may specify the view you wish to browse. Note that for all the commands, you may either use its number or accessibility label.
71
66
 
72
67
  ```
73
- (main)> v 1
74
- => #<UITabBarButton:0x9380560>
68
+ (main)> v 1
69
+ => #<UILabel:0x8feda00>
75
70
  ```
76
71
 
77
72
  #### `touch`
78
73
  The `touch` command lets you interact with the various controls. It works on all standard UIControls. `touch` can accept an argument depending on the type of control. For example, you can pass a UITextField a string to set its value.
79
74
 
80
75
  ```
81
- (main)> touch 2,"motion-accessibility rocks!"
82
- Browsing UIWindow
83
- 1 UILabel Hello!
84
- 2 Touchable UITextField motion-accessibility rocks!
85
- 3 Touchable UIRoundedRectButton Update
86
- 4 UINavigationBar
87
- 5 UITabBar with 2 subviews
76
+ (main)> touch 2,"Motion-accessibility rocks!"
77
+ Browsing UIView
78
+ 0 Superview UIWindow
79
+ 1 UILabel Hello!
80
+ 2 Touchable UITextField Motion-accessibility rocks!
81
+ 3 Touchable UIButton Update
88
82
  => nil
89
83
  ```
90
84
 
91
85
  UIButtons can take a UIControlEvent, but default to `UIControlEventTouchUpInside`. Note here the use of an accessibility label to reference the view.
92
86
 
93
87
  ```
94
- (main)> touch "update"
95
- Browsing UIWindow
96
- 1 UILabel motion-accessibility rocks!
97
- 2 Touchable UITextField motion-accessibility rocks!
98
- 3 Touchable UIRoundedRectButton Update
99
- 4 UINavigationBar
100
- 5 UITabBar with 2 subviews
88
+ (main)> touch "update"
89
+ Browsing UIView
90
+ 0 Superview UIWindow
91
+ 1 UILabel Motion-accessibility rocks!
92
+ 2 Touchable UITextField Motion-accessibility rocks!
93
+ 3 Touchable UIButton Update
101
94
  => nil
102
95
  ```
103
96
 
104
97
  ### The Accessibility Inspector
105
98
 
106
- You can easily see the state of any of the following attributes and methods by using the accessibility inspector. Just call the `inspect_accessibility` method on any object.
99
+ You can easily see the state of any of the following attributes and methods by using the accessibility inspector. Just call `Accessibility.inspect` and pass any object as an argument.
107
100
 
108
101
  ```
109
- main)> label=UILabel.alloc.initWithFrame(CGRectMake(0, 0, 100, 100))
110
- => #<UILabel:0xb062870>
111
- (main)> label.text="Hello!"
112
- => "Hello!"
113
- (main)> label.inspect_accessibility
102
+ (main)> label=UILabel.alloc.initWithFrame(CGRect.new([0,0], [100,100]))
103
+ => #<UILabel:0x103b8c40>
104
+ (main)> label.text="Hello!"
105
+ => "Hello!"
106
+ (main)> Accessibility.inspect label
107
+ #<UILabel:0x103b8c40>
114
108
  Accessibility label: Hello!
115
109
  Accessibility hint: nil
116
110
  Accessibility traits: Static text
117
111
  Accessibility value: nil
118
112
  Accessibility language: nil
119
- Accessibility frame: x=0.0 y=20.0 width=320.0 height=189.3
120
- Accessibility activation point: x=160.0 y=114.7
113
+ Accessibility frame: x=0.0 y=0.0 width=100.0 height=100.0
114
+ Accessibility activation point: x=50.0 y=50.0
121
115
  Accessibility path: nil
122
116
  Accessibility view is modal: false
123
117
  Should group accessibility children: false
124
118
  Accessibility elements hidden: false
125
119
  Is accessibility element: true
126
120
  Accessibility identifier: nil
121
+ Accessible: true
127
122
  => nil
128
123
  ```
129
124
 
130
- By the way, `a11y` stands for `accessibility`, because it has a, then 11 letters, then y. Hence, you can use `inspect_a11y` as a shortcut. You can also use this abreviation when referring to the Accessibility class, for instance `A11y::Element`.
125
+ By the way, `a11y` stands for `accessibility`, because it has a, then 11 letters, then y. Hence, you can use `A11y.inspect` as a shortcut.
126
+
127
+ ### Automated Accessibility Testing
128
+
129
+ Below you will find detailed documentation about all of the accessibility protocols. Don’t feel overwhelmed. The accessibility inspector will tell you exactly what you have to do. Let’s start by creating an unlabeled button, the bane of all VOiceOver users.
130
+
131
+ ```
132
+ (main)> button=UIButton.new
133
+ => #<UIButton:0xd7831a0>
134
+ (main)> A11y.inspect button
135
+ #<UIButton:0xd7831a0>
136
+ Accessibility label: nil
137
+ Accessibility hint: nil
138
+ Accessibility traits: Button
139
+ Accessibility value: nil
140
+ Accessibility language: nil
141
+ Accessibility frame: x=0.0 y=0.0 width=0.0 height=0.0
142
+ Accessibility activation point: x=0.0 y=0.0
143
+ Accessibility path: nil
144
+ Accessibility view is modal: false
145
+ Should group accessibility children: false
146
+ Accessibility elements hidden: false
147
+ Is accessibility element: false
148
+ Accessibility identifier: nil
149
+ Accessible: false
150
+ 2014-05-27 19:02:50.209 motion-accessibility[8851:70b] #<UIButton:0xd7831a0>: You must set the accessibility_label. You can use the setTitle:forState method to do this on a button.
151
+ 2014-05-27 19:02:50.223 motion-accessibility[8851:70b] #<UIButton:0xd7831a0>: You must set is_accessibility_element=true to make VoiceOver aware of it. This will often happen automatically when a view becomes visible by giving it a frame and adding it to a subview.
152
+ => #<UIButton:0xd7831a0>
153
+ ```
154
+
155
+ This incorporates two features discussed below.
156
+
157
+ #### `accessible?`
158
+ Call the `accessible?` predicate on any object to determine its accessibility. Like all predicates it returns true or false. You can include this in your specs. If you build in accessibility testing you will never break accessibility, something even worse than no accessibility at all. For example, if you have a variable `@label` which contains a label, you could write:
159
+ ```
160
+ @label.should.be.accessible
161
+ ```
162
+ So simple! This works recursively. Say you run some functional tests on a view controller.
163
+
164
+ ```
165
+ tests Test_Controller
166
+ it “#accessible?`
167
+ controller.should.be.accessible
168
+ end
169
+ ```
170
+
171
+ Or for the ultimate in laziness:
172
+
173
+ ```
174
+ before do
175
+ @app=UIApplication.sharedApplication
176
+ end
177
+
178
+ it “accessible?” do
179
+ @app.should.be.accessible
180
+ end
181
+ ```
182
+ You may not want to do this however, because it can get confusing navigating down subview hierarchies, though it will report the path taken. Still, better to do that then nothing at all.
183
+
184
+ #### `Accessibility.doctor`
185
+ The accessibility doctor will report on what you have to do. It writes this to the NSLog. If given no arguments it will report on the last object called with the `accessible?` predicate. It returns the object with the problem, or nil if it finds nothing wrong. The accessibility inspector returns this as well. If a spec fails remember to put this before the test.
186
+
187
+ #### `accessibility_test`
188
+ Finally, you can specify which accessibility test applies to an object by setting this value. You may do this in the same way you set other attributes. You can use a setter:
189
+
190
+ ```
191
+ view=UIView.new
192
+ view.accessibility_test=:UILabel
193
+ ```
194
+
195
+ Or you may define it in a class. If you do this make sure that it returns a symbol of the class you want to test against, since it has no error checking unlike the setter.
196
+ ```
197
+ class Custom_View < UIView
198
+
199
+ def accessibility_test
200
+ :UILabel
201
+ end
202
+
203
+ end
204
+ ```
205
+
206
+ ### Accessibility vs. Usability
207
+
208
+ A difference exists between accessibility and usability, though often the two get lumped together under the umbrella of the former. Accessibility refers to whether or not a user can view and interact with something in a meaningful way. In this case, this means making VoiceOver aware of the different elements of your app. Usability gets into more intangible realms, and has to do with whether or not it actually makes sense to a user. In this case this means a blind human using VoiceOver, and only a blind human using VoiceOver can tell you this. A computer can test for accessibility, but only a human can test for usability.
131
209
 
132
210
  ### UIAccessibility Informal Protocol
133
211
 
@@ -171,15 +249,15 @@ Hints describe the results of performing an action. Only provide one when not ob
171
249
  #### `accessibility_traits`
172
250
 
173
251
  Traits describe an element's state, behavior, or usage. They tell
174
- VoiceOver how to respond to a view. To combine them, use the single vertical bar `|` binary or operator.
252
+ VoiceOver how to respond to a view. To combine them, use the single vertical bar `|` binary or operator. Remember to call `super.accessibility_traits` if defining them in a method.
175
253
 
176
- The `accessibility_attribute=` method accepts a symbol or array of symbols, and applies the accessibility_attribute method to them. For example, if a view displays an image that opens a link, you can do this.
254
+ The `accessibility_traits=` method also accepts a symbol or array of symbols, and applies the accessibility_traits method to them. For example, if a view displays an image that opens a link, you can do this.
177
255
 
178
256
  ```
179
257
  class ImageLinkView < UIView
180
258
  # ....
181
259
  def accessibility_traits
182
- :image.accessibility_trait|:link.accessibility_trait
260
+ super.accessibility_traits|:image.accessibility_trait|:link.accessibility_trait
183
261
  end
184
262
  end
185
263
  ```
@@ -252,7 +330,7 @@ Ignores elements within views which are siblings of the receiver. If you present
252
330
 
253
331
  #### `group_accessibility_children?` or `should_group_accessibility_children`
254
332
 
255
- VoiceOver gives two ways to browse the screen. The user can drag their finger around the screen and hear the contents. They can also swipe right or left with one finger to hear the next or previous element. Normally, VoiceOver reads from left to right, and from top to bottom. Sometimes this can get confusing, depending on the layout of the screen. Setting this to true tells VoiceOver to read the views in the order defined in the subviews array.
333
+ VoiceOver gives two ways to browse the screen. The user can drag their finger around the screen and hear the contents. They can also swipe right or left with one finger to hear the next or previous element. When swiping to the next element, VoiceOver reads the elements from left to right, and from top to bottom. Sometimes this can get confusing, depending on the layout of the screen. Setting this to true tells VoiceOver to read the views in the order defined in the subviews array.
256
334
 
257
335
  #### `accessibility_elements_hidden?` or `accessibility_elements_hidden`
258
336
 
@@ -313,9 +391,9 @@ Increments the value of the accessibility element. Make sure to have the :adjust
313
391
 
314
392
  Decrements the value of the accessibility element. Make sure to have the :adjustable accessibility trait set for this to work.
315
393
 
316
- ### UIAccessibilityElement
394
+ ### Accessibility::Element
317
395
 
318
- If you have something in your view that does not inherit from UIView or UIControl and you want to make it accessible, you need to define it as an accessibility element. Accessibility elements belong to an accessibility container, in other words the view which contains them. To create one, just call `Accessibility::Element.new` with the container, usually self. Like a UIView, an accessibility element has attributes, and you get and set them in exactly the same way.
396
+ If you have something in your view that does not inherit from UIView or UIControl and you want to make it accessible, you need to define it as an accessibility element. Accessibility elements belong to an accessibility container, in other words the view which contains them. To create one, just call `Accessibility::Element.init_with_accessibility_container` with the container, usually self. Like a UIView, an accessibility element has attributes, and you get and set them in exactly the same way.
319
397
 
320
398
  ```
321
399
  class CustomView < UIView
@@ -325,6 +403,7 @@ super
325
403
  # …
326
404
  accessibility=Accessibility::Element.new(self)
327
405
  accessibility.label="Hello."
406
+ accessibility.hint=“Presses the magic button”
328
407
  accessibility.frame=view.frame
329
408
  accessibility.traits=:button
330
409
  end
@@ -458,3 +537,7 @@ iOS 7 adds some speech attributes to use in attributed strings. To get them, jus
458
537
  3. Commit your changes (`git commit -am 'Add some feature'`)
459
538
  4. Push to the branch (`git push origin my-new-feature`)
460
539
  5. Create new Pull Request
540
+
541
+ ## A Special Offer for You
542
+
543
+ I do [freelance accessibility consulting.](austinseraphin.com) If you use my gem I will give you a discount. Contact me for more information.
@@ -42,22 +42,19 @@ Attributes = {
42
42
  Element_Attributes = {
43
43
  :container => :accessibilityContainer,
44
44
  :container= => :setAccessibilityContainer,
45
- :frame => :accessibilityFrame,
46
- :frame= => :setAccessibilityFrame,
47
- :hint => :accessibilityHint,
48
- :hint= => :setAccessibilityHint,
49
45
  :label => :accessibilityLabel,
50
46
  :label= => :setAccessibilityLabel,
47
+ :hint => :accessibilityHint,
48
+ :hint= => :setAccessibilityHint,
51
49
  :traits => :accessibilityTraits,
52
50
  :value => :accessibilityValue,
53
51
  :value= => :setAccessibilityValue,
52
+ :frame => :accessibilityFrame,
53
+ :frame= => :setAccessibilityFrame,
54
54
  :did_become_focused => :accessibilityElementDidBecomeFocused,
55
55
  :did_lose_focus => :accessibilityElementDidLoseFocus,
56
56
  :is_focused => :accessibilityElementIsFocused,
57
- :focused? => :accessibilityElementIsFocused,
58
- :is_accessibility_element => :isAccessibilityElement,
59
- :is_accessibility_element= => :setIsAccessibilityElement,
60
- :accessibility_element? => :isAccessibilityElement
57
+ :focused? => :accessibilityElementIsFocused
61
58
  }
62
59
 
63
60
  Traits = {
@@ -204,7 +201,7 @@ Touchable_Types = ["UITextField", "UIButton", "UIPickerView", "UIDatePicker",
204
201
  "UISegmentedControl", "UISlider", "UIStepper", "UISwitch",
205
202
  "UITableViewCell", "UITabBarButton","UINavigationItemButtonView"]
206
203
 
207
- Ignored_Views = ["UIView", "UILayoutContainerView", "UITransitionView", "UINavigationTransitionView", "UIViewControllerWrapperView", "UITableViewCellContentView", "UINavigationItemView", "UITableViewWrapperView"]
204
+ Ignored_Views = ["UILayoutContainerView", "UITransitionView", "UINavigationTransitionView", "UIViewControllerWrapperView", "UITableViewCellContentView", "UINavigationItemView", "UITableViewWrapperView"]
208
205
 
209
206
  Ignored_ImageViews = ["UINavigationBar", "UITabBar", "UITableView"]
210
207
 
@@ -1,12 +1,9 @@
1
1
  class UIAccessibilityElement
2
2
 
3
- def initialize(container)
4
- raise "Please initialize with a container, usually self." unless container
5
- UIAccessibilityElement.alloc.initWithAccessibilityContainer(self)
3
+ def self.init_with_accessibility_container(container)
4
+ UIAccessibilityElement.alloc.initWithAccessibilityContainer(container)
6
5
  end
7
6
 
8
- alias :init_with_accessibility_container :initialize
9
-
10
7
  Accessibility::Element_Attributes.each do |ruby,ios|
11
8
  if ruby=~/=$/
12
9
  define_method(ruby) {|value| self.send(ios,value)}
@@ -22,18 +19,8 @@ define_method(ruby) {self.send(ios)}
22
19
  end
23
20
  end
24
21
 
25
- def accessibility_traits=(traits)
26
- bits=0
27
- if traits.kind_of?(Fixnum)
28
- bits=traits
29
- elsif traits.kind_of?(Symbol)
30
- bits=traits.accessibility_trait
31
- elsif traits.kind_of?(Array)
32
- traits.each {|trait| bits|=trait.accessibility_trait}
33
- else
34
- raise "Pass a bitmask, a symbol, or an array to accessibility_traits="
35
- end
36
- OAself.accessibilityTraits=bits
22
+ def traits=(traits)
23
+ self.accessibility_traits=traits
37
24
  end
38
25
 
39
26
  if self.respond_to?(:method_added)
@@ -61,6 +48,20 @@ define_method(ruby) { self.send(ios)}
61
48
  end
62
49
  end
63
50
 
51
+ def self.container?(obj)
52
+ !obj.accessibility_element_at_index(0).nil?
53
+ end
54
+
64
55
  end
65
56
 
66
57
  Accessibility::Element=UIAccessibilityElement
58
+
59
+ class NSObject
60
+
61
+ def each_accessibility_element
62
+ return unless A11y::Element.container?(self)
63
+ self.accessibility_element_count.times {|n| yield(self.accessibility_element_at_index(n))}
64
+ end
65
+
66
+ end
67
+
@@ -1,33 +1,36 @@
1
1
  # Accessibility Inspector
2
2
 
3
- class Object
3
+ module Accessibility
4
4
 
5
- def inspect_accessibility
5
+ def self.inspect(obj)
6
6
  displayed=[]
7
+ if obj.class==Accessibility::Element
8
+ attributes=A11y::Element_Attributes.dup
9
+ else
7
10
  attributes=Accessibility::All_Attributes.dup
8
- attributes.merge(Accessibility::PickerView_Attributes) if self.class==UIPickerView
9
- puts self.inspect
11
+ attributes.merge(Accessibility::PickerView_Attributes) if obj.class==UIPickerView
12
+ end
13
+ puts obj.inspect
10
14
  attributes.each do |ruby,ios|
11
15
  next if ios=~/^set/
12
16
  next if displayed.member?(ios)
13
- self.inspect_accessibility_attribute(ios)
17
+ self.inspect_accessibility_attribute(obj,ios)
14
18
  displayed<<ios
15
19
  end
16
- nil
20
+ puts "Accessibility test: #{obj.accessibility_test}" if obj.accessibility_test
21
+ puts "Accessibility container: #{obj.accessibility_element_count} elements" if A11y::Element.container?(obj)
22
+ puts "Accessible: #{obj.accessible?}"
23
+ A11y.doctor
17
24
  end
18
25
 
19
- alias :inspect_a11y :inspect_accessibility
20
-
21
- protected
22
-
23
- def inspect_accessibility_attribute(attribute)
26
+ def self.inspect_accessibility_attribute(obj,attribute)
24
27
  name=attribute.gsub(/(.)([A-Z])/,'\1 \2').capitalize
25
28
  if Accessibility::Attribute_Types.member?(attribute)
26
29
  begin
27
30
  case attribute
28
- when :accessibilityTraits then value=inspect_accessibility_traits
31
+ when :accessibilityTraits then value=self.inspect_accessibility_traits(obj)
29
32
  else
30
- value=self.send(attribute)
33
+ value=obj.send(attribute)
31
34
  end
32
35
  if value
33
36
  case Accessibility.attribute_type(attribute)
@@ -50,4 +53,17 @@ puts "#{name}: Defined"
50
53
  end
51
54
  end
52
55
 
56
+ def self.inspect_accessibility_traits(obj)
57
+ return obj.accessibility_traits if obj.accessibility_traits>Accessibility::Traits.values.max
58
+ traits=[]
59
+ Accessibility::Traits.each do |trait, bitmask|
60
+ if obj.accessibility_traits&bitmask>0
61
+ name=trait.gsub(/_/,' ').capitalize
62
+ traits<<name
63
+ end
64
+ end
65
+ traits=["None"] if traits.empty?
66
+ traits.join(', ')
67
+ end
68
+
53
69
  end
@@ -70,7 +70,7 @@ found=$browser_current.find(request)
70
70
  if found
71
71
  if found.subviews.empty?
72
72
  $browser_cursor=found
73
- return found.view.inspect_a11y
73
+ return A11y.inspect found.view
74
74
  end
75
75
  A11y::Console.init unless A11y::Data[:refresh]
76
76
  $browser_current=found
@@ -27,6 +27,12 @@ self.subviews.each_index {|index| return false unless self.subviews[index]==othe
27
27
  return true
28
28
  end
29
29
 
30
+ def [](n)
31
+ a=[@view]
32
+ a+=@subviews if @subviews
33
+ a[n]
34
+ end
35
+
30
36
  def browsable_nodes
31
37
  nodes=[@superview]
32
38
  if @subviews
@@ -80,11 +86,12 @@ display.join(" ")
80
86
  end
81
87
 
82
88
  def self.accessible_view?(view)
83
- return view.accessibility_element?||view.accessibility_label||view.accessibility_value||view.accessibility_traits
89
+ result= view.accessibility_element?||view.accessibility_label||view.accessibility_value||view.accessibility_traits
90
+ (result)?true:false
84
91
  end
85
92
 
86
93
  def self.ignore_view?(view)
87
- return true if view.subviews.empty?&&!self.accessible_view?(view)
94
+ return true if view.subviews.empty?&&!view.accessible?
88
95
  if view.superview
89
96
  sv=view.superview
90
97
  while sv&&self.ignore_view?(sv)
@@ -102,6 +109,13 @@ def self.build(view=nil, superview=nil)
102
109
  tree=self.new
103
110
  view=UIApplication.sharedApplication.keyWindow if view.nil?
104
111
  subviews=[]
112
+ if A11y::Element.container?(view)
113
+ view.each_accessibility_element do |element|
114
+ subview_tree=self.build(element, tree)
115
+ subviews<<subview_tree
116
+ end
117
+ end
118
+ if view.respond_to?(:subviews)
105
119
  view.subviews.each do |subview|
106
120
  subview_tree=self.build(subview, tree)
107
121
  if self.ignore_view?(subview)
@@ -111,6 +125,7 @@ else
111
125
  subviews<<subview_tree
112
126
  end
113
127
  end
128
+ end
114
129
  tree.view=view
115
130
  tree.subviews=subviews
116
131
  tree.superview=superview
@@ -50,18 +50,6 @@ self.accessibilityTraits=bits
50
50
  self
51
51
  end
52
52
 
53
- def inspect_accessibility_traits
54
- traits=[]
55
- Accessibility::Traits.each do |trait, bitmask|
56
- if self.accessibility_traits&bitmask>0
57
- name=trait.gsub(/_/,' ').capitalize
58
- traits<<name
59
- end
60
- end
61
- traits=["None"] if traits.empty?
62
- traits.join(', ')
63
- end
64
-
65
53
  if self.respond_to?(:method_added)
66
54
  class << self
67
55
  alias :method_added_motion_accessibility :method_added
@@ -0,0 +1,456 @@
1
+ module Accessibility
2
+ module Test
3
+
4
+ Path=Array.new
5
+ Data= {
6
+ depth: 0,
7
+ debug: false
8
+ }
9
+
10
+ Options = {
11
+ recurse: true
12
+ }
13
+
14
+ Tests = {
15
+ NSObject: {
16
+ accessibility_label: [String, "You must set an accessibility label to tell VoiceOver what to read."],
17
+ accessibility_traits: [UIAccessibilityTraitNone, "You must set accessibility_trait to :none.accessibility_trait"],
18
+ accessibility_value: nil,
19
+ accessibility_frame: [CGRect, "You must set an accessibility_frame to tell VoiceOver the bounds of the view." ],
20
+ accessibility_activation_point: [CGPoint, "You must set an accessibility_activation_point so VoiceOver knows where to touch."],
21
+ accessibility_path: nil,
22
+ accessibility_view_is_modal: false,
23
+ should_group_accessibility_children: false,
24
+ accessibility_elements_hidden: false,
25
+ is_accessibility_element: [true, "You must set is_accessibility_element=true to make VoiceOver aware of it. This will often happen automatically when a view becomes visible by giving it a frame and adding it to a subview."]
26
+ },
27
+ UIActionSheet: {
28
+ accessibility_label: nil,
29
+ is_accessibility_element: false,
30
+ accessibility_view_is_modal: true
31
+ },
32
+ UIActivityIndicatorView: {
33
+ accessibility_label: [String, "You must set the accessibility_label to the title of the activity indicator."],
34
+ accessibility_value: [String, "You must set the accessibility_value to the value of the indicator."],
35
+ accessibility_elements_hidden: true,
36
+ is_accessibility_element: false
37
+ },
38
+ UIApplication: {
39
+ accessibility_label: [String,"You must set the accessibility_label. Setting the app's title will do this."],
40
+ is_accessibility_element: false,
41
+ options: {
42
+ test: :application
43
+ }
44
+ },
45
+ UIBarItem: {
46
+ title: [String, "Set the title to tell VoiceOver what to say."],
47
+ accessibility_label: nil,
48
+ is_accessibility_element: false,
49
+ },
50
+ UIButton: {
51
+ accessibility_label: [String,"You must set the accessibility_label. You can use the setTitle:forState method to do this on a button."],
52
+ accessibility_traits: UIAccessibilityTraitButton,
53
+ },
54
+ UICollectionReusableView: {
55
+ accessibility_label: nil,
56
+ is_accessibility_element: false},
57
+ UIDatePicker: {
58
+ accessibility_label: nil,
59
+ is_accessibility_element: false,
60
+ options: {
61
+ recurse: false
62
+ }
63
+ },
64
+ UIImageView: {
65
+ accessibility_label: nil,
66
+ accessibility_traits: [UIAccessibilityTraitImage, "You must set accessibility_trait to :image"],
67
+ is_accessibility_element: false
68
+ },
69
+ UILabel: {
70
+ accessibility_label: [String, "You must set the accessibility_label. You can use the text method to do this."],
71
+ accessibility_traits: [UIAccessibilityTraitStaticText, "You must set accessibility_traits to :static_text"]
72
+ },
73
+ UINavigationBar: {
74
+ accessibility_label: nil,
75
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
76
+ accessibility_elements_hidden: false,
77
+ should_group_accessibility_children: true,
78
+ accessibility_identifier: [String, "You must set the accessibility_identifier to the title of the view. You can set the title of the view controller or of the navigation item."],
79
+ is_accessibility_element: false,
80
+ options: {
81
+ recurse: false,
82
+ test: :bar
83
+ }
84
+ },
85
+ UINavigationController: {
86
+ accessibility_label: nil,
87
+ is_accessibility_element: false,
88
+ options: {
89
+ recurse: false,
90
+ test: :navigationViewController
91
+ }
92
+ },
93
+ UINavigationTransitionView: {
94
+ accessibility_label: nil,
95
+ should_group_accessibility_children: true,
96
+ is_accessibility_element: false
97
+ },
98
+ UIPageControl: {
99
+ accessibility_label: nil,
100
+ is_accessibility_element: false,
101
+ accessibility_value: [String, "You must set the accessibility_value to something meaningful, for example 'Page 1 of 1'"],
102
+ accessibility_traits: UIAccessibilityTraitUpdatesFrequently
103
+ },
104
+ UIPickerView: {
105
+ accessibility_label: nil,
106
+ is_accessibility_element: false,
107
+ options: {
108
+ recurse: false,
109
+ test: :pickerView
110
+ }
111
+ },
112
+ UIAccessibilityPickerComponent: {
113
+ accessibility_label: nil,
114
+ accessibility_traits: Fixnum,
115
+ accessibility_value: String
116
+ },
117
+ UIProgressView: {
118
+ accessibility_label: String,
119
+ accessibility_traits: UIAccessibilityTraitUpdatesFrequently,
120
+ accessibility_value: [String, "The accessibility_value should contain a textual representation of the progress, for instance \"50%\""],
121
+ is_accessibility_element: false
122
+ },
123
+ UISegment: {
124
+ accessibility_label: String,
125
+ accessibility_traits: [UIAccessibilityTraitButton, "You must make this a button by setting accessibility_trait to :button"],
126
+ accessibility_value: String
127
+ },
128
+ UISegmentedControl: {
129
+ accessibility_label: nil,
130
+ is_accessibility_element: false,
131
+ should_group_accessibility_children: true
132
+ },
133
+ UISlider: {
134
+ accessibility_label: nil,
135
+ accessibility_value: String,
136
+ accessibility_traits: UIAccessibilityTraitAdjustable,
137
+ options: {recurse: false}
138
+ },
139
+ UIStepper: {
140
+ accessibility_label: nil,
141
+ is_accessibility_element: false,
142
+ options: {recurse: false}
143
+ },
144
+ UISwitch: {
145
+ accessibility_label: nil,
146
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t, custom: UIAccessibilityTraitButton, message: "You can use :button.")},
147
+ accessibility_value: [String, "You must set the accessibility_value to \"1\" or \"0\""]
148
+ },
149
+ UITabBar: {
150
+ accessibility_label: nil,
151
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
152
+ should_group_accessibility_children: true,
153
+ is_accessibility_element: false,
154
+ options: {
155
+ recurse: false,
156
+ test: :bar
157
+ }
158
+ },
159
+ UITabBarButton: {
160
+ accessibility_label: [String, "You must set the title of this button. You can se tthe title of the UITabBarItem."],
161
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Bignum, custom: UIAccessibilityTraitButton, message: "You can use :button.")},
162
+ },
163
+ UITabBarController: {
164
+ accessibility_label: nil,
165
+ is_accessibility_element: false,
166
+ options: {
167
+ recurse: false,
168
+ test: :tabBarViewController
169
+ }
170
+ },
171
+ UITableView: {
172
+ accessibility_label: [String, "You must set the accessibility_label to the default contents of the table view, for example \"Empty List\""],
173
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
174
+ should_group_accessibility_children: true,
175
+ is_accessibility_element: false
176
+ },
177
+ UITableViewCell: {
178
+ accessibility_label: :ignore,
179
+ accessibility_value: :ignore,
180
+ is_accessibility_element: false,
181
+ options: {
182
+ recurse: false,
183
+ test: :tableViewCell
184
+ }
185
+ },
186
+ UITableViewCellAccessibilityElement: {
187
+ accessibility_label: :ignore,
188
+ accessibility_traits: :ignore,
189
+ accessibility_value: String,
190
+ should_group_accessibility_children: :ignore,
191
+ is_accessibility_element: :ignore
192
+ },
193
+ UITableTextAccessibilityElement: {
194
+ accessibility_label: [String, "Set the accessibility_label to the text of the view."],
195
+ accessibility_traits: UIAccessibilityTraitStaticText,
196
+ accessibility_value: String,
197
+ },
198
+ UITableViewHeaderFooterView: {
199
+ accessibility_label: [String, "Set the accessibility_label to tell VoiceOver what to read. You can do this with the textLabel.text property."]
200
+ },
201
+ UITextField: {
202
+ accessibility_label: nil,
203
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Fixnum)},
204
+ accessibility_value: [:something, "You must set the text of the textfield."],
205
+ is_accessibility_element: false
206
+ },
207
+ UIAccessibilityTextFieldElement: {
208
+ accessibility_label: nil,
209
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t, apple: Fixnum)},
210
+ accessibility_value: [:something, "You must set the text of the textfield."],
211
+ is_accessibility_element: true
212
+ },
213
+ UIToolbar: {
214
+ accessibility_label: nil,
215
+ accessibility_traits: ->(t){A11y::Test.nonstandard(t)},
216
+ should_group_accessibility_children: true,
217
+ is_accessibility_element:false,
218
+ options: {
219
+ test: :bar
220
+ }
221
+ },
222
+ UIToolbarButton: {
223
+ accessibility_label: [String, "You must set the accessibility_label. You can do this by setting the UIBarButtonItem's title with the setTItle:style:target:action: method."],
224
+ accessibility_traits: UIAccessibilityTraitButton
225
+ },
226
+ UIView: {
227
+ accessibility_label: nil,
228
+ is_accessibility_element: false
229
+ },
230
+ UIViewController: {
231
+ accessibility_label: nil,
232
+ is_accessibility_element: false,
233
+ options: {
234
+ test: :viewController
235
+ }
236
+ },
237
+ UIViewControllerWrapperView: {
238
+ accessibility_label: nil,
239
+ should_group_accessibility_children: true,
240
+ is_accessibility_element: false
241
+ },
242
+ UIWebView: {
243
+ accessibility_label: nil,
244
+ is_accessibility_element: false,
245
+ options: {
246
+ recurse: false
247
+ }
248
+ },
249
+ UIWindow: {
250
+ accessibility_label: nil,
251
+ is_accessibility_element: false,
252
+ options: {
253
+ test: :window
254
+ }
255
+ }
256
+ }
257
+
258
+ def self.debug
259
+ Data[:debug]
260
+ end
261
+ def self.debug=(d)
262
+ Data[:debug]=d
263
+ end
264
+
265
+ def self.nonstandard(value, options={})
266
+ options[:apple]||=Bignum
267
+ options[:custom]||=:none.accessibility_trait
268
+ return true if value.class==options[:apple]||value==options[:custom]
269
+ options[:attribute]||=:accessibility_traits
270
+ message="Apple has this set to a non-standard value."
271
+ options[:message]||="Hopefully you can get away with using :none."
272
+ options[:message]="#{options[:attribute]}: #{message} #{options[:message]}"
273
+ A11y::Test::Log.add(Path, options[:message])
274
+ false
275
+ end
276
+
277
+ def self.application(app)
278
+ self.run_tests(app.keyWindow)
279
+ end
280
+
281
+ def self.bar(obj)
282
+ result=true
283
+ obj.items {|item| result=result&&self.run_tests(item)}
284
+ result
285
+ end
286
+
287
+ def self.container(container)
288
+ container.accessibility_element_count.times {|n| return false unless container.accessibility_element_at_index(n).accessible?}
289
+ true
290
+ end
291
+
292
+ def self.navigationViewController(controller)
293
+ result=true
294
+ result&&self.run_tests(controller.navigationBar)
295
+ controller.viewControllers.each {|c| result=result&&self.run_tests(c)}
296
+ result
297
+ end
298
+
299
+ def self.pickerView(picker)
300
+ result=true
301
+ picker.numberOfComponents.times do |component|
302
+ picker.numberOfRowsInComponent(component).times do |row|
303
+ title=picker.delegate.pickerView(picker, titleForRow: row, forComponent: component)
304
+ view=picker. viewForRow(row, forComponent: component)
305
+ if !title&&!self.run_tests(view)
306
+ A11y::Test::Log(Path, picker.inspect+": component #{component} row #{row} not accessible. You can use the pickerView:titleForRow:forComponent or pickerView:accessibility_label_for_component methods to do this.")
307
+ result=false
308
+ end
309
+ end
310
+ end
311
+ result
312
+ end
313
+
314
+ def self.tabBarViewController(controller)
315
+ result=true
316
+ result&&self.run_tests(controller.tabBar)
317
+ controller.viewControllers.each {|c| result=result&&self.run_tests(c)}
318
+ result
319
+ end
320
+
321
+ def self.tableViewCell(cell)
322
+ return true if cell.accessibility_label||cell.textLabel.text
323
+ A11y::Test::Log.add(Path, "Please set the accessibility_label of the UITableViewCell. You can do this by setting the textLabel.text property.")
324
+ false
325
+ end
326
+
327
+ def self.viewController(controller)
328
+ self.run_tests(controller.view)
329
+ end
330
+
331
+ def self.window(window)
332
+ self.run_tests(window.rootViewController)
333
+ end
334
+
335
+ def self.find_tests(obj)
336
+ return Tests[obj] if obj.kind_of?(Symbol)
337
+ obj_tests=A11y::Test::Tests[:NSObject].clone
338
+ cl=obj.class
339
+ class_name=cl.to_s.to_sym
340
+ if obj.accessibility_test
341
+ tests=Tests[obj.accessibility_test]
342
+ else
343
+ tests=Tests[class_name]
344
+ until tests do
345
+ cl=cl.superclass
346
+ class_name=cl.to_s.to_sym
347
+ tests=Tests[class_name]
348
+ end
349
+ end
350
+ return self.find_tests(tests) if tests.kind_of?(Symbol)
351
+ tests.each do |attribute, test|
352
+ obj_tests[attribute]=test
353
+ end
354
+ obj_tests
355
+ end
356
+
357
+ def self.run_test(obj, attribute, expected, message=nil)
358
+ return true if expected==:ignore
359
+ value=obj.send(attribute) if obj.respond_to?(attribute)
360
+ return value if expected==:something
361
+ result=true
362
+ if expected.class==Class
363
+ if value.class!=expected
364
+ result=false
365
+ message||="#{attribute} must have an object of type #{expected} instead of #{value}"
366
+ end
367
+ elsif expected.kind_of?(Proc)
368
+ r=expected.call(value)
369
+ unless r
370
+ result=false
371
+ message||="The test function for #{attribute} failed."
372
+ end
373
+ else
374
+ unless expected==value
375
+ result=false
376
+ message||="#{attribute} must have the value \"#{expected}\" instead of \"#{value}\""
377
+ end
378
+ end
379
+ A11y::Test::Log.add(Path, message) unless result
380
+ puts "Testing #{attribute}... #{result}" if Data[:debug]
381
+ result
382
+ end
383
+
384
+ def self.run_tests(obj)
385
+ puts "Entering run_tests: #{obj.inspect}" if Data[:debug]
386
+ if Data[:depth]==0
387
+ A11y::Test::Log::Events.clear
388
+ Path.clear
389
+ end
390
+ Path<<obj
391
+ tests=self.find_tests(obj)
392
+ tests[:options]||={}
393
+ tests[:options]=self::Options.merge(tests[:options])
394
+ result=true
395
+ tests.each do |attribute, test|
396
+ next if attribute==:options
397
+ if test.kind_of?(Array)
398
+ (expected, message)=test
399
+ this_result=self.run_test(obj, attribute, expected, message)
400
+ else
401
+ this_result=self.run_test(obj, attribute, test)
402
+ end
403
+ result=result&&this_result
404
+ end
405
+ after=tests[:options][:test]
406
+ if after&&self.respond_to?(after)
407
+ puts "Running the after test: #{after}" if Data[:debug]
408
+ Data[:depth]=Data[:depth]+1
409
+ this_result=self.send(after, obj)
410
+ result=result&&this_result
411
+ Data[:depth]=Data[:depth]-1
412
+ end
413
+ if result&&A11y::Element.container?(obj)
414
+ result=result&&self.container(obj)
415
+ end
416
+ if result&&tests[:options][:recurse]&&obj.respond_to?(:subviews)&&obj.subviews
417
+ puts "Recursing..." if Data[:debug]
418
+ Data[:depth]=Data[:depth]+1
419
+ obj.subviews.each {|view| result=result&&A11y::Test.run_tests(view)}
420
+ Data[:depth]=Data[:depth]-1
421
+ end
422
+ Path.pop
423
+ puts "Returning #{result}" if Data[:debug]
424
+ result
425
+ end
426
+
427
+ end
428
+
429
+ def self.doctor(view=nil)
430
+ view.accessible? if view
431
+ return if A11y::Test::Log::Events.empty?
432
+ A11y::Test::Log::Events.each do |event|
433
+ NSLog(event.to_s)
434
+ end
435
+ A11y::Test::Log::Events.last.path.last
436
+ end
437
+
438
+ end
439
+
440
+ class NSObject
441
+
442
+ attr_reader :accessibility_test
443
+
444
+ def accessibility_test=(t)
445
+ t=t.to_s.to_sym if t.kind_of?(Class)
446
+ @accessibility_test=t if A11y::Test::Tests[t]
447
+ @accessibility_test
448
+ end
449
+
450
+ def accessible?
451
+ A11y::Test::Data[:depth]=0
452
+ A11y::Test.run_tests(self)
453
+ end
454
+
455
+ end
456
+
@@ -0,0 +1,25 @@
1
+ class Accessibility
2
+ class Test
3
+ class Log
4
+
5
+ Events=Array.new
6
+ attr_reader :path, :message
7
+
8
+ def initialize(path, message)
9
+ @path=path||Array.new
10
+ @message=message
11
+ end
12
+
13
+ def to_s
14
+ @path.join(" -> ")+": "+@message
15
+ end
16
+
17
+ def self.add(path, name)
18
+ event=self.new(path.clone, name)
19
+ Events<<event
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-accessibility
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.2'
4
+ version: '3.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Seraphin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-20 00:00:00.000000000 Z
11
+ date: 2014-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,7 +24,8 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- description: AA RubyMotion wrapper around the UIAccessibility procotols
27
+ description: A RubyMotion gem to make iOS accessibility easier for the sighted and
28
+ blind developer
28
29
  email:
29
30
  - austin@austinseraphin.com
30
31
  executables: []
@@ -44,6 +45,8 @@ files:
44
45
  - lib/project/object.rb
45
46
  - lib/project/picker.rb
46
47
  - lib/project/status.rb
48
+ - lib/project/test.rb
49
+ - lib/project/test_log.rb
47
50
  homepage: https://github.com/austinseraphin/motion-accessibility
48
51
  licenses:
49
52
  - GPL
@@ -64,9 +67,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
67
  version: '0'
65
68
  requirements: []
66
69
  rubyforge_project:
67
- rubygems_version: 2.0.3
70
+ rubygems_version: 2.0.6
68
71
  signing_key:
69
72
  specification_version: 4
70
73
  summary: This gem provides easy ruby-like wrappers around the protocols which interact
71
- with VoiceOver and other assistive technologies. Making accessibility accessible!
74
+ with VoiceOver and other assistive technologies. It has an accessibiity inspector,
75
+ a text console, and automated testing. Making accessibility accessible!
72
76
  test_files: []