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

Implement UIAccessibilityContainer informal protocol and make error labels accessible #5

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions MaterialTextField.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
CE58F7E01BEBE77E0082924A /* UIImage+MFTint.m in Sources */ = {isa = PBXBuildFile; fileRef = CE58F7D81BEBE77E0082924A /* UIImage+MFTint.m */; };
CE58F7E11BEBE77E0082924A /* UITextField+MFClearButton.h in Headers */ = {isa = PBXBuildFile; fileRef = CE58F7D91BEBE77E0082924A /* UITextField+MFClearButton.h */; };
CE58F7E21BEBE77E0082924A /* UITextField+MFClearButton.m in Sources */ = {isa = PBXBuildFile; fileRef = CE58F7DA1BEBE77E0082924A /* UITextField+MFClearButton.m */; };
CE83F0CF1C2234BE0031E333 /* MFAccessibilityElementProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = CE83F0CD1C2234BE0031E333 /* MFAccessibilityElementProxy.h */; };
CE83F0D01C2234BE0031E333 /* MFAccessibilityElementProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = CE83F0CE1C2234BE0031E333 /* MFAccessibilityElementProxy.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -49,6 +51,8 @@
CE58F7D81BEBE77E0082924A /* UIImage+MFTint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+MFTint.m"; sourceTree = "<group>"; };
CE58F7D91BEBE77E0082924A /* UITextField+MFClearButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+MFClearButton.h"; sourceTree = "<group>"; };
CE58F7DA1BEBE77E0082924A /* UITextField+MFClearButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+MFClearButton.m"; sourceTree = "<group>"; };
CE83F0CD1C2234BE0031E333 /* MFAccessibilityElementProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFAccessibilityElementProxy.h; sourceTree = "<group>"; };
CE83F0CE1C2234BE0031E333 /* MFAccessibilityElementProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFAccessibilityElementProxy.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -112,6 +116,8 @@
CE58F7BC1BEBE64C0082924A /* MaterialTextField.h */,
CE58F7D31BEBE77E0082924A /* MFTextField.h */,
CE58F7D41BEBE77E0082924A /* MFTextField.m */,
CE83F0CD1C2234BE0031E333 /* MFAccessibilityElementProxy.h */,
CE83F0CE1C2234BE0031E333 /* MFAccessibilityElementProxy.m */,
CE58F7D51BEBE77E0082924A /* UIColor+MaterialTextField.h */,
CE58F7D61BEBE77E0082924A /* UIColor+MaterialTextField.m */,
82125D271BEF36E20017F72C /* UIFont+MaterialTextField.h */,
Expand Down Expand Up @@ -144,6 +150,7 @@
CE58F7DF1BEBE77E0082924A /* UIImage+MFTint.h in Headers */,
CE58F7DD1BEBE77E0082924A /* UIColor+MaterialTextField.h in Headers */,
CE58F7E11BEBE77E0082924A /* UITextField+MFClearButton.h in Headers */,
CE83F0CF1C2234BE0031E333 /* MFAccessibilityElementProxy.h in Headers */,
CE58F7BD1BEBE64C0082924A /* MaterialTextField.h in Headers */,
82125D291BEF36E20017F72C /* UIFont+MaterialTextField.h in Headers */,
CE58F7DB1BEBE77E0082924A /* MFTextField.h in Headers */,
Expand Down Expand Up @@ -249,6 +256,7 @@
CE58F7DC1BEBE77E0082924A /* MFTextField.m in Sources */,
CE58F7DE1BEBE77E0082924A /* UIColor+MaterialTextField.m in Sources */,
CE58F7E21BEBE77E0082924A /* UITextField+MFClearButton.m in Sources */,
CE83F0D01C2234BE0031E333 /* MFAccessibilityElementProxy.m in Sources */,
CE58F7E01BEBE77E0082924A /* UIImage+MFTint.m in Sources */,
82125D2A1BEF36E20017F72C /* UIFont+MaterialTextField.m in Sources */,
);
Expand Down
21 changes: 21 additions & 0 deletions MaterialTextField/MFAccessibilityElementProxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// MFAccessibilityElementProxy.h
// MaterialTextField
//
// Created by Adam Sharp on 17/12/2015.
// Copyright © 2015 Stephanie Sharp. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MFAccessibilityElementProxy : UIAccessibilityElement

