Skip to content

Commit

Permalink
Public release 1.9.3
Browse files Browse the repository at this point in the history
Public release 1.9.3
  • Loading branch information
EugeneIOs authored Nov 5, 2021
2 parents d83ad5b + 4d3a54f commit 52f0bba
Show file tree
Hide file tree
Showing 119 changed files with 426 additions and 155 deletions.
22 changes: 21 additions & 1 deletion Sources/VGSCollectSDK/Core/VGSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ public class VGSConfiguration: VGSTextFieldConfigurationProtocol {
public var isRequiredValidOnly: Bool = false

/// Input data visual format pattern. If not applied, will be set by default depending on field `type`.
public var formatPattern: String?
public var formatPattern: String? {
didSet {
logWarningForFormatPatternIfNeeded()
}
}

/// String, used to replace not default `VGSConfiguration.formatPattern` characters in input text on send request.
public var divider: String?
Expand All @@ -74,6 +78,22 @@ public class VGSConfiguration: VGSTextFieldConfigurationProtocol {

/// Validation rules for field input. Defines `State.isValide` result.
public var validationRules: VGSValidationRuleSet?

/// Max input length. **IMPORTANT!** Can conflict with `.formatPattern` attribute.
public var maxInputLength: Int? {
didSet {
logWarningForFormatPatternIfNeeded()
}
}

/// Logs warning when both `.formatPattern` and `.maxInputLength` are used.
internal func logWarningForFormatPatternIfNeeded() {
if !formatPattern.isNilOrEmpty && maxInputLength != nil {
let message = "Format pattern (\(formatPattern)) and maxInputLength (\(maxInputLength)) can conflict when both are in use!"
let event = VGSLogEvent(level: .warning, text: message, severityLevel: .warning)
VGSCollectLogger.shared.forwardLogEvent(event)
}
}

// MARK: - Initialization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ extension MaskedTextField {
/// :nodoc: Replace native textfield delgate with custom.
override public var delegate: UITextFieldDelegate? {
get { return nil }
set {}
set {
// Use only internal masked text field as a delegate.
if let value = newValue as? MaskedTextField {
super.delegate = value
}
}
}

func addSomeTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
import UIKit
#endif

/// Defines interface for masked text field delegate.
internal protocol MaskedTextFieldDelegate: AnyObject {
func maskedTextField(_ maskedTextField: UITextField, shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool
}

/// :nodoc: Internal wrapper for `UITextField`.
internal class MaskedTextField: UITextField {
enum MaskedTextReplacementChar: String, CaseIterable {
Expand All @@ -20,6 +26,9 @@ internal class MaskedTextField: UITextField {
case digits = "#"
}

/// Internal delegate that acts as a proxy of `UITextFieldDelegate`.
internal weak var customDelegate: MaskedTextFieldDelegate?

/**
Var that holds the format pattern that you wish to apply
to some text
Expand Down Expand Up @@ -266,6 +275,19 @@ internal class MaskedTextField: UITextField {
}
}

// MARK: - UITextFieldDelegate

extension MaskedTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
guard let customTextFieldDelegate = customDelegate else {
return true
}

return customTextFieldDelegate.maskedTextField(textField, shouldChangeCharactersInRange: range, replacementString: string)
}
}

extension MaskedTextField {
override var description: String {
return NSStringFromClass(self.classForCoder)
Expand Down
55 changes: 51 additions & 4 deletions Sources/VGSCollectSDK/UIElements/Text Field/VGSTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ public class VGSTextField: UIView {
/// - Note: This will not change `State.isDirty` attribute.
/// - Discussion: probably you should want to set field configuration before setting default value, so the input format will be update as required.
public func setDefaultText(_ text: String?) {
updateTextFieldInput(text)
let trimmedText = trimTextIfNeeded(text)
updateTextFieldInput(trimmedText)
}

/// :nodoc: Set textfield text.
public func setText(_ text: String?) {
isDirty = true
updateTextFieldInput(text)
isDirty = true
let trimmedText = trimTextIfNeeded(text)
updateTextFieldInput(trimmedText)
}

/// Removes input from field.
Expand Down Expand Up @@ -255,7 +257,9 @@ internal extension VGSTextField {
@objc
func addTextFieldObservers() {
//delegates
textField.addSomeTarget(self, action: #selector(textFieldDidBeginEditing), for: .editingDidBegin)
textField.delegate = textField
textField.customDelegate = self
textField.addSomeTarget(self, action: #selector(textFieldDidBeginEditing), for: .editingDidBegin)
//Note: .allEditingEvents doesn't work proparly when set text programatically. Use setText instead!
textField.addSomeTarget(self, action: #selector(textFieldDidEndEditing), for: .editingDidEnd)
textField.addSomeTarget(self, action: #selector(textFieldDidEndEditingOnExit), for: .editingDidEndOnExit)
Expand Down Expand Up @@ -335,6 +339,49 @@ internal extension VGSTextField {
textFieldValueChanged()
delegate?.vgsTextFieldDidChange?(self)
}

/// Returns trimmed text if `.maxInputLength` is set.
/// - Parameter text: `String?` object, new text to set.
/// - Returns: `String?` object, trimmed text or initial text if `.maxInputLength` not set.
func trimTextIfNeeded(_ text: String?) -> String? {
guard let maxInputLength = configuration?.maxInputLength, let newText = text else {
return text
}

let trimmedText = String(newText.prefix(maxInputLength))
return trimmedText
}

/// `true` if has format pattern.
fileprivate var hasFormatPattern: Bool {
return !textField.formatPattern.isEmpty
}
}

// MARK: - MaskedTextFieldDelegate

extension VGSTextField: MaskedTextFieldDelegate {
func maskedTextField(_ maskedTextField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let maxInputLength = configuration?.maxInputLength, let currentString: NSString = textField.secureText as? NSString else {return true}

let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString

// Do not filter text when format pattern is not set (empty). Spaces and non alpha-numeric chards will be treated as valid characters and will be used in maxInputLength check.
// Otherwise when formatPattern is set filter text only for alphanumeric characters.
let rawText: NSString
if hasFormatPattern {
rawText = getFilteredString(newString as String) as NSString
} else {
rawText = newString
}

return rawText.length <= maxInputLength
}

fileprivate func getFilteredString(_ string: String) -> String {
let charactersArray = string.components(separatedBy: CharacterSet.alphanumerics.inverted)
return charactersArray.joined(separator: "")
}
}

// MARK: - Main style for text field
Expand Down
2 changes: 1 addition & 1 deletion Sources/VGSCollectSDK/Utils/Extensions/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal class Utils {

/// VGS Collect SDK Version.
/// Necessary since SPM doesn't track info plist correctly: https://forums.swift.org/t/add-info-plist-on-spm-bundle/40274/5
static let vgsCollectVersion: String = "1.9.2"
static let vgsCollectVersion: String = "1.9.3"
}

extension Dictionary {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// TextFieldMaxInputLengthTests.swift
// FrameworkTests

import Foundation
import XCTest
@testable import VGSCollectSDK

/// Tests for max input length.
class TextFieldMaxInputLengthTests: VGSCollectBaseTestCase {

/// Collect instance.
var collector: VGSCollect!

/// Configuration instance.
var configuration: VGSConfiguration!

/// VGS text field.
var textfield: VGSTextField!

/// Default setup.
override func setUp() {
super.setUp()
collector = VGSCollect(id: "vaultid")
configuration = VGSConfiguration.init(collector: collector, fieldName: "test1")
textfield = VGSTextField()
textfield.configuration = configuration
}

/// Default tear down.
override func tearDown() {
collector = nil
configuration = nil
textfield = nil
}

/// Test set default text with max input length.
func testSetDefaultTextWithMaxLength() {
configuration.type = .none
configuration.maxInputLength = 3
textfield.configuration = configuration
textfield.setDefaultText("Joe Doe")
XCTAssertTrue(textfield.textField.secureText == "Joe")
XCTAssertTrue(textfield.state.inputLength == 3)
XCTAssertTrue(textfield.state.isDirty == false)
XCTAssertTrue(textfield.state.isEmpty == false)
XCTAssertTrue(textfield.state.isValid == true)

// Test set nil.
textfield.setDefaultText(nil)
XCTAssertTrue(textfield.textField.secureText == "")

// Disable max input length.
configuration.maxInputLength = nil
textfield.setDefaultText("Joe Doe")
XCTAssertTrue(textfield.textField.secureText == "Joe Doe")
XCTAssertTrue(textfield.state.inputLength == 7)
XCTAssertTrue(textfield.state.isDirty == false)
XCTAssertTrue(textfield.state.isEmpty == false)
XCTAssertTrue(textfield.state.isValid == true)
}

/// Test set text with max input length.
func testSetTextWithMaxLength() {
configuration.type = .none
configuration.maxInputLength = 3
textfield.configuration = configuration
textfield.setText("Joe Doe")
XCTAssertTrue(textfield.textField.secureText == "Joe")
XCTAssertTrue(textfield.state.inputLength == 3)
XCTAssertTrue(textfield.state.isDirty == true)
XCTAssertTrue(textfield.state.isEmpty == false)
XCTAssertTrue(textfield.state.isValid == true)

// Test set nil.
textfield.setText(nil)
XCTAssertTrue(textfield.textField.secureText == "")

// Disable max input length.
configuration.maxInputLength = nil
textfield.setText("Joe Doe")
XCTAssertTrue(textfield.textField.secureText == "Joe Doe")
XCTAssertTrue(textfield.state.inputLength == 7)
XCTAssertTrue(textfield.state.isDirty == true)
XCTAssertTrue(textfield.state.isEmpty == false)
XCTAssertTrue(textfield.state.isValid == true)
}

/// Test clean text with max input length.
func testCleanTextWithMaxInputLength() {
configuration.type = .none
configuration.maxInputLength = 3
textfield.configuration = configuration
textfield.setDefaultText("Joe")
textfield.cleanText()
XCTAssertTrue(textfield.textField.secureText == "")
XCTAssertTrue(textfield.state.inputLength == 0)
XCTAssertTrue(textfield.state.isDirty == false)
XCTAssertTrue(textfield.state.isEmpty == true)

configuration.type = .none
configuration.maxInputLength = 3
textfield.configuration = configuration
textfield.setText("Joe")
textfield.cleanText()
XCTAssertTrue(textfield.textField.secureText == "")
XCTAssertTrue(textfield.state.inputLength == 0)
XCTAssertTrue(textfield.state.isDirty == true)
XCTAssertTrue(textfield.state.isEmpty == true)
}
}
2 changes: 1 addition & 1 deletion VGSCollectSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'VGSCollectSDK'
spec.version = '1.9.2'
spec.version = '1.9.3'
spec.summary = 'VGS Collect - is a product suite that allows customers to collect information securely without possession of it.'
spec.swift_version = '5.0'
spec.description = <<-DESC
Expand Down
8 changes: 6 additions & 2 deletions VGSCollectSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
32F4B7F424C881FA006086F3 /* Calendar+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F4B7F324C881FA006086F3 /* Calendar+extension.swift */; };
4404600F256801890064BEA0 /* APIHostnameValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4404600D2568017E0064BEA0 /* APIHostnameValidatorTests.swift */; };
44168FBF2632945D0088E515 /* VGSExpirationDateTextFieldUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44168FBE2632945D0088E515 /* VGSExpirationDateTextFieldUtilsTests.swift */; };
441A727D272AAEEA0036BECB /* TextFieldMaxInputLengthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441A727C272AAEEA0036BECB /* TextFieldMaxInputLengthTests.swift */; };
441B26D225DB88C50099AA3A /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441B26CC25DB88C50099AA3A /* APIClient.swift */; };
441B26D325DB88C50099AA3A /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441B26CD25DB88C50099AA3A /* HTTPMethod.swift */; };
441B26D425DB88C50099AA3A /* APIHostURLPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 441B26CF25DB88C50099AA3A /* APIHostURLPolicy.swift */; };
Expand Down Expand Up @@ -294,6 +295,7 @@
32F4B7F324C881FA006086F3 /* Calendar+extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+extension.swift"; sourceTree = "<group>"; };
4404600D2568017E0064BEA0 /* APIHostnameValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHostnameValidatorTests.swift; sourceTree = "<group>"; };
44168FBE2632945D0088E515 /* VGSExpirationDateTextFieldUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VGSExpirationDateTextFieldUtilsTests.swift; sourceTree = "<group>"; };
441A727C272AAEEA0036BECB /* TextFieldMaxInputLengthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldMaxInputLengthTests.swift; sourceTree = "<group>"; };
441B26CC25DB88C50099AA3A /* APIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
441B26CD25DB88C50099AA3A /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = "<group>"; };
441B26CF25DB88C50099AA3A /* APIHostURLPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIHostURLPolicy.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1092,6 +1094,7 @@
FDA680DE239844FC00372817 /* CardTextFieldTests.swift */,
324B5E7924A3A34F0036867E /* ValidationRulesTest.swift */,
32AFB93624FFD218007FFCAE /* VGSTextFieldTests.swift */,
441A727C272AAEEA0036BECB /* TextFieldMaxInputLengthTests.swift */,
);
path = "Text Fields Tests";
sourceTree = "<group>";
Expand Down Expand Up @@ -1674,6 +1677,7 @@
44D520AE2672218A005AB588 /* VGSFlatJSONStructMappingTests.swift in Sources */,
FDF696E123463ACB00063507 /* TextFielsStyleUI.swift in Sources */,
3213005B241FA0BE0062FEF0 /* VGSCollectTest+Validation.swift in Sources */,
441A727D272AAEEA0036BECB /* TextFieldMaxInputLengthTests.swift in Sources */,
32059CD42417EACC003E9481 /* UtilsTest.swift in Sources */,
32448009244606E4003A0676 /* CardHolderNameFieldTests.swift in Sources */,
32C894DE256D4AE400DC5648 /* UIDevice+extension.swift in Sources */,
Expand Down Expand Up @@ -2198,7 +2202,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.9.2;
MARKETING_VERSION = 1.9.3;
PRODUCT_BUNDLE_IDENTIFIER = com.vgs.framework;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -2234,7 +2238,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.9.2;
MARKETING_VERSION = 1.9.3;
PRODUCT_BUNDLE_IDENTIFIER = com.vgs.framework;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class CardsDataCollectingViewController: UIViewController {
expDateConfiguration.type = .expDate
expDateConfiguration.inputDateFormat = .shortYear
expDateConfiguration.outputDateFormat = .longYear

/// Default .expDate format is "##/##"
expDateConfiguration.formatPattern = "##/##"

Expand All @@ -162,6 +162,8 @@ class CardsDataCollectingViewController: UIViewController {
/// Required to be not empty

cardHolderName.textAlignment = .natural
// Set max input length
//holderConfiguration.maxInputLength = 32
cardHolderName.configuration = holderConfiguration
cardHolderName.placeholder = "Cardholder Name"

Expand Down
2 changes: 1 addition & 1 deletion docs/Classes/CardState.html
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ <h4>Declaration</h4>
</article>
</div>
<section class="footer">
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-10-21)</p>
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-11-04)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.13.5</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
</section>
</body>
Expand Down
2 changes: 1 addition & 1 deletion docs/Classes/SSNState.html
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ <h4>Declaration</h4>
</article>
</div>
<section class="footer">
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-10-21)</p>
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-11-04)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.13.5</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
</section>
</body>
Expand Down
2 changes: 1 addition & 1 deletion docs/Classes/State.html
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ <h4>Declaration</h4>
</article>
</div>
<section class="footer">
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-10-21)</p>
<p>&copy; 2021 <a class="link" href="https://verygoodsecurity.com" target="_blank" rel="external">Very Good Security</a>. All rights reserved. (Last updated: 2021-11-04)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.13.5</a>, a <a class="link" href="https://realm.io" target="_blank" rel="external">Realm</a> project.</p>
</section>
</body>
Expand Down
Loading

0 comments on commit 52f0bba

Please sign in to comment.