Skip to content

Commit 48e9fba

Browse files
committed
added all basic features, cleaned and documented files
1 parent 0ed3f55 commit 48e9fba

File tree

6 files changed

+442
-1
lines changed

6 files changed

+442
-1
lines changed

iOSFormUtils.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
Pod::Spec.new do |s|
1010
s.name = "iOSFormUtils"
11-
s.version = "0.0.2"
11+
s.version = "0.0.3"
1212
s.summary = "iOSFormUtils helps you developping validated forms in iOS apps."
1313

1414
# This description is used to generate tags and improve search results.

iOSFormUtils.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,35 @@
77
objects = {
88

99
/* Begin PBXFileReference section */
10+
C88353E81CFDBA6900C1E8E3 /* AccessorizedFormInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AccessorizedFormInput.swift; path = iOSFormUtils/AccessorizedFormInput.swift; sourceTree = "<group>"; };
1011
C8C6A7FA1CEDB48D00B9B9D8 /* test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = test.swift; path = iOSFormUtils/test.swift; sourceTree = "<group>"; };
12+
C8C6A7FC1CEE092B00B9B9D8 /* plop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = plop.swift; path = iOSFormUtils/plop.swift; sourceTree = "<group>"; };
13+
C8F50C0F1CEF0A26006D8C51 /* ValidatedForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ValidatedForm.swift; path = iOSFormUtils/ValidatedForm.swift; sourceTree = "<group>"; };
14+
C8F50C101CEF0A26006D8C51 /* ValidatedInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ValidatedInput.swift; path = iOSFormUtils/ValidatedInput.swift; sourceTree = "<group>"; };
15+
C8F50C111CEF0A26006D8C51 /* ValidatedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ValidatedTextField.swift; path = iOSFormUtils/ValidatedTextField.swift; sourceTree = "<group>"; };
16+
C8F50C121CEF0A26006D8C51 /* ValidatedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ValidatedTextView.swift; path = iOSFormUtils/ValidatedTextView.swift; sourceTree = "<group>"; };
1117
/* End PBXFileReference section */
1218

1319
/* Begin PBXGroup section */
1420
C8714A461CECC5360006E038 = {
1521
isa = PBXGroup;
1622
children = (
23+
C8C6A7FB1CEDBDED00B9B9D8 /* iOSFormUtils */,
24+
);
25+
sourceTree = "<group>";
26+
};
27+
C8C6A7FB1CEDBDED00B9B9D8 /* iOSFormUtils */ = {
28+
isa = PBXGroup;
29+
children = (
30+
C88353E81CFDBA6900C1E8E3 /* AccessorizedFormInput.swift */,
31+
C8F50C0F1CEF0A26006D8C51 /* ValidatedForm.swift */,
32+
C8F50C101CEF0A26006D8C51 /* ValidatedInput.swift */,
33+
C8F50C111CEF0A26006D8C51 /* ValidatedTextField.swift */,
34+
C8F50C121CEF0A26006D8C51 /* ValidatedTextView.swift */,
1735
C8C6A7FA1CEDB48D00B9B9D8 /* test.swift */,
36+
C8C6A7FC1CEE092B00B9B9D8 /* plop.swift */,
1837
);
38+
name = iOSFormUtils;
1939
sourceTree = "<group>";
2040
};
2141
/* End PBXGroup section */

iOSFormUtils/Form.swift

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// Form.swift
3+
// Pods
4+
//
5+
// Created by Nicolas LELOUP on 15/09/2016.
6+
//
7+
//
8+
9+
import Foundation
10+
11+
// MARK: Protocols
12+
13+
/// Delegate protocol to handle form submitting
14+
public protocol ValidatedFormKeyboardDelegate {
15+
/**
16+
Triggered when the keybiard return key is touched on the last field.
17+
*/
18+
func goReturnKeyTouched()
19+
}
20+
21+
// MARK: Class
22+
/// UIScrollView child class for forms handling
23+
public class Form: UIScrollView {
24+
// MARK: Class properties
25+
/// The original frame of the form
26+
var originalFrame: CGRect!
27+
28+
/// Flag to stor either it has been scrolled because of keyboard appearing
29+
var viewScrolledForKeyboard = false
30+
31+
/// The keyboard frame height
32+
var keyboardViewHeight: CGFloat = 216
33+
34+
/// The stored delegate
35+
public var keyboardDelegate: ValidatedFormKeyboardDelegate!
36+
37+
/// The form inputs
38+
public var inputs: [FormInput] = [] {
39+
didSet {
40+
handleInputsReturnKeys()
41+
}
42+
}
43+
44+
// MARK: Superclass overrides
45+
override init(frame: CGRect) {
46+
super.init(frame: frame)
47+
commonInit()
48+
}
49+
50+
required public init(coder: NSCoder) {
51+
super.init(coder: coder)!
52+
commonInit()
53+
}
54+
55+
override public func addSubview(view: UIView) {
56+
super.addSubview(view)
57+
58+
if let input: FormInput = view as? FormInput {
59+
input.formInputDelegate = self
60+
inputs.append(input)
61+
}
62+
}
63+
64+
// MARK: Private own methods
65+
66+
/**
67+
Custom initializer
68+
*/
69+
private func commonInit() {
70+
NSNotificationCenter.defaultCenter().addObserver(
71+
self,
72+
selector: #selector(Form.keyboardShown(_:)),
73+
name: UIKeyboardDidShowNotification,
74+
object: nil
75+
)
76+
77+
NSNotificationCenter.defaultCenter().addObserver(
78+
self,
79+
selector: #selector(Form.textFieldReturnedFired(_:)),
80+
name: tfReturnedNotifName,
81+
object: nil
82+
)
83+
}
84+
85+
/**
86+
Handles return keys type for inputs
87+
*/
88+
private func handleInputsReturnKeys() {
89+
for input in inputs {
90+
if let textField: UITextField = input as? UITextField {
91+
if textField == inputs.last as? UITextField {
92+
textField.returnKeyType = .Go
93+
} else {
94+
textField.returnKeyType = .Next
95+
}
96+
}
97+
}
98+
}
99+
100+
/**
101+
Updates the scrollview frame when keyboard appears.
102+
Scrolls to make the current field visible.
103+
*/
104+
private func minimizeScrollingZone(input: FormInput) {
105+
if (!viewScrolledForKeyboard) {
106+
viewScrolledForKeyboard = true
107+
originalFrame = self.frame
108+
self.translatesAutoresizingMaskIntoConstraints = true
109+
let newFrame = CGRect(
110+
x: frame.origin.x,
111+
y: frame.origin.y,
112+
width: frame.width,
113+
height: frame.height - keyboardViewHeight
114+
)
115+
self.frame = newFrame
116+
}
117+
118+
UIView.animateWithDuration(0.2) {
119+
self.contentOffset = CGPoint(x: 0, y: input.frame.origin.y - self.frame.height/2 + input.frame.height/2)
120+
}
121+
}
122+
123+
/**
124+
Resets the scrolling zone to its original value.
125+
*/
126+
private func resetScrollingZone() {
127+
viewScrolledForKeyboard = false
128+
self.frame = originalFrame
129+
}
130+
131+
// MARK: NSNotification listeners
132+
133+
/**
134+
If input attached to the notification is the last of the form, submit is triggered. If not, focus is given to the following input.
135+
136+
- Parameter notification: the received notification.
137+
*/
138+
func textFieldReturnedFired(notification: NSNotification) {
139+
if let textfield = notification.object as? FormInput {
140+
if let index: Int = indexForInput(textfield) {
141+
if isLastInput(textfield) {
142+
textfield.stopEditing()
143+
resetScrollingZone()
144+
if let _ = keyboardDelegate {
145+
keyboardDelegate.goReturnKeyTouched()
146+
}
147+
} else {
148+
inputs[index + 1].becomeFirstResponder()
149+
}
150+
}
151+
}
152+
}
153+
154+
/**
155+
Updates the keyboard height with the right value.
156+
157+
- Parameter notification: the received notification
158+
*/
159+
func keyboardShown(notification: NSNotification) {
160+
let info = notification.userInfo!
161+
let value: AnyObject = info[UIKeyboardFrameEndUserInfoKey]!
162+
163+
let rawFrame = value.CGRectValue
164+
let keyboardFrame = self.convertRect(rawFrame, fromView: nil)
165+
166+
keyboardViewHeight = keyboardFrame.height
167+
}
168+
169+
/**
170+
Checks if the given input is the last one.
171+
172+
- Parameter input: the input to compare
173+
*/
174+
private func isLastInput(input: FormInput) -> Bool {
175+
return input == inputs.last
176+
}
177+
178+
/**
179+
Gives the index of a given input
180+
181+
- Parameter input: the input to get the index.
182+
*/
183+
private func indexForInput(input: FormInput) -> Int? {
184+
return inputs.indexOf(input)
185+
}
186+
}
187+
188+
// MARK: Extensions
189+
extension Form: FormInputDelegate {
190+
public func didEnterEditionMode(input: FormInput) {
191+
dispatch_async(dispatch_get_main_queue()) {
192+
self.minimizeScrollingZone(input)
193+
}
194+
}
195+
196+
public func didExitEditionMode(input: FormInput) {}
197+
}

iOSFormUtils/FormInput.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// ValidatedInput.swift
3+
// application
4+
//
5+
// Created by Nicolas LELOUP on 18/09/2015.
6+
// Copyright © 2015 Nicolas LELOUP - Buzznative. All rights reserved.
7+
//
8+
9+
// MARK: Constants
10+
let tfBecameFirstResponderNotifName = "textFieldBecameFirstResponder"
11+
let tfResignedFirstResponderNotifName = "textFieldResignedFirstResponder"
12+
let tfReturnedNotifName = "textFieldReturned"
13+
14+
// MARK: Protocols
15+
16+
/// Delegate protocol for FormInput
17+
public protocol FormInputDelegate {
18+
func didEnterEditionMode(input: FormInput)
19+
func didExitEditionMode(input: FormInput)
20+
}
21+
22+
/// Data source protocol for FormInput
23+
public protocol FormInputDataSource {
24+
func applyCustomInit(input: FormInput)
25+
}
26+
27+
// MARK: Class
28+
/// UITextfield child class for easy form inputs handling
29+
public class FormInput: UITextField {
30+
// MARK: Class variables
31+
public var inputDataSource: FormInputDataSource!
32+
public var formInputDelegate: FormInputDelegate!
33+
private var inputAccessory: UIView!
34+
private var validationHandler: ValidatedFormInput!
35+
var validationDelegate: ValidatedFormInputDelegate!
36+
var validationType = ValidatedFormInputType.NoValidation
37+
38+
// MARK: Superclass overrides
39+
override init(frame: CGRect) {
40+
super.init(frame: frame)
41+
self.commonInit()
42+
}
43+
44+
required public init(coder aDecoder: NSCoder) {
45+
super.init(coder: aDecoder)!
46+
self.commonInit()
47+
}
48+
49+
// MARK: Public own methods
50+
51+
/**
52+
Main initializer
53+
*/
54+
public func commonInit() {
55+
self.delegate = self
56+
self.validationHandler = self
57+
58+
if let _ = inputDataSource {
59+
inputDataSource.applyCustomInit(self)
60+
}
61+
}
62+
63+
// MARK: Public own methods
64+
65+
/**
66+
Stops the current edition.
67+
*/
68+
public func stopEditing() {
69+
NSNotificationCenter.defaultCenter().postNotificationName(tfResignedFirstResponderNotifName, object: self)
70+
self.resignFirstResponder()
71+
}
72+
}
73+
74+
// MARK: Extensions
75+
extension FormInput: UITextFieldDelegate {
76+
public func textFieldDidBeginEditing(textField: UITextField) {
77+
if let _ = formInputDelegate {
78+
formInputDelegate.didEnterEditionMode(self)
79+
}
80+
if let _ = validationDelegate {
81+
validationDelegate.didExitErrorMode(self)
82+
}
83+
NSNotificationCenter.defaultCenter().postNotificationName(tfBecameFirstResponderNotifName, object: self)
84+
}
85+
86+
public func textFieldDidEndEditing(textField: UITextField) {
87+
if let _ = formInputDelegate {
88+
formInputDelegate.didExitEditionMode(self)
89+
}
90+
}
91+
92+
public func textFieldShouldReturn(textField: UITextField) -> Bool {
93+
NSNotificationCenter.defaultCenter().postNotificationName(tfReturnedNotifName, object: self)
94+
95+
return true;
96+
}
97+
}
98+

iOSFormUtils/ValidatedForm.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// ValidatedForm.swift
3+
// application
4+
//
5+
// Created by Nicolas LELOUP on 09/09/2015.
6+
// Copyright (c) 2015 Nicolas LELOUP - Buzznative. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
// MARK: Class
12+
/// Form class able to validates its fields
13+
public class ValidatedForm: Form {
14+
15+
// MARK: Public own methods
16+
17+
/**
18+
Checks if all fields are valid
19+
20+
- Return: validation result
21+
*/
22+
public func validate() -> Bool {
23+
for input in inputs {
24+
if (!input.validateFormat()) {
25+
return false
26+
}
27+
}
28+
29+
return true
30+
}
31+
}

0 commit comments

Comments
 (0)