From 8632d8b696a468785514103a7ef3753ec4f19c59 Mon Sep 17 00:00:00 2001 From: Okhan Okbay Date: Thu, 28 Sep 2023 17:27:02 +0100 Subject: [PATCH 1/6] Add security code component --- .../PaymentForm/View/PaymentHeaderView.swift | 2 +- .../PaymentForm/View/SecurityCodeView.swift | 5 +- ...faultSecurityCodeFormStyle+Extension.swift | 37 ++ .../SecurityCodeComponent.swift | 60 ++ .../SecurityCodeComponentConfiguration.swift | 48 ++ .../SecurityCodeComponentStyle.swift | 38 ++ .../project.pbxproj | 40 +- .../Storyboards/Base.lproj/Main.storyboard | 602 +++++++++++------- .../SecurityCodeViewController.swift | 87 +++ 9 files changed, 660 insertions(+), 259 deletions(-) create mode 100644 Source/UI/SecurityCodeComponent/DefaultSecurityCodeFormStyle+Extension.swift create mode 100644 Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift create mode 100644 Source/UI/SecurityCodeComponent/SecurityCodeComponentConfiguration.swift create mode 100644 Source/UI/SecurityCodeComponent/SecurityCodeComponentStyle.swift create mode 100644 iOS Example Frame/iOS Example Frame/ViewController/SecurityCodeViewController.swift diff --git a/Source/UI/PaymentForm/View/PaymentHeaderView.swift b/Source/UI/PaymentForm/View/PaymentHeaderView.swift index 0558716c1..03a1dd7da 100644 --- a/Source/UI/PaymentForm/View/PaymentHeaderView.swift +++ b/Source/UI/PaymentForm/View/PaymentHeaderView.swift @@ -9,7 +9,7 @@ import UIKit import Checkout -public final class PaymentHeaderView: UIView { +final class PaymentHeaderView: UIView { private var style: PaymentHeaderCellStyle? private let supportedSchemes: [Card.Scheme] diff --git a/Source/UI/PaymentForm/View/SecurityCodeView.swift b/Source/UI/PaymentForm/View/SecurityCodeView.swift index 4a2699049..9e00158ce 100644 --- a/Source/UI/PaymentForm/View/SecurityCodeView.swift +++ b/Source/UI/PaymentForm/View/SecurityCodeView.swift @@ -67,10 +67,11 @@ extension SecurityCodeView: TextFieldViewDelegate { } func textFieldShouldChangeCharactersIn(textField: UITextField, replacementString string: String) { - guard let style = style else { return } - codeInputView.updateBorderColor(with: style.textfield.borderStyle.focusColor) viewModel.updateInput(to: textField.text) delegate?.update(securityCode: viewModel.cvv) + + guard let style = style else { return } + codeInputView.updateBorderColor(with: style.textfield.borderStyle.focusColor) } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { diff --git a/Source/UI/SecurityCodeComponent/DefaultSecurityCodeFormStyle+Extension.swift b/Source/UI/SecurityCodeComponent/DefaultSecurityCodeFormStyle+Extension.swift new file mode 100644 index 000000000..90bd479b0 --- /dev/null +++ b/Source/UI/SecurityCodeComponent/DefaultSecurityCodeFormStyle+Extension.swift @@ -0,0 +1,37 @@ +// +// DefaultSecurityCodeFormStyle+Extension.swift +// +// +// Created by Okhan Okbay on 05/10/2023. +// + +import Foundation + +extension DefaultSecurityCodeFormStyle { + init(securityCodeComponentStyle: SecurityCodeComponentStyle) { + self.isMandatory = false + self.backgroundColor = .clear + self.title = nil + self.hint = nil + self.mandatory = nil + self.error = nil + self.textfield = DefaultTextField(textAlignment: securityCodeComponentStyle.textAlignment, + isHidden: false, + isSupportingNumericKeyboard: true, + text: securityCodeComponentStyle.text, + placeholder: securityCodeComponentStyle.placeholder, + textColor: securityCodeComponentStyle.textColor, + backgroundColor: .clear, + tintColor: securityCodeComponentStyle.tintColor, + width: .zero, + height: .zero, + font: securityCodeComponentStyle.font, + borderStyle: DefaultBorderStyle(cornerRadius: .zero, + borderWidth: .zero, + normalColor: .clear, + focusColor: .clear, + errorColor: .clear)) + + + } +} diff --git a/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift new file mode 100644 index 000000000..6e90de63a --- /dev/null +++ b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift @@ -0,0 +1,60 @@ +// +// SecurityCodeComponent.swift +// +// +// Created by Okhan Okbay on 26/09/2023. +// + +import Checkout +import UIKit + +public final class SecurityCodeComponent: UIView { + private var view: SecurityCodeView! + + private var configuration: SecurityCodeComponentConfiguration! + private var isSecurityCodeValid: ((Bool) -> Void)! + private var cardValidator: CardValidating! + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +extension SecurityCodeComponent { + public func configure(with configuration: SecurityCodeComponentConfiguration, isSecurityCodeValid: @escaping (Bool) -> Void) { + self.configuration = configuration + self.isSecurityCodeValid = isSecurityCodeValid + + self.cardValidator = CardValidator(environment: configuration.environment.checkoutEnvironment) + + let viewModel = SecurityCodeViewModel(cardValidator: cardValidator) + if let initialCardScheme = configuration.cardScheme { + viewModel.updateScheme(to: initialCardScheme) + } + + let view = SecurityCodeView(viewModel: viewModel) + view.update(style: DefaultSecurityCodeFormStyle(securityCodeComponentStyle: configuration.style)) + view.accessibilityIdentifier = AccessibilityIdentifiers.PaymentForm.cardSecurityCode + view.delegate = self + + view.frame = bounds + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + addSubview(view) + + self.view = view + } +} + +extension SecurityCodeComponent: SecurityCodeViewDelegate { + func update(securityCode: String) { + guard securityCode.count > 0 else { + isSecurityCodeValid(false) + return + } + isSecurityCodeValid(cardValidator.isValid(cvv: securityCode, for: configuration.cardScheme ?? .unknown)) + } +} diff --git a/Source/UI/SecurityCodeComponent/SecurityCodeComponentConfiguration.swift b/Source/UI/SecurityCodeComponent/SecurityCodeComponentConfiguration.swift new file mode 100644 index 000000000..0ee97c263 --- /dev/null +++ b/Source/UI/SecurityCodeComponent/SecurityCodeComponentConfiguration.swift @@ -0,0 +1,48 @@ +// +// SecurityCodeConfiguration.swift +// +// +// Created by Okhan Okbay on 05/10/2023. +// + +import Checkout +import UIKit + +/** + Lets you configure and style the SecurityCodeComponent + + - apiKey: The API Key you receive from checkout.com + - environment: Production or sandbox + - cardScheme: Optional card scheme + - If provided, card scheme's validation rules apply (e.g. VISA = 3 digits, American Express = 4 digits etc.) + - If not provided, security code is treated as valid for 3 and 4 digits + - style: Security Code Component wraps a text field in a secure way. + To style the inner properties like font, textColor etc, you must alter the style. + */ + +public struct SecurityCodeComponentConfiguration { + let apiKey: String + let environment: Environment + public var style: SecurityCodeComponentStyle + public var cardScheme: Card.Scheme? + + public init(apiKey: String, + environment: Environment, + style: SecurityCodeComponentStyle? = nil, + cardScheme: Card.Scheme? = nil) { + self.apiKey = apiKey + self.environment = environment + self.cardScheme = cardScheme + + if let style = style { + self.style = style + } else { + self.style = .init(text: .init(), + font: FramesUIStyle.Font.inputLabel, + textAlignment: .natural, + textColor: FramesUIStyle.Color.textPrimary, + tintColor: FramesUIStyle.Color.textPrimary, + placeholder: nil) + } + } +} diff --git a/Source/UI/SecurityCodeComponent/SecurityCodeComponentStyle.swift b/Source/UI/SecurityCodeComponent/SecurityCodeComponentStyle.swift new file mode 100644 index 000000000..364846de3 --- /dev/null +++ b/Source/UI/SecurityCodeComponent/SecurityCodeComponentStyle.swift @@ -0,0 +1,38 @@ +// +// SecurityCodeComponentStyle.swift +// +// +// Created by Okhan Okbay on 05/10/2023. +// + +import UIKit + +/** + All the other UI relevant changes shall be done on the UIView instance of your project. + The reason that these properties are presented to be modified here is that + they are embedded in SecureDisplayView and shouldn't be reachable other than + via SecurityCodeComponentStyle. + */ + +public struct SecurityCodeComponentStyle { + public let text: String + public let font: UIFont + public let textAlignment: NSTextAlignment + public let textColor: UIColor + public let tintColor: UIColor + public let placeholder: String? + + public init(text: String, + font: UIFont, + textAlignment: NSTextAlignment, + textColor: UIColor, + tintColor: UIColor, + placeholder: String?) { + self.text = text + self.font = font + self.textAlignment = textAlignment + self.textColor = textColor + self.tintColor = tintColor + self.placeholder = placeholder + } +} diff --git a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj index 724ac9982..c043409a0 100644 --- a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj +++ b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj @@ -25,14 +25,13 @@ 0BD2ECBF28B65DA10041A942 /* FrameUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD2ECBE28B65DA10041A942 /* FrameUITests.swift */; }; 0BD2ECC128B65EC20041A942 /* XCUIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD2ECC028B65EC20041A942 /* XCUIApplication+Extension.swift */; }; 162CD88E2A6827E900027FEF /* XCUIElement+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 162CD88D2A6827E900027FEF /* XCUIElement+TestHelpers.swift */; }; + 166FE3AC2AC30F1800BF5775 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 166FE3AB2AC30F1800BF5775 /* Frames */; }; 16857B742A65F15F005CAE39 /* CardTypeTokenCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */; }; 16857B7E2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */; }; - 16857B802A65F9A0005CAE39 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16857B7F2A65F9A0005CAE39 /* Frames */; }; + 168CEC952AC5C71700BB52B0 /* SecurityCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */; }; 169DF1482A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169DF1472A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift */; }; - 16AE74C22A5C1B210031F794 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16AE74C12A5C1B210031F794 /* Frames */; }; 16C3F8402A7927ED00690639 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 16C3F83F2A7927ED00690639 /* SnapshotTesting */; }; 16C3F8422A7956EA00690639 /* CardValidationSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C3F8412A7956EA00690639 /* CardValidationSnapshotTests.swift */; }; - 16D4D5AF2A5C2A0200597925 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16D4D5AE2A5C2A0200597925 /* Frames */; }; 16E0AD482A8455F0003C9DDC /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E0AD472A8455F0003C9DDC /* Helper.swift */; }; 16E5ADDD2A8547F700E78675 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E5ADDC2A8547F700E78675 /* Language.swift */; }; 16E5ADDF2A854ED700E78675 /* ExpiryDateEdgeCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16E5ADDE2A854ED700E78675 /* ExpiryDateEdgeCaseTests.swift */; }; @@ -126,9 +125,11 @@ 0BD2ECBE28B65DA10041A942 /* FrameUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameUITests.swift; sourceTree = ""; }; 0BD2ECC028B65EC20041A942 /* XCUIApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+Extension.swift"; sourceTree = ""; }; 162CD88D2A6827E900027FEF /* XCUIElement+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+TestHelpers.swift"; sourceTree = ""; }; + 166FE3AA2AC30BB600BF5775 /* frames-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "frames-ios"; path = ..; sourceTree = ""; }; 16857B712A65F15F005CAE39 /* iOS Example Frame Regression Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS Example Frame Regression Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTypeTokenCreationTests.swift; sourceTree = ""; }; 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+TestHelpers.swift"; sourceTree = ""; }; + 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCodeViewController.swift; sourceTree = ""; }; 169DF1472A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardSchemeFormatSnapshotTests.swift; sourceTree = ""; }; 16AE74C32A5C1EBB0031F794 /* iOS Example Frame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example Frame.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 16AE74C42A5C1EBB0031F794 /* iOS Example FrameUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS Example FrameUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -204,7 +205,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 16857B802A65F9A0005CAE39 /* Frames in Frameworks */, 16C3F8402A7927ED00690639 /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -213,7 +213,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 16D4D5AF2A5C2A0200597925 /* Frames in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -221,7 +220,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 16AE74C22A5C1B210031F794 /* Frames in Frameworks */, + 166FE3AC2AC30F1800BF5775 /* Frames in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -302,6 +301,7 @@ isa = PBXGroup; children = ( 95C8C0C928AD5FEE00B8D3D0 /* HomeViewController.swift */, + 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -441,6 +441,7 @@ E6646F8120CE6C0900D8353A = { isa = PBXGroup; children = ( + 166FE3AA2AC30BB600BF5775 /* frames-ios */, CBA81CAA27ABDF21000DF192 /* iOS Example Frame */, CB9412D82825714C00C889E8 /* iOS Example FrameUITests */, 16857B722A65F15F005CAE39 /* iOS Example Frame Regression Tests */, @@ -469,7 +470,6 @@ ); name = "iOS Example Frame Regression Tests"; packageProductDependencies = ( - 16857B7F2A65F9A0005CAE39 /* Frames */, 16C3F83F2A7927ED00690639 /* SnapshotTesting */, ); productName = "iOS Example Frame Regression Tests"; @@ -491,7 +491,6 @@ ); name = "iOS Example FrameUITests"; packageProductDependencies = ( - 16D4D5AE2A5C2A0200597925 /* Frames */, ); productName = "iOS Example FrameUITests"; productReference = 16AE74C42A5C1EBB0031F794 /* iOS Example FrameUITests.xctest */; @@ -511,7 +510,7 @@ ); name = "iOS Example Frame"; packageProductDependencies = ( - 16AE74C12A5C1B210031F794 /* Frames */, + 166FE3AB2AC30F1800BF5775 /* Frames */, ); productName = "iOS Example Frame"; productReference = 16AE74C32A5C1EBB0031F794 /* iOS Example Frame.app */; @@ -559,7 +558,6 @@ ); mainGroup = E6646F8120CE6C0900D8353A; packageReferences = ( - 16AE74C02A5C1B210031F794 /* XCRemoteSwiftPackageReference "frames-ios" */, 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, ); productRefGroup = E6646F8120CE6C0900D8353A; @@ -691,6 +689,7 @@ 95EB97DC2A54753000EE6776 /* ThemeDemo.swift in Sources */, 955B092B291CEF0F00DEEAF5 /* ApplePayCreator.swift in Sources */, 95C8C11A28AD5FEE00B8D3D0 /* UIFont+SFMono.swift in Sources */, + 168CEC952AC5C71700BB52B0 /* SecurityCodeViewController.swift in Sources */, 95C8C10F28AD5FEE00B8D3D0 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1211,14 +1210,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 16AE74C02A5C1B210031F794 /* XCRemoteSwiftPackageReference "frames-ios" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/checkout/frames-ios"; - requirement = { - kind = exactVersion; - version = 4.2.1; - }; - }; 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; @@ -1230,14 +1221,8 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 16857B7F2A65F9A0005CAE39 /* Frames */ = { + 166FE3AB2AC30F1800BF5775 /* Frames */ = { isa = XCSwiftPackageProductDependency; - package = 16AE74C02A5C1B210031F794 /* XCRemoteSwiftPackageReference "frames-ios" */; - productName = Frames; - }; - 16AE74C12A5C1B210031F794 /* Frames */ = { - isa = XCSwiftPackageProductDependency; - package = 16AE74C02A5C1B210031F794 /* XCRemoteSwiftPackageReference "frames-ios" */; productName = Frames; }; 16C3F83F2A7927ED00690639 /* SnapshotTesting */ = { @@ -1245,11 +1230,6 @@ package = 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; productName = SnapshotTesting; }; - 16D4D5AE2A5C2A0200597925 /* Frames */ = { - isa = XCSwiftPackageProductDependency; - package = 16AE74C02A5C1B210031F794 /* XCRemoteSwiftPackageReference "frames-ios" */; - productName = Frames; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = E6646F8220CE6C0900D8353A /* Project object */; diff --git a/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard b/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard index 761bed116..9073fe8a9 100644 --- a/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard +++ b/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -38,19 +38,19 @@ - + - + - - + + - - - + + - - - - - - - - - - + - - - - - - - - - - - + + + + + - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + - - + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + - - - - - - - - - - - - - - + + + - - + + + + + + + + + + + + + + + - - + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + - - - - - - - - - - - - - - + + + - - + + + + + + + + - - + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - + - + - + @@ -361,7 +419,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS Example Frame/iOS Example Frame/ViewController/SecurityCodeViewController.swift b/iOS Example Frame/iOS Example Frame/ViewController/SecurityCodeViewController.swift new file mode 100644 index 000000000..751001a07 --- /dev/null +++ b/iOS Example Frame/iOS Example Frame/ViewController/SecurityCodeViewController.swift @@ -0,0 +1,87 @@ +// +// SecurityCodeViewController.swift +// iOS Example Frame +// +// Created by Okhan Okbay on 28/09/2023. +// Copyright © 2023 Checkout. All rights reserved. +// + +import Frames +import UIKit + +final class SecurityCodeViewController: UIViewController { + + @IBOutlet private weak var defaultSecurityCodeComponent: SecurityCodeComponent! + @IBOutlet private weak var defaultPayButton: UIButton! + + @IBOutlet private weak var customSecurityCodeComponent: SecurityCodeComponent! + @IBOutlet private weak var customPayButton: UIButton! + + var configuration = SecurityCodeComponentConfiguration(apiKey: Factory.apiKey, + environment: Factory.environment) + + func setupDefaultSecurityCodeComponent() { + configuration.cardScheme = Card.Scheme(rawValue: "VISA") + + defaultSecurityCodeComponent.configure(with: configuration) { [weak self] isSecurityCodeValid in + self?.defaultPayButton.isEnabled = isSecurityCodeValid + } + } + + func setupCustomSecurityCodeComponent() { + let style = SecurityCodeComponentStyle(text: .init(), + font: UIFont.systemFont(ofSize: 24), + textAlignment: .natural, + textColor: .red, + tintColor: .red, + placeholder: "Enter the security code here") + + configuration.cardScheme = Card.Scheme(rawValue: "AMERICAN EXPRESS") + configuration.style = style + + customSecurityCodeComponent.backgroundColor = .green + customSecurityCodeComponent.layer.borderColor = UIColor.blue.cgColor + customSecurityCodeComponent.layer.borderWidth = 2 + + customSecurityCodeComponent.configure(with: configuration) { [weak self] isSecurityCodeValid in + self?.customPayButton.isEnabled = isSecurityCodeValid + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupNavigationBar() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + navigationController?.setNavigationBarHidden(false, animated: animated) + + setupDefaultSecurityCodeComponent() + setupCustomSecurityCodeComponent() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + defaultSecurityCodeComponent.becomeFirstResponder() + } +} + +extension SecurityCodeViewController { + private func setupNavigationBar() { + if #available(iOS 13.0, *) { + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage.init(systemName: "arrow.backward"), + style: .plain, + target: self, + action: #selector(popViewController)) + } + } + + @objc private func popViewController() { + navigationController?.popViewController(animated: true) + } +} From 189431effcda8983ddb95edaba0b537f05df71f9 Mon Sep 17 00:00:00 2001 From: Okhan Okbay Date: Fri, 20 Oct 2023 19:15:16 +0100 Subject: [PATCH 2/6] Add some unit tests --- .../SecurityCodeComponent.swift | 3 +- .../SecurityCodeComponentTests.swift | 57 +++++++++++++++++++ .../project.pbxproj | 22 ++++--- 3 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift diff --git a/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift index 6e90de63a..92cd02b5a 100644 --- a/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift +++ b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift @@ -25,7 +25,8 @@ public final class SecurityCodeComponent: UIView { } extension SecurityCodeComponent { - public func configure(with configuration: SecurityCodeComponentConfiguration, isSecurityCodeValid: @escaping (Bool) -> Void) { + public func configure(with configuration: SecurityCodeComponentConfiguration, + isSecurityCodeValid: @escaping (Bool) -> Void) { self.configuration = configuration self.isSecurityCodeValid = isSecurityCodeValid diff --git a/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift b/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift new file mode 100644 index 000000000..0de5a88b9 --- /dev/null +++ b/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift @@ -0,0 +1,57 @@ +// +// SecurityCodeComponentTests.swift +// +// +// Created by Okhan Okbay on 20/10/2023. +// + +@testable import Frames +import XCTest + +final class SecurityCodeComponentTests: XCTestCase { + let sut = SecurityCodeComponent() + var mockconfig = SecurityCodeComponentConfiguration(apiKey: "some_api_key", environment: .sandbox) + + func test_whenConfigureIsCalled_thenRequiredPropertiesAreSet() { + + } + + func test_whenUpdateIsCalledWith_thenISSecurityCodeValidCalledWithCorrectResult() { + let testMatrix: [(scheme: Card.Scheme, securityCode: String, expectedResult: Bool)] = [ + (.visa, "", false), + (.visa, "1", false), + (.visa, "12", false), + (.visa, "123", true), + (.visa, "1234", false), + (.americanExpress, "", false), + (.americanExpress, "1", false), + (.americanExpress, "12", false), + (.americanExpress, "123", false), + (.americanExpress, "1234", true), + (.unknown, "", false), + (.unknown, "1", false), + (.unknown, "12", false), + (.unknown, "123", true), + (.unknown, "1234", true), + ] + + testMatrix.forEach { testData in + mockconfig.cardScheme = testData.scheme + verify(securityCode: testData.securityCode, expectedResult: testData.expectedResult) + } + } + + func verify(securityCode: String, + expectedResult: Bool, + file: StaticString = #file, + line: UInt = #line) { + sut.configure(with: mockconfig) { [weak self] isSecurityCodeValid in + XCTAssertEqual(expectedResult, + isSecurityCodeValid, + "Security code: \(securityCode) Card Scheme: \(self?.mockconfig.cardScheme?.rawValue ?? "nil") \n Expected: \(expectedResult) but found: \(isSecurityCodeValid)", + file: file, + line: line) + } + sut.update(securityCode: securityCode) + } +} diff --git a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj index c043409a0..40b5a19c4 100644 --- a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj +++ b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -25,10 +25,10 @@ 0BD2ECBF28B65DA10041A942 /* FrameUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD2ECBE28B65DA10041A942 /* FrameUITests.swift */; }; 0BD2ECC128B65EC20041A942 /* XCUIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD2ECC028B65EC20041A942 /* XCUIApplication+Extension.swift */; }; 162CD88E2A6827E900027FEF /* XCUIElement+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 162CD88D2A6827E900027FEF /* XCUIElement+TestHelpers.swift */; }; - 166FE3AC2AC30F1800BF5775 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 166FE3AB2AC30F1800BF5775 /* Frames */; }; 16857B742A65F15F005CAE39 /* CardTypeTokenCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */; }; 16857B7E2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */; }; 168CEC952AC5C71700BB52B0 /* SecurityCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */; }; + 16900D412AE2FAB3009A7CE9 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16900D402AE2FAB3009A7CE9 /* Frames */; }; 169DF1482A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169DF1472A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift */; }; 16C3F8402A7927ED00690639 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 16C3F83F2A7927ED00690639 /* SnapshotTesting */; }; 16C3F8422A7956EA00690639 /* CardValidationSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C3F8412A7956EA00690639 /* CardValidationSnapshotTests.swift */; }; @@ -125,7 +125,6 @@ 0BD2ECBE28B65DA10041A942 /* FrameUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameUITests.swift; sourceTree = ""; }; 0BD2ECC028B65EC20041A942 /* XCUIApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+Extension.swift"; sourceTree = ""; }; 162CD88D2A6827E900027FEF /* XCUIElement+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+TestHelpers.swift"; sourceTree = ""; }; - 166FE3AA2AC30BB600BF5775 /* frames-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "frames-ios"; path = ..; sourceTree = ""; }; 16857B712A65F15F005CAE39 /* iOS Example Frame Regression Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS Example Frame Regression Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTypeTokenCreationTests.swift; sourceTree = ""; }; 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+TestHelpers.swift"; sourceTree = ""; }; @@ -220,7 +219,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 166FE3AC2AC30F1800BF5775 /* Frames in Frameworks */, + 16900D412AE2FAB3009A7CE9 /* Frames in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -441,7 +440,6 @@ E6646F8120CE6C0900D8353A = { isa = PBXGroup; children = ( - 166FE3AA2AC30BB600BF5775 /* frames-ios */, CBA81CAA27ABDF21000DF192 /* iOS Example Frame */, CB9412D82825714C00C889E8 /* iOS Example FrameUITests */, 16857B722A65F15F005CAE39 /* iOS Example Frame Regression Tests */, @@ -510,7 +508,7 @@ ); name = "iOS Example Frame"; packageProductDependencies = ( - 166FE3AB2AC30F1800BF5775 /* Frames */, + 16900D402AE2FAB3009A7CE9 /* Frames */, ); productName = "iOS Example Frame"; productReference = 16AE74C32A5C1EBB0031F794 /* iOS Example Frame.app */; @@ -559,6 +557,7 @@ mainGroup = E6646F8120CE6C0900D8353A; packageReferences = ( 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */, ); productRefGroup = E6646F8120CE6C0900D8353A; projectDirPath = ""; @@ -1210,6 +1209,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "file:///Users/okhanokbay/Documents/frames-ios"; + requirement = { + branch = "feature/cvv-component"; + kind = branch; + }; + }; 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; @@ -1221,8 +1228,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 166FE3AB2AC30F1800BF5775 /* Frames */ = { + 16900D402AE2FAB3009A7CE9 /* Frames */ = { isa = XCSwiftPackageProductDependency; + package = 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */; productName = Frames; }; 16C3F83F2A7927ED00690639 /* SnapshotTesting */ = { From 6353a5be5023b03a1bc1b84d5ae6a3d4e3499ebc Mon Sep 17 00:00:00 2001 From: Okhan Okbay Date: Fri, 20 Oct 2023 19:18:29 +0100 Subject: [PATCH 3/6] Add test cases for when the card scheme is not set --- .../SecurityCodeComponentTests.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift b/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift index 0de5a88b9..9e830e7ac 100644 --- a/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift +++ b/Tests/UI/SecurityCodeComponent/SecurityCodeComponentTests.swift @@ -12,12 +12,8 @@ final class SecurityCodeComponentTests: XCTestCase { let sut = SecurityCodeComponent() var mockconfig = SecurityCodeComponentConfiguration(apiKey: "some_api_key", environment: .sandbox) - func test_whenConfigureIsCalled_thenRequiredPropertiesAreSet() { - - } - func test_whenUpdateIsCalledWith_thenISSecurityCodeValidCalledWithCorrectResult() { - let testMatrix: [(scheme: Card.Scheme, securityCode: String, expectedResult: Bool)] = [ + let testMatrix: [(scheme: Card.Scheme?, securityCode: String, expectedResult: Bool)] = [ (.visa, "", false), (.visa, "1", false), (.visa, "12", false), @@ -33,6 +29,11 @@ final class SecurityCodeComponentTests: XCTestCase { (.unknown, "12", false), (.unknown, "123", true), (.unknown, "1234", true), + (nil, "", false), + (nil, "1", false), + (nil, "12", false), + (nil, "123", true), + (nil, "1234", true), ] testMatrix.forEach { testData in From e78fd772f2e0b556e9ecec8de96232733d2b2b81 Mon Sep 17 00:00:00 2001 From: Okhan Okbay Date: Mon, 23 Oct 2023 21:03:43 +0100 Subject: [PATCH 4/6] Add UI tests for the security code component --- .../Constants/AccessibilityIdentifiers.swift | 4 ++ .../SecurityCodeComponent.swift | 4 +- .../Helpers/TestCard.swift | 2 +- .../project.pbxproj | 24 ++++---- .../Storyboards/Base.lproj/Main.storyboard | 19 ++++--- .../ViewController/HomeViewController.swift | 4 +- .../SecurityCodeViewController.swift | 9 ++- .../SecurityCodeComponentUITests.swift | 57 +++++++++++++++++++ .../XCUIApplication+Extension.swift | 4 ++ 9 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 iOS Example Frame/iOS Example FrameUITests/SecurityCodeComponentUITests.swift diff --git a/Source/Core/Constants/AccessibilityIdentifiers.swift b/Source/Core/Constants/AccessibilityIdentifiers.swift index 0c86760ac..5c907daaa 100644 --- a/Source/Core/Constants/AccessibilityIdentifiers.swift +++ b/Source/Core/Constants/AccessibilityIdentifiers.swift @@ -40,4 +40,8 @@ public enum AccessibilityIdentifiers { static public let phoneNumber = "PhoneNumberInput" } + public enum SecurityCodeComponent { + /// Identify security code component text field + static public let textField = "SecurityCodeTextField" + } } diff --git a/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift index 92cd02b5a..390819822 100644 --- a/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift +++ b/Source/UI/SecurityCodeComponent/SecurityCodeComponent.swift @@ -39,7 +39,7 @@ extension SecurityCodeComponent { let view = SecurityCodeView(viewModel: viewModel) view.update(style: DefaultSecurityCodeFormStyle(securityCodeComponentStyle: configuration.style)) - view.accessibilityIdentifier = AccessibilityIdentifiers.PaymentForm.cardSecurityCode + view.accessibilityIdentifier = AccessibilityIdentifiers.SecurityCodeComponent.textField view.delegate = self view.frame = bounds @@ -52,7 +52,7 @@ extension SecurityCodeComponent { extension SecurityCodeComponent: SecurityCodeViewDelegate { func update(securityCode: String) { - guard securityCode.count > 0 else { + guard securityCode.isEmpty else { isSecurityCodeValid(false) return } diff --git a/iOS Example Frame SPM/iOS Example Frame Regression Tests/Helpers/TestCard.swift b/iOS Example Frame SPM/iOS Example Frame Regression Tests/Helpers/TestCard.swift index 860b13aca..bb5d4774c 100644 --- a/iOS Example Frame SPM/iOS Example Frame Regression Tests/Helpers/TestCard.swift +++ b/iOS Example Frame SPM/iOS Example Frame Regression Tests/Helpers/TestCard.swift @@ -38,7 +38,7 @@ let tokenableTestCards: [TestCard] = [ ] /** - These are luhn numbers that are just 1 character less with the least character count of the relevant card schemes + These are luhn numbers that are just 1 character less than the min character count of the relevant card schemes For example, Visa cards must be at least 13 characters and must start with 44. So, we needed a luhn number that starts with 4 and is 12 digits. To see that we check the character count beyond the luhn verifications. diff --git a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj index 40b5a19c4..69614b41d 100644 --- a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj +++ b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj @@ -28,7 +28,8 @@ 16857B742A65F15F005CAE39 /* CardTypeTokenCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */; }; 16857B7E2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */; }; 168CEC952AC5C71700BB52B0 /* SecurityCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */; }; - 16900D412AE2FAB3009A7CE9 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16900D402AE2FAB3009A7CE9 /* Frames */; }; + 16900D442AE6BA38009A7CE9 /* SecurityCodeComponentUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16900D432AE6BA38009A7CE9 /* SecurityCodeComponentUITests.swift */; }; + 16900D4B2AE70F90009A7CE9 /* Frames in Frameworks */ = {isa = PBXBuildFile; productRef = 16900D4A2AE70F90009A7CE9 /* Frames */; }; 169DF1482A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169DF1472A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift */; }; 16C3F8402A7927ED00690639 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 16C3F83F2A7927ED00690639 /* SnapshotTesting */; }; 16C3F8422A7956EA00690639 /* CardValidationSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16C3F8412A7956EA00690639 /* CardValidationSnapshotTests.swift */; }; @@ -129,6 +130,7 @@ 16857B732A65F15F005CAE39 /* CardTypeTokenCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTypeTokenCreationTests.swift; sourceTree = ""; }; 16857B7D2A65F3F3005CAE39 /* XCUIApplication+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+TestHelpers.swift"; sourceTree = ""; }; 168CEC942AC5C71700BB52B0 /* SecurityCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCodeViewController.swift; sourceTree = ""; }; + 16900D432AE6BA38009A7CE9 /* SecurityCodeComponentUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCodeComponentUITests.swift; sourceTree = ""; }; 169DF1472A7BFB1B00891DF0 /* CardSchemeFormatSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardSchemeFormatSnapshotTests.swift; sourceTree = ""; }; 16AE74C32A5C1EBB0031F794 /* iOS Example Frame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example Frame.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 16AE74C42A5C1EBB0031F794 /* iOS Example FrameUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS Example FrameUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -219,7 +221,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 16900D412AE2FAB3009A7CE9 /* Frames in Frameworks */, + 16900D4B2AE70F90009A7CE9 /* Frames in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -413,6 +415,7 @@ isa = PBXGroup; children = ( 0BD2ECBE28B65DA10041A942 /* FrameUITests.swift */, + 16900D432AE6BA38009A7CE9 /* SecurityCodeComponentUITests.swift */, 0BD2ECC028B65EC20041A942 /* XCUIApplication+Extension.swift */, ); name = "iOS Example FrameUITests"; @@ -508,7 +511,7 @@ ); name = "iOS Example Frame"; packageProductDependencies = ( - 16900D402AE2FAB3009A7CE9 /* Frames */, + 16900D4A2AE70F90009A7CE9 /* Frames */, ); productName = "iOS Example Frame"; productReference = 16AE74C32A5C1EBB0031F794 /* iOS Example Frame.app */; @@ -557,7 +560,7 @@ mainGroup = E6646F8120CE6C0900D8353A; packageReferences = ( 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, - 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */, + 16900D492AE70F90009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */, ); productRefGroup = E6646F8120CE6C0900D8353A; projectDirPath = ""; @@ -666,6 +669,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 16900D442AE6BA38009A7CE9 /* SecurityCodeComponentUITests.swift in Sources */, 0BD2ECBF28B65DA10041A942 /* FrameUITests.swift in Sources */, 0BD2ECC128B65EC20041A942 /* XCUIApplication+Extension.swift in Sources */, ); @@ -1209,12 +1213,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */ = { + 16900D492AE70F90009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "file:///Users/okhanokbay/Documents/frames-ios"; + repositoryURL = "https://github.com/checkout/frames-ios"; requirement = { - branch = "feature/cvv-component"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 4.2.1; }; }; 16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { @@ -1228,9 +1232,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 16900D402AE2FAB3009A7CE9 /* Frames */ = { + 16900D4A2AE70F90009A7CE9 /* Frames */ = { isa = XCSwiftPackageProductDependency; - package = 16900D3F2AE2FAB3009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */; + package = 16900D492AE70F90009A7CE9 /* XCRemoteSwiftPackageReference "frames-ios" */; productName = Frames; }; 16C3F83F2A7927ED00690639 /* SnapshotTesting */ = { diff --git a/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard b/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard index 9073fe8a9..61948043f 100644 --- a/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard +++ b/iOS Example Frame/iOS Example Frame/Storyboards/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -320,8 +320,8 @@ -