Skip to content
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