Skip to content

Commit 8b79384

Browse files
committed
Rotation support, code refactoring and Hoshi support.
1 parent 8746a14 commit 8b79384

File tree

6 files changed

+689
-105
lines changed

6 files changed

+689
-105
lines changed

TextFieldEffects/TextFieldEffects.xcodeproj/project.pbxproj

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,58 @@
88

99
/* Begin PBXBuildFile section */
1010
4CBF76511A71AE4500073B6A /* TextFieldEffects.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CBF76501A71AE4500073B6A /* TextFieldEffects.h */; settings = {ATTRIBUTES = (Public, ); }; };
11-
4CBF76681A71AE6700073B6A /* TextFieldEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBF76671A71AE6700073B6A /* TextFieldEffects.swift */; };
11+
4CBF76681A71AE6700073B6A /* KaedeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBF76671A71AE6700073B6A /* KaedeTextField.swift */; };
1212
4CBF76721A71AF7700073B6A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBF76711A71AF7700073B6A /* AppDelegate.swift */; };
1313
4CBF76741A71AF7700073B6A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBF76731A71AF7700073B6A /* ViewController.swift */; };
1414
4CBF76771A71AF7700073B6A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CBF76751A71AF7700073B6A /* Main.storyboard */; };
1515
4CBF76791A71AF7700073B6A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CBF76781A71AF7700073B6A /* Images.xcassets */; };
1616
4CBF767C1A71AF7700073B6A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4CBF767A1A71AF7700073B6A /* LaunchScreen.xib */; };
1717
4CBF768F1A71B1E200073B6A /* TextFieldEffects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CBF764B1A71AE4500073B6A /* TextFieldEffects.framework */; };
18+
4CE365841A7327CD0021A842 /* HoshiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE365831A7327CD0021A842 /* HoshiTextField.swift */; };
19+
4CE365861A73C59B0021A842 /* TextFieldsEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE365851A73C59B0021A842 /* TextFieldsEffects.swift */; };
20+
4CE365881A73CC510021A842 /* TextFieldEffects.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4CBF764B1A71AE4500073B6A /* TextFieldEffects.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
21+
4CE3658D1A7464D80021A842 /* TextFieldEffectsJiro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE3658C1A7464D80021A842 /* TextFieldEffectsJiro.swift */; };
1822
/* End PBXBuildFile section */
1923

24+
/* Begin PBXContainerItemProxy section */
25+
4CE365891A73CC510021A842 /* PBXContainerItemProxy */ = {
26+
isa = PBXContainerItemProxy;
27+
containerPortal = 4CBF76421A71AE4500073B6A /* Project object */;
28+
proxyType = 1;
29+
remoteGlobalIDString = 4CBF764A1A71AE4500073B6A;
30+
remoteInfo = TextFieldEffects;
31+
};
32+
/* End PBXContainerItemProxy section */
33+
34+
/* Begin PBXCopyFilesBuildPhase section */
35+
4CE3658B1A73CC520021A842 /* Embed Frameworks */ = {
36+
isa = PBXCopyFilesBuildPhase;
37+
buildActionMask = 2147483647;
38+
dstPath = "";
39+
dstSubfolderSpec = 10;
40+
files = (
41+
4CE365881A73CC510021A842 /* TextFieldEffects.framework in Embed Frameworks */,
42+
);
43+
name = "Embed Frameworks";
44+
runOnlyForDeploymentPostprocessing = 0;
45+
};
46+
/* End PBXCopyFilesBuildPhase section */
47+
2048
/* Begin PBXFileReference section */
2149
4CBF764B1A71AE4500073B6A /* TextFieldEffects.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TextFieldEffects.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2250
4CBF764F1A71AE4500073B6A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2351
4CBF76501A71AE4500073B6A /* TextFieldEffects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextFieldEffects.h; sourceTree = "<group>"; };
24-
4CBF76671A71AE6700073B6A /* TextFieldEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldEffects.swift; sourceTree = "<group>"; };
52+
4CBF76671A71AE6700073B6A /* KaedeTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KaedeTextField.swift; sourceTree = "<group>"; };
2553
4CBF766D1A71AF7700073B6A /* TextFieldsDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextFieldsDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
2654
4CBF76701A71AF7700073B6A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2755
4CBF76711A71AF7700073B6A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
2856
4CBF76731A71AF7700073B6A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
2957
4CBF76761A71AF7700073B6A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
3058
4CBF76781A71AF7700073B6A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
3159
4CBF767B1A71AF7700073B6A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
60+
4CE365831A7327CD0021A842 /* HoshiTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoshiTextField.swift; sourceTree = "<group>"; };
61+
4CE365851A73C59B0021A842 /* TextFieldsEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldsEffects.swift; sourceTree = "<group>"; };
62+
4CE3658C1A7464D80021A842 /* TextFieldEffectsJiro.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldEffectsJiro.swift; sourceTree = "<group>"; };
3263
/* End PBXFileReference section */
3364

