Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility support for iCarousel #653

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
0148359513A36C3700E687AC /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0148358F13A36C3700E687AC /* MainWindow.xib */; };
0148359613A36C3700E687AC /* iCarouselExampleViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0148359113A36C3700E687AC /* iCarouselExampleViewController.xib */; };
018AE98A179D7EBA00FB7311 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 018AE989179D7EBA00FB7311 /* Default-568h@2x.png */; };
1D59A4501B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D59A44F1B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.m */; };
B2E5E6C1146145C900EADB7A /* iCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5E6C0146145C900EADB7A /* iCarousel.m */; };
B2E5E72D1461544B00EADB7A /* background.png in Resources */ = {isa = PBXBuildFile; fileRef = B2E5E72B1461544B00EADB7A /* background.png */; };
B2E5E72E1461544B00EADB7A /* page.png in Resources */ = {isa = PBXBuildFile; fileRef = B2E5E72C1461544B00EADB7A /* page.png */; };
Expand All @@ -39,6 +40,8 @@
0148359213A36C3700E687AC /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/iCarouselExampleViewController.xib; sourceTree = SOURCE_ROOT; };
018AE989179D7EBA00FB7311 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = SOURCE_ROOT; };
018BBCB413A375AF005CA505 /* iCarouselExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iCarouselExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
1D59A44E1B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "iCarousel+AccessibilityScrolling.h"; sourceTree = "<group>"; };
1D59A44F1B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "iCarousel+AccessibilityScrolling.m"; sourceTree = "<group>"; };
B2E5E6BF146145C900EADB7A /* iCarousel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iCarousel.h; sourceTree = "<group>"; };
B2E5E6C0146145C900EADB7A /* iCarousel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iCarousel.m; sourceTree = "<group>"; };
B2E5E72B1461544B00EADB7A /* background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = background.png; path = ../Resources/background.png; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -123,6 +126,8 @@
children = (
B2E5E6BF146145C900EADB7A /* iCarousel.h */,
B2E5E6C0146145C900EADB7A /* iCarousel.m */,
1D59A44E1B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.h */,
1D59A44F1B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.m */,
);
name = iCarousel;
path = ../../iCarousel;
Expand Down Expand Up @@ -203,6 +208,7 @@
buildActionMask = 2147483647;
files = (
0148358813A36C2000E687AC /* main.m in Sources */,
1D59A4501B37AEC70092AB37 /* iCarousel+AccessibilityScrolling.m in Sources */,
0148359313A36C3700E687AC /* iCarouselExampleViewController.m in Sources */,
0148359413A36C3700E687AC /* iCarouselExampleAppDelegate.m in Sources */,
B2E5E6C1146145C900EADB7A /* iCarousel.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
0148359413A36C3700E687AC /* iCarouselExampleAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0148358D13A36C3700E687AC /* iCarouselExampleAppDelegate.m */; };
0148359513A36C3700E687AC /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0148358F13A36C3700E687AC /* MainWindow.xib */; };
0148359613A36C3700E687AC /* iCarouselExampleViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0148359113A36C3700E687AC /* iCarouselExampleViewController.xib */; };
1D39196B1B77995700798841 /* iCarousel+AccessiblityButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D39196A1B77995700798841 /* iCarousel+AccessiblityButtons.m */; };
B2E5E6C1146145C900EADB7A /* iCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5E6C0146145C900EADB7A /* iCarousel.m */; };
/* End PBXBuildFile section */

