BKPasscodeInputView.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. //
  2. // BKPasscodeInputView.m
  3. // BKPasscodeViewDemo
  4. //
  5. // Created by Byungkook Jang on 2014. 4. 20..
  6. // Copyright (c) 2014년 Byungkook Jang. All rights reserved.
  7. //
  8. #import "BKPasscodeInputView.h"
  9. #define kLabelPasscodeSpacePortrait (30.0f)
  10. #define kLabelPasscodeSpaceLandscape (10.0f)
  11. #define kTextLeftRightSpace (20.0f)
  12. #define kErrorMessageLeftRightPadding (10.0f)
  13. #define kErrorMessageTopBottomPadding (5.0f)
  14. #define kDefaultNumericPasscodeMaximumLength (4)
  15. #define kDefaultNormalPasscodeMaximumLength (20)
  16. @interface BKPasscodeInputView () {
  17. BOOL _isKeyboardTypeSet;
  18. }
  19. @property (nonatomic, strong) UILabel *titleLabel;
  20. @property (nonatomic, strong) UILabel *messageLabel;
  21. @property (nonatomic, strong) UILabel *errorMessageLabel;
  22. @property (nonatomic, strong) UIControl *passcodeField;
  23. @end
  24. @implementation BKPasscodeInputView
  25. @synthesize maximumLength = _maximumLength;
  26. @synthesize keyboardType = _keyboardType;
  27. @synthesize passcodeField = _passcodeField;
  28. - (instancetype)initWithFrame:(CGRect)frame
  29. {
  30. self = [super initWithFrame:frame];
  31. if (self) {
  32. [self _initialize];
  33. }
  34. return self;
  35. }
  36. - (instancetype)initWithCoder:(NSCoder *)coder
  37. {
  38. self = [super initWithCoder:coder];
  39. if (self) {
  40. [self _initialize];
  41. }
  42. return self;
  43. }
  44. - (void)_initialize
  45. {
  46. self.backgroundColor = [UIColor clearColor];
  47. _enabled = YES;
  48. _passcodeStyle = BKPasscodeInputViewNumericPasscodeStyle;
  49. _keyboardType = UIKeyboardTypeNumberPad;
  50. _maximumLength = 0;
  51. _titleLabel = [[UILabel alloc] init];
  52. [[self class] configureTitleLabel:_titleLabel];
  53. [self addSubview:_titleLabel];
  54. _messageLabel = [[UILabel alloc] init];
  55. [[self class] configureMessageLabel:_messageLabel];
  56. [self addSubview:_messageLabel];
  57. _errorMessageLabel = [[UILabel alloc] init];
  58. [[self class] configureErrorMessageLabel:_errorMessageLabel];
  59. _errorMessageLabel.hidden = YES;
  60. [self addSubview:_errorMessageLabel];
  61. }
  62. + (void)configureTitleLabel:(UILabel *)aLabel
  63. {
  64. aLabel.backgroundColor = [UIColor clearColor];
  65. aLabel.numberOfLines = 1;
  66. aLabel.textAlignment = NSTextAlignmentCenter;
  67. aLabel.lineBreakMode = NSLineBreakByTruncatingTail;
  68. aLabel.font = [UIFont boldSystemFontOfSize:15.0f];
  69. }
  70. + (void)configureMessageLabel:(UILabel *)aLabel
  71. {
  72. aLabel.backgroundColor = [UIColor clearColor];
  73. aLabel.numberOfLines = 0;
  74. aLabel.textAlignment = NSTextAlignmentCenter;
  75. aLabel.lineBreakMode = NSLineBreakByWordWrapping;
  76. aLabel.font = [UIFont systemFontOfSize:15.0f];
  77. }
  78. + (void)configureErrorMessageLabel:(UILabel *)aLabel
  79. {
  80. aLabel.backgroundColor = [UIColor clearColor];
  81. aLabel.numberOfLines = 0;
  82. aLabel.textAlignment = NSTextAlignmentCenter;
  83. aLabel.lineBreakMode = NSLineBreakByWordWrapping;
  84. aLabel.backgroundColor = [UIColor colorWithRed:0.63 green:0.2 blue:0.13 alpha:1];
  85. aLabel.textColor = [UIColor whiteColor];
  86. aLabel.font = [UIFont systemFontOfSize:15.0f];
  87. aLabel.layer.cornerRadius = 10.0f;
  88. aLabel.layer.masksToBounds = YES;
  89. }
  90. - (void)setPasscodeStyle:(BKPasscodeInputViewPasscodeStyle)passcodeStyle
  91. {
  92. if (_passcodeStyle != passcodeStyle) {
  93. _passcodeStyle = passcodeStyle;
  94. if (_passcodeField) {
  95. _passcodeField = nil;
  96. [self passcodeField]; // load passcode field immediately if already exists before.
  97. }
  98. }
  99. }
  100. - (UIControl *)passcodeField
  101. {
  102. if (nil == _passcodeField) {
  103. switch (_passcodeStyle) {
  104. case BKPasscodeInputViewNumericPasscodeStyle:
  105. {
  106. if (_maximumLength == 0) {
  107. _maximumLength = kDefaultNumericPasscodeMaximumLength;
  108. }
  109. if (NO == _isKeyboardTypeSet) {
  110. _keyboardType = UIKeyboardTypeNumberPad;
  111. }
  112. BKPasscodeField *passcodeField = [[BKPasscodeField alloc] init];
  113. passcodeField.delegate = self;
  114. passcodeField.keyboardType = _keyboardType;
  115. passcodeField.maximumLength = _maximumLength;
  116. [passcodeField addTarget:self action:@selector(passcodeControlEditingChanged:) forControlEvents:UIControlEventEditingChanged];
  117. [self setPasscodeField:passcodeField];
  118. break;
  119. }
  120. case BKPasscodeInputViewNormalPasscodeStyle:
  121. {
  122. if (_maximumLength == 0) {
  123. _maximumLength = kDefaultNormalPasscodeMaximumLength;
  124. }
  125. if (NO == _isKeyboardTypeSet) {
  126. _keyboardType = UIKeyboardTypeASCIICapable;
  127. }
  128. UITextField *textField = [[UITextField alloc] init];
  129. textField.delegate = self;
  130. textField.borderStyle = UITextBorderStyleRoundedRect;
  131. textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
  132. textField.autocorrectionType = UITextAutocorrectionTypeNo;
  133. textField.spellCheckingType = UITextSpellCheckingTypeNo;
  134. textField.enablesReturnKeyAutomatically = YES;
  135. textField.keyboardType = _keyboardType;
  136. textField.secureTextEntry = YES;
  137. textField.font = [UIFont systemFontOfSize:25.0f];
  138. textField.clearButtonMode = UITextFieldViewModeWhileEditing;
  139. textField.returnKeyType = UIReturnKeyDone;
  140. [self setPasscodeField:textField];
  141. break;
  142. }
  143. }
  144. }
  145. return _passcodeField;
  146. }
  147. - (void)setPasscodeField:(UIControl *)passcodeField
  148. {
  149. if (_passcodeField != passcodeField) {
  150. [_passcodeField removeFromSuperview];
  151. _passcodeField = passcodeField;
  152. if (_passcodeField) {
  153. [self addSubview:_passcodeField];
  154. }
  155. [self setNeedsLayout];
  156. }
  157. }
  158. - (void)setMaximumLength:(NSUInteger)maximumLength
  159. {
  160. _maximumLength = maximumLength;
  161. if ([_passcodeField isKindOfClass:[BKPasscodeField class]]) {
  162. [(BKPasscodeField *)_passcodeField setMaximumLength:maximumLength];
  163. }
  164. }
  165. - (void)setKeyboardType:(UIKeyboardType)keyboardType
  166. {
  167. _isKeyboardTypeSet = YES;
  168. _keyboardType = keyboardType;
  169. [(id<UITextInputTraits>)_passcodeField setKeyboardType:keyboardType];
  170. }
  171. - (void)setTitle:(NSString *)title
  172. {
  173. self.titleLabel.text = title;
  174. [self setNeedsLayout];
  175. }
  176. - (NSString *)title
  177. {
  178. return self.titleLabel.text;
  179. }
  180. - (void)setMessage:(NSString *)message
  181. {
  182. self.messageLabel.text = message;
  183. self.messageLabel.hidden = NO;
  184. self.errorMessageLabel.text = nil;
  185. self.errorMessageLabel.hidden = YES;
  186. [self setNeedsLayout];
  187. }
  188. - (NSString *)message
  189. {
  190. return self.messageLabel.text;
  191. }
  192. - (void)setErrorMessage:(NSString *)errorMessage
  193. {
  194. self.errorMessageLabel.text = errorMessage;
  195. self.errorMessageLabel.hidden = NO;
  196. self.messageLabel.text = nil;
  197. self.messageLabel.hidden = YES;
  198. [self setNeedsLayout];
  199. }
  200. - (NSString *)errorMessage
  201. {
  202. return self.errorMessageLabel.text;
  203. }
  204. - (NSString *)passcode
  205. {
  206. switch (self.passcodeStyle) {
  207. case BKPasscodeInputViewNumericPasscodeStyle:
  208. return [(BKPasscodeField *)self.passcodeField passcode];
  209. case BKPasscodeInputViewNormalPasscodeStyle:
  210. return [(UITextField *)self.passcodeField text];
  211. }
  212. }
  213. - (void)setPasscode:(NSString *)passcode
  214. {
  215. switch (self.passcodeStyle) {
  216. case BKPasscodeInputViewNumericPasscodeStyle:
  217. [(BKPasscodeField *)self.passcodeField setPasscode:passcode];
  218. break;
  219. case BKPasscodeInputViewNormalPasscodeStyle:
  220. [(UITextField *)self.passcodeField setText:passcode];
  221. break;
  222. }
  223. }
  224. #pragma mark - UIView
  225. - (CGFloat)labelPasscodeSpace
  226. {
  227. #ifdef EXTENSION
  228. return (self.frame.size.width < self.frame.size.height) ? kLabelPasscodeSpacePortrait : kLabelPasscodeSpaceLandscape;
  229. #else
  230. return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? kLabelPasscodeSpacePortrait : kLabelPasscodeSpaceLandscape;
  231. #endif
  232. }
  233. - (void)layoutSubviews
  234. {
  235. [super layoutSubviews];
  236. // layout passcode control to center
  237. [self.passcodeField sizeToFit];
  238. if ([self.passcodeField isKindOfClass:[UITextField class]]) {
  239. self.passcodeField.frame = CGRectMake(0, 0, self.frame.size.width - kTextLeftRightSpace * 2.0f, CGRectGetHeight(self.passcodeField.frame) + 10.0f);
  240. }
  241. self.passcodeField.center = CGPointMake(CGRectGetWidth(self.frame) * 0.5f, CGRectGetHeight(self.frame) * 0.5f);
  242. CGFloat maxTextWidth = self.frame.size.width - (kTextLeftRightSpace * 2.0f);
  243. CGFloat labelPasscodeSpace = [self labelPasscodeSpace];
  244. // layout title label
  245. _titleLabel.frame = CGRectMake(kTextLeftRightSpace, 0, maxTextWidth, self.frame.size.height);
  246. [_titleLabel sizeToFit];
  247. CGRect rect = _titleLabel.frame;
  248. rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f);
  249. rect.origin.y = CGRectGetMinY(self.passcodeField.frame) - labelPasscodeSpace - CGRectGetHeight(_titleLabel.frame);
  250. _titleLabel.frame = rect;
  251. // layout message label
  252. if (!_messageLabel.hidden) {
  253. _messageLabel.frame = CGRectMake(kTextLeftRightSpace, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace, maxTextWidth, self.frame.size.height);
  254. [_messageLabel sizeToFit];
  255. rect = _messageLabel.frame;
  256. rect.origin.x = floorf((self.frame.size.width - CGRectGetWidth(rect)) * 0.5f);
  257. _messageLabel.frame = rect;
  258. }
  259. // layout error message label
  260. if (!_errorMessageLabel.hidden) {
  261. _errorMessageLabel.frame = CGRectMake(0, CGRectGetMaxY(self.passcodeField.frame) + labelPasscodeSpace,
  262. maxTextWidth - kErrorMessageLeftRightPadding * 2.0f,
  263. self.frame.size.height);
  264. [_errorMessageLabel sizeToFit];
  265. rect = _errorMessageLabel.frame;
  266. rect.size.width += (kErrorMessageLeftRightPadding * 2.0f);
  267. rect.size.height += (kErrorMessageTopBottomPadding * 2.0f);
  268. rect.origin.x = floorf((self.frame.size.width - rect.size.width) * 0.5f);
  269. _errorMessageLabel.frame = rect;
  270. }
  271. }
  272. #pragma mark - UIResponder
  273. - (BOOL)canBecomeFirstResponder
  274. {
  275. return [self.passcodeField canBecomeFirstResponder];
  276. }
  277. - (BOOL)becomeFirstResponder
  278. {
  279. return [self.passcodeField becomeFirstResponder];
  280. }
  281. - (BOOL)canResignFirstResponder
  282. {
  283. return [self.passcodeField canResignFirstResponder];
  284. }
  285. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  286. {
  287. [super touchesBegan:touches withEvent:event];
  288. [self.passcodeField becomeFirstResponder];
  289. }
  290. #pragma mark - Actions
  291. - (void)passcodeControlEditingChanged:(id)sender
  292. {
  293. if (![self.passcodeField isKindOfClass:[BKPasscodeField class]]) {
  294. return;
  295. }
  296. BKPasscodeField *passcodeField = (BKPasscodeField *)self.passcodeField;
  297. if (passcodeField.passcode.length == passcodeField.maximumLength) {
  298. if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) {
  299. [self.delegate passcodeInputViewDidFinish:self];
  300. }
  301. }
  302. }
  303. #pragma mark - BKPasscodeFieldDelegate
  304. - (BOOL)passcodeField:(BKPasscodeField *)aPasscodeField shouldInsertText:(NSString *)aText
  305. {
  306. return self.isEnabled;
  307. }
  308. - (BOOL)passcodeFieldShouldDeleteBackward:(BKPasscodeField *)aPasscodeField
  309. {
  310. return self.isEnabled;
  311. }
  312. #pragma mark - UITextFieldDelegate
  313. - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
  314. {
  315. if (self.isEnabled == NO) {
  316. return NO;
  317. }
  318. NSUInteger length = textField.text.length - range.length + string.length;
  319. if (length > self.maximumLength) {
  320. return NO;
  321. }
  322. return YES;
  323. }
  324. - (BOOL)textFieldShouldReturn:(UITextField *)textField
  325. {
  326. if (self.isEnabled == NO) {
  327. return NO;
  328. }
  329. if ([self.delegate respondsToSelector:@selector(passcodeInputViewDidFinish:)]) {
  330. [self.delegate passcodeInputViewDidFinish:self];
  331. return NO;
  332. } else {
  333. return YES; // default behavior
  334. }
  335. }
  336. #pragma mark - NSCopying
  337. - (id)copyWithZone:(NSZone *)zone
  338. {
  339. BKPasscodeInputView *view = [[[self class] alloc] initWithFrame:self.bounds];
  340. view.delegate = self.delegate;
  341. view.autoresizingMask = self.autoresizingMask;
  342. view.passcodeStyle = self.passcodeStyle;
  343. view.keyboardType = self.keyboardType;
  344. view.maximumLength = self.maximumLength;
  345. return view;
  346. }
  347. @end