3465
/* Begin PBXFrameworksBuildPhase section */
@@ -72,7 +103,10 @@
72103
isa = PBXGroup;
73104
children = (
74105
4CBF76501A71AE4500073B6A /* TextFieldEffects.h */,
75-
4CBF76671A71AE6700073B6A /* TextFieldEffects.swift */,
106+
4CE365851A73C59B0021A842 /* TextFieldsEffects.swift */,
107+
4CBF76671A71AE6700073B6A /* KaedeTextField.swift */,
108+
4CE365831A7327CD0021A842 /* HoshiTextField.swift */,
109+
4CE3658C1A7464D80021A842 /* TextFieldEffectsJiro.swift */,
76110
4CBF764E1A71AE4500073B6A /* Supporting Files */,
77111
);
78112
path = TextFieldEffects;
@@ -146,10 +180,12 @@
146180
4CBF76691A71AF7700073B6A /* Sources */,
147181
4CBF766A1A71AF7700073B6A /* Frameworks */,
148182
4CBF766B1A71AF7700073B6A /* Resources */,
183+
4CE3658B1A73CC520021A842 /* Embed Frameworks */,
149184
);
150185
buildRules = (
151186
);
152187
dependencies = (
188+
4CE3658A1A73CC510021A842 /* PBXTargetDependency */,
153189
);
154190
name = TextFieldsDemo;
155191
productName = TextFieldsDemo;
@@ -217,7 +253,10 @@
217253
isa = PBXSourcesBuildPhase;
218254
buildActionMask = 2147483647;
219255
files = (
220-
4CBF76681A71AE6700073B6A /* TextFieldEffects.swift in Sources */,
256+
4CE365841A7327CD0021A842 /* HoshiTextField.swift in Sources */,
257+
4CE365861A73C59B0021A842 /* TextFieldsEffects.swift in Sources */,
258+
4CE3658D1A7464D80021A842 /* TextFieldEffectsJiro.swift in Sources */,
259+
4CBF76681A71AE6700073B6A /* KaedeTextField.swift in Sources */,
221260
);
222261
runOnlyForDeploymentPostprocessing = 0;
223262
};
@@ -232,6 +271,14 @@
232271
};
233272
/* End PBXSourcesBuildPhase section */
234273

