Last active
August 29, 2015 14:02
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample app: https://github.com/kristopherjohnson/KJTipCalculator