Skip to content

Instantly share code, notes, and snippets.

@soxjke
Last active February 24, 2019 15:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soxjke/4f5a29f2e411140d3811bfd577edf130 to your computer and use it in GitHub Desktop.
Save soxjke/4f5a29f2e411140d3811bfd577edf130 to your computer and use it in GitHub Desktop.
PINCodeField
@interface PINCodeView : UIView
- (instancetype)initWithNumberOfDigits:(NSUInteger)digits
keyboardType:(UIKeyboardType)keyboardType
autocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType;
@property (nonatomic, copy) NSString *code;
- (void)becameFirstResponder;
@end
#import "PINCodeView.h"
#import "PINTextField.h"
@interface PINCodeView() <PINTextFieldDelegate>
@property (nonatomic, copy) NSArray<PINTextField *> *textFields;
@end
@implementation PINCodeView
@synthesize code = _code;
- (instancetype)initWithNumberOfDigits:(NSUInteger)digits
keyboardType:(UIKeyboardType)keyboardType
autocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType {
self = [super initWithFrame:CGRectZero];
if (self) {
_code = @"";
NSMutableArray *array = [NSMutableArray new];
for(NSUInteger i = 0; i < digits; i++) {
PINTextField *textField = [PINTextField new];
textField.keyboardType = keyboardType;
textField.autocapitalizationType = autocapitalizationType;
textField.keyboardAppearance = UIKeyboardAppearanceDark;
textField.delegate = self;
[array addObject:textField];
[self addSubview:textField];
}
self.textFields = array;
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
NSUInteger count = self.textFields.count;
NSUInteger spaces = count - 1;
CGFloat totalWidth = self.frame.size.width;
CGFloat totalHeight = self.frame.size.height;
CGFloat viewWidth = totalWidth / (count + spaces * 0.25);
CGFloat spaceWidth = (totalWidth - (count * viewWidth)) / spaces;
[self.textFields eachWithIndex:^(PINTextField *field, NSUInteger index) {
field.frame = CGRectMake(index * (viewWidth + spaceWidth), 0, viewWidth, totalHeight);
}];
}
- (void)becameFirstResponder {
self.code = [self.code substringToIndex:0];
[self.textFields[0] becomeFirstResponder];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidDelete:(PINTextField *)textField {
NSUInteger index = [self.textFields indexOfObject:textField];
if (index > 0) {
if (index <= self.code.length) {
[self.code substringToIndex:index - 1];
}
[self.textFields[index - 1] becomeFirstResponder];
}
}
- (void)textField:(PINTextField *)textField didInsert:(NSString *)text {
NSUInteger index = [self.textFields indexOfObject:textField];
if (index < self.code.length) {
self.code = [[self.code substringToIndex:index] stringByAppendingString:text];
}
else {
self.code = [self.code stringByAppendingString:text];
}
if (index < self.textFields.count - 1) {
[self.textFields[index + 1] becomeFirstResponder];
}
else {
[textField resignFirstResponder];
}
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
NSUInteger index = [self.textFields indexOfObject:(PINTextField *)textField];
if (index > self.code.length) {
@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@strongify(self);
if (self.code.length < self.textFields.count) {
[self.textFields[self.code.length] becomeFirstResponder];
}
});
return NO;
} else if (index != self.code.length) {
self.code = [self.code substringToIndex:index];
}
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
textField.text = @"";
_code = [self collectCode];
}
- (void)setCode:(NSString *)code {
_code = code;
[self propagateCode:code];
}
- (NSString *)collectCode {
return [[self.textFields.rac_sequence map:^NSString *(PINTextField *textField) {
return textField.text ?: @"";
}] foldLeftWithStart:@"" reduce:^NSString *(NSString *accumulator, NSString *value) {
return [accumulator stringByAppendingString:value];
}];
}
- (void)propagateCode:(NSString *)code {
NSMutableArray *placeholderStrings = [[self.textFields map:^id(id _) {
return @"";
}] mutableCopy];
[placeholderStrings insertObjects:code.rac_sequence.array
atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, code.length)]];
[placeholderStrings removeObjectsInRange:NSMakeRange(self.textFields.count,
placeholderStrings.count - self.textFields.count)];
NSArray *tuples = [[placeholderStrings.rac_sequence zipWith:self.textFields.rac_sequence] array];
[tuples each:^(RACTuple *tuple) {
RACTupleUnpack(NSString *text, PINTextField *textField) = tuple;
textField.text = text;
}];
}
@end
@class PINTextField;
@protocol PINTextFieldDelegate <UITextFieldDelegate>
- (void)textFieldDidDelete:(PINTextField *)textField;
- (void)textField:(PINTextField *)textField didInsert:(NSString *)text;
@end
@interface PINTextField : UITextField
@property (nonatomic, weak) id<PINTextFieldDelegate> delegate;
@end
#import "PINTextField.h"
#import "UIColor+Additions.h"
#import "UIFont+Additions.h"
@implementation PINTextField
@dynamic delegate;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.autocorrectionType = UITextAutocorrectionTypeNo;
self.tintColor = UIColor.nl_tintColor;
self.font = [UIFont systemFontOfSize:24 weight:UIFontWeightMedium];
self.textColor = UIColor.nl_titleColor;
self.textAlignment = NSTextAlignmentCenter;
self.layer.cornerRadius = 2;
self.backgroundColor = [UIColor.nl_highlightedBackgroundColor colorWithAlphaComponent:0.8];
self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
self.textAlignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{
NSForegroundColorAttributeName: [UIColor.nl_titleColor colorWithAlphaComponent:0.4],
NSFontAttributeName: [UIFont systemFontOfSize:24 weight:UIFontWeightMedium]
};
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"*" attributes:attributes];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
[self addTarget:self
action:@selector(editingChanged:)
forControlEvents:UIControlEventEditingChanged];
}
}
return self;
}
- (void)editingChanged:(PINTextField *)sender {
[self insertText:sender.text];
}
- (void)deleteBackward {
[self.delegate textFieldDidDelete:self];
}
- (void)insertText:(NSString *)text {
[self.delegate textField:self didInsert:(NSString *)text];
}
- (void)select:(id)sender {} // do nothing
- (void)selectAll:(id)sender {} // do nothing
- (void)paste:(id)sender {} // do nothing
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; } // no popup menu actions
- (BOOL)becomeFirstResponder {
self.selectedTextRange = [self textRangeFromPosition:self.endOfDocument toPosition:self.endOfDocument];
return [super becomeFirstResponder];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment