diff --git a/MaterialTextField.xcodeproj/project.pbxproj b/MaterialTextField.xcodeproj/project.pbxproj index f65dd4c..6a332b5 100644 --- a/MaterialTextField.xcodeproj/project.pbxproj +++ b/MaterialTextField.xcodeproj/project.pbxproj @@ -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 */ @@ -49,6 +51,8 @@ CE58F7D81BEBE77E0082924A /* UIImage+MFTint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+MFTint.m"; sourceTree = ""; }; CE58F7D91BEBE77E0082924A /* UITextField+MFClearButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+MFClearButton.h"; sourceTree = ""; }; CE58F7DA1BEBE77E0082924A /* UITextField+MFClearButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+MFClearButton.m"; sourceTree = ""; }; + CE83F0CD1C2234BE0031E333 /* MFAccessibilityElementProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFAccessibilityElementProxy.h; sourceTree = ""; }; + CE83F0CE1C2234BE0031E333 /* MFAccessibilityElementProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFAccessibilityElementProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -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 */, @@ -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 */, @@ -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 */, ); diff --git a/MaterialTextField/MFAccessibilityElementProxy.h b/MaterialTextField/MFAccessibilityElementProxy.h new file mode 100644 index 0000000..99066fa --- /dev/null +++ b/MaterialTextField/MFAccessibilityElementProxy.h @@ -0,0 +1,21 @@ +// +// MFAccessibilityElementProxy.h +// MaterialTextField +// +// Created by Adam Sharp on 17/12/2015. +// Copyright © 2015 Stephanie Sharp. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MFAccessibilityElementProxy : UIAccessibilityElement + +@property (nonatomic, readonly) NSObject *underlyingElement; + +- (instancetype)initWithAccessibilityContainer:(id)container underlyingElement:(NSObject *)element; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MaterialTextField/MFAccessibilityElementProxy.m b/MaterialTextField/MFAccessibilityElementProxy.m new file mode 100644 index 0000000..5c05b7e --- /dev/null +++ b/MaterialTextField/MFAccessibilityElementProxy.m @@ -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 *)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 diff --git a/MaterialTextField/MFTextField.m b/MaterialTextField/MFTextField.m index ebaa5cb..16db6c0 100644 --- a/MaterialTextField/MFTextField.m +++ b/MaterialTextField/MFTextField.m @@ -7,6 +7,7 @@ // #import "MFTextField.h" +#import "MFAccessibilityElementProxy.h" #import "UIColor+MaterialTextField.h" #import "UITextField+MFClearButton.h" #import "UIFont+MaterialTextField.h" @@ -15,6 +16,9 @@ static NSTimeInterval const MFDefaultAnimationDuration = 0.3; @interface MFTextField () +{ + NSMutableArray *_accessibilityElements; +} @property (nonatomic) CGRect textRect; @property (nonatomic) CALayer *underlineLayer; @@ -34,6 +38,8 @@ @interface MFTextField () @property (nonatomic, readonly) BOOL hasError; @property (nonatomic) BOOL errorIsAnimating; +@property (nonatomic, readonly) MFAccessibilityElementProxy *accessibilityProxy; + @end @implementation MFTextField @@ -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); @@ -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 @@ -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]; } } @@ -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]; @@ -570,7 +592,7 @@ - (void)hideErrorLabelAnimated:(BOOL)animated delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ - [self.superview layoutIfNeeded]; + [self.superview layoutIfNeeded]; } completion:^(BOOL finished) { self.errorIsAnimating = NO; [self updateErrorLabelText]; @@ -578,6 +600,7 @@ - (void)hideErrorLabelAnimated:(BOOL)animated if (self.hasError) { [self showErrorLabelAnimated:NO]; } + [self updateErrorLabelAccessibility]; }]; }]; } @@ -585,6 +608,7 @@ - (void)hideErrorLabelAnimated:(BOOL)animated self.errorLabel.alpha = 0.0f; self.errorLabelTopConstraint.constant = [self topPaddingForErrorLabelHidden:YES]; self.errorLabelHeightConstraint.active = YES; + [self updateErrorLabelAccessibility]; } } @@ -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); @@ -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 diff --git a/MaterialTextFieldDemo/MaterialTextFieldDemo.xcodeproj/project.pbxproj b/MaterialTextFieldDemo/MaterialTextFieldDemo.xcodeproj/project.pbxproj index 6b4a30d..aeed655 100644 --- a/MaterialTextFieldDemo/MaterialTextFieldDemo.xcodeproj/project.pbxproj +++ b/MaterialTextFieldDemo/MaterialTextFieldDemo.xcodeproj/project.pbxproj @@ -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 = ""; }; @@ -35,7 +50,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CECEC4381BEBECB500FDD23F /* MaterialTextField.framework in Frameworks */, + CE3E46F41C2239B4002D0A92 /* MaterialTextField.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,6 +118,7 @@ 8229BFCD1B5E2A7600C40181 /* Sources */, 8229BFCE1B5E2A7600C40181 /* Frameworks */, 8229BFCF1B5E2A7600C40181 /* Resources */, + CE3E46F61C2239B4002D0A92 /* Embed Frameworks */, ); buildRules = ( );