Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active August 29, 2015 14:02
Show Gist options
  • Save kristopherjohnson/fbb5a0d8dc888cd8bde9 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/fbb5a0d8dc888cd8bde9 to your computer and use it in GitHub Desktop.
Reusable functions/protocols/classes for text-to-string/string-to-text conversions in Swift
/*
Copyright (c) 2014 Kristopher Johnson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import UIKit
// Read Int value from String, returning nil if it is not a valid integer string
func integerValueForText(s: String) -> Int? {
let length = countElements(s)
let scanner = NSScanner(string: s)
scanner.charactersToBeSkipped = nil
var value: Int = 0
if scanner.scanInteger(&value) && (scanner.scanLocation == length) {
return value
}
else {
return nil
}
}
// Read Double value from String, returning nil if it is not a valid floating-point string
func doubleValueForText(s: String) -> Double? {
let length = countElements(s)
let scanner = NSScanner(string: s)
scanner.charactersToBeSkipped = nil
var value: Double = 0.0
if scanner.scanDouble(&value) && (scanner.scanLocation == length) {
return value
}
else {
return nil
}
}
// Determine whether given string is a valid integer string
func isValidIntegerText(s: String) -> Bool {
if let value = integerValueForText(s) {
return true
}
else {
return false
}
}
// Determine whether given string is a valid floating-point string
func isValidDoubleText(s: String) -> Bool {
if let value = doubleValueForText(s) {
return true
}
else {
return false
}
}
// Protocol for object with a read-write "text" property
protocol TextSettable {
var text: String! { get set }
}
// Return Int value of text property, or nil if empty
func integerValueForText(ts: TextSettable) -> Int? {
return integerValueForText(ts.text)
}
// Return Double value of text property, or nil if empty
func doubleValueForText(ts: TextSettable) -> Double? {
return doubleValueForText(ts.text)
}
// Set text property to string representation of given number
func setNumericValueForText(var ts: TextSettable, value: NSNumber) {
ts.text = value.stringValue
}
// Set text property to string representation of given number using a Double format string ("%f", "%e", "%g", etc.)
func setNumericValueForText(var ts: TextSettable, value: NSNumber, doubleFormat: NSString) {
ts.text = NSString(format: doubleFormat, value.doubleValue)
}
// Add these methods to UILabel
extension UILabel: TextSettable {
func textIntegerValue() -> Int? {
return integerValueForText(self)
}
func textDoubleValue() -> Double? {
return doubleValueForText(self)
}
func setTextNumericValue(value: NSNumber) {
setNumericValueForText(self, value)
}
func setTextNumericValue(value: NSNumber, doubleFormat: NSString) {
setNumericValueForText(self, value, doubleFormat)
}
}
// Add these methods to UITextField
extension UITextField: TextSettable {
func textIntegerValue() -> Int? {
return integerValueForText(self)
}
func textDoubleValue() -> Double? {
return doubleValueForText(self)
}
func setTextNumericValue(value: NSNumber) {
setNumericValueForText(self, value)
}
func setTextNumericValue(value: NSNumber, doubleFormat: NSString) {
setNumericValueForText(self, value, doubleFormat)
}
}
/*
Copyright (c) 2014 Kristopher Johnson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import Foundation
import UIKit
// Implementation of UITextFieldDelegate that prevents non-numeric characters
// from being entered in a numeric text field.
class NumericTextFieldDelegate: NSObject, UITextFieldDelegate {
var maxLength: Int
var allowDecimal: Bool
init(maxLength: Int, allowDecimal: Bool) {
self.maxLength = maxLength
self.allowDecimal = allowDecimal
}
convenience init(maxLength: Int) {
self.init(maxLength: maxLength, allowDecimal: false)
}
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool
{
let originalText: NSString = textField.text
let proposedText: NSString = originalText.stringByReplacingCharactersInRange(range,
withString: string)
let proposedLength = proposedText.length
if proposedLength > maxLength {
return false
}
if allowDecimal {
if proposedLength > 0 && !isValidDoubleText(proposedText) {
return false
}
}
else {
if proposedLength > 0 && !isValidIntegerText(proposedText) {
return false
}
}
return true
}
}
/*
Copyright (c) 2014 Kristopher Johnson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import XCTest
import Foundation
import UIKit
class NumericTextFieldDelegateTests: XCTestCase {
var delegate: NumericTextFieldDelegate!
var textField: UITextField!
override func setUp() {
super.setUp()
delegate = NumericTextFieldDelegate(maxLength: 5)
textField = UITextField()
textField.delegate = delegate
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testAllowAdditionOfNumericCharacters() {
textField.text = ""
let allowed = delegate.textField(textField,
shouldChangeCharactersInRange: NSRange(location: 0, length: 0),
replacementString: "12345")
XCTAssertTrue(allowed)
}
func testDisallowAdditionOfTooManyNumericCharacters() {
textField.text = ""
let allowed = delegate.textField(textField,
shouldChangeCharactersInRange: NSRange(location:0, length: 0),
replacementString: "123456")
XCTAssertFalse(allowed, "should allow no more than 5 characters")
}
func testDisallowAdditionOfNonnumericCharacters() {
textField.text = ""
let allowed = delegate.textField(textField,
shouldChangeCharactersInRange: NSRange(location:0, length: 0),
replacementString: "z")
XCTAssertFalse(allowed, "should not allow non-numeric characters")
}
func testAllowDeletionOfAllCharacters() {
textField.text = "1"
let allowed = delegate.textField(textField,
shouldChangeCharactersInRange: NSRange(location:0, length: 1),
replacementString: "")
XCTAssertTrue(allowed, "should allow all characters to be deleted")
}
}
/*
Copyright (c) 2014 Kristopher Johnson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import XCTest
import KJTipCalculator
class NumericTextTests: XCTestCase {
func testIntegerValueForValidText() {
let value = integerValueForText("123")
XCTAssertEqual(123, value!)
}
func testNegativeIntegerValueForValidText() {
let value = integerValueForText("-321")
XCTAssertEqual(-321, value!)
}
func testDoubleValueForValidText() {
let value = doubleValueForText("123.4")
XCTAssertEqual(123.4, value!)
}
func testNegativeDoubleValueForValidText() {
let value = doubleValueForText("-4.321")
XCTAssertEqual(-4.321, value!)
}
func testRejectIntegerValueWithLeadingSpace() {
let value = integerValueForText(" 123")
XCTAssert(value == nil)
}
func testRejectDoubleValueWithLeadingSpace() {
let value = doubleValueForText(" 123.4")
XCTAssert(value == nil)
}
func testRejectIntegerValueWithTrailingSpace() {
let value = integerValueForText("123 ")
XCTAssert(value == nil)
}
func testRejectDoubleValueWithTrailingSpace() {
let value = doubleValueForText("123.4 ")
XCTAssert(value == nil)
}
func testRejectIntegerValueWithLeadingNonnumericCharacter() {
let value = integerValueForText("z123")
XCTAssert(value == nil)
}
func testRejectDoubleValueWithLeadingNonnumericCharacter() {
let value = doubleValueForText("z123.4")
XCTAssert(value == nil)
}
func testRejectIntegerValueWithTrailingNonnumericCharacter() {
let value = integerValueForText("123a")
XCTAssert(value == nil)
}
func testRejectDoubleValueWithTrailingNonnumericCharacter() {
let value = doubleValueForText("123.4a")
XCTAssert(value == nil)
}
}
@kristopherjohnson
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment