https://gitee.com/cityleaf/LYPaymentField
//
// LYSecurityField.h
// PaymentSecurityField
//
// Created by 劉宇 on 2017/8/2.
// Copyright © 2017年 劉宇. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, SecurityCharacterType) {
SecurityCharacterTypeSecurityDot, ///<dot● ,default.
SecurityCharacterTypePlainText, ///<Instead of dot with using plaintext.
SecurityCharacterTypeCustomImage, ///<Instead of dot with using custom image.
SecurityCharacterTypeHorizontalLine,///<Instead of dot with using horizontal line.
};
typedef NS_ENUM(NSUInteger, BorderType) {
BorderTypeHaveRoundedCorner,///<The border has rounded corners,default.
BorderTypeNoRoundedCorner, ///<The border is not rounded.
BorderTypeSeparateBox, ///<Each input bit has a separate box.(No support yet.)
BorderTypeNoBorder, ///<The control has no borders.
};
@class LYSecurityField;
@protocol LYPaymentFieldDelegate <NSObject>
@required
/**
輸入完成 end editing
@param paymentField 支付框
*/
- (void)lYPaymentFieldDidFinishedEditing:(LYSecurityField *)paymentField;
@optional
/**
開始輸入 begin editing
@param paymentField 支付框
*/
- (void)lYPaymentFieldDidBeginEditing:(LYSecurityField *)paymentField;
/**
刪除一個字符 delete a character
@param paymentField 支付框
*/
- (void)lYPaymentFieldDidDelete:(LYSecurityField *)paymentField;
/**
清除完成 clear all characters
@param paymentField 支付框
*/
- (void)lYPaymentFieldDidClear:(LYSecurityField *)paymentField;
@end
@interface LYSecurityField : UIControl
@property (nonatomic, assign) id<LYPaymentFieldDelegate> delegate;
@property (nonatomic, assign) NSInteger numberOfCharacters;
@property (nonatomic, assign) SecurityCharacterType securityCharacterType;
@property (nonatomic, assign) BorderType borderType;
@property (nonatomic, assign) CGFloat diameterOfDot;
@property (nonatomic, assign) CGSize securityImageSize;
@property (nonatomic, assign, readonly) NSInteger countOfVerification;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) void (^completion)(LYSecurityField *field, NSString *text);
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *colorOfCharacter;
@property (nonatomic, strong) UIImage *securityImage;
- (instancetype)initWithNumberOfCharacters:(NSInteger)numberOfCharacters
securityCharacterType:(SecurityCharacterType)securityCharacterType
borderType:(BorderType)borderType;
- (void)clear;
@end
NS_ASSUME_NONNULL_END
//
// LYSecurityField.m
// PaymentSecurityField
//
// Created by 劉宇 on 2017/8/2.
// Copyright © 2017年 劉宇. All rights reserved.
//
#import "LYSecurityField.h"
NS_ASSUME_NONNULL_BEGIN
#define DEFAULT_COUNT_CHARACTERS 6;
#define DEFAULT_RADIUS 6;
#define DEFAULT_BODER_WIDTH 0.5;
#define DEFAULT_HORIZONTALLINE_WIDTH 20
#define DEFAULT_CUSTOMIMAGE_WIDTH 20
void *KVOFrameContext;
@interface LYSecurityField()<UIKeyInput,UITextInputTraits>
{
int completion_count;
}
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) NSMutableArray <CALayer *>*lines;
@property (nonatomic, strong) NSMutableArray <CALayer *>*boxs;
/**
An array of layers holding secure placeholders.
*/
@property (nonatomic, strong) NSMutableArray <CAShapeLayer *>*dots;
@property (nonatomic, strong) NSMutableArray <CATextLayer *>*plaintexts;
@property (nonatomic, strong) NSMutableArray <CALayer *>*horizontalLines;
@property (nonatomic, strong) NSMutableArray <CALayer *>*customImages;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *,NSMutableArray *>*placeholderDic;
- (CAShapeLayer *)createSecurityDot;
- (CALayer *)createSeparaterLine;
- (CATextLayer *)createCharacter;
- (CALayer *)createCustomImage;
- (CALayer *)createHorizontalLine;
@end
@implementation LYSecurityField
- (void)dealloc
{
[self removeObserver:self forKeyPath:@"frame" context:KVOFrameContext];
}
- (instancetype)initWithNumberOfCharacters:(NSInteger)numberOfCharacters securityCharacterType:(SecurityCharacterType)securityCharacterType borderType:(BorderType)borderType
{
self = [super init];
if (self) {
[self steup];
self.numberOfCharacters = numberOfCharacters;
self.securityCharacterType = securityCharacterType;
self.borderType = borderType;
}
return self;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self steup];
}
return self;
}
- (void)steup
{
self.contentView = ({
UIView *contentView = [[UIView alloc] init];
contentView.backgroundColor = UIColor.clearColor;
contentView.userInteractionEnabled = NO;
contentView;
});
[self addSubview:self.contentView];
self.diameterOfDot = 15;
self.colorOfCharacter = UIColor.blackColor;
self.text = @"";
self.placeholderDic = [NSMutableDictionary dictionary];
self.lines = [NSMutableArray array];
[self addTarget:self action:@selector(hangdleAction:) forControlEvents:UIControlEventTouchUpInside];
[self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:KVOFrameContext];
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.contentView.frame = self.bounds;
CGFloat width = CGRectGetWidth(self.frame);
CGFloat height = CGRectGetHeight(self.frame);
CGFloat pieceWidth = width / self.numberOfCharacters;
!self.placeholderDic[@(self.securityCharacterType)]?:[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.position = CGPointMake(pieceWidth / 2.f + (pieceWidth * idx), height / 2.f);
}];
if (self.borderType != BorderTypeNoBorder) {
[self.lines enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self.contentView.layer addSublayer:obj];
obj.frame = CGRectMake(pieceWidth * (idx + 1) - self.layer.borderWidth / 2.f, 0, self.layer.borderWidth, height);
}];
}
}
#pragma mark - createPlaceholderCharacter
/**
Create a black dot.
@return dot
*/
- (CAShapeLayer *)createSecurityDot
{
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.bounds = CGRectMake(0, 0, self.diameterOfDot, self.diameterOfDot);
layer.path = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:self.diameterOfDot / 2.f].CGPath;
layer.opacity = 0;
[self.contentView.layer addSublayer:layer];
return layer;
}
/**
Create a split line.
@return line
*/
- (CALayer *)createSeparaterLine
{
CALayer *layer = [[CALayer alloc] init];
layer.backgroundColor = self.tintColor.CGColor?:self.layer.borderColor;
return layer;
}
/**
Create an unencrypted character.
@return character
*/
- (CATextLayer *)createCharacter
{
CATextLayer *layer = [[CATextLayer alloc] init];
layer.bounds = CGRectMake(0, 0, self.diameterOfDot*3, self.diameterOfDot*3);
layer.foregroundColor = UIColor.blackColor.CGColor;
layer.contentsScale = [UIScreen mainScreen].scale;
layer.alignmentMode = kCAAlignmentCenter;
layer.wrapped = YES;
layer.opacity = 0;
[self.contentView.layer addSublayer:layer];
return layer;
}
/**
Customize a picture that represents security.
@return layer
*/
- (CALayer *)createCustomImage
{
CALayer *layer = [[CALayer alloc] init];
layer.opacity = 0;
[self.contentView.layer addSublayer:layer];
return layer;
}
- (CALayer *)createHorizontalLine
{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0, 0, DEFAULT_HORIZONTALLINE_WIDTH, 5);
layer.backgroundColor = UIColor.blackColor.CGColor;
layer.opacity = 0;
[self.contentView.layer addSublayer:layer];
return layer;
}
#pragma mark - set
- (void)setNumberOfCharacters:(NSInteger)numberOfCharacters
{
if (numberOfCharacters != _numberOfCharacters) {
_numberOfCharacters = numberOfCharacters;
for (int i = 0; i < self.numberOfCharacters-1; i++) {
[self.lines addObject:[self createSeparaterLine]];
}
}
}
- (void)setDiameterOfDot:(CGFloat)diameterOfDot
{
_diameterOfDot = diameterOfDot;
if (self.placeholderDic[@(SecurityCharacterTypeSecurityDot)]) {
[self.dots enumerateObjectsUsingBlock:^(CAShapeLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.bounds = CGRectMake(0, 0, diameterOfDot, diameterOfDot);
obj.path = [UIBezierPath bezierPathWithRoundedRect:obj.bounds cornerRadius:diameterOfDot / 2.f].CGPath;
}];
}
}
- (void)setSecurityImage:(UIImage *)securityImage
{
_securityImage = securityImage;
!self.placeholderDic[@(self.securityCharacterType)]?:[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.contents = (id)securityImage.CGImage;
}];
}
- (void)setTintColor:(UIColor *)tintColor
{
_tintColor = tintColor;
self.layer.borderColor = tintColor.CGColor;
}
- (void)setColorOfCharacter:(UIColor *)colorOfCharacter
{
_colorOfCharacter = colorOfCharacter;
if (self.securityCharacterType == SecurityCharacterTypeSecurityDot) {
[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CAShapeLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.strokeColor = colorOfCharacter.CGColor;
}];
} else if (self.securityCharacterType == SecurityCharacterTypePlainText) {
[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CATextLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.foregroundColor = colorOfCharacter.CGColor;
}];
} else if (self.securityCharacterType == SecurityCharacterTypeHorizontalLine) {
[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CATextLayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.backgroundColor = colorOfCharacter.CGColor;
}];
}
}
- (void)setSecurityCharacterType:(SecurityCharacterType)securityCharacterType
{
_securityCharacterType = securityCharacterType;
if (!self.placeholderDic[@(securityCharacterType)]) {
if (securityCharacterType == SecurityCharacterTypeSecurityDot) {
self.placeholderDic[@(securityCharacterType)] = self.dots;
} else if (securityCharacterType == SecurityCharacterTypePlainText) {
self.placeholderDic[@(securityCharacterType)] = self.plaintexts;
} else if (securityCharacterType == SecurityCharacterTypeCustomImage) {
self.placeholderDic[@(securityCharacterType)] = self.customImages;
} else if (securityCharacterType == SecurityCharacterTypeHorizontalLine) {
self.placeholderDic[@(securityCharacterType)] = self.horizontalLines;
}
}
}
- (void)setBorderType:(BorderType)borderType
{
_borderType = borderType;
switch (borderType) {
case BorderTypeHaveRoundedCorner:
{
self.layer.borderColor = self.tintColor.CGColor;
self.layer.borderWidth = DEFAULT_BODER_WIDTH;
self.layer.cornerRadius = DEFAULT_RADIUS;
break;
}
case BorderTypeNoRoundedCorner:
{
self.layer.borderColor = self.tintColor.CGColor;
self.layer.borderWidth = DEFAULT_BODER_WIDTH;
self.layer.cornerRadius = 0;
break;
}
case BorderTypeNoBorder:
{
self.layer.borderWidth = 0;
break;
}
default:
break;
}
}
#pragma mark - get
- (NSInteger)countOfVerification
{
return completion_count;
}
- (NSMutableArray <CAShapeLayer *>*)dots
{
if (!_dots) {
_dots = [NSMutableArray array];
for (int i = 0; i < self.numberOfCharacters; i++) {
[_dots addObject:[self createSecurityDot]];
}
}
return _dots;
}
- (NSMutableArray <CATextLayer *>*)plaintexts
{
if (!_plaintexts) {
_plaintexts = [NSMutableArray array];
for (int i = 0; i < self.numberOfCharacters; i++) {
[_plaintexts addObject:[self createCharacter]];
}
}
return _plaintexts;
}
- (NSMutableArray <CALayer *>*)horizontalLines
{
if (!_horizontalLines) {
_horizontalLines = [NSMutableArray array];
for (int i = 0; i < self.numberOfCharacters; i++) {
[_horizontalLines addObject:[self createHorizontalLine]];
}
}
return _horizontalLines;
}
- (NSMutableArray <CALayer *>*)customImages
{
if (!_customImages) {
_customImages = [NSMutableArray array];
for (int i = 0; i < self.numberOfCharacters; i++) {
[_customImages addObject:[self createCustomImage]];
}
}
return _customImages;
}
- (NSMutableArray <CALayer *>*)boxs
{
if (!_boxs) {
_boxs = [NSMutableArray array];
}
return _boxs;
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey,id> *)change context:(nullable void *)context
{
if ([keyPath isEqualToString:@"frame"]) {
if (self.securityCharacterType == SecurityCharacterTypeSecurityDot || self.securityCharacterType == SecurityCharacterTypeHorizontalLine) {
} else if (self.securityCharacterType == SecurityCharacterTypePlainText) {
[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.bounds = CGRectMake(0, 0, CGRectGetWidth(self.frame)/self.numberOfCharacters, CGRectGetHeight(self.frame));
}];
} else {
[self.placeholderDic[@(self.securityCharacterType)] enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (self.securityImageSize.width == 0) {
obj.bounds = CGRectMake(0, 0, DEFAULT_CUSTOMIMAGE_WIDTH, DEFAULT_CUSTOMIMAGE_WIDTH);
} else {
obj.bounds = CGRectMake(0, 0, self.securityImageSize.width, self.securityImageSize.height);
}
}];
}
}
}
#pragma mark - UITextInputTraits
- (UIKeyboardType)keyboardType
{
return UIKeyboardTypeNumberPad;
}
#pragma mark - UIKeyInput
- (BOOL)hasText
{
return self.text.length > 0;
}
- (void)insertText:(NSString *)text
{
if (self.text.length < self.numberOfCharacters) {
NSInteger index = self.text.length;
NSMutableArray<CALayer *>*placeholders = self.placeholderDic[@(self.securityCharacterType)];
placeholders[index].opacity = 1;
if (self.securityCharacterType == SecurityCharacterTypePlainText) {
CATextLayer *layer = (CATextLayer *)placeholders[index];
layer.string = text;
}
self.text = [self.text stringByAppendingString:text];
if (index == self.numberOfCharacters - 1) {
completion_count++;
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidFinishedEditing:)]) {
[self.delegate lYPaymentFieldDidFinishedEditing:self];
}
!self.completion?:self.completion(self, self.text);
} else {
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidBeginEditing:)]) {
[self.delegate lYPaymentFieldDidBeginEditing:self];
}
}
[self setNeedsLayout];
}
}
- (void)deleteBackward
{
if (self.text.length > 0) {
self.text = [self.text substringWithRange:NSMakeRange(0, self.text.length - 1)];
NSInteger index = self.text.length;
NSMutableArray<CALayer *>*placeholders = self.placeholderDic[@(self.securityCharacterType)];
placeholders[index].opacity = 0;
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidDelete:)]) {
[self.delegate lYPaymentFieldDidDelete:self];
}
if (!self.text.length) {
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidClear:)]) {
[self.delegate lYPaymentFieldDidClear:self];
}
} else {
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidBeginEditing:)]) {
[self.delegate lYPaymentFieldDidBeginEditing:self];
}
}
}
}
#pragma mark - firstReponder
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)hangdleAction:(id)sender
{
[self becomeFirstResponder];
}
#pragma mark - public methods
- (void)clear
{
self.text = @"";
[self.placeholderDic[@(self.securityCharacterType)] setValue:@0 forKeyPath:@"opacity"];
if (@protocol(LYPaymentFieldDelegate) && [self.delegate respondsToSelector:@selector(lYPaymentFieldDidClear:)]) {
[self.delegate lYPaymentFieldDidClear:self];
}
}
@end
NS_ASSUME_NONNULL_END