Skip to content

Commit a7d4baa

Browse files
committed
Added variable input field view
1 parent 960865f commit a7d4baa

8 files changed

+199
-15
lines changed

TOPasscodeViewController/Models/TOPasscodeCircleImage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
1212

1313
@interface TOPasscodeCircleImage : UIImage
1414

15-
+ (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding;
15+
+ (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding antialias:(BOOL)antialias;
1616
+ (UIImage *)hollowCircleImageOfSize:(CGFloat)size strokeWidth:(CGFloat)strokeWidth padding:(CGFloat)padding;
1717

1818
@end

TOPasscodeViewController/Models/TOPasscodeCircleImage.m

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010

1111
@implementation TOPasscodeCircleImage
1212

13-
+ (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding
13+
+ (UIImage *)circleImageOfSize:(CGFloat)size inset:(CGFloat)inset padding:(CGFloat)padding antialias:(BOOL)antialias
1414
{
1515
UIImage *image = nil;
1616
CGSize imageSize = (CGSize){size + (padding * 2), size + (padding * 2)};
1717

1818
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0f);
1919
{
2020
CGContextRef context = UIGraphicsGetCurrentContext();
21-
CGContextSetShouldAntialias(context, NO);
21+
22+
if (!antialias) {
23+
CGContextSetShouldAntialias(context, NO);
24+
}
2225

2326
CGRect rect = (CGRect){padding + inset, padding + inset, size - (inset * 2), size - (inset * 2)};
2427
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect:rect];

TOPasscodeViewController/Views/TOPasscodeFixedInputView.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ - (void)setCircleViewsForLength:(NSInteger)length
106106
- (void)setCircleImagesForDiameter:(CGFloat)diameter
107107
{
108108
self.circleImage = [TOPasscodeCircleImage hollowCircleImageOfSize:diameter strokeWidth:1.0f padding:1.0f];
109-
self.highlightedCircleImage = [TOPasscodeCircleImage circleImageOfSize:diameter inset:0.5f padding:1.0f];
109+
self.highlightedCircleImage = [TOPasscodeCircleImage circleImageOfSize:diameter inset:0.5f padding:1.0f antialias:NO];
110110

111111
for (TOPasscodeCircleView *circleView in self.circleViews) {
112112
[self setImagesOfCircleView:circleView];
@@ -124,6 +124,7 @@ - (void)setImagesOfCircleView:(TOPasscodeCircleView *)circleView
124124
- (NSArray<TOPasscodeCircleView *> *)circleViews
125125
{
126126
if (_circleViews) { return _circleViews; }
127+
_circleViews = [NSArray array];
127128
[self setCircleViewsForLength:self.length];
128129
[self setCircleImagesForDiameter:self.circleDiameter];
129130
return _circleViews;

TOPasscodeViewController/Views/TOPasscodeInputField.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
NS_ASSUME_NONNULL_BEGIN
1212

1313
typedef NS_ENUM(NSInteger, TOPasscodeInputFieldStyle) {
14-
TOPasscodeInputFieldStyleFixed, // The passcode explicitly requires a specific number of characters
15-
TOPasscodeInputFieldStyleVariable // The passcode can be any arbitrary numher of characters
14+
TOPasscodeInputFieldStyleFixed, // The passcode explicitly requires a specific number of characters (Shows hollow circles)
15+
TOPasscodeInputFieldStyleVariable // The passcode can be any arbitrary number of characters (Shows an empty rectangle)
1616
};
1717

1818
@interface TOPasscodeInputField : UIVisualEffectView <UIKeyInput>

TOPasscodeViewController/Views/TOPasscodeInputField.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ - (void)layoutSubviews
7373
- (void)updateCircleImagesForDiameter:(CGFloat)diameter
7474
{
7575
self.circleImage = [TOPasscodeCircleImage hollowCircleImageOfSize:diameter strokeWidth:1.0f padding:1.0f];
76-
self.highlightedCircleImage = [TOPasscodeCircleImage circleImageOfSize:diameter inset:0.5f padding:1.0f];
76+
self.highlightedCircleImage = [TOPasscodeCircleImage circleImageOfSize:diameter inset:0.5f padding:1.0f antialias:NO];
7777

7878
for (TOPasscodeCircleView *circleView in self.fixedCircleViews) {
7979
circleView.circleImage = self.circleImage;

TOPasscodeViewController/Views/TOPasscodeKeypadView.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ - (UIImage *)buttonImage
163163
- (UIImage *)tappedButtonImage
164164
{
165165
if (!_tappedButtonImage) {
166-
_tappedButtonImage = [TOPasscodeCircleImage circleImageOfSize:self.buttonDiameter inset:self.buttonStrokeWidth * 0.5f padding:1.0f];
166+
_tappedButtonImage = [TOPasscodeCircleImage circleImageOfSize:self.buttonDiameter inset:self.buttonStrokeWidth * 0.5f padding:1.0f antialias:NO];
167167
}
168168

169169
return _tappedButtonImage;

TOPasscodeViewController/Views/TOPasscodeVariableInputField.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#import <UIKit/UIKit.h>
1010

11-
@interface TOPasscodeVariableInputField : UIView
11+
@interface TOPasscodeVariableInputField : UIImageView
1212

1313
/* The thickness of the stroke around the view (Default is 1.5) */
1414
@property (nonatomic, assign) CGFloat outlineThickness;
@@ -28,7 +28,7 @@
2828
/* The maximum number of circles to show (This will indicate the view's width) (Default is 10) */
2929
@property (nonatomic, assign) NSInteger maximumVisibleLength;
3030

31-
/* Set the number of characters entered into this view (May be larger than `maximumVisibleLenght`) */
31+
/* Set the number of characters entered into this view (May be larger than `maximumVisibleLength`) */
3232
@property (nonatomic, assign) NSInteger length;
3333

3434
/* Set the number of characters represented by this field, animated if desired */

TOPasscodeViewController/Views/TOPasscodeVariableInputField.m

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,203 @@
77
//
88

99
#import "TOPasscodeVariableInputField.h"
10+
#import "TOPasscodeCircleImage.h"
11+
12+
@interface TOPasscodeVariableInputField ()
13+
14+
@property (nonatomic, strong) UIImage *backgroundImage; // The outline image for this view
15+
@property (nonatomic, strong) UIImage *circleImage; // The circle image representing a single character
16+
17+
@property (nonatomic, strong) NSMutableArray<UIImageView *> *circleViews;
18+
19+
@end
1020

1121
@implementation TOPasscodeVariableInputField
1222

23+
#pragma mark - Class Creation -
24+
1325
- (instancetype)initWithFrame:(CGRect)frame
1426
{
1527
if (self = [super initWithFrame:frame]) {
16-
_outlineThickness = 1.5f;
17-
_outlineCornerRadius = 15.0f;
18-
_circleDiameter = 13.0f;
19-
_circleSpacing = 10.0f;
28+
_outlineThickness = 1.0f;
29+
_outlineCornerRadius = 5.0f;
30+
_circleDiameter = 11.0f;
31+
_circleSpacing = 7.0f;
2032
_outlinePadding = (CGSize){10,10};
21-
_maximumVisibleLength = 10.0f;
33+
_maximumVisibleLength = 10;
2234
}
2335

2436
return self;
2537
}
2638

39+
#pragma mark - View Setup -
40+
- (void)setUpImageForCircleViews
41+
{
42+
if (self.circleImage != nil) { return; }
43+
44+
self.circleImage = [TOPasscodeCircleImage circleImageOfSize:_circleDiameter inset:0.0f padding:1.0f antialias:YES];
45+
for (UIImageView *circleView in self.circleViews) {
46+
circleView.image = self.circleImage;
47+
[circleView sizeToFit];
48+
}
49+
}
50+
51+
- (void)setUpCircleViewsForLength:(NSInteger)length
52+
{
53+
// Set up the number of circle views if needed
54+
if (self.circleViews.count == length) { return; }
55+
56+
if (self.circleViews == nil) {
57+
self.circleViews = [NSMutableArray arrayWithCapacity:_maximumVisibleLength];
58+
}
59+
60+
// Reduce the number of views
61+
while (self.circleViews.count > length) {
62+
UIImageView *circleView = self.circleViews.lastObject;
63+
[circleView removeFromSuperview];
64+
[self.circleViews removeLastObject];
65+
}
66+
67+
// Increase the number of views
68+
while (self.circleViews.count < length) {
69+
UIImageView *circleView = [[UIImageView alloc] initWithImage:self.circleImage];
70+
circleView.alpha = 0.0f;
71+
[self addSubview:circleView];
72+
[self.circleViews addObject:circleView];
73+
}
74+
}
75+
76+
- (void)setUpBackgroundImage
77+
{
78+
if (self.backgroundImage != nil) { return; }
79+
80+
self.backgroundImage = [[self class] backgroundImageWithThickness:_outlineThickness cornerRadius:_outlineCornerRadius];
81+
self.image = self.backgroundImage;
82+
}
83+
84+
#pragma mark - View Layout -
85+
86+
- (void)sizeToFit
87+
{
88+
CGRect frame = self.frame;
89+
90+
// Calculate the width
91+
frame.size.width = self.outlineThickness * 2.0f;
92+
frame.size.width += (self.outlinePadding.width * 2.0f);
93+
frame.size.width += (self.maximumVisibleLength * (self.circleDiameter+2.0f)); // +2 for padding
94+
frame.size.width += (self.maximumVisibleLength - 1) * self.circleSpacing;
95+
96+
// Height
97+
frame.size.height = self.outlineThickness * 2.0f;
98+
frame.size.height += self.outlinePadding.height * 2.0f;
99+
frame.size.height += self.circleDiameter;
100+
101+
self.frame = frame;
102+
}
103+
104+
- (void)layoutSubviews
105+
{
106+
[super layoutSubviews];
107+
108+
// Genearate the background image if we don't have one yet
109+
[self setUpBackgroundImage];
110+
111+
// Set up the circle view image
112+
[self setUpImageForCircleViews];
113+
114+
// Set up the circle views
115+
[self setUpCircleViewsForLength:self.maximumVisibleLength];
116+
117+
// Layout the circle views for the current length
118+
CGRect frame = CGRectZero;
119+
frame.size = self.circleImage.size;
120+
frame.origin.y = CGRectGetMidY(self.bounds) - (frame.size.height * 0.5f);
121+
frame.origin.x = self.outlinePadding.width + self.outlineThickness;
122+
123+
for (UIImageView *circleView in self.circleViews) {
124+
circleView.frame = frame;
125+
frame.origin.x += frame.size.width + self.circleSpacing;
126+
}
127+
}
128+
129+
#pragma mark - Accessors -
130+
131+
- (void)setOutlineThickness:(CGFloat)outlineThickness
132+
{
133+
if (_outlineThickness == outlineThickness) { return; }
134+
_outlineThickness = outlineThickness;
135+
self.backgroundImage = nil;
136+
[self setNeedsLayout];
137+
}
138+
139+
- (void)setOutlineCornerRadius:(CGFloat)outlineCornerRadius
140+
{
141+
if (_outlineCornerRadius == outlineCornerRadius) { return; }
142+
_outlineCornerRadius = outlineCornerRadius;
143+
self.backgroundImage = nil;
144+
[self setNeedsLayout];
145+
}
146+
147+
- (void)setCircleDiameter:(CGFloat)circleDiameter
148+
{
149+
if (_circleDiameter == circleDiameter) { return; }
150+
_circleDiameter = circleDiameter;
151+
[self setUpImageForCircleViews];
152+
}
153+
154+
- (void)setLength:(NSInteger)length
155+
{
156+
[self setLength:length animated:NO];
157+
}
158+
159+
- (void)setLength:(NSInteger)length animated:(BOOL)animated
160+
{
161+
if (length == _length) { return; }
162+
163+
void (^animationBlock)() = ^{
164+
NSInteger i = 0;
165+
for (UIImageView *circleView in self.circleViews) {
166+
circleView.alpha = i < length ? 1.0f : 0.0f;
167+
}
168+
};
169+
170+
if (!animated) {
171+
animationBlock();
172+
return;
173+
}
174+
175+
[UIView animateWithDuration:0.4f animations:animationBlock];
176+
}
177+
178+
#pragma mark - Image Creation -
27179

180+
+ (UIImage *)backgroundImageWithThickness:(CGFloat)thickness cornerRadius:(CGFloat)radius
181+
{
182+
CGFloat inset = thickness / 2.0f;
183+
CGFloat dimension = (radius * 2.0f) + 2.0f;
184+
185+
CGRect frame = CGRectZero;
186+
frame.origin = CGPointMake(inset, inset);
187+
frame.size = CGSizeMake(dimension, dimension);
188+
189+
CGSize canvasSize = frame.size;
190+
canvasSize.width += thickness;
191+
canvasSize.height += thickness;
192+
193+
UIGraphicsBeginImageContextWithOptions(canvasSize, NO, 0.0f);
194+
{
195+
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius];
196+
path.lineWidth = thickness;
197+
[[UIColor blackColor] setStroke];
198+
[path stroke];
199+
}
200+
201+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
202+
UIGraphicsEndImageContext();
203+
204+
UIEdgeInsets insets = UIEdgeInsetsMake(radius+1, radius+1, radius+1, radius+1);
205+
image = [image resizableImageWithCapInsets:insets];
206+
return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
207+
}
28208

29209
@end

0 commit comments

Comments
 (0)