@property (nonatomic, readonly) NSObject<UIAccessibilityIdentification> *underlyingElement;

- (instancetype)initWithAccessibilityContainer:(id)container underlyingElement:(NSObject<UIAccessibilityIdentification> *)element;

@end

NS_ASSUME_NONNULL_END
50 changes: 50 additions & 0 deletions MaterialTextField/MFAccessibilityElementProxy.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// MFAccessibilityElementProxy.m
// MaterialTextField
//
// Created by Adam Sharp on 17/12/2015.
// Copyright © 2015 Stephanie Sharp. All rights reserved.
//

#import "MFAccessibilityElementProxy.h"

@implementation MFAccessibilityElementProxy

- (nonnull instancetype)initWithAccessibilityContainer:(nonnull id)container underlyingElement:(nonnull NSObject<UIAccessibilityIdentification> *)element
{
self = [self initWithAccessibilityContainer:container];
_underlyingElement = element;
return self;
}

- (NSString *)accessibilityLabel
{
return self.underlyingElement.accessibilityLabel;
}

- (NSString *)accessibilityHint
{
return self.underlyingElement.accessibilityHint;
}

- (NSString *)accessibilityValue
{
return self.underlyingElement.accessibilityValue;
}

- (NSString *)accessibilityIdentifier
{
return self.underlyingElement.accessibilityIdentifier;
}

- (CGRect)accessibilityFrame
{
return self.underlyingElement.accessibilityFrame;
}

- (UIAccessibilityTraits)accessibilityTraits
{
return self.underlyingElement.accessibilityTraits;
}

@end
87 changes: 80 additions & 7 deletions MaterialTextField/MFTextField.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "MFTextField.h"
#import "MFAccessibilityElementProxy.h"
#import "UIColor+MaterialTextField.h"
#import "UITextField+MFClearButton.h"
#import "UIFont+MaterialTextField.h"
Expand All @@ -15,6 +16,9 @@
static NSTimeInterval const MFDefaultAnimationDuration = 0.3;

@interface MFTextField ()
{
NSMutableArray *_accessibilityElements;
}

@property (nonatomic) CGRect textRect;
@property (nonatomic) CALayer *underlineLayer;
Expand All @@ -34,6 +38,8 @@ @interface MFTextField ()
@property (nonatomic, readonly) BOOL hasError;
@property (nonatomic) BOOL errorIsAnimating;

@property (nonatomic, readonly) MFAccessibilityElementProxy *accessibilityProxy;

@end

@implementation MFTextField
Expand Down Expand Up @@ -64,10 +70,19 @@ - (void)sharedInit
[self setupTextField];
[self setupUnderline];
[self setupErrorLabel];
[self setupAccessibility];
}

#pragma mark - Setup

- (void)setupAccessibility
{
_accessibilityProxy = [[MFAccessibilityElementProxy alloc] initWithAccessibilityContainer:self underlyingElement:self];

_accessibilityElements = [NSMutableArray new];
[_accessibilityElements addObject:self.accessibilityProxy];
}

- (void)setDefaults
{
self.textPadding = CGSizeMake(0.0f, 8.0f);
Expand Down Expand Up @@ -303,6 +318,11 @@ - (BOOL)placeholderIsHidden
return self.placeholderLabel.alpha == 0.0f;
}

- (CGRect)accessibilityFrame
{
return [self convertRect:self.textRect toView:self.superview];
}

#pragma mark - Layout

- (void)layoutSubviews
Expand Down Expand Up @@ -537,17 +557,19 @@ - (void)showErrorLabelAnimated:(BOOL)animated
[self.superview layoutIfNeeded];
self.errorLabel.alpha = 1.0f;
} completion:^(BOOL finished) {
self.errorIsAnimating = NO;
// Layout error label without animation if isValid has changed since animation started.
if (!self.hasError) {
[self hideErrorLabelAnimated:NO];
}
self.errorIsAnimating = NO;
// Layout error label without animation if isValid has changed since animation started.
if (!self.hasError) {
[self hideErrorLabelAnimated:NO];
}
[self updateErrorLabelAccessibility];
}];
}
else if (!animated) {
self.errorLabel.alpha = 1.0f;
self.errorLabelTopConstraint.constant = [self topPaddingForErrorLabelHidden:NO];
self.errorLabelHeightConstraint.active = NO;
[self updateErrorLabelAccessibility];
}
}