Expand All @@ -37,6 +38,8 @@
0148359013A36C3700E687AC /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainWindow.xib; sourceTree = SOURCE_ROOT; };
0148359213A36C3700E687AC /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/iCarouselExampleViewController.xib; sourceTree = SOURCE_ROOT; };
018BBCB413A375AF005CA505 /* iCarouselExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iCarouselExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
1D3919691B77995700798841 /* iCarousel+AccessiblityButtons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "iCarousel+AccessiblityButtons.h"; sourceTree = "<group>"; };
1D39196A1B77995700798841 /* iCarousel+AccessiblityButtons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "iCarousel+AccessiblityButtons.m"; sourceTree = "<group>"; };
B2E5E6BF146145C900EADB7A /* iCarousel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iCarousel.h; sourceTree = "<group>"; };
B2E5E6C0146145C900EADB7A /* iCarousel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iCarousel.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -116,6 +119,8 @@
children = (
B2E5E6BF146145C900EADB7A /* iCarousel.h */,
B2E5E6C0146145C900EADB7A /* iCarousel.m */,
1D3919691B77995700798841 /* iCarousel+AccessiblityButtons.h */,
1D39196A1B77995700798841 /* iCarousel+AccessiblityButtons.m */,
);
name = iCarousel;
path = ../../iCarousel;
Expand Down Expand Up @@ -187,6 +192,7 @@
0148358813A36C2000E687AC /* main.m in Sources */,
0148359313A36C3700E687AC /* iCarouselExampleViewController.m in Sources */,
0148359413A36C3700E687AC /* iCarouselExampleAppDelegate.m in Sources */,
1D39196B1B77995700798841 /* iCarousel+AccessiblityButtons.m in Sources */,
B2E5E6C1146145C900EADB7A /* iCarousel.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
5 changes: 5 additions & 0 deletions Examples/Paging Example/iCarouselExampleViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,9 @@ - (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index r
return view;
}

- (NSString *)accessibilityAnnoucement:(NSInteger)index isForwarded:(BOOL)forwarded{
NSString *announcement = [NSString stringWithFormat:@"%@, Page %ld of %ld", forwarded ? @"Scrolling Forward" : @"Scrolling Backward", (long)index, (long)_items.count];
return announcement;
}

@end
12 changes: 12 additions & 0 deletions iCarousel/iCarousel+AccessibilityScrolling.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// iCarousel+Accessibility.h
// CardCompanion
//
// Created by Wang, Jinlian(Sunny) on 5/29/15.
//

#import "iCarousel.h"

@interface iCarousel (AccessibilityScrolling)

@end
60 changes: 60 additions & 0 deletions iCarousel/iCarousel+AccessibilityScrolling.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// iCarousel+Accessibility.m
// CardCompanion
//
// Created by Wang, Jinlian(Sunny) on 5/29/15.
//

#import "iCarousel+AccessibilityScrolling.h"

@implementation iCarousel (AccessibilityScrolling)

- (void)setUpAccessiblity{
//Need UIAccessibilityTraitCausesPageTurn flag to announce hint if iCarousel is made to be accessibible
self.accessibilityTraits = self.accessibilityTraits | UIAccessibilityTraitCausesPageTurn;
}

-(BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction{
BOOL result = YES;
switch (direction) {
case UIAccessibilityScrollDirectionNext:
case UIAccessibilityScrollDirectionRight:
if(self.currentItemIndex > 0){
[self scrollToItemAtIndex:(self.currentItemIndex-1) animated:YES completionHandler:^(NSInteger index){
NSString *announcement = [self accessibilityAnnouncement:index isForwarded:NO];
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, announcement);
}];
} else {
result = NO;
}
break;
case UIAccessibilityScrollDirectionPrevious:
case UIAccessibilityScrollDirectionLeft:
if(self.currentItemIndex < (self.numberOfItems-1)){
[self scrollToItemAtIndex:(self.currentItemIndex+1) animated:YES completionHandler:^(NSInteger index){
NSString *announcement = [self accessibilityAnnouncement:index isForwarded:YES];
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, announcement);
}];
} else {
result = NO;
}
break;
case UIAccessibilityScrollDirectionUp:
case UIAccessibilityScrollDirectionDown:
result = NO;
break;
}
return result;
}

-(NSString *)accessibilityAnnouncement:(NSInteger)index isForwarded:(BOOL)forwarded{
NSString *announcement = nil;
__strong id<iCarouselDelegate> delegate = self.delegate;
if([delegate respondsToSelector:@selector(accessibilityAnnoucement:isForwarded:)]){
announcement = [delegate accessibilityAnnoucement:index isForwarded:forwarded];
}
announcement = announcement ? announcement: [NSString stringWithFormat:@"Item %ld", (long) index];
return announcement;
}

@end
14 changes: 14 additions & 0 deletions iCarousel/iCarousel+AccessiblityButtons.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// iCarousel+AccessiblityButtons.h
// CardCompanion
//
// Created by Wang, Jinlian(Sunny) on 5/29/15.
//

#import "iCarousel.h"

@interface iCarousel (AccessiblityButtons)

@property (nonatomic, strong) id auxiliaryButtons;

@end
116 changes: 116 additions & 0 deletions iCarousel/iCarousel+AccessiblityButtons.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// iCarousel+AccessiblityButtons.m
// CardCompanion
//
// Created by Wang, Jinlian(Sunny) on 5/29/15.
//

#import <objc/runtime.h>
#import "iCarousel+AccessiblityButtons.h"
#import "iCarousel.h"

#define BUTTON_WIDTH 50

@interface iCarousel (accessibilityAuxiliaryViews)

@end

@implementation iCarousel (AccessiblityButtons)
@dynamic auxiliaryButtons;

-(BOOL)isAccessibilityElement{
return NO;
}

-(void)setUpAccessiblity{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(voiceOverChanged:) name:UIAccessibilityVoiceOverStatusChanged object:nil];
[self handleVoiceOverStatusChange];
}

-(void)cleanUpAccessibility{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityVoiceOverStatusChanged object:nil];
}

-(void)voiceOverChanged:(NSNotification *)notification {
[self handleVoiceOverStatusChange];
}

-(void)handleVoiceOverStatusChange{
if(UIAccessibilityIsVoiceOverRunning() && !self.auxiliaryButtons){
[self setupAuxiliaryButtons];
}

NSArray *array = (NSArray *)self.auxiliaryButtons;
[array enumerateObjectsUsingBlock:^(UIView *button, NSUInteger index, BOOL *stop){
button.hidden = !UIAccessibilityIsVoiceOverRunning();
}];
}


- (void)setAuxiliaryButtons:(id)array {
objc_setAssociatedObject(self, @selector(auxiliaryButtons), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)auxiliaryButtons {
return objc_getAssociatedObject(self, @selector(auxiliaryButtons));
}

-(void)setupAuxiliaryButtons{
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:2];
UIButton *forwardButton = [UIButton buttonWithType:UIButtonTypeCustom];
forwardButton.accessibilityLabel = @"Scroll Forward";
[forwardButton setTitle: @"\u2329" forState: UIControlStateNormal];
forwardButton.accessibilityTraits = forwardButton.accessibilityTraits | UIAccessibilityTraitStartsMediaSession;
forwardButton.backgroundColor = [UIColor colorWithWhite:0.7f alpha:0.7];
[forwardButton addTarget:self action:@selector(forwardButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:forwardButton];
forwardButton.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(forwardButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[forwardButton]-0-|" options:0 metrics:nil views:viewsDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[forwardButton(50)]" options:0 metrics:nil views:viewsDictionary]];
[array addObject:forwardButton];

UIButton *backwardButton = [UIButton buttonWithType:UIButtonTypeCustom];
backwardButton.accessibilityLabel = @"Scroll Backward";
backwardButton.accessibilityTraits = backwardButton.accessibilityTraits | UIAccessibilityTraitStartsMediaSession;
[backwardButton setTitle: @"\u232a" forState: UIControlStateNormal];
backwardButton.backgroundColor = [UIColor colorWithWhite:0.7f alpha:0.7];
[backwardButton addTarget:self action:@selector(backwardButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:backwardButton];
backwardButton.translatesAutoresizingMaskIntoConstraints = NO;
viewsDictionary = NSDictionaryOfVariableBindings(backwardButton);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[backwardButton]-0-|" options:0 metrics:nil views:viewsDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[backwardButton(50)]-0-|" options:0 metrics:nil views:viewsDictionary]];
[array addObject:backwardButton];

self.auxiliaryButtons = array;
}

-(void)forwardButtonTapped:(id)sender{
if(self.currentItemIndex > 0){
[self scrollToItemAtIndex:(self.currentItemIndex-1) animated:YES completionHandler:^(NSInteger index){
NSString *announcement = [self accessibilityAnnouncement:index isForwarded:YES];
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, announcement);
}];
}
}

-(void)backwardButtonTapped:(id)sender{
if(self.currentItemIndex < (self.numberOfItems -1)){
[self scrollToItemAtIndex:(self.currentItemIndex+1) animated:YES completionHandler:^(NSInteger index){
NSString *announcement = [self accessibilityAnnouncement:index isForwarded:NO];
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification,announcement);
}];
}
}

-(NSString *)accessibilityAnnouncement:(NSInteger)index isForwarded:(BOOL)forwarded{
NSString *announcement = nil;
if([self.delegate respondsToSelector:@selector(accessibilityAnnoucement:isForwarded:)]){
announcement = [self.delegate accessibilityAnnoucement:index isForwarded:forwarded];
}
announcement = announcement ? announcement: [NSString stringWithFormat:@"Item %ld", (long) index];
return announcement;
}

@end
3 changes: 3 additions & 0 deletions iCarousel/iCarousel.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)scrollByNumberOfItems:(NSInteger)itemCount duration:(NSTimeInterval)duration;
- (void)scrollToItemAtIndex:(NSInteger)index duration:(NSTimeInterval)duration;
- (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated;
- (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated completionHandler:(void (^)(NSInteger currentItemIndex))completionHandler;

- (nullable UIView *)itemViewAtIndex:(NSInteger)index;
- (NSInteger)indexOfItemView:(UIView *)view;
Expand Down Expand Up @@ -195,6 +196,8 @@ NS_ASSUME_NONNULL_BEGIN
- (CATransform3D)carousel:(iCarousel *)carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform;
- (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value;

- (NSString *)accessibilityAnnoucement:(NSInteger)index isForwarded:(BOOL)forwarded;

@end

NS_ASSUME_NONNULL_END
Expand Down
37 changes: 32 additions & 5 deletions iCarousel/iCarousel.m
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ @interface iCarousel ()
@property (nonatomic, assign, getter = isDragging) BOOL dragging;
@property (nonatomic, assign) BOOL didDrag;
@property (nonatomic, assign) NSTimeInterval toggleTime;
@property (nonatomic, copy) void (^completionHandler)(NSInteger currentItemIndex);

NSComparisonResult compareViewDepth(UIView *view1, UIView *view2, iCarousel *self);

Expand Down Expand Up @@ -165,10 +166,6 @@ - (void)setUp
tapGesture.delegate = (id <UIGestureRecognizerDelegate>)self;
[_contentView addGestureRecognizer:tapGesture];

//set up accessibility
self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction;
self.isAccessibilityElement = YES;

#else

[_contentView setWantsLayer:YES];
Expand All @@ -181,6 +178,19 @@ - (void)setUp
{
[self reloadData];
}

//set up accessibility
if([self respondsToSelector:@selector(setUpAccessiblity)]){
[self performSelector:@selector(setUpAccessiblity)];
}
}

-(void) setUpAccessiblity{
//leave the implementation to the accessibility category or subclass
}

-(void) cleanUpAccessiblity{
//leave the implementation to the accessibility category or subclass
}

#ifndef USING_CHAMELEON
Expand Down Expand Up @@ -219,7 +229,10 @@ - (id)initWithFrame:(NSRect)frame
}

- (void)dealloc
{
{
if([self respondsToSelector:@selector(cleanUpAccessiblity)]){
[self performSelector:@selector(cleanUpAccessiblity)];
}
[self stopAnimation];
}

Expand Down Expand Up @@ -1495,6 +1508,16 @@ - (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated
[self scrollToItemAtIndex:index duration:animated? SCROLL_DURATION: 0];
}

- (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated completionHandler:(void (^)(NSInteger currentItemIndex))completionHandler{
if(animated){
self.completionHandler = completionHandler;
}
[self scrollToItemAtIndex:index animated:animated];
if(!animated){
completionHandler(self.currentItemIndex);
}
}

- (void)removeItemAtIndex:(NSInteger)index animated:(BOOL)animated
{
index = [self clampedIndex:index];
Expand Down Expand Up @@ -1764,6 +1787,10 @@ - (void)step
_scrolling = NO;
[self depthSortViews];
[self pushAnimationState:YES];
if(self.completionHandler){
self.completionHandler(self.currentItemIndex);
self.completionHandler = nil;
}
[_delegate carouselDidEndScrollingAnimation:self];
[self popAnimationState];
}
Expand Down