274+
/* Begin PBXTargetDependency section */
275+
4CE3658A1A73CC510021A842 /* PBXTargetDependency */ = {
276+
isa = PBXTargetDependency;
277+
target = 4CBF764A1A71AE4500073B6A /* TextFieldEffects */;
278+
targetProxy = 4CE365891A73CC510021A842 /* PBXContainerItemProxy */;
279+
};
280+
/* End PBXTargetDependency section */
281+
235282
/* Begin PBXVariantGroup section */
236283
4CBF76751A71AF7700073B6A /* Main.storyboard */ = {
237284
isa = PBXVariantGroup;
@@ -374,6 +421,7 @@
374421
isa = XCBuildConfiguration;
375422
buildSettings = {
376423
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
424+
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
377425
GCC_PREPROCESSOR_DEFINITIONS = (
378426
"DEBUG=1",
379427
"$(inherited)",
@@ -388,6 +436,7 @@
388436
isa = XCBuildConfiguration;
389437
buildSettings = {
390438
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
439+
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
391440
INFOPLIST_FILE = TextFieldsDemo/Info.plist;
392441
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
393442
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -413,6 +462,7 @@
413462
4CBF76631A71AE4500073B6A /* Release */,
414463
);
415464
defaultConfigurationIsVisible = 0;
465+
defaultConfigurationName = Release;
416466
};
417467
4CBF76891A71AF7800073B6A /* Build configuration list for PBXNativeTarget "TextFieldsDemo" */ = {
418468
isa = XCConfigurationList;
@@ -421,6 +471,7 @@
421471
4CBF768B1A71AF7800073B6A /* Release */,
422472
);
423473
defaultConfigurationIsVisible = 0;
474+
defaultConfigurationName = Release;
424475
};
425476
/* End XCConfigurationList section */
426477
};
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//
2+
// HoshiTextField.swift
3+
// TextFieldEffects
4+
//
5+
// Created by Raúl Riera on 24/01/2015.
6+
// Copyright (c) 2015 Raul Riera. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
@IBDesignable class HoshiTextField: UITextField, UITextFieldDelegate {
12+
13+
@IBInspectable var borderInactiveColor: UIColor = UIColor(red: 106, green: 121, blue: 137, alpha: 1) {
14+
didSet {
15+
updateBorder()
16+
}
17+
}
18+
@IBInspectable var borderActiveColor: UIColor = UIColor(red: 106, green: 121, blue: 137, alpha: 1) {
19+
didSet {
20+
updateBorder()
21+
}
22+
}
23+
@IBInspectable var placeholderColor: UIColor = UIColor(red: 106, green: 121, blue: 137, alpha: 1) {
24+
didSet {
25+
updatePlaceholder()
26+
}
27+
}
28+
29+
override var placeholder: String? {
30+
didSet {
31+
updatePlaceholder()
32+
}
33+
}
34+
35+
override var bounds: CGRect {
36+
didSet {
37+
updateBorder()
38+
updatePlaceholder()
39+
}
40+
}
41+
42+
private let borderThickness: (active: CGFloat, inactive: CGFloat) = (active: 2, inactive: 0.5)
43+
private let placeholderLabel = UILabel()
44+
private let placeholderInsets = CGPoint(x: 0, y: 6)
45+
private let textFieldInsets = CGPoint(x: 0, y: 12)
46+
private let inactiveBorderLayer = CALayer()
47+
private let activeBorderLayer = CALayer()
48+
49+
/**
50+
Draws all the requires view on top of the textfield
51+
52+
:param: rect to based the views from
53+
*/
54+
private func drawViewsForRect(rect: CGRect) {
55+
let frame = CGRect(origin: CGPointZero, size: CGSize(width: rect.size.width, height: rect.size.height))
56+
57+
placeholderLabel.frame = CGRectInset(frame, placeholderInsets.x, placeholderInsets.y)
58+
placeholderLabel.font = placeholderFontFromFont(self.font)
59+
60+
updateBorder()
61+
updatePlaceholder()
62+
63+
layer.addSublayer(inactiveBorderLayer)
64+
layer.addSublayer(activeBorderLayer)
65+
addSubview(placeholderLabel)
66+
67+
delegate = self
68+
}
69+
70+
private func updateBorder() {
71+
inactiveBorderLayer.frame = rectForBorder(borderThickness.inactive, isFill: true)
72+
inactiveBorderLayer.backgroundColor = borderInactiveColor.CGColor
73+
74+
activeBorderLayer.frame = rectForBorder(borderThickness.active, isFill: false)
75+
activeBorderLayer.backgroundColor = borderActiveColor.CGColor
76+
}
77+
78+
private func updatePlaceholder() {
79+
placeholderLabel.text = placeholder
80+
placeholderLabel.textColor = placeholderColor
81+
placeholderLabel.sizeToFit()
82+
layoutPlaceholderInTextRect()
83+
84+
if isFirstResponder() || !text.isEmpty {
85+
animateViewsForTextEntry()
86+
}
87+
}
88+
89+
private func placeholderFontFromFont(font: UIFont) -> UIFont! {
90+
let smallerFont = UIFont(name: font.fontName, size: font.pointSize * 0.65)
91+
return smallerFont
92+
}
93+
94+
private func rectForBorder(thickness: CGFloat, isFill: Bool) -> CGRect {
95+
if isFill {
96+
return CGRect(origin: CGPoint(x: 0, y: CGRectGetHeight(frame)-thickness), size: CGSize(width: CGRectGetWidth(frame), height: thickness))
97+
} else {
98+
return CGRect(origin: CGPoint(x: 0, y: CGRectGetHeight(frame)-thickness), size: CGSize(width: 0, height: thickness))
99+
}
100+
}
101+
102+
private func layoutPlaceholderInTextRect() {
103+
104+
if !text.isEmpty {
105+
return
106+
}
107+
108+
let textRect = textRectForBounds(bounds)
109+
var originX = textRect.origin.x
110+
switch self.textAlignment {
111+
case .Center:
112+
originX += textRect.size.width/2 - placeholderLabel.bounds.width/2
113+
case .Right:
114+
originX += textRect.size.width - placeholderLabel.bounds.width
115+
default:
116+
break
117+
}
118+
placeholderLabel.frame = CGRect(x: originX, y: textRect.size.height/2,
119+
width: placeholderLabel.frame.size.width, height: placeholderLabel.frame.size.height)
120+
}
121+
122+
private func animateViewsForTextEntry() {
123+
if text.isEmpty {
124+
UIView.animateWithDuration(0.3, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: ({
125+
self.placeholderLabel.frame.origin = CGPoint(x: 10, y: self.placeholderLabel.frame.origin.y)
126+
self.placeholderLabel.alpha = 0
127+
}), completion: { (completed) -> Void in
128+
129+
self.layoutPlaceholderInTextRect()
130+
self.placeholderLabel.frame.origin = CGPoint(x: self.placeholderLabel.frame.origin.x, y: self.placeholderLabel.frame.origin.y - self.placeholderLabel.frame.size.height - self.placeholderInsets.y)
131+
132+
UIView.animateWithDuration(0.2, animations: { () -> Void in
133+
self.placeholderLabel.alpha = 0.5
134+
})
135+
})
136+
}
137+
138+
UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.CurveEaseInOut, animations: ({
139+
self.activeBorderLayer.frame = self.rectForBorder(self.borderThickness.active, isFill: true)
140+
}), completion: nil)
141+
}
142+
143+
private func animateViewsForTextDisplay() {
144+
if text.isEmpty {
145+
UIView.animateWithDuration(0.35, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 2.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: ({
146+
self.layoutPlaceholderInTextRect()
147+
self.placeholderLabel.alpha = 1
148+
}), completion: nil)
149+
150+
UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.BeginFromCurrentState, animations: ({
151+
self.activeBorderLayer.frame = self.rectForBorder(self.borderThickness.active, isFill: false)
152+
}), completion: nil)
153+
}
154+
}
155+
156+
// MARK: - Overrides
157+
158+
override func drawRect(rect: CGRect) {
159+
drawViewsForRect(rect)
160+
}
161+
162+
override func drawPlaceholderInRect(rect: CGRect) {
163+
// Don't draw any placeholders
164+
}
165+
166+
override func editingRectForBounds(bounds: CGRect) -> CGRect {
167+
return CGRectOffset(bounds, textFieldInsets.x, textFieldInsets.y)
168+
}
169+
170+
override func textRectForBounds(bounds: CGRect) -> CGRect {
171+
return CGRectOffset(bounds, textFieldInsets.x, textFieldInsets.y)
172+
}
173+
174+
override func prepareForInterfaceBuilder() {
175+
drawViewsForRect(frame)
176+
}
177+
178+
// MARK: - UITextFieldDelegate
179+
180+
func textFieldDidBeginEditing(textField: UITextField) {
181+
animateViewsForTextEntry()
182+
}
183+
184+
func textFieldDidEndEditing(textField: UITextField) {
185+
animateViewsForTextDisplay()
186+
}
187+
}

0 commit comments

Comments
 (0)