Expand All @@ -559,7 +581,7 @@ - (void)hideErrorLabelAnimated:(BOOL)animated
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.errorLabel.alpha = 0.0f;
self.errorLabel.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.superview layoutIfNeeded];

Expand All @@ -570,21 +592,23 @@ - (void)hideErrorLabelAnimated:(BOOL)animated
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[self.superview layoutIfNeeded];
[self.superview layoutIfNeeded];
} completion:^(BOOL finished) {
self.errorIsAnimating = NO;
[self updateErrorLabelText];
// Layout error label without animation if isValid has changed since animation started.
if (self.hasError) {
[self showErrorLabelAnimated:NO];
}
[self updateErrorLabelAccessibility];
}];
}];
}
else if (!animated) {
self.errorLabel.alpha = 0.0f;
self.errorLabelTopConstraint.constant = [self topPaddingForErrorLabelHidden:YES];
self.errorLabelHeightConstraint.active = YES;
[self updateErrorLabelAccessibility];
}
}

Expand All @@ -594,6 +618,28 @@ - (void)updateErrorLabelText
[self.errorLabel sizeToFit];
}

- (void)updateErrorLabelAccessibility
{
BOOL accessibilityElementsIncludesError = [self indexOfAccessibilityElement:self.errorLabel] != NSNotFound;

if (self.hasError && accessibilityElementsIncludesError) {
return;
}

if (!self.hasError && !accessibilityElementsIncludesError) {
return;
}

if (self.hasError) {
[_accessibilityElements addObject:self.errorLabel];
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.errorLabel);
}
else {
[_accessibilityElements removeObject:self.errorLabel];
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}

- (CGFloat)topPaddingForErrorLabelHidden:(BOOL)hidden
{
CGFloat topPadding = CGRectGetMaxY(self.underlineLayer.frame);
Expand Down Expand Up @@ -681,4 +727,31 @@ - (void)prepareForInterfaceBuilder
[self.errorLabel removeFromSuperview];
}

#pragma mark - Accessibility

- (BOOL)isAccessibilityElement
{
return NO;
}

- (NSInteger)indexOfAccessibilityElement:(id)element
{
return [self.accessibilityElements indexOfObject:element];
}

- (id)accessibilityElementAtIndex:(NSInteger)index
{
return [self.accessibilityElements objectAtIndex:index];
}

- (NSInteger)accessibilityElementCount
{
return self.accessibilityElements.count;
}

- (NSArray *)accessibilityElements
{
return _accessibilityElements;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,24 @@
8229BFE01B5E2A7600C40181 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8229BFDE1B5E2A7600C40181 /* Main.storyboard */; };
8229BFE21B5E2A7600C40181 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8229BFE11B5E2A7600C40181 /* Images.xcassets */; };
8229BFE51B5E2A7600C40181 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8229BFE31B5E2A7600C40181 /* LaunchScreen.xib */; };
CECEC4381BEBECB500FDD23F /* MaterialTextField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4B7E621BEBE94300CD6620 /* MaterialTextField.framework */; };
CE3E46F41C2239B4002D0A92 /* MaterialTextField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4B7E621BEBE94300CD6620 /* MaterialTextField.framework */; };
CE3E46F51C2239B4002D0A92 /* MaterialTextField.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE4B7E621BEBE94300CD6620 /* MaterialTextField.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
CE3E46F61C2239B4002D0A92 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
CE3E46F51C2239B4002D0A92 /* MaterialTextField.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
8229BFD11B5E2A7600C40181 /* MaterialTextFieldDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MaterialTextFieldDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
8229BFD51B5E2A7600C40181 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand All @@ -35,7 +50,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CECEC4381BEBECB500FDD23F /* MaterialTextField.framework in Frameworks */,
CE3E46F41C2239B4002D0A92 /* MaterialTextField.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -103,6 +118,7 @@
8229BFCD1B5E2A7600C40181 /* Sources */,
8229BFCE1B5E2A7600C40181 /* Frameworks */,
8229BFCF1B5E2A7600C40181 /* Resources */,
CE3E46F61C2239B4002D0A92 /* Embed Frameworks */,
);
buildRules = (
);
Expand Down