diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..53b36f53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,73 @@
+### macOS ###
+# General
+.DS_Store
+
+
+#Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+.DS_Store
+
+## Other
+*.moved-aside
+*.xccheckout
+*.xcscmblueprint
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+# Package.pins
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
\ No newline at end of file
diff --git a/README.md b/README.md
index 73f60d6c..278c7a80 100644
--- a/README.md
+++ b/README.md
@@ -1,83 +1,22 @@
-# Show me the code
+# Santander Challenge
-### # DESAFIO:
+Link para o desafio: [https://github.com/SantanderTecnologia/TesteiOS](https://github.com/SantanderTecnologia/TesteiOS)
-Em uma tela terá um formulário dinâmico com alguns campos predefinidos, conforme o arquivo JSON disponível no link ([https://floating-mountain-50292.herokuapp.com/cells.json](https://floating-mountain-50292.herokuapp.com/cells.json)) que deverá
- ser consumido. Este formulário terá de ser desenhado e exibir uma tela de sucesso quando as informações preenchidas estiverem corretas.
+# Como começar
-Na segunda tela terá o detalhe de um ativo financeiro. As informações devem ser consumidas através do link ([https://floating-mountain-50292.herokuapp.com/fund.json](https://floating-mountain-50292.herokuapp.com/fund.json)).
+1. Clone ou baixe o repositório
+2. Abra o arquivo `SantanderChallenge.xcodeproj`
+3. Execute a aplicação: `cmd + R`.
-O visual do aplicativo está em anexo no arquivo telas.png e em um arquivo do [Sketch](https://www.sketchapp.com) (30 dias grátis, caso não tenha a licença).
+## Environments
+Há dois ambientes configurados.
+- *Development*:
+No ambiente `Development` os dados serão recuperados do Bundle, ou seja, mock.
+- *Production*:
+ No ambiente `Production` os dados serão buscados do servidor.
-
+Para alterar o ambiente escolha entre os schemas, ao lado do botão de parar a execução.
+### Candidato pela: [Zup](https://www.zup.com.br/)
-### # Avaliação
-
-Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura do app. É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits.
-
-* Swift 3.0 ou superior
-* Autolayout
-* O app deve funcionar no iOS 9
-* Testes unitários (De preferência XCTest). Mas pode usar o que você tem mais experiência, só nos explique o que ele tem de bom.
-* Arquitetura a ser utilizada: Swift Clean ([https://clean-swift.com/handbook/](https://clean-swift.com/handbook/) && [https://github.com/Clean-Swift/CleanStore](https://github.com/Clean-Swift/CleanStore) && [https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html)).
-* Uso do git.
-
-### # Dicas para o layout
-
-* O formulário deve respeitar o conteúdo do cells.json ([https://floating-mountain-50292.herokuapp.com/cells.json](https://floating-mountain-50292.herokuapp.com/cells.json)) .
-* Se o texto estiver muito grande, quebre em linhas e exiba por completo.
-* O sketch está na proporção do iPhone 6, para iPhones menores/maiores é indicado que os espaçamentos se adaptem proporcionalmente.
-* Na tela Fundos, o botão baixar irá abrir um SafariViewController no [google.com](http://google.com).
-* A fonte a ser utilizada está em anexo no repositório.
-
-### # Como interpretar o cells.json:
-
-```Swift
-Enum Type {
- case field = 1,
- case text = 2,
- case image = 3,
- case checkbox = 4,
- case send = 5
-}
-```
-
-```Swift
-Enum TypeField {
- case text = 1,
- case telNumber = 2,
- case email = 3
-}
-```
-
-`"type":` tipo da célula;
-
-`"message":` mensagem que vai aparecer na label para type = text ou placeholder para field;
-
-`typeField":` tipo do field a ser exibido, para exibir corretamente a validação daquele campo.
-
-`hidden":` indica se o campo está visível;
-
-`topSpacing":` espaçamento entre o topo da célula e o topo da label/field/ checkbox;
-
-`show":` indica o campo que será exibido quando este campo for selecionado. No caso é o id do campo a ser exibido.
-
-`type":` "send" esse botão irá validar todas informações que foram preenchidas e ir para a tela de sucesso quando tudo tiver ok;
-
-`risk":` pode ser um int de 1 a 5
-
-O tipo `text` a validação é digitou alguma coisa, já ficou válido.
-Para "telNumber" o campo deve ser formatado `(##) ####-#### || (##) #####-####` e validado de acordo.
-Para "email" o email deve ser válido.
-
-### # Observações gerais
-
-Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto.
-Pedimos que trabalhe sozinho e não divulgue o resultado na internet.
-
-Faça um fork desse desse repositório em seu Github e nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando.
-
-### # Importante: não há prazo de entrega, faça com qualidade!
-
-# BOA SORTE!
+### Dúvidas / Sugestões: _luizfelipeap.contato@gmail.com_
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/project.pbxproj b/SantanderChallenge/SantanderChallenge.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..e8e2ff85
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/project.pbxproj
@@ -0,0 +1,1679 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 2C114EF522D018A3009D49E9 /* ProductionNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EF422D018A3009D49E9 /* ProductionNetworkProvider.swift */; };
+ 2C114EF722D018C1009D49E9 /* NetworkProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EF622D018C1009D49E9 /* NetworkProviderProtocol.swift */; };
+ 2C114EFC22D01E4A009D49E9 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EFB22D01E4A009D49E9 /* NetworkError.swift */; };
+ 2C114F0022D02CE9009D49E9 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EFF22D02CE9009D49E9 /* NetworkManager.swift */; };
+ 2C114F0822D03A35009D49E9 /* DevelopmentNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114F0722D03A35009D49E9 /* DevelopmentNetworkProvider.swift */; };
+ 2C114F0B22D03AE1009D49E9 /* Bundle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114F0A22D03AE1009D49E9 /* Bundle+Extension.swift */; };
+ 2C26FF6922D294FC0001E250 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C26FF6822D294FC0001E250 /* UIView+Extension.swift */; };
+ 2C3BCA4822CECB800064EC0D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3BCA4722CECB800064EC0D /* AppDelegate.swift */; };
+ 2C3BCA4D22CECB800064EC0D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C3BCA4B22CECB800064EC0D /* Main.storyboard */; };
+ 2C3BCA5222CECB810064EC0D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C3BCA5022CECB810064EC0D /* LaunchScreen.storyboard */; };
+ 2C4787D122D306BD00366259 /* EnvironmentManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4787D022D306BC00366259 /* EnvironmentManger.swift */; };
+ 2C4787D422D3070C00366259 /* Development.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C4787D222D3070C00366259 /* Development.plist */; };
+ 2C4787D522D3070C00366259 /* Production.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C4787D322D3070C00366259 /* Production.plist */; };
+ 2C4787D822D3071600366259 /* Development.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 2C4787D622D3071600366259 /* Development.xcconfig */; };
+ 2C4787D922D3071600366259 /* Production.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 2C4787D722D3071600366259 /* Production.xcconfig */; };
+ 2C62613F22D2A8A500AE3303 /* FundDefinitionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C62613D22D2A8A500AE3303 /* FundDefinitionTableViewCell.swift */; };
+ 2C62614022D2A8A500AE3303 /* FundDefinitionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C62613E22D2A8A500AE3303 /* FundDefinitionTableViewCell.xib */; };
+ 2C62614422D2A9E300AE3303 /* FundInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C62614222D2A9E300AE3303 /* FundInfoTableViewCell.swift */; };
+ 2C62614522D2A9E300AE3303 /* FundInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C62614322D2A9E300AE3303 /* FundInfoTableViewCell.xib */; };
+ 2C62614922D2AAC900AE3303 /* FundDownInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C62614722D2AAC900AE3303 /* FundDownInfoTableViewCell.swift */; };
+ 2C62614A22D2AAC900AE3303 /* FundDownInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C62614822D2AAC900AE3303 /* FundDownInfoTableViewCell.xib */; };
+ 2C639C5D22D2A68D00986F4C /* FundRiskTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C3922D2A68D00986F4C /* FundRiskTableViewCell.swift */; };
+ 2C639C5E22D2A68D00986F4C /* FundRiskTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C3A22D2A68D00986F4C /* FundRiskTableViewCell.xib */; };
+ 2C639C6122D2A68D00986F4C /* FundInfoTitleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C3F22D2A68D00986F4C /* FundInfoTitleTableViewCell.xib */; };
+ 2C639C6222D2A68D00986F4C /* FundInfoTitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4022D2A68D00986F4C /* FundInfoTitleTableViewCell.swift */; };
+ 2C639C6322D2A68D00986F4C /* FundNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4222D2A68D00986F4C /* FundNameTableViewCell.swift */; };
+ 2C639C6422D2A68D00986F4C /* FundNameTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C4322D2A68D00986F4C /* FundNameTableViewCell.xib */; };
+ 2C639C6522D2A68D00986F4C /* FundMoreInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C4522D2A68D00986F4C /* FundMoreInfoTableViewCell.xib */; };
+ 2C639C6622D2A68D00986F4C /* FundMoreInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4622D2A68D00986F4C /* FundMoreInfoTableViewCell.swift */; };
+ 2C639C6722D2A68D00986F4C /* FundTitleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C4822D2A68D00986F4C /* FundTitleTableViewCell.xib */; };
+ 2C639C6822D2A68D00986F4C /* FundTitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4922D2A68D00986F4C /* FundTitleTableViewCell.swift */; };
+ 2C639C6922D2A68D00986F4C /* CheckboxTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C4B22D2A68D00986F4C /* CheckboxTableViewCell.xib */; };
+ 2C639C6A22D2A68D00986F4C /* CheckboxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4C22D2A68D00986F4C /* CheckboxTableViewCell.swift */; };
+ 2C639C6B22D2A68D00986F4C /* InputTextTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C4E22D2A68D00986F4C /* InputTextTableViewCell.xib */; };
+ 2C639C6C22D2A68D00986F4C /* InputTextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C4F22D2A68D00986F4C /* InputTextTableViewCell.swift */; };
+ 2C639C6D22D2A68D00986F4C /* InputTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C5022D2A68D00986F4C /* InputTextFieldTableViewCell.swift */; };
+ 2C639C6E22D2A68D00986F4C /* TitleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C5222D2A68D00986F4C /* TitleTableViewCell.xib */; };
+ 2C639C6F22D2A68D00986F4C /* TitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C5322D2A68D00986F4C /* TitleTableViewCell.swift */; };
+ 2C639C7022D2A68D00986F4C /* InputPhoneTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C5522D2A68D00986F4C /* InputPhoneTableViewCell.xib */; };
+ 2C639C7122D2A68D00986F4C /* InputPhoneTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C5622D2A68D00986F4C /* InputPhoneTableViewCell.swift */; };
+ 2C639C7222D2A68D00986F4C /* InputEmailTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C5822D2A68D00986F4C /* InputEmailTableViewCell.swift */; };
+ 2C639C7322D2A68D00986F4C /* InputEmailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C5922D2A68D00986F4C /* InputEmailTableViewCell.xib */; };
+ 2C639C7422D2A68D00986F4C /* ActionButtonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C639C5B22D2A68D00986F4C /* ActionButtonTableViewCell.swift */; };
+ 2C639C7522D2A68D00986F4C /* ActionButtonTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C639C5C22D2A68D00986F4C /* ActionButtonTableViewCell.xib */; };
+ 2C6E8BD622D2432F002EDEA9 /* ContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BD522D2432F002EDEA9 /* ContainerViewController.swift */; };
+ 2C6E8BE322D2722E002EDEA9 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BE222D2722E002EDEA9 /* UIViewController+Extension.swift */; };
+ 2C6E8BE522D27CB3002EDEA9 /* Funds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BE422D27CB3002EDEA9 /* Funds.swift */; };
+ 2C6E8BE722D28108002EDEA9 /* FundMoreInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BE622D28108002EDEA9 /* FundMoreInfo.swift */; };
+ 2C6E8BE922D28247002EDEA9 /* FundPercentages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BE822D28247002EDEA9 /* FundPercentages.swift */; };
+ 2C6E8BEB22D282C4002EDEA9 /* FundInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BEA22D282C4002EDEA9 /* FundInfo.swift */; };
+ 2C6E8BED22D28304002EDEA9 /* DownInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BEC22D28304002EDEA9 /* DownInfo.swift */; };
+ 2C6E8BEF22D28591002EDEA9 /* FundsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E8BEE22D28591002EDEA9 /* FundsResponse.swift */; };
+ 2C78DB1622D0E08E0092DE8C /* FormCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB1522D0E08E0092DE8C /* FormCell.swift */; };
+ 2C78DB1822D0E1130092DE8C /* CellType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB1722D0E1130092DE8C /* CellType.swift */; };
+ 2C78DB1A22D0E2020092DE8C /* FieldType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB1922D0E2020092DE8C /* FieldType.swift */; };
+ 2C78DB1C22D0E6640092DE8C /* FormCellsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB1B22D0E6640092DE8C /* FormCellsResponse.swift */; };
+ 2C78DB5322D100070092DE8C /* UITableViewCell+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB5222D100070092DE8C /* UITableViewCell+Extension.swift */; };
+ 2C78DB5522D1006A0092DE8C /* UITableView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB5422D1006A0092DE8C /* UITableView+Extension.swift */; };
+ 2C78DB6022D19A960092DE8C /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB5F22D19A960092DE8C /* String+Extension.swift */; };
+ 2C78DB6622D1A3A90092DE8C /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB6522D1A3A90092DE8C /* StringExtensions.swift */; };
+ 2C78DB6A22D1A3EC0092DE8C /* SwiftMaskField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB6922D1A3EC0092DE8C /* SwiftMaskField.swift */; };
+ 2C78DB8422D1A79B0092DE8C /* IQTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB6E22D1A79B0092DE8C /* IQTextView.swift */; };
+ 2C78DB8522D1A79B0092DE8C /* IQPreviousNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7022D1A79B0092DE8C /* IQPreviousNextView.swift */; };
+ 2C78DB8622D1A79B0092DE8C /* IQTitleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7122D1A79B0092DE8C /* IQTitleBarButtonItem.swift */; };
+ 2C78DB8722D1A79B0092DE8C /* IQBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7222D1A79B0092DE8C /* IQBarButtonItem.swift */; };
+ 2C78DB8822D1A79B0092DE8C /* IQToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7322D1A79B0092DE8C /* IQToolbar.swift */; };
+ 2C78DB8922D1A79B0092DE8C /* IQUIView+IQKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7422D1A79B0092DE8C /* IQUIView+IQKeyboardToolbar.swift */; };
+ 2C78DB8A22D1A79B0092DE8C /* IQInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7522D1A79B0092DE8C /* IQInvocation.swift */; };
+ 2C78DB8B22D1A79B0092DE8C /* IQKeyboardManagerConstantsInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7722D1A79B0092DE8C /* IQKeyboardManagerConstantsInternal.swift */; };
+ 2C78DB8C22D1A79B0092DE8C /* IQKeyboardManagerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7822D1A79B0092DE8C /* IQKeyboardManagerConstants.swift */; };
+ 2C78DB8D22D1A79B0092DE8C /* IQKeyboardManager.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 2C78DB7A22D1A79B0092DE8C /* IQKeyboardManager.bundle */; };
+ 2C78DB8E22D1A79B0092DE8C /* IQKeyboardReturnKeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7B22D1A79B0092DE8C /* IQKeyboardReturnKeyHandler.swift */; };
+ 2C78DB8F22D1A79B0092DE8C /* IQKeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7C22D1A79B0092DE8C /* IQKeyboardManager.swift */; };
+ 2C78DB9022D1A79B0092DE8C /* IQUIScrollView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB7F22D1A79B0092DE8C /* IQUIScrollView+Additions.swift */; };
+ 2C78DB9122D1A79B0092DE8C /* IQUIViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB8022D1A79B0092DE8C /* IQUIViewController+Additions.swift */; };
+ 2C78DB9222D1A79B0092DE8C /* IQUITextFieldView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB8122D1A79B0092DE8C /* IQUITextFieldView+Additions.swift */; };
+ 2C78DB9322D1A79B0092DE8C /* IQUIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB8222D1A79B0092DE8C /* IQUIView+Hierarchy.swift */; };
+ 2C78DB9422D1A79B0092DE8C /* IQNSArray+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB8322D1A79B0092DE8C /* IQNSArray+Sort.swift */; };
+ 2C78DB9822D1AD490092DE8C /* SuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB9722D1AD490092DE8C /* SuccessViewController.swift */; };
+ 2CD9394922D2B14E0066E958 /* SeparatorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9394722D2B14E0066E958 /* SeparatorTableViewCell.swift */; };
+ 2CD9394A22D2B14E0066E958 /* SeparatorTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9394822D2B14E0066E958 /* SeparatorTableViewCell.xib */; };
+ 2CD9394C22D2DB220066E958 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9394B22D2DB220066E958 /* BaseViewController.swift */; };
+ 2CD9394E22D2DF780066E958 /* ContactData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9394D22D2DF780066E958 /* ContactData.swift */; };
+ 2CD9396322D2EA290066E958 /* EnvironmentManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9396222D2EA290066E958 /* EnvironmentManagerTests.swift */; };
+ 2CD9396922D2EC740066E958 /* StringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9396822D2EC740066E958 /* StringTests.swift */; };
+ 2CD9396A22D2ECD50066E958 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78DB5F22D19A960092DE8C /* String+Extension.swift */; };
+ 2CD9396F22D2EE510066E958 /* ProductionNetworkProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EF422D018A3009D49E9 /* ProductionNetworkProvider.swift */; };
+ 2CD9397122D2EE680066E958 /* NetworkProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EF622D018C1009D49E9 /* NetworkProviderProtocol.swift */; };
+ 2CD9397222D2EEAF0066E958 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C114EFB22D01E4A009D49E9 /* NetworkError.swift */; };
+ 2CD9398522D2F4910066E958 /* FormInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9398422D2F4910066E958 /* FormInteractorTests.swift */; };
+ 2CD9398922D2F7AB0066E958 /* FundInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9398822D2F7AB0066E958 /* FundInteractorTests.swift */; };
+ 2CD9399D22D3030E0066E958 /* fund.json in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9398F22D3030E0066E958 /* fund.json */; };
+ 2CD9399E22D3030E0066E958 /* cells.json in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399022D3030E0066E958 /* cells.json */; };
+ 2CD9399F22D3030E0066E958 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9399122D3030E0066E958 /* Colors.swift */; };
+ 2CD939A022D3030E0066E958 /* DINNeuzeitGroteskStd-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399422D3030E0066E958 /* DINNeuzeitGroteskStd-Light.otf */; };
+ 2CD939A122D3030E0066E958 /* DINPro-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399522D3030E0066E958 /* DINPro-Medium.otf */; };
+ 2CD939A222D3030E0066E958 /* DINPro-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399622D3030E0066E958 /* DINPro-Bold.otf */; };
+ 2CD939A322D3030E0066E958 /* DINNeuzeitGroteskStd-BdCond.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399722D3030E0066E958 /* DINNeuzeitGroteskStd-BdCond.otf */; };
+ 2CD939A422D3030E0066E958 /* DINPro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399822D3030E0066E958 /* DINPro-Black.otf */; };
+ 2CD939A522D3030E0066E958 /* DINEngschriftStd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399922D3030E0066E958 /* DINEngschriftStd.otf */; };
+ 2CD939A622D3030E0066E958 /* DINPro-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399A22D3030E0066E958 /* DINPro-Regular.otf */; };
+ 2CD939A722D3030E0066E958 /* DINPro-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399B22D3030E0066E958 /* DINPro-Light.otf */; };
+ 2CD939A822D3030E0066E958 /* DINMittelschriftStd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9399C22D3030E0066E958 /* DINMittelschriftStd.otf */; };
+ 2CD939AD22D303DB0066E958 /* FormInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939AA22D303DB0066E958 /* FormInteractor.swift */; };
+ 2CD939AE22D303DB0066E958 /* FormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939AB22D303DB0066E958 /* FormViewController.swift */; };
+ 2CD939AF22D303DB0066E958 /* FormPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939AC22D303DB0066E958 /* FormPresenter.swift */; };
+ 2CD939B422D303F10066E958 /* FundsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939B122D303F10066E958 /* FundsInteractor.swift */; };
+ 2CD939B522D303F10066E958 /* FundsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939B222D303F10066E958 /* FundsViewController.swift */; };
+ 2CD939B622D303F10066E958 /* FundsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD939B322D303F10066E958 /* FundsPresenter.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 2CD9395822D2E9420066E958 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 2C3BCA3C22CECB800064EC0D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 2C3BCA4322CECB800064EC0D;
+ remoteInfo = SantanderChallenge;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 2C114EF422D018A3009D49E9 /* ProductionNetworkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductionNetworkProvider.swift; sourceTree = ""; };
+ 2C114EF622D018C1009D49E9 /* NetworkProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProviderProtocol.swift; sourceTree = ""; };
+ 2C114EFB22D01E4A009D49E9 /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; };
+ 2C114EFF22D02CE9009D49E9 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NetworkManager.swift; path = Managers/NetworkManager.swift; sourceTree = ""; };
+ 2C114F0722D03A35009D49E9 /* DevelopmentNetworkProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentNetworkProvider.swift; sourceTree = ""; };
+ 2C114F0A22D03AE1009D49E9 /* Bundle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extension.swift"; sourceTree = ""; };
+ 2C26FF6822D294FC0001E250 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; };
+ 2C3BCA4422CECB800064EC0D /* SantanderChallenge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SantanderChallenge.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2C3BCA4722CECB800064EC0D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Base/AppDelegate.swift; sourceTree = ""; };
+ 2C3BCA4C22CECB800064EC0D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 2C3BCA5122CECB810064EC0D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 2C3BCA5322CECB810064EC0D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 2C4787D022D306BC00366259 /* EnvironmentManger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentManger.swift; sourceTree = ""; };
+ 2C4787D222D3070C00366259 /* Development.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Development.plist; sourceTree = ""; };
+ 2C4787D322D3070C00366259 /* Production.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Production.plist; sourceTree = ""; };
+ 2C4787D622D3071600366259 /* Development.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Development.xcconfig; sourceTree = ""; };
+ 2C4787D722D3071600366259 /* Production.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = ""; };
+ 2C62613D22D2A8A500AE3303 /* FundDefinitionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundDefinitionTableViewCell.swift; sourceTree = ""; };
+ 2C62613E22D2A8A500AE3303 /* FundDefinitionTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FundDefinitionTableViewCell.xib; sourceTree = ""; };
+ 2C62614222D2A9E300AE3303 /* FundInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundInfoTableViewCell.swift; sourceTree = ""; };
+ 2C62614322D2A9E300AE3303 /* FundInfoTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FundInfoTableViewCell.xib; sourceTree = ""; };
+ 2C62614722D2AAC900AE3303 /* FundDownInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundDownInfoTableViewCell.swift; sourceTree = ""; };
+ 2C62614822D2AAC900AE3303 /* FundDownInfoTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FundDownInfoTableViewCell.xib; sourceTree = ""; };
+ 2C639C3922D2A68D00986F4C /* FundRiskTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundRiskTableViewCell.swift; sourceTree = ""; };
+ 2C639C3A22D2A68D00986F4C /* FundRiskTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FundRiskTableViewCell.xib; sourceTree = ""; };
+ 2C639C3F22D2A68D00986F4C /* FundInfoTitleTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FundInfoTitleTableViewCell.xib; sourceTree = ""; };
+ 2C639C4022D2A68D00986F4C /* FundInfoTitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundInfoTitleTableViewCell.swift; sourceTree = ""; };
+ 2C639C4222D2A68D00986F4C /* FundNameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundNameTableViewCell.swift; sourceTree = ""; };
+ 2C639C4322D2A68D00986F4C /* FundNameTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FundNameTableViewCell.xib; sourceTree = ""; };
+ 2C639C4522D2A68D00986F4C /* FundMoreInfoTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FundMoreInfoTableViewCell.xib; sourceTree = ""; };
+ 2C639C4622D2A68D00986F4C /* FundMoreInfoTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundMoreInfoTableViewCell.swift; sourceTree = ""; };
+ 2C639C4822D2A68D00986F4C /* FundTitleTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FundTitleTableViewCell.xib; sourceTree = ""; };
+ 2C639C4922D2A68D00986F4C /* FundTitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundTitleTableViewCell.swift; sourceTree = ""; };
+ 2C639C4B22D2A68D00986F4C /* CheckboxTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CheckboxTableViewCell.xib; sourceTree = ""; };
+ 2C639C4C22D2A68D00986F4C /* CheckboxTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxTableViewCell.swift; sourceTree = ""; };
+ 2C639C4E22D2A68D00986F4C /* InputTextTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InputTextTableViewCell.xib; sourceTree = ""; };
+ 2C639C4F22D2A68D00986F4C /* InputTextTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextTableViewCell.swift; sourceTree = ""; };
+ 2C639C5022D2A68D00986F4C /* InputTextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFieldTableViewCell.swift; sourceTree = ""; };
+ 2C639C5222D2A68D00986F4C /* TitleTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TitleTableViewCell.xib; sourceTree = ""; };
+ 2C639C5322D2A68D00986F4C /* TitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleTableViewCell.swift; sourceTree = ""; };
+ 2C639C5522D2A68D00986F4C /* InputPhoneTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InputPhoneTableViewCell.xib; sourceTree = ""; };
+ 2C639C5622D2A68D00986F4C /* InputPhoneTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputPhoneTableViewCell.swift; sourceTree = ""; };
+ 2C639C5822D2A68D00986F4C /* InputEmailTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputEmailTableViewCell.swift; sourceTree = ""; };
+ 2C639C5922D2A68D00986F4C /* InputEmailTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = InputEmailTableViewCell.xib; sourceTree = ""; };
+ 2C639C5B22D2A68D00986F4C /* ActionButtonTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionButtonTableViewCell.swift; sourceTree = ""; };
+ 2C639C5C22D2A68D00986F4C /* ActionButtonTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ActionButtonTableViewCell.xib; sourceTree = ""; };
+ 2C6E8BD522D2432F002EDEA9 /* ContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContainerViewController.swift; path = Scenes/Container/ContainerViewController.swift; sourceTree = ""; };
+ 2C6E8BE222D2722E002EDEA9 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; };
+ 2C6E8BE422D27CB3002EDEA9 /* Funds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Funds.swift; sourceTree = ""; };
+ 2C6E8BE622D28108002EDEA9 /* FundMoreInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundMoreInfo.swift; sourceTree = ""; };
+ 2C6E8BE822D28247002EDEA9 /* FundPercentages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundPercentages.swift; sourceTree = ""; };
+ 2C6E8BEA22D282C4002EDEA9 /* FundInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundInfo.swift; sourceTree = ""; };
+ 2C6E8BEC22D28304002EDEA9 /* DownInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownInfo.swift; sourceTree = ""; };
+ 2C6E8BEE22D28591002EDEA9 /* FundsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundsResponse.swift; sourceTree = ""; };
+ 2C78DB1522D0E08E0092DE8C /* FormCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormCell.swift; sourceTree = ""; };
+ 2C78DB1722D0E1130092DE8C /* CellType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellType.swift; sourceTree = ""; };
+ 2C78DB1922D0E2020092DE8C /* FieldType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldType.swift; sourceTree = ""; };
+ 2C78DB1B22D0E6640092DE8C /* FormCellsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormCellsResponse.swift; sourceTree = ""; };
+ 2C78DB5222D100070092DE8C /* UITableViewCell+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+Extension.swift"; sourceTree = ""; };
+ 2C78DB5422D1006A0092DE8C /* UITableView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Extension.swift"; sourceTree = ""; };
+ 2C78DB5F22D19A960092DE8C /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; };
+ 2C78DB6522D1A3A90092DE8C /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
+ 2C78DB6922D1A3EC0092DE8C /* SwiftMaskField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMaskField.swift; sourceTree = ""; };
+ 2C78DB6E22D1A79B0092DE8C /* IQTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQTextView.swift; sourceTree = ""; };
+ 2C78DB7022D1A79B0092DE8C /* IQPreviousNextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQPreviousNextView.swift; sourceTree = ""; };
+ 2C78DB7122D1A79B0092DE8C /* IQTitleBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQTitleBarButtonItem.swift; sourceTree = ""; };
+ 2C78DB7222D1A79B0092DE8C /* IQBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQBarButtonItem.swift; sourceTree = ""; };
+ 2C78DB7322D1A79B0092DE8C /* IQToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQToolbar.swift; sourceTree = ""; };
+ 2C78DB7422D1A79B0092DE8C /* IQUIView+IQKeyboardToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQUIView+IQKeyboardToolbar.swift"; sourceTree = ""; };
+ 2C78DB7522D1A79B0092DE8C /* IQInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQInvocation.swift; sourceTree = ""; };
+ 2C78DB7722D1A79B0092DE8C /* IQKeyboardManagerConstantsInternal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQKeyboardManagerConstantsInternal.swift; sourceTree = ""; };
+ 2C78DB7822D1A79B0092DE8C /* IQKeyboardManagerConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQKeyboardManagerConstants.swift; sourceTree = ""; };
+ 2C78DB7A22D1A79B0092DE8C /* IQKeyboardManager.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = IQKeyboardManager.bundle; sourceTree = ""; };
+ 2C78DB7B22D1A79B0092DE8C /* IQKeyboardReturnKeyHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQKeyboardReturnKeyHandler.swift; sourceTree = ""; };
+ 2C78DB7C22D1A79B0092DE8C /* IQKeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IQKeyboardManager.swift; sourceTree = ""; };
+ 2C78DB7D22D1A79B0092DE8C /* IQKeyboardManagerSwift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQKeyboardManagerSwift.h; sourceTree = ""; };
+ 2C78DB7F22D1A79B0092DE8C /* IQUIScrollView+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQUIScrollView+Additions.swift"; sourceTree = ""; };
+ 2C78DB8022D1A79B0092DE8C /* IQUIViewController+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQUIViewController+Additions.swift"; sourceTree = ""; };
+ 2C78DB8122D1A79B0092DE8C /* IQUITextFieldView+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQUITextFieldView+Additions.swift"; sourceTree = ""; };
+ 2C78DB8222D1A79B0092DE8C /* IQUIView+Hierarchy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQUIView+Hierarchy.swift"; sourceTree = ""; };
+ 2C78DB8322D1A79B0092DE8C /* IQNSArray+Sort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IQNSArray+Sort.swift"; sourceTree = ""; };
+ 2C78DB9722D1AD490092DE8C /* SuccessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SuccessViewController.swift; path = Scenes/Success/SuccessViewController.swift; sourceTree = ""; };
+ 2CD9394722D2B14E0066E958 /* SeparatorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorTableViewCell.swift; sourceTree = ""; };
+ 2CD9394822D2B14E0066E958 /* SeparatorTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SeparatorTableViewCell.xib; sourceTree = ""; };
+ 2CD9394B22D2DB220066E958 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BaseViewController.swift; path = Scenes/BaseViewController.swift; sourceTree = ""; };
+ 2CD9394D22D2DF780066E958 /* ContactData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactData.swift; sourceTree = ""; };
+ 2CD9395322D2E9410066E958 /* SantanderChallengeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SantanderChallengeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2CD9395722D2E9420066E958 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 2CD9396222D2EA290066E958 /* EnvironmentManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentManagerTests.swift; sourceTree = ""; };
+ 2CD9396822D2EC740066E958 /* StringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringTests.swift; sourceTree = ""; };
+ 2CD9398422D2F4910066E958 /* FormInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormInteractorTests.swift; sourceTree = ""; };
+ 2CD9398822D2F7AB0066E958 /* FundInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FundInteractorTests.swift; sourceTree = ""; };
+ 2CD9398F22D3030E0066E958 /* fund.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fund.json; sourceTree = ""; };
+ 2CD9399022D3030E0066E958 /* cells.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cells.json; sourceTree = ""; };
+ 2CD9399122D3030E0066E958 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; };
+ 2CD9399422D3030E0066E958 /* DINNeuzeitGroteskStd-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINNeuzeitGroteskStd-Light.otf"; sourceTree = ""; };
+ 2CD9399522D3030E0066E958 /* DINPro-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Medium.otf"; sourceTree = ""; };
+ 2CD9399622D3030E0066E958 /* DINPro-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Bold.otf"; sourceTree = ""; };
+ 2CD9399722D3030E0066E958 /* DINNeuzeitGroteskStd-BdCond.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINNeuzeitGroteskStd-BdCond.otf"; sourceTree = ""; };
+ 2CD9399822D3030E0066E958 /* DINPro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Black.otf"; sourceTree = ""; };
+ 2CD9399922D3030E0066E958 /* DINEngschriftStd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DINEngschriftStd.otf; sourceTree = ""; };
+ 2CD9399A22D3030E0066E958 /* DINPro-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Regular.otf"; sourceTree = ""; };
+ 2CD9399B22D3030E0066E958 /* DINPro-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "DINPro-Light.otf"; sourceTree = ""; };
+ 2CD9399C22D3030E0066E958 /* DINMittelschriftStd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DINMittelschriftStd.otf; sourceTree = ""; };
+ 2CD939AA22D303DB0066E958 /* FormInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormInteractor.swift; sourceTree = ""; };
+ 2CD939AB22D303DB0066E958 /* FormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormViewController.swift; sourceTree = ""; };
+ 2CD939AC22D303DB0066E958 /* FormPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormPresenter.swift; sourceTree = ""; };
+ 2CD939B122D303F10066E958 /* FundsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundsInteractor.swift; sourceTree = ""; };
+ 2CD939B222D303F10066E958 /* FundsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundsViewController.swift; sourceTree = ""; };
+ 2CD939B322D303F10066E958 /* FundsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundsPresenter.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 2C3BCA4122CECB800064EC0D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2CD9395022D2E9410066E958 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 2C114EF222D01874009D49E9 /* Services */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114EF322D01885009D49E9 /* Network */,
+ );
+ path = Services;
+ sourceTree = "";
+ };
+ 2C114EF322D01885009D49E9 /* Network */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114EF622D018C1009D49E9 /* NetworkProviderProtocol.swift */,
+ 2C114EFA22D01E2B009D49E9 /* Models */,
+ 2C114EF822D01E10009D49E9 /* Providers */,
+ );
+ name = Network;
+ sourceTree = "";
+ };
+ 2C114EF822D01E10009D49E9 /* Providers */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114EF422D018A3009D49E9 /* ProductionNetworkProvider.swift */,
+ 2C114F0722D03A35009D49E9 /* DevelopmentNetworkProvider.swift */,
+ );
+ path = Providers;
+ sourceTree = "";
+ };
+ 2C114EFA22D01E2B009D49E9 /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114EFB22D01E4A009D49E9 /* NetworkError.swift */,
+ 2C78DB1B22D0E6640092DE8C /* FormCellsResponse.swift */,
+ 2C6E8BEE22D28591002EDEA9 /* FundsResponse.swift */,
+ );
+ name = Models;
+ sourceTree = "";
+ };
+ 2C114EFD22D02CCA009D49E9 /* Managers */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114EFF22D02CE9009D49E9 /* NetworkManager.swift */,
+ );
+ name = Managers;
+ sourceTree = "";
+ };
+ 2C114F0922D03AD0009D49E9 /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ 2C114F0A22D03AE1009D49E9 /* Bundle+Extension.swift */,
+ 2C78DB5222D100070092DE8C /* UITableViewCell+Extension.swift */,
+ 2C78DB5422D1006A0092DE8C /* UITableView+Extension.swift */,
+ 2C78DB5F22D19A960092DE8C /* String+Extension.swift */,
+ 2C6E8BE222D2722E002EDEA9 /* UIViewController+Extension.swift */,
+ 2C26FF6822D294FC0001E250 /* UIView+Extension.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ 2C3BCA3B22CECB800064EC0D = {
+ isa = PBXGroup;
+ children = (
+ 2C3BCA4622CECB800064EC0D /* SantanderChallenge */,
+ 2CD9395422D2E9420066E958 /* SantanderChallengeTests */,
+ 2C3BCA4522CECB800064EC0D /* Products */,
+ );
+ sourceTree = "";
+ };
+ 2C3BCA4522CECB800064EC0D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 2C3BCA4422CECB800064EC0D /* SantanderChallenge.app */,
+ 2CD9395322D2E9410066E958 /* SantanderChallengeTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 2C3BCA4622CECB800064EC0D /* SantanderChallenge */ = {
+ isa = PBXGroup;
+ children = (
+ 2CB34CF122CED0DB00D4C74C /* Base */,
+ 2C639C3522D2A68D00986F4C /* CustomViews */,
+ 2C114F0922D03AD0009D49E9 /* Extensions */,
+ 2CD9398B22D302580066E958 /* Frameworks */,
+ 2CD9398D22D3030E0066E958 /* Resources */,
+ 2C114EFD22D02CCA009D49E9 /* Managers */,
+ 2C78DB1422D0E07F0092DE8C /* Models */,
+ 2C78DB1222D0E02E0092DE8C /* Scenes */,
+ 2C114EF222D01874009D49E9 /* Services */,
+ );
+ path = SantanderChallenge;
+ sourceTree = "";
+ };
+ 2C4787CD22D306AC00366259 /* Environment */ = {
+ isa = PBXGroup;
+ children = (
+ 2C4787D022D306BC00366259 /* EnvironmentManger.swift */,
+ 2C4787CE22D306AC00366259 /* Plist */,
+ 2C4787CF22D306AC00366259 /* ConfigFiles */,
+ );
+ name = Environment;
+ path = Base/Environment;
+ sourceTree = "";
+ };
+ 2C4787CE22D306AC00366259 /* Plist */ = {
+ isa = PBXGroup;
+ children = (
+ 2C4787D222D3070C00366259 /* Development.plist */,
+ 2C4787D322D3070C00366259 /* Production.plist */,
+ );
+ path = Plist;
+ sourceTree = "";
+ };
+ 2C4787CF22D306AC00366259 /* ConfigFiles */ = {
+ isa = PBXGroup;
+ children = (
+ 2C4787D622D3071600366259 /* Development.xcconfig */,
+ 2C4787D722D3071600366259 /* Production.xcconfig */,
+ );
+ path = ConfigFiles;
+ sourceTree = "";
+ };
+ 2C62613C22D2A88600AE3303 /* FundDefinitionTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C62613D22D2A8A500AE3303 /* FundDefinitionTableViewCell.swift */,
+ 2C62613E22D2A8A500AE3303 /* FundDefinitionTableViewCell.xib */,
+ );
+ path = FundDefinitionTableViewCell;
+ sourceTree = "";
+ };
+ 2C62614122D2A9D600AE3303 /* FundInfoTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C62614222D2A9E300AE3303 /* FundInfoTableViewCell.swift */,
+ 2C62614322D2A9E300AE3303 /* FundInfoTableViewCell.xib */,
+ );
+ path = FundInfoTableViewCell;
+ sourceTree = "";
+ };
+ 2C62614622D2AAA400AE3303 /* FundDownInfoTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C62614722D2AAC900AE3303 /* FundDownInfoTableViewCell.swift */,
+ 2C62614822D2AAC900AE3303 /* FundDownInfoTableViewCell.xib */,
+ );
+ path = FundDownInfoTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C3522D2A68D00986F4C /* CustomViews */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C3622D2A68D00986F4C /* Cells */,
+ 2C639C5022D2A68D00986F4C /* InputTextFieldTableViewCell.swift */,
+ );
+ path = CustomViews;
+ sourceTree = "";
+ };
+ 2C639C3622D2A68D00986F4C /* Cells */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9394622D2B13E0066E958 /* SeparatorTableViewCell */,
+ 2C639C3722D2A68D00986F4C /* FundsCells */,
+ 2C639C4A22D2A68D00986F4C /* CheckboxTableViewCell */,
+ 2C639C4D22D2A68D00986F4C /* InputTextTableViewCell */,
+ 2C639C5122D2A68D00986F4C /* TitleTableViewCell */,
+ 2C639C5422D2A68D00986F4C /* InputPhoneTableViewCell */,
+ 2C639C5722D2A68D00986F4C /* InputEmailTableViewCell */,
+ 2C639C5A22D2A68D00986F4C /* ActionButtonTableViewCell */,
+ );
+ path = Cells;
+ sourceTree = "";
+ };
+ 2C639C3722D2A68D00986F4C /* FundsCells */ = {
+ isa = PBXGroup;
+ children = (
+ 2C62614622D2AAA400AE3303 /* FundDownInfoTableViewCell */,
+ 2C62614122D2A9D600AE3303 /* FundInfoTableViewCell */,
+ 2C62613C22D2A88600AE3303 /* FundDefinitionTableViewCell */,
+ 2C639C3822D2A68D00986F4C /* FundRiskTableViewCell */,
+ 2C639C3E22D2A68D00986F4C /* FundInfoTitleTableViewCell */,
+ 2C639C4122D2A68D00986F4C /* FundNameTableViewCell */,
+ 2C639C4422D2A68D00986F4C /* FundMoreInfoTableViewCell */,
+ 2C639C4722D2A68D00986F4C /* FundTitleTableViewCell */,
+ );
+ path = FundsCells;
+ sourceTree = "";
+ };
+ 2C639C3822D2A68D00986F4C /* FundRiskTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C3922D2A68D00986F4C /* FundRiskTableViewCell.swift */,
+ 2C639C3A22D2A68D00986F4C /* FundRiskTableViewCell.xib */,
+ );
+ path = FundRiskTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C3E22D2A68D00986F4C /* FundInfoTitleTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C3F22D2A68D00986F4C /* FundInfoTitleTableViewCell.xib */,
+ 2C639C4022D2A68D00986F4C /* FundInfoTitleTableViewCell.swift */,
+ );
+ path = FundInfoTitleTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C4122D2A68D00986F4C /* FundNameTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C4222D2A68D00986F4C /* FundNameTableViewCell.swift */,
+ 2C639C4322D2A68D00986F4C /* FundNameTableViewCell.xib */,
+ );
+ path = FundNameTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C4422D2A68D00986F4C /* FundMoreInfoTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C4522D2A68D00986F4C /* FundMoreInfoTableViewCell.xib */,
+ 2C639C4622D2A68D00986F4C /* FundMoreInfoTableViewCell.swift */,
+ );
+ path = FundMoreInfoTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C4722D2A68D00986F4C /* FundTitleTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C4822D2A68D00986F4C /* FundTitleTableViewCell.xib */,
+ 2C639C4922D2A68D00986F4C /* FundTitleTableViewCell.swift */,
+ );
+ path = FundTitleTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C4A22D2A68D00986F4C /* CheckboxTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C4B22D2A68D00986F4C /* CheckboxTableViewCell.xib */,
+ 2C639C4C22D2A68D00986F4C /* CheckboxTableViewCell.swift */,
+ );
+ path = CheckboxTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C4D22D2A68D00986F4C /* InputTextTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C4E22D2A68D00986F4C /* InputTextTableViewCell.xib */,
+ 2C639C4F22D2A68D00986F4C /* InputTextTableViewCell.swift */,
+ );
+ path = InputTextTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C5122D2A68D00986F4C /* TitleTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C5222D2A68D00986F4C /* TitleTableViewCell.xib */,
+ 2C639C5322D2A68D00986F4C /* TitleTableViewCell.swift */,
+ );
+ path = TitleTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C5422D2A68D00986F4C /* InputPhoneTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C5522D2A68D00986F4C /* InputPhoneTableViewCell.xib */,
+ 2C639C5622D2A68D00986F4C /* InputPhoneTableViewCell.swift */,
+ );
+ path = InputPhoneTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C5722D2A68D00986F4C /* InputEmailTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C5822D2A68D00986F4C /* InputEmailTableViewCell.swift */,
+ 2C639C5922D2A68D00986F4C /* InputEmailTableViewCell.xib */,
+ );
+ path = InputEmailTableViewCell;
+ sourceTree = "";
+ };
+ 2C639C5A22D2A68D00986F4C /* ActionButtonTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2C639C5B22D2A68D00986F4C /* ActionButtonTableViewCell.swift */,
+ 2C639C5C22D2A68D00986F4C /* ActionButtonTableViewCell.xib */,
+ );
+ path = ActionButtonTableViewCell;
+ sourceTree = "";
+ };
+ 2C6E8BD222D242F1002EDEA9 /* Container */ = {
+ isa = PBXGroup;
+ children = (
+ 2C6E8BD522D2432F002EDEA9 /* ContainerViewController.swift */,
+ );
+ name = Container;
+ sourceTree = "";
+ };
+ 2C78DB1222D0E02E0092DE8C /* Scenes */ = {
+ isa = PBXGroup;
+ children = (
+ 2C3BCA4B22CECB800064EC0D /* Main.storyboard */,
+ 2C6E8BD222D242F1002EDEA9 /* Container */,
+ 2CD939A922D303DB0066E958 /* Form */,
+ 2C78DB9522D1AD340092DE8C /* Success */,
+ 2CD939B022D303F10066E958 /* Funds */,
+ 2CD9394B22D2DB220066E958 /* BaseViewController.swift */,
+ );
+ name = Scenes;
+ sourceTree = "";
+ };
+ 2C78DB1422D0E07F0092DE8C /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB1522D0E08E0092DE8C /* FormCell.swift */,
+ 2C78DB1722D0E1130092DE8C /* CellType.swift */,
+ 2C78DB1922D0E2020092DE8C /* FieldType.swift */,
+ 2C6E8BE422D27CB3002EDEA9 /* Funds.swift */,
+ 2C6E8BE622D28108002EDEA9 /* FundMoreInfo.swift */,
+ 2C6E8BEA22D282C4002EDEA9 /* FundInfo.swift */,
+ 2C6E8BE822D28247002EDEA9 /* FundPercentages.swift */,
+ 2C6E8BEC22D28304002EDEA9 /* DownInfo.swift */,
+ 2CD9394D22D2DF780066E958 /* ContactData.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
+ 2C78DB6B22D1A3FA0092DE8C /* SwiftMaskText */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB6522D1A3A90092DE8C /* StringExtensions.swift */,
+ 2C78DB6922D1A3EC0092DE8C /* SwiftMaskField.swift */,
+ );
+ path = SwiftMaskText;
+ sourceTree = "";
+ };
+ 2C78DB6C22D1A79B0092DE8C /* IQKeyboardManagerSwift */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB6D22D1A79B0092DE8C /* IQTextView */,
+ 2C78DB6F22D1A79B0092DE8C /* IQToolbar */,
+ 2C78DB7622D1A79B0092DE8C /* Constants */,
+ 2C78DB7922D1A79B0092DE8C /* Resources */,
+ 2C78DB7B22D1A79B0092DE8C /* IQKeyboardReturnKeyHandler.swift */,
+ 2C78DB7C22D1A79B0092DE8C /* IQKeyboardManager.swift */,
+ 2C78DB7D22D1A79B0092DE8C /* IQKeyboardManagerSwift.h */,
+ 2C78DB7E22D1A79B0092DE8C /* Categories */,
+ );
+ path = IQKeyboardManagerSwift;
+ sourceTree = "";
+ };
+ 2C78DB6D22D1A79B0092DE8C /* IQTextView */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB6E22D1A79B0092DE8C /* IQTextView.swift */,
+ );
+ path = IQTextView;
+ sourceTree = "";
+ };
+ 2C78DB6F22D1A79B0092DE8C /* IQToolbar */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB7022D1A79B0092DE8C /* IQPreviousNextView.swift */,
+ 2C78DB7122D1A79B0092DE8C /* IQTitleBarButtonItem.swift */,
+ 2C78DB7222D1A79B0092DE8C /* IQBarButtonItem.swift */,
+ 2C78DB7322D1A79B0092DE8C /* IQToolbar.swift */,
+ 2C78DB7422D1A79B0092DE8C /* IQUIView+IQKeyboardToolbar.swift */,
+ 2C78DB7522D1A79B0092DE8C /* IQInvocation.swift */,
+ );
+ path = IQToolbar;
+ sourceTree = "";
+ };
+ 2C78DB7622D1A79B0092DE8C /* Constants */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB7722D1A79B0092DE8C /* IQKeyboardManagerConstantsInternal.swift */,
+ 2C78DB7822D1A79B0092DE8C /* IQKeyboardManagerConstants.swift */,
+ );
+ path = Constants;
+ sourceTree = "";
+ };
+ 2C78DB7922D1A79B0092DE8C /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB7A22D1A79B0092DE8C /* IQKeyboardManager.bundle */,
+ );
+ path = Resources;
+ sourceTree = "";
+ };
+ 2C78DB7E22D1A79B0092DE8C /* Categories */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB7F22D1A79B0092DE8C /* IQUIScrollView+Additions.swift */,
+ 2C78DB8022D1A79B0092DE8C /* IQUIViewController+Additions.swift */,
+ 2C78DB8122D1A79B0092DE8C /* IQUITextFieldView+Additions.swift */,
+ 2C78DB8222D1A79B0092DE8C /* IQUIView+Hierarchy.swift */,
+ 2C78DB8322D1A79B0092DE8C /* IQNSArray+Sort.swift */,
+ );
+ path = Categories;
+ sourceTree = "";
+ };
+ 2C78DB9522D1AD340092DE8C /* Success */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB9722D1AD490092DE8C /* SuccessViewController.swift */,
+ );
+ name = Success;
+ sourceTree = "";
+ };
+ 2CB34CF122CED0DB00D4C74C /* Base */ = {
+ isa = PBXGroup;
+ children = (
+ 2C3BCA5322CECB810064EC0D /* Info.plist */,
+ 2C3BCA4722CECB800064EC0D /* AppDelegate.swift */,
+ 2C3BCA5022CECB810064EC0D /* LaunchScreen.storyboard */,
+ 2C4787CD22D306AC00366259 /* Environment */,
+ );
+ name = Base;
+ sourceTree = "";
+ };
+ 2CD9394622D2B13E0066E958 /* SeparatorTableViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9394722D2B14E0066E958 /* SeparatorTableViewCell.swift */,
+ 2CD9394822D2B14E0066E958 /* SeparatorTableViewCell.xib */,
+ );
+ path = SeparatorTableViewCell;
+ sourceTree = "";
+ };
+ 2CD9395422D2E9420066E958 /* SantanderChallengeTests */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9398322D2F4820066E958 /* Interactors */,
+ 2CD9396722D2EC670066E958 /* Extension */,
+ 2CD9395722D2E9420066E958 /* Info.plist */,
+ 2CD9396222D2EA290066E958 /* EnvironmentManagerTests.swift */,
+ );
+ path = SantanderChallengeTests;
+ sourceTree = "";
+ };
+ 2CD9396722D2EC670066E958 /* Extension */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9396822D2EC740066E958 /* StringTests.swift */,
+ );
+ path = Extension;
+ sourceTree = "";
+ };
+ 2CD9398322D2F4820066E958 /* Interactors */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9398422D2F4910066E958 /* FormInteractorTests.swift */,
+ 2CD9398822D2F7AB0066E958 /* FundInteractorTests.swift */,
+ );
+ path = Interactors;
+ sourceTree = "";
+ };
+ 2CD9398B22D302580066E958 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 2C78DB6C22D1A79B0092DE8C /* IQKeyboardManagerSwift */,
+ 2C78DB6B22D1A3FA0092DE8C /* SwiftMaskText */,
+ );
+ path = Frameworks;
+ sourceTree = "";
+ };
+ 2CD9398D22D3030E0066E958 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9398E22D3030E0066E958 /* MockResources */,
+ 2CD9399122D3030E0066E958 /* Colors.swift */,
+ 2CD9399222D3030E0066E958 /* Fonts */,
+ );
+ path = Resources;
+ sourceTree = "";
+ };
+ 2CD9398E22D3030E0066E958 /* MockResources */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9398F22D3030E0066E958 /* fund.json */,
+ 2CD9399022D3030E0066E958 /* cells.json */,
+ );
+ path = MockResources;
+ sourceTree = "";
+ };
+ 2CD9399222D3030E0066E958 /* Fonts */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9399322D3030E0066E958 /* DIN */,
+ );
+ path = Fonts;
+ sourceTree = "";
+ };
+ 2CD9399322D3030E0066E958 /* DIN */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD9399422D3030E0066E958 /* DINNeuzeitGroteskStd-Light.otf */,
+ 2CD9399522D3030E0066E958 /* DINPro-Medium.otf */,
+ 2CD9399622D3030E0066E958 /* DINPro-Bold.otf */,
+ 2CD9399722D3030E0066E958 /* DINNeuzeitGroteskStd-BdCond.otf */,
+ 2CD9399822D3030E0066E958 /* DINPro-Black.otf */,
+ 2CD9399922D3030E0066E958 /* DINEngschriftStd.otf */,
+ 2CD9399A22D3030E0066E958 /* DINPro-Regular.otf */,
+ 2CD9399B22D3030E0066E958 /* DINPro-Light.otf */,
+ 2CD9399C22D3030E0066E958 /* DINMittelschriftStd.otf */,
+ );
+ path = DIN;
+ sourceTree = "";
+ };
+ 2CD939A922D303DB0066E958 /* Form */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD939AA22D303DB0066E958 /* FormInteractor.swift */,
+ 2CD939AB22D303DB0066E958 /* FormViewController.swift */,
+ 2CD939AC22D303DB0066E958 /* FormPresenter.swift */,
+ );
+ name = Form;
+ path = Scenes/Form;
+ sourceTree = "";
+ };
+ 2CD939B022D303F10066E958 /* Funds */ = {
+ isa = PBXGroup;
+ children = (
+ 2CD939B122D303F10066E958 /* FundsInteractor.swift */,
+ 2CD939B222D303F10066E958 /* FundsViewController.swift */,
+ 2CD939B322D303F10066E958 /* FundsPresenter.swift */,
+ );
+ name = Funds;
+ path = Scenes/Funds;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 2C3BCA4322CECB800064EC0D /* SantanderChallenge */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 2C3BCA5622CECB810064EC0D /* Build configuration list for PBXNativeTarget "SantanderChallenge" */;
+ buildPhases = (
+ 2C3BCA4022CECB800064EC0D /* Sources */,
+ 2C3BCA4122CECB800064EC0D /* Frameworks */,
+ 2C3BCA4222CECB800064EC0D /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SantanderChallenge;
+ productName = SantanderChallenge;
+ productReference = 2C3BCA4422CECB800064EC0D /* SantanderChallenge.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 2CD9395222D2E9410066E958 /* SantanderChallengeTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 2CD9395E22D2E9420066E958 /* Build configuration list for PBXNativeTarget "SantanderChallengeTests" */;
+ buildPhases = (
+ 2CD9394F22D2E9410066E958 /* Sources */,
+ 2CD9395022D2E9410066E958 /* Frameworks */,
+ 2CD9395122D2E9410066E958 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 2CD9395922D2E9420066E958 /* PBXTargetDependency */,
+ );
+ name = SantanderChallengeTests;
+ productName = SantanderChallengeTests;
+ productReference = 2CD9395322D2E9410066E958 /* SantanderChallengeTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 2C3BCA3C22CECB800064EC0D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1020;
+ LastUpgradeCheck = 1020;
+ ORGANIZATIONNAME = LFAP;
+ TargetAttributes = {
+ 2C3BCA4322CECB800064EC0D = {
+ CreatedOnToolsVersion = 10.2.1;
+ };
+ 2CD9395222D2E9410066E958 = {
+ CreatedOnToolsVersion = 10.2.1;
+ TestTargetID = 2C3BCA4322CECB800064EC0D;
+ };
+ };
+ };
+ buildConfigurationList = 2C3BCA3F22CECB800064EC0D /* Build configuration list for PBXProject "SantanderChallenge" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 2C3BCA3B22CECB800064EC0D;
+ productRefGroup = 2C3BCA4522CECB800064EC0D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 2C3BCA4322CECB800064EC0D /* SantanderChallenge */,
+ 2CD9395222D2E9410066E958 /* SantanderChallengeTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 2C3BCA4222CECB800064EC0D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2C78DB8D22D1A79B0092DE8C /* IQKeyboardManager.bundle in Resources */,
+ 2C3BCA5222CECB810064EC0D /* LaunchScreen.storyboard in Resources */,
+ 2CD939A722D3030E0066E958 /* DINPro-Light.otf in Resources */,
+ 2CD939A022D3030E0066E958 /* DINNeuzeitGroteskStd-Light.otf in Resources */,
+ 2CD939A822D3030E0066E958 /* DINMittelschriftStd.otf in Resources */,
+ 2C639C7022D2A68D00986F4C /* InputPhoneTableViewCell.xib in Resources */,
+ 2C62614522D2A9E300AE3303 /* FundInfoTableViewCell.xib in Resources */,
+ 2C639C7322D2A68D00986F4C /* InputEmailTableViewCell.xib in Resources */,
+ 2C639C6B22D2A68D00986F4C /* InputTextTableViewCell.xib in Resources */,
+ 2CD939A222D3030E0066E958 /* DINPro-Bold.otf in Resources */,
+ 2C62614A22D2AAC900AE3303 /* FundDownInfoTableViewCell.xib in Resources */,
+ 2C4787D522D3070C00366259 /* Production.plist in Resources */,
+ 2CD939A122D3030E0066E958 /* DINPro-Medium.otf in Resources */,
+ 2C639C6122D2A68D00986F4C /* FundInfoTitleTableViewCell.xib in Resources */,
+ 2C3BCA4D22CECB800064EC0D /* Main.storyboard in Resources */,
+ 2C4787D422D3070C00366259 /* Development.plist in Resources */,
+ 2CD9399D22D3030E0066E958 /* fund.json in Resources */,
+ 2C639C6922D2A68D00986F4C /* CheckboxTableViewCell.xib in Resources */,
+ 2C639C7522D2A68D00986F4C /* ActionButtonTableViewCell.xib in Resources */,
+ 2C62614022D2A8A500AE3303 /* FundDefinitionTableViewCell.xib in Resources */,
+ 2CD939A522D3030E0066E958 /* DINEngschriftStd.otf in Resources */,
+ 2CD939A322D3030E0066E958 /* DINNeuzeitGroteskStd-BdCond.otf in Resources */,
+ 2C4787D822D3071600366259 /* Development.xcconfig in Resources */,
+ 2CD9399E22D3030E0066E958 /* cells.json in Resources */,
+ 2C639C5E22D2A68D00986F4C /* FundRiskTableViewCell.xib in Resources */,
+ 2C639C6522D2A68D00986F4C /* FundMoreInfoTableViewCell.xib in Resources */,
+ 2C639C6722D2A68D00986F4C /* FundTitleTableViewCell.xib in Resources */,
+ 2CD939A422D3030E0066E958 /* DINPro-Black.otf in Resources */,
+ 2C639C6E22D2A68D00986F4C /* TitleTableViewCell.xib in Resources */,
+ 2C4787D922D3071600366259 /* Production.xcconfig in Resources */,
+ 2C639C6422D2A68D00986F4C /* FundNameTableViewCell.xib in Resources */,
+ 2CD9394A22D2B14E0066E958 /* SeparatorTableViewCell.xib in Resources */,
+ 2CD939A622D3030E0066E958 /* DINPro-Regular.otf in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2CD9395122D2E9410066E958 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 2C3BCA4022CECB800064EC0D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2C78DB6A22D1A3EC0092DE8C /* SwiftMaskField.swift in Sources */,
+ 2C639C7222D2A68D00986F4C /* InputEmailTableViewCell.swift in Sources */,
+ 2C78DB1622D0E08E0092DE8C /* FormCell.swift in Sources */,
+ 2C3BCA4822CECB800064EC0D /* AppDelegate.swift in Sources */,
+ 2C114F0822D03A35009D49E9 /* DevelopmentNetworkProvider.swift in Sources */,
+ 2C6E8BEF22D28591002EDEA9 /* FundsResponse.swift in Sources */,
+ 2CD939B522D303F10066E958 /* FundsViewController.swift in Sources */,
+ 2CD939B622D303F10066E958 /* FundsPresenter.swift in Sources */,
+ 2C78DB9122D1A79B0092DE8C /* IQUIViewController+Additions.swift in Sources */,
+ 2C639C6322D2A68D00986F4C /* FundNameTableViewCell.swift in Sources */,
+ 2CD939AE22D303DB0066E958 /* FormViewController.swift in Sources */,
+ 2C78DB1822D0E1130092DE8C /* CellType.swift in Sources */,
+ 2C6E8BE722D28108002EDEA9 /* FundMoreInfo.swift in Sources */,
+ 2C114F0B22D03AE1009D49E9 /* Bundle+Extension.swift in Sources */,
+ 2C78DB8A22D1A79B0092DE8C /* IQInvocation.swift in Sources */,
+ 2C78DB6022D19A960092DE8C /* String+Extension.swift in Sources */,
+ 2CD9394922D2B14E0066E958 /* SeparatorTableViewCell.swift in Sources */,
+ 2C6E8BE922D28247002EDEA9 /* FundPercentages.swift in Sources */,
+ 2C639C6F22D2A68D00986F4C /* TitleTableViewCell.swift in Sources */,
+ 2CD9394E22D2DF780066E958 /* ContactData.swift in Sources */,
+ 2C639C6622D2A68D00986F4C /* FundMoreInfoTableViewCell.swift in Sources */,
+ 2CD9394C22D2DB220066E958 /* BaseViewController.swift in Sources */,
+ 2C62614922D2AAC900AE3303 /* FundDownInfoTableViewCell.swift in Sources */,
+ 2C62614422D2A9E300AE3303 /* FundInfoTableViewCell.swift in Sources */,
+ 2C78DB8C22D1A79B0092DE8C /* IQKeyboardManagerConstants.swift in Sources */,
+ 2C6E8BED22D28304002EDEA9 /* DownInfo.swift in Sources */,
+ 2C78DB8B22D1A79B0092DE8C /* IQKeyboardManagerConstantsInternal.swift in Sources */,
+ 2C639C7422D2A68D00986F4C /* ActionButtonTableViewCell.swift in Sources */,
+ 2C6E8BD622D2432F002EDEA9 /* ContainerViewController.swift in Sources */,
+ 2C78DB9022D1A79B0092DE8C /* IQUIScrollView+Additions.swift in Sources */,
+ 2C78DB8722D1A79B0092DE8C /* IQBarButtonItem.swift in Sources */,
+ 2C639C7122D2A68D00986F4C /* InputPhoneTableViewCell.swift in Sources */,
+ 2C78DB9422D1A79B0092DE8C /* IQNSArray+Sort.swift in Sources */,
+ 2C114EF522D018A3009D49E9 /* ProductionNetworkProvider.swift in Sources */,
+ 2C114F0022D02CE9009D49E9 /* NetworkManager.swift in Sources */,
+ 2C78DB8E22D1A79B0092DE8C /* IQKeyboardReturnKeyHandler.swift in Sources */,
+ 2C639C6C22D2A68D00986F4C /* InputTextTableViewCell.swift in Sources */,
+ 2C114EFC22D01E4A009D49E9 /* NetworkError.swift in Sources */,
+ 2CD9399F22D3030E0066E958 /* Colors.swift in Sources */,
+ 2C78DB8822D1A79B0092DE8C /* IQToolbar.swift in Sources */,
+ 2C639C5D22D2A68D00986F4C /* FundRiskTableViewCell.swift in Sources */,
+ 2C4787D122D306BD00366259 /* EnvironmentManger.swift in Sources */,
+ 2C26FF6922D294FC0001E250 /* UIView+Extension.swift in Sources */,
+ 2CD939AD22D303DB0066E958 /* FormInteractor.swift in Sources */,
+ 2CD939AF22D303DB0066E958 /* FormPresenter.swift in Sources */,
+ 2C114EF722D018C1009D49E9 /* NetworkProviderProtocol.swift in Sources */,
+ 2C639C6A22D2A68D00986F4C /* CheckboxTableViewCell.swift in Sources */,
+ 2C639C6D22D2A68D00986F4C /* InputTextFieldTableViewCell.swift in Sources */,
+ 2C78DB9222D1A79B0092DE8C /* IQUITextFieldView+Additions.swift in Sources */,
+ 2C6E8BE522D27CB3002EDEA9 /* Funds.swift in Sources */,
+ 2C639C6222D2A68D00986F4C /* FundInfoTitleTableViewCell.swift in Sources */,
+ 2C78DB9822D1AD490092DE8C /* SuccessViewController.swift in Sources */,
+ 2C78DB8622D1A79B0092DE8C /* IQTitleBarButtonItem.swift in Sources */,
+ 2C78DB5522D1006A0092DE8C /* UITableView+Extension.swift in Sources */,
+ 2C78DB5322D100070092DE8C /* UITableViewCell+Extension.swift in Sources */,
+ 2CD939B422D303F10066E958 /* FundsInteractor.swift in Sources */,
+ 2C62613F22D2A8A500AE3303 /* FundDefinitionTableViewCell.swift in Sources */,
+ 2C6E8BE322D2722E002EDEA9 /* UIViewController+Extension.swift in Sources */,
+ 2C639C6822D2A68D00986F4C /* FundTitleTableViewCell.swift in Sources */,
+ 2C78DB6622D1A3A90092DE8C /* StringExtensions.swift in Sources */,
+ 2C78DB8F22D1A79B0092DE8C /* IQKeyboardManager.swift in Sources */,
+ 2C78DB8922D1A79B0092DE8C /* IQUIView+IQKeyboardToolbar.swift in Sources */,
+ 2C78DB8422D1A79B0092DE8C /* IQTextView.swift in Sources */,
+ 2C78DB8522D1A79B0092DE8C /* IQPreviousNextView.swift in Sources */,
+ 2C78DB1C22D0E6640092DE8C /* FormCellsResponse.swift in Sources */,
+ 2C78DB1A22D0E2020092DE8C /* FieldType.swift in Sources */,
+ 2C78DB9322D1A79B0092DE8C /* IQUIView+Hierarchy.swift in Sources */,
+ 2C6E8BEB22D282C4002EDEA9 /* FundInfo.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2CD9394F22D2E9410066E958 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2CD9396A22D2ECD50066E958 /* String+Extension.swift in Sources */,
+ 2CD9396322D2EA290066E958 /* EnvironmentManagerTests.swift in Sources */,
+ 2CD9396922D2EC740066E958 /* StringTests.swift in Sources */,
+ 2CD9397122D2EE680066E958 /* NetworkProviderProtocol.swift in Sources */,
+ 2CD9396F22D2EE510066E958 /* ProductionNetworkProvider.swift in Sources */,
+ 2CD9398922D2F7AB0066E958 /* FundInteractorTests.swift in Sources */,
+ 2CD9398522D2F4910066E958 /* FormInteractorTests.swift in Sources */,
+ 2CD9397222D2EEAF0066E958 /* NetworkError.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 2CD9395922D2E9420066E958 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 2C3BCA4322CECB800064EC0D /* SantanderChallenge */;
+ targetProxy = 2CD9395822D2E9420066E958 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 2C3BCA4B22CECB800064EC0D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 2C3BCA4C22CECB800064EC0D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 2C3BCA5022CECB810064EC0D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 2C3BCA5122CECB810064EC0D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 2C3BCA5422CECB810064EC0D /* Debug (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = "Debug (Development)";
+ };
+ 2C3BCA5522CECB810064EC0D /* Release (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = "Release (Development)";
+ };
+ 2C3BCA5722CECB810064EC0D /* Debug (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = SantanderChallenge/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallenge;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = "Debug (Development)";
+ };
+ 2C3BCA5822CECB810064EC0D /* Release (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = SantanderChallenge/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallenge;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = "Release (Development)";
+ };
+ 2CB34CEC22CECFF000D4C74C /* Debug (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = "Debug (Production)";
+ };
+ 2CB34CED22CECFF000D4C74C /* Debug (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = SantanderChallenge/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallenge;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = "Debug (Production)";
+ };
+ 2CB34CEE22CECFF700D4C74C /* Release (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = "Release (Production)";
+ };
+ 2CB34CEF22CECFF700D4C74C /* Release (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = SantanderChallenge/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallenge;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = "Release (Production)";
+ };
+ 2CD9395A22D2E9420066E958 /* Debug (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = CZ23F6F8X3;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = SantanderChallengeTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallengeTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SantanderChallenge.app/SantanderChallenge";
+ };
+ name = "Debug (Development)";
+ };
+ 2CD9395B22D2E9420066E958 /* Debug (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = CZ23F6F8X3;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = SantanderChallengeTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallengeTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SantanderChallenge.app/SantanderChallenge";
+ };
+ name = "Debug (Production)";
+ };
+ 2CD9395C22D2E9420066E958 /* Release (Development) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D622D3071600366259 /* Development.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = CZ23F6F8X3;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = SantanderChallengeTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallengeTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SantanderChallenge.app/SantanderChallenge";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = "Release (Development)";
+ };
+ 2CD9395D22D2E9420066E958 /* Release (Production) */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2C4787D722D3071600366259 /* Production.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = CZ23F6F8X3;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INFOPLIST_FILE = SantanderChallengeTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.lfap.SantanderChallengeTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SantanderChallenge.app/SantanderChallenge";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = "Release (Production)";
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 2C3BCA3F22CECB800064EC0D /* Build configuration list for PBXProject "SantanderChallenge" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2C3BCA5422CECB810064EC0D /* Debug (Development) */,
+ 2CB34CEC22CECFF000D4C74C /* Debug (Production) */,
+ 2C3BCA5522CECB810064EC0D /* Release (Development) */,
+ 2CB34CEE22CECFF700D4C74C /* Release (Production) */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = "Release (Development)";
+ };
+ 2C3BCA5622CECB810064EC0D /* Build configuration list for PBXNativeTarget "SantanderChallenge" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2C3BCA5722CECB810064EC0D /* Debug (Development) */,
+ 2CB34CED22CECFF000D4C74C /* Debug (Production) */,
+ 2C3BCA5822CECB810064EC0D /* Release (Development) */,
+ 2CB34CEF22CECFF700D4C74C /* Release (Production) */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = "Release (Development)";
+ };
+ 2CD9395E22D2E9420066E958 /* Build configuration list for PBXNativeTarget "SantanderChallengeTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2CD9395A22D2E9420066E958 /* Debug (Development) */,
+ 2CD9395B22D2E9420066E958 /* Debug (Production) */,
+ 2CD9395C22D2E9420066E958 /* Release (Development) */,
+ 2CD9395D22D2E9420066E958 /* Release (Production) */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = "Release (Development)";
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 2C3BCA3C22CECB800064EC0D /* Project object */;
+}
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..eeae5e28
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..0c67376e
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Development.xcscheme b/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Development.xcscheme
new file mode 100644
index 00000000..5be1cdca
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Development.xcscheme
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Production.xcscheme b/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Production.xcscheme
new file mode 100644
index 00000000..ad6c3ee6
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge.xcodeproj/xcshareddata/xcschemes/Production.xcscheme
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Base.lproj/LaunchScreen.storyboard b/SantanderChallenge/SantanderChallenge/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..bfa36129
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Base.lproj/Main.storyboard b/SantanderChallenge/SantanderChallenge/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..053dcab6
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base.lproj/Main.storyboard
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Base/AppDelegate.swift b/SantanderChallenge/SantanderChallenge/Base/AppDelegate.swift
new file mode 100644
index 00000000..347960ce
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/AppDelegate.swift
@@ -0,0 +1,45 @@
+//
+// AppDelegate.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 04/07/19.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ IQKeyboardManager.shared.enable = true
+ return true
+ }
+
+ func applicationWillResignActive(_ application: UIApplication) {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+ }
+
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+ }
+
+ func applicationWillEnterForeground(_ application: UIApplication) {
+ // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
+ }
+
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+ }
+
+
+}
+
diff --git a/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Development.xcconfig b/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Development.xcconfig
new file mode 100644
index 00000000..b9f5b213
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Development.xcconfig
@@ -0,0 +1,11 @@
+//
+// Development.xcconfig
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 04/07/19.
+//
+
+// Configuration settings file format documentation can be found at:
+// https://help.apple.com/xcode/#/dev745c5c974
+
+ENVIRONMENT_NAME = Development
diff --git a/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Production.xcconfig b/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Production.xcconfig
new file mode 100644
index 00000000..370924fd
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/Environment/ConfigFiles/Production.xcconfig
@@ -0,0 +1,11 @@
+//
+// Production.xcconfig
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 04/07/19.
+//
+
+// Configuration settings file format documentation can be found at:
+// https://help.apple.com/xcode/#/dev745c5c974
+
+ENVIRONMENT_NAME = Production
diff --git a/SantanderChallenge/SantanderChallenge/Base/Environment/EnvironmentManger.swift b/SantanderChallenge/SantanderChallenge/Base/Environment/EnvironmentManger.swift
new file mode 100644
index 00000000..020e7f96
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/Environment/EnvironmentManger.swift
@@ -0,0 +1,58 @@
+//
+// EnvironmentManger.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 04/07/19.
+//
+
+import Foundation
+
+enum EnvironmentIdentifier: String {
+ case production = "Production"
+ case development = "Development"
+}
+
+enum EnvironmentKey: String {
+ case environmentName = "ENVIRONMENT_NAME"
+ case test = "test_key"
+}
+
+class EnvironmentManager {
+
+ static let shared: EnvironmentManager = EnvironmentManager()
+
+ private init() {}
+
+ lazy var environmentId: EnvironmentIdentifier? = {
+ guard let name = environmentName else { return nil }
+ return EnvironmentIdentifier(rawValue: name)
+ }()
+
+ fileprivate var infoDict: [String: Any] {
+ if let dict = Bundle.main.infoDictionary {
+ return dict
+ } else {
+ fatalError("Info.plist file not found")
+ }
+ }
+
+ private lazy var environmentName: String? = {
+ guard let name = infoDict[EnvironmentKey.environmentName.rawValue] as? String else {
+ fatalError("No environment defined at Info.plist file")
+ }
+ return name
+ }()
+
+ private var environmentDict: [String: Any] {
+ if let url = Bundle.main.url(forResource: environmentName, withExtension: "plist"),
+ let dict = NSDictionary(contentsOf: url) as? [String: Any] {
+ return dict
+ } else {
+ fatalError("Environment plist file not found")
+ }
+ }
+
+ func value(forKey key: EnvironmentKey) -> T? {
+ return environmentDict[key.rawValue] as? T
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Development.plist b/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Development.plist
new file mode 100644
index 00000000..22b4122e
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Development.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ test_key
+ test_value
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Production.plist b/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Production.plist
new file mode 100644
index 00000000..80f51845
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Base/Environment/Plist/Production.plist
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.swift
new file mode 100644
index 00000000..779bff7c
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.swift
@@ -0,0 +1,42 @@
+//
+// ActionButtonTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+protocol ActionButtonTableViewCellDelegate: AnyObject {
+ func didTouchActionButton(atCell cell: ActionButtonTableViewCell)
+}
+
+class ActionButtonTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var actionButton: UIButton!
+
+ weak var delegate: ActionButtonTableViewCellDelegate?
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ setupButtonLayout()
+ }
+
+ override func setSelected(_ selected: Bool, animated: Bool) {
+ super.setSelected(selected, animated: animated)
+
+ // Configure the view for the selected state
+ }
+ @IBAction func didTouchAt(_ button: UIButton) {
+ delegate?.didTouchActionButton(atCell: self)
+ }
+
+ private func setupButtonLayout() {
+ actionButton.layer.cornerRadius = actionButton.frame.height / 2.0
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.xib
new file mode 100644
index 00000000..bc783ba2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/ActionButtonTableViewCell/ActionButtonTableViewCell.xib
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.swift
new file mode 100644
index 00000000..2159f383
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.swift
@@ -0,0 +1,89 @@
+//
+// CheckboxTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+protocol CheckBoxTableViewDelegate: AnyObject {
+ func checkboxUpdated(status: CheckBoxSelectionStatus, atCell cell: CheckboxTableViewCell)
+}
+
+enum CheckBoxSelectionStatus {
+ case selected
+ case unselected
+}
+
+class CheckboxTableViewCell: UITableViewCell {
+ @IBOutlet weak var checkBoxContainerView: UIView!
+ @IBOutlet weak var checkBoxIndicatorView: UIView!
+ @IBOutlet weak var checkBoxTextLabel: UILabel!
+
+ weak var delegate: CheckBoxTableViewDelegate?
+
+ var cellData: FormCell? {
+ didSet {
+ checkBoxTextLabel.text = cellData?.message
+ }
+ }
+
+ var checkBoxStatus: CheckBoxSelectionStatus = .unselected{
+ didSet {
+ setCheckBoxUI(status: self.checkBoxStatus)
+ }
+ }
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ setupLayout()
+ addGestures()
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ checkBoxStatus = .unselected
+ }
+
+ private func setupLayout() {
+ checkBoxContainerView.layer.cornerRadius = 3.0
+ checkBoxContainerView.layer.borderWidth = 1.0
+ checkBoxContainerView.layer.borderColor = UIColor(red: 92/255, green: 92/255, blue: 92/255, alpha: 1.0).cgColor
+
+ checkBoxIndicatorView.layer.cornerRadius = 3.0
+ }
+
+ func addGestures() {
+ checkBoxTextLabel.isUserInteractionEnabled = true
+
+ addGestureToToggleCheckBox(inView: checkBoxContainerView)
+ addGestureToToggleCheckBox(inView: checkBoxIndicatorView)
+ addGestureToToggleCheckBox(inView: checkBoxTextLabel)
+ }
+
+ private func addGestureToToggleCheckBox(inView view: UIView) {
+ let tap = UITapGestureRecognizer(target: self, action: #selector(toggleCheckbox))
+ view.addGestureRecognizer(tap)
+ }
+
+ @objc private func toggleCheckbox() {
+ switch checkBoxStatus {
+ case .selected:
+ checkBoxStatus = .unselected
+ case .unselected:
+ checkBoxStatus = .selected
+ }
+
+ delegate?.checkboxUpdated(status: checkBoxStatus, atCell: self)
+ }
+
+ private func setCheckBoxUI(status: CheckBoxSelectionStatus) {
+ switch status {
+ case .selected:
+ checkBoxIndicatorView.isHidden = false
+ case .unselected:
+ checkBoxIndicatorView.isHidden = true
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.xib
new file mode 100644
index 00000000..74227670
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/CheckboxTableViewCell/CheckboxTableViewCell.xib
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.swift
new file mode 100644
index 00000000..a11c0355
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.swift
@@ -0,0 +1,26 @@
+//
+// FundDefinitionTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundDefinitionTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var topLabel: UILabel!
+ @IBOutlet weak var descriptionLabel: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+
+ override func setSelected(_ selected: Bool, animated: Bool) {
+ super.setSelected(selected, animated: animated)
+
+ // Configure the view for the selected state
+ }
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.xib
new file mode 100644
index 00000000..05792f84
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDefinitionTableViewCell/FundDefinitionTableViewCell.xib
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.swift
new file mode 100644
index 00000000..41a1095d
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.swift
@@ -0,0 +1,40 @@
+//
+// FundDownInfoTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+protocol FundDownInfoTableViewCellDelegate: AnyObject {
+ func didTouchDownload(at cell: FundDownInfoTableViewCell)
+}
+
+class FundDownInfoTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var leftLabel: UILabel!
+ @IBOutlet weak var downloadLabel: UILabel!
+ @IBOutlet weak var downloadImageView: UIImageView!
+
+ weak var delegate: FundDownInfoTableViewCellDelegate?
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ downloadLabel.isUserInteractionEnabled = true
+ downloadImageView.isUserInteractionEnabled = true
+
+ addTapGestureTo(downloadLabel)
+ addTapGestureTo(downloadImageView)
+ }
+
+ private func addTapGestureTo(_ view: UIView) {
+ let tap = UITapGestureRecognizer(target: self, action: #selector(didTouchAtDownload))
+ view.addGestureRecognizer(tap)
+ }
+
+ @objc private func didTouchAtDownload() {
+ delegate?.didTouchDownload(at: self)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.xib
new file mode 100644
index 00000000..98ff4ec1
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundDownInfoTableViewCell/FundDownInfoTableViewCell.xib
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.swift
new file mode 100644
index 00000000..c0c3c63a
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.swift
@@ -0,0 +1,26 @@
+//
+// FundInfoTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundInfoTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var leftLabel: UILabel!
+ @IBOutlet weak var rightLabel: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+
+ override func setSelected(_ selected: Bool, animated: Bool) {
+ super.setSelected(selected, animated: animated)
+
+ // Configure the view for the selected state
+ }
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.xib
new file mode 100644
index 00000000..19343b54
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTableViewCell/FundInfoTableViewCell.xib
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.swift
new file mode 100644
index 00000000..fa477b68
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.swift
@@ -0,0 +1,12 @@
+//
+// FundInfoTitleTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundInfoTitleTableViewCell: UITableViewCell {
+ @IBOutlet weak var infoTitleLabel: UILabel!
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.xib
new file mode 100644
index 00000000..a1f4a744
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundInfoTitleTableViewCell/FundInfoTitleTableViewCell.xib
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.swift
new file mode 100644
index 00000000..0b552baf
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.swift
@@ -0,0 +1,37 @@
+//
+// FundMoreInfoTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundMoreInfoTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var monthFundLabel: UILabel!
+ @IBOutlet weak var monthCDILabel: UILabel!
+
+ @IBOutlet weak var yearFundLabel: UILabel!
+ @IBOutlet weak var yearCDILabel: UILabel!
+
+ @IBOutlet weak var twelveMonthsFundLabel: UILabel!
+ @IBOutlet weak var twelveMonthCDILabel: UILabel!
+
+ var dataSource: FundMoreInfo? {
+ didSet {
+ setLabels()
+ }
+ }
+
+ private func setLabels() {
+ monthFundLabel.text = dataSource?.month.fundPresentable
+ monthCDILabel.text = dataSource?.month.cdiPresentable
+
+ yearFundLabel.text = dataSource?.year.fundPresentable
+ yearCDILabel.text = dataSource?.year.cdiPresentable
+
+ twelveMonthsFundLabel.text = dataSource?.twelveMonths.fundPresentable
+ twelveMonthCDILabel.text = dataSource?.twelveMonths.cdiPresentable
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.xib
new file mode 100644
index 00000000..e114cb68
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundMoreInfoTableViewCell/FundMoreInfoTableViewCell.xib
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.swift
new file mode 100644
index 00000000..1ec8aff2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.swift
@@ -0,0 +1,12 @@
+//
+// FundNameTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundNameTableViewCell: UITableViewCell {
+ @IBOutlet weak var nameLabel: UILabel!
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.xib
new file mode 100644
index 00000000..cc1d892d
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundNameTableViewCell/FundNameTableViewCell.xib
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.swift
new file mode 100644
index 00000000..7d7af63a
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.swift
@@ -0,0 +1,43 @@
+//
+// FundRiskTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundRiskTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var indicatorConstraint: NSLayoutConstraint!
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet var levelViews: [UIView]!
+ @IBOutlet var levelsViewHeightConstraints: [NSLayoutConstraint]!
+ @IBOutlet weak var container: UIStackView!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ }
+
+ func set(level: Int) {
+
+ let index = level - 1
+
+ let view = levelViews[index]
+ let constraint = levelsViewHeightConstraints[index]
+ constraint.constant = 10
+
+ UIView.animate(withDuration: 0.3) {
+ self.layoutIfNeeded()
+ }
+
+ let gaps = CGFloat(index)
+
+ let originX = container.frame.origin.x
+ let jumps = gaps * view.frame.width
+ let midView = view.frame.width / 2.0
+ let total = originX + jumps + midView - gaps
+
+ indicatorConstraint.constant = total
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.xib
new file mode 100644
index 00000000..232b9d49
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundRiskTableViewCell/FundRiskTableViewCell.xib
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.swift
new file mode 100644
index 00000000..f7ec633a
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.swift
@@ -0,0 +1,12 @@
+//
+// FundTitleTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class FundTitleTableViewCell: UITableViewCell {
+ @IBOutlet weak var titleLabel: UILabel!
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.xib
new file mode 100644
index 00000000..9b476450
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/FundsCells/FundTitleTableViewCell/FundTitleTableViewCell.xib
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Medium
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.swift
new file mode 100644
index 00000000..97dfab22
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.swift
@@ -0,0 +1,16 @@
+//
+// InputEmailTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+class InputEmailTableViewCell: InputTextFieldTableViewCell {
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.xib
new file mode 100644
index 00000000..de46d66b
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputEmailTableViewCell/InputEmailTableViewCell.xib
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.swift
new file mode 100644
index 00000000..485a2228
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.swift
@@ -0,0 +1,35 @@
+//
+// InputPhoneTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+class InputPhoneTableViewCell: InputTextFieldTableViewCell {
+
+ private let olderMask: String = "(NN) NNNN-NNNN"
+ private let newerMask: String = "(NN) NNNNN-NNNN"
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+
+ override func editingChanged() {
+ let text = textField.text ?? ""
+ let raw = text.removingNonDigitCharacters
+ let maskTextField = textField as? SwiftMaskField
+
+ if raw.count < 11 {
+ maskTextField?.maskString = olderMask
+ } else {
+ maskTextField?.maskString = newerMask
+ }
+
+ if raw.count <= 11 {
+ delegate?.editingChanged(text: text, atCell: self)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.xib
new file mode 100644
index 00000000..d8f16dea
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputPhoneTableViewCell/InputPhoneTableViewCell.xib
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.swift
new file mode 100644
index 00000000..93a85af4
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.swift
@@ -0,0 +1,15 @@
+//
+// InputTextTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+class InputTextTableViewCell: InputTextFieldTableViewCell {
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.xib
new file mode 100644
index 00000000..11c09e69
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/InputTextTableViewCell/InputTextTableViewCell.xib
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.swift
new file mode 100644
index 00000000..c3c8cf28
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.swift
@@ -0,0 +1,23 @@
+//
+// SeparatorTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class SeparatorTableViewCell: UITableViewCell {
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+
+ override func setSelected(_ selected: Bool, animated: Bool) {
+ super.setSelected(selected, animated: animated)
+
+ // Configure the view for the selected state
+ }
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.xib
new file mode 100644
index 00000000..e58190a4
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/SeparatorTableViewCell/SeparatorTableViewCell.xib
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.swift
new file mode 100644
index 00000000..e7347412
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.swift
@@ -0,0 +1,18 @@
+//
+// TitleTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+class TitleTableViewCell: UITableViewCell {
+
+ @IBOutlet weak var titleLabel: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.xib b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.xib
new file mode 100644
index 00000000..042fcf5c
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/Cells/TitleTableViewCell/TitleTableViewCell.xib
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DINPro-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/CustomViews/InputTextFieldTableViewCell.swift b/SantanderChallenge/SantanderChallenge/CustomViews/InputTextFieldTableViewCell.swift
new file mode 100644
index 00000000..1504a78e
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/CustomViews/InputTextFieldTableViewCell.swift
@@ -0,0 +1,51 @@
+//
+// InputTextFieldTableViewCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+protocol InputTextFieldTableViewCellDelegate: AnyObject {
+ func editingChanged(text: String, atCell cell: InputTextFieldTableViewCell)
+}
+
+enum ValidIndicator {
+ case valid
+ case invalid
+ case notChecked
+}
+
+class InputTextFieldTableViewCell: UITableViewCell {
+ @IBOutlet weak var textField: UITextField!
+ @IBOutlet weak var separatorView: UIView!
+
+ weak var delegate: InputTextFieldTableViewCellDelegate?
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ textField.addTarget(self, action: #selector(editingChanged), for: UIControl.Event.editingChanged)
+ }
+
+ @objc func editingChanged() {
+ let text = textField.text ?? ""
+ delegate?.editingChanged(text: text, atCell: self)
+ }
+
+ func set(placeholder: String? = "") {
+ self.textField.placeholder = placeholder
+ }
+
+ func setValidIndicatorColor(status: ValidIndicator) {
+ switch status {
+ case .valid:
+ separatorView.backgroundColor = Colors.goodGreen
+ case .invalid:
+ separatorView.backgroundColor = Colors.badRed
+ case .notChecked:
+ separatorView.backgroundColor = Colors.shallowGray
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/Bundle+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/Bundle+Extension.swift
new file mode 100644
index 00000000..4fa54dd5
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/Bundle+Extension.swift
@@ -0,0 +1,21 @@
+//
+// Bundle+Extension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+extension Bundle {
+ static func loadJSONFromBundle(resourceName: String) -> Data? {
+ guard let url = Bundle.main.url(forResource: resourceName, withExtension: "json") else {
+ fatalError("⚠️ Invalid URL")
+ }
+ do {
+ return try Data(contentsOf: url)
+ } catch {
+ fatalError(error.localizedDescription)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/String+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/String+Extension.swift
new file mode 100644
index 00000000..f0202e1c
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/String+Extension.swift
@@ -0,0 +1,22 @@
+//
+// String+Extension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+extension String {
+ var removingNonDigitCharacters: String {
+ return replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
+ }
+
+ var validEmail: Bool {
+ return self.range(
+ of: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}",
+ options: String.CompareOptions.regularExpression,
+ range: nil,
+ locale: nil) != nil
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/UITableView+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/UITableView+Extension.swift
new file mode 100644
index 00000000..542a7e43
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/UITableView+Extension.swift
@@ -0,0 +1,30 @@
+//
+// UITableView+Extension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+extension UITableView {
+ func registerCellNib(cellClass: UITableViewCell.Type) {
+ let name = cellClass.reusableIdentifier
+ let nib = UINib(nibName: name, bundle: nil)
+ self.register(nib, forCellReuseIdentifier: name)
+ }
+
+ func registerCellsNib(cellsClass: [UITableViewCell.Type]) {
+ cellsClass.forEach {
+ registerCellNib(cellClass: $0)
+ }
+ }
+
+ func dequeueReusableCell(cellType: UITableViewCell.Type) -> CellType {
+ guard let cell = dequeueReusableCell(withIdentifier: cellType.reusableIdentifier) as? CellType else {
+ fatalError("⚠️ Verify the Type of the cell you are trying to cast")
+ }
+ return cell
+ }
+}
+
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/UITableViewCell+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/UITableViewCell+Extension.swift
new file mode 100644
index 00000000..1c926f71
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/UITableViewCell+Extension.swift
@@ -0,0 +1,14 @@
+//
+// UITableViewCell+Extension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+extension UITableViewCell {
+ static var reusableIdentifier: String {
+ return String(describing: self.classForCoder())
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/UIView+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/UIView+Extension.swift
new file mode 100644
index 00000000..5ff1ec08
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/UIView+Extension.swift
@@ -0,0 +1,17 @@
+//
+// UIViewExtension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+extension UIView {
+ func roundCorners(corners: UIRectCorner, radius: CGFloat) {
+ let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+ let mask = CAShapeLayer()
+ mask.path = path.cgPath
+ layer.mask = mask
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Extensions/UIViewController+Extension.swift b/SantanderChallenge/SantanderChallenge/Extensions/UIViewController+Extension.swift
new file mode 100644
index 00000000..2c29b275
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Extensions/UIViewController+Extension.swift
@@ -0,0 +1,12 @@
+//
+// UIViewController+Extension.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+extension UIViewController {
+ static let blankView = UIView()
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift
new file mode 100755
index 00000000..442ef43f
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift
@@ -0,0 +1,60 @@
+//
+// IQNSArray+Sort.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+/**
+UIView.subviews sorting category.
+*/
+internal extension Array where Element: UIView {
+
+ ///--------------
+ /// MARK: Sorting
+ ///--------------
+
+ /**
+ Returns the array by sorting the UIView's by their tag property.
+ */
+ func sortedArrayByTag() -> [Element] {
+
+ return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
+
+ return (obj1.tag < obj2.tag)
+ })
+ }
+
+ /**
+ Returns the array by sorting the UIView's by their tag property.
+ */
+ func sortedArrayByPosition() -> [Element] {
+
+ return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
+ if obj1.frame.minY != obj2.frame.minY {
+ return obj1.frame.minY < obj2.frame.minY
+ } else {
+ return obj1.frame.minX < obj2.frame.minX
+ }
+ })
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift
new file mode 100755
index 00000000..3884d508
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift
@@ -0,0 +1,111 @@
+//
+// IQUIScrollView+Additions.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+private var kIQShouldIgnoreScrollingAdjustment = "kIQShouldIgnoreScrollingAdjustment"
+private var kIQShouldRestoreScrollViewContentOffset = "kIQShouldRestoreScrollViewContentOffset"
+
+@objc public extension UIScrollView {
+
+ /**
+ If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position. Default is NO.
+ */
+ @objc var shouldIgnoreScrollingAdjustment: Bool {
+ get {
+
+ if let aValue = objc_getAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment) as? Bool {
+ return aValue
+ } else {
+ return false
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+
+ /**
+ To set customized distance from keyboard for textField/textView. Can't be less than zero
+ */
+ @objc var shouldRestoreScrollViewContentOffset: Bool {
+ get {
+
+ if let aValue = objc_getAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset) as? Bool {
+ return aValue
+ } else {
+ return false
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+}
+
+internal extension UITableView {
+
+ func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
+ var previousRow = indexPath.row - 1
+ var previousSection = indexPath.section
+
+ //Fixing indexPath
+ if previousRow < 0 {
+ previousSection -= 1
+
+ if previousSection >= 0 {
+ previousRow = self.numberOfRows(inSection: previousSection) - 1
+ }
+ }
+
+ if previousRow >= 0 && previousSection >= 0 {
+ return IndexPath(row: previousRow, section: previousSection)
+ } else {
+ return nil
+ }
+ }
+}
+
+internal extension UICollectionView {
+
+ func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
+ var previousRow = indexPath.row - 1
+ var previousSection = indexPath.section
+
+ //Fixing indexPath
+ if previousRow < 0 {
+ previousSection -= 1
+
+ if previousSection >= 0 {
+ previousRow = self.numberOfItems(inSection: previousSection) - 1
+ }
+ }
+
+ if previousRow >= 0 && previousSection >= 0 {
+ return IndexPath(item: previousRow, section: previousSection)
+ } else {
+ return nil
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift
new file mode 100755
index 00000000..31c17812
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift
@@ -0,0 +1,109 @@
+//
+// IQUITextFieldView+Additions.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+/**
+Uses default keyboard distance for textField.
+*/
+public let kIQUseDefaultKeyboardDistance = CGFloat.greatestFiniteMagnitude
+
+private var kIQKeyboardDistanceFromTextField = "kIQKeyboardDistanceFromTextField"
+//private var kIQKeyboardEnableMode = "kIQKeyboardEnableMode"
+private var kIQShouldResignOnTouchOutsideMode = "kIQShouldResignOnTouchOutsideMode"
+private var kIQIgnoreSwitchingByNextPrevious = "kIQIgnoreSwitchingByNextPrevious"
+
+/**
+UIView category for managing UITextField/UITextView
+*/
+@objc public extension UIView {
+
+ /**
+ To set customized distance from keyboard for textField/textView. Can't be less than zero
+ */
+ @objc var keyboardDistanceFromTextField: CGFloat {
+ get {
+
+ if let aValue = objc_getAssociatedObject(self, &kIQKeyboardDistanceFromTextField) as? CGFloat {
+ return aValue
+ } else {
+ return kIQUseDefaultKeyboardDistance
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQKeyboardDistanceFromTextField, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+
+ /**
+ If shouldIgnoreSwitchingByNextPrevious is true then library will ignore this textField/textView while moving to other textField/textView using keyboard toolbar next previous buttons. Default is false
+ */
+ @objc var ignoreSwitchingByNextPrevious: Bool {
+ get {
+
+ if let aValue = objc_getAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious) as? Bool {
+ return aValue
+ } else {
+ return false
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+
+// /**
+// Override Enable/disable managing distance between keyboard and textField behaviour for this particular textField.
+// */
+// @objc public var enableMode: IQEnableMode {
+// get {
+//
+// if let savedMode = objc_getAssociatedObject(self, &kIQKeyboardEnableMode) as? IQEnableMode {
+// return savedMode
+// } else {
+// return .default
+// }
+// }
+// set(newValue) {
+// objc_setAssociatedObject(self, &kIQKeyboardEnableMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+// }
+// }
+
+ /**
+ Override resigns Keyboard on touching outside of UITextField/View behaviour for this particular textField.
+ */
+ @objc var shouldResignOnTouchOutsideMode: IQEnableMode {
+ get {
+
+ if let savedMode = objc_getAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode) as? IQEnableMode {
+ return savedMode
+ } else {
+ return .default
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift
new file mode 100755
index 00000000..33172b26
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift
@@ -0,0 +1,336 @@
+//
+// IQUIView+Hierarchy.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+/**
+UIView hierarchy category.
+*/
+@objc public extension UIView {
+
+ ///----------------------
+ /// MARK: viewControllers
+ ///----------------------
+
+ /**
+ Returns the UIViewController object that manages the receiver.
+ */
+ @objc func viewContainingController() -> UIViewController? {
+
+ var nextResponder: UIResponder? = self
+
+ repeat {
+ nextResponder = nextResponder?.next
+
+ if let viewController = nextResponder as? UIViewController {
+ return viewController
+ }
+
+ } while nextResponder != nil
+
+ return nil
+ }
+
+ /**
+ Returns the topMost UIViewController object in hierarchy.
+ */
+ @objc func topMostController() -> UIViewController? {
+
+ var controllersHierarchy = [UIViewController]()
+
+ if var topController = window?.rootViewController {
+ controllersHierarchy.append(topController)
+
+ while let presented = topController.presentedViewController {
+
+ topController = presented
+
+ controllersHierarchy.append(presented)
+ }
+
+ var matchController: UIResponder? = viewContainingController()
+
+ while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false {
+
+ repeat {
+ matchController = matchController?.next
+
+ } while matchController != nil && matchController is UIViewController == false
+ }
+
+ return matchController as? UIViewController
+
+ } else {
+ return viewContainingController()
+ }
+ }
+
+ /**
+ Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController.
+ */
+ @objc func parentContainerViewController() -> UIViewController? {
+
+ var matchController = viewContainingController()
+ var parentContainerViewController: UIViewController?
+
+ if var navController = matchController?.navigationController {
+
+ while let parentNav = navController.navigationController {
+ navController = parentNav
+ }
+
+ var parentController: UIViewController = navController
+
+ while let parent = parentController.parent,
+ (parent.isKind(of: UINavigationController.self) == false &&
+ parent.isKind(of: UITabBarController.self) == false &&
+ parent.isKind(of: UISplitViewController.self) == false) {
+
+ parentController = parent
+ }
+
+ if navController == parentController {
+ parentContainerViewController = navController.topViewController
+ } else {
+ parentContainerViewController = parentController
+ }
+ } else if let tabController = matchController?.tabBarController {
+
+ if let navController = tabController.selectedViewController as? UINavigationController {
+ parentContainerViewController = navController.topViewController
+ } else {
+ parentContainerViewController = tabController.selectedViewController
+ }
+ } else {
+ while let parentController = matchController?.parent,
+ (parentController.isKind(of: UINavigationController.self) == false &&
+ parentController.isKind(of: UITabBarController.self) == false &&
+ parentController.isKind(of: UISplitViewController.self) == false) {
+
+ matchController = parentController
+ }
+
+ parentContainerViewController = matchController
+ }
+
+ let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController
+
+ return finalController
+
+ }
+
+ ///-----------------------------------
+ /// MARK: Superviews/Subviews/Siglings
+ ///-----------------------------------
+
+ /**
+ Returns the superView of provided class type.
+
+
+ @param classType class type of the object which is to be search in above hierarchy and return
+
+ @param belowView view object in upper hierarchy where method should stop searching and return nil
+*/
+ @objc func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? {
+
+ var superView = superview
+
+ while let unwrappedSuperView = superView {
+
+ if unwrappedSuperView.isKind(of: classType) {
+
+ //If it's UIScrollView, then validating for special cases
+ if unwrappedSuperView.isKind(of: UIScrollView.self) {
+
+ let classNameString = NSStringFromClass(type(of: unwrappedSuperView.self))
+
+ // If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView.
+ // If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell.
+ //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes
+ if unwrappedSuperView.superview?.isKind(of: UITableView.self) == false &&
+ unwrappedSuperView.superview?.isKind(of: UITableViewCell.self) == false &&
+ classNameString.hasPrefix("_") == false {
+ return superView
+ }
+ } else {
+ return superView
+ }
+ } else if unwrappedSuperView == belowView {
+ return nil
+ }
+
+ superView = unwrappedSuperView.superview
+ }
+
+ return nil
+ }
+
+ /**
+ Returns all siblings of the receiver which canBecomeFirstResponder.
+ */
+ internal func responderSiblings() -> [UIView] {
+
+ //Array of (UITextField/UITextView's).
+ var tempTextFields = [UIView]()
+
+ // Getting all siblings
+ if let siblings = superview?.subviews {
+
+ for textField in siblings {
+
+ if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true {
+ tempTextFields.append(textField)
+ }
+ }
+ }
+
+ return tempTextFields
+ }
+
+ /**
+ Returns all deep subViews of the receiver which canBecomeFirstResponder.
+ */
+ internal func deepResponderViews() -> [UIView] {
+
+ //Array of (UITextField/UITextView's).
+ var textfields = [UIView]()
+
+ for textField in subviews {
+
+ if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true {
+ textfields.append(textField)
+ }
+
+ //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458)
+ //Uncommented else (Bug ID: #625)
+ if textField.subviews.count != 0 && isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 {
+ for deepView in textField.deepResponderViews() {
+ textfields.append(deepView)
+ }
+ }
+ }
+
+ //subviews are returning in opposite order. Sorting according the frames 'y'.
+ return textfields.sorted(by: { (view1: UIView, view2: UIView) -> Bool in
+
+ let frame1 = view1.convert(view1.bounds, to: self)
+ let frame2 = view2.convert(view2.bounds, to: self)
+
+ if frame1.minY != frame2.minY {
+ return frame1.minY < frame2.minY
+ } else {
+ return frame1.minX < frame2.minX
+ }
+ })
+ }
+
+ private func IQcanBecomeFirstResponder() -> Bool {
+
+ var IQcanBecomeFirstResponder = false
+
+ // Setting toolbar to keyboard.
+ if let textField = self as? UITextField {
+ IQcanBecomeFirstResponder = textField.isEnabled
+ } else if let textView = self as? UITextView {
+ IQcanBecomeFirstResponder = textView.isEditable
+ }
+
+ if IQcanBecomeFirstResponder == true {
+ IQcanBecomeFirstResponder = isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 && isAlertViewTextField() == false && textFieldSearchBar() == nil
+ }
+
+ return IQcanBecomeFirstResponder
+ }
+
+ ///-------------------------
+ /// MARK: Special TextFields
+ ///-------------------------
+
+ /**
+ Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil.
+ */
+ internal func textFieldSearchBar() -> UISearchBar? {
+
+ var responder: UIResponder? = self.next
+
+ while let bar = responder {
+
+ if let searchBar = bar as? UISearchBar {
+ return searchBar
+ } else if bar is UIViewController {
+ break
+ }
+
+ responder = bar.next
+ }
+
+ return nil
+ }
+
+ /**
+ Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO.
+ */
+ internal func isAlertViewTextField() -> Bool {
+
+ var alertViewController: UIResponder? = viewContainingController()
+
+ var isAlertViewTextField = false
+
+ while let controller = alertViewController, isAlertViewTextField == false {
+
+ if controller.isKind(of: UIAlertController.self) {
+ isAlertViewTextField = true
+ break
+ }
+
+ alertViewController = controller.next
+ }
+
+ return isAlertViewTextField
+ }
+
+ private func depth() -> Int {
+ var depth: Int = 0
+
+ if let superView = superview {
+ depth = superView.depth()+1
+ }
+
+ return depth
+ }
+
+}
+
+@objc public extension UIViewController {
+
+ func parentIQContainerViewController() -> UIViewController? {
+ return self
+ }
+}
+
+extension NSObject {
+
+ internal func _IQDescription() -> String {
+ return "<\(self) \(Unmanaged.passUnretained(self).toOpaque())>"
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift
new file mode 100755
index 00000000..6f845f7b
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift
@@ -0,0 +1,46 @@
+//
+// IQUIViewController+Additions.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+private var kIQLayoutGuideConstraint = "kIQLayoutGuideConstraint"
+
+@objc public extension UIViewController {
+
+ /**
+ To set customized distance from keyboard for textField/textView. Can't be less than zero
+
+ @deprecated Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview
+ */
+ @available(*, deprecated, message: "Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.")
+ @IBOutlet @objc var IQLayoutGuideConstraint: NSLayoutConstraint? {
+ get {
+
+ return objc_getAssociatedObject(self, &kIQLayoutGuideConstraint) as? NSLayoutConstraint
+ }
+
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQLayoutGuideConstraint, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift
new file mode 100755
index 00000000..60b910dd
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift
@@ -0,0 +1,149 @@
+//
+// IQKeyboardManagerConstants.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+///-----------------------------------
+/// MARK: IQAutoToolbarManageBehaviour
+///-----------------------------------
+
+/**
+`IQAutoToolbarBySubviews`
+Creates Toolbar according to subview's hirarchy of Textfield's in view.
+
+`IQAutoToolbarByTag`
+Creates Toolbar according to tag property of TextField's.
+
+`IQAutoToolbarByPosition`
+Creates Toolbar according to the y,x position of textField in it's superview coordinate.
+*/
+@objc public enum IQAutoToolbarManageBehaviour: Int {
+ case bySubviews
+ case byTag
+ case byPosition
+}
+
+/**
+ `IQPreviousNextDisplayModeDefault`
+ Show NextPrevious when there are more than 1 textField otherwise hide.
+
+ `IQPreviousNextDisplayModeAlwaysHide`
+ Do not show NextPrevious buttons in any case.
+
+ `IQPreviousNextDisplayModeAlwaysShow`
+ Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
+ */
+@objc public enum IQPreviousNextDisplayMode: Int {
+ case `default`
+ case alwaysHide
+ case alwaysShow
+}
+
+/**
+ `IQEnableModeDefault`
+ Pick default settings.
+
+ `IQEnableModeEnabled`
+ setting is enabled.
+
+ `IQEnableModeDisabled`
+ setting is disabled.
+ */
+@objc public enum IQEnableMode: Int {
+ case `default`
+ case enabled
+ case disabled
+}
+
+/*
+ /---------------------------------------------------------------------------------------------------\
+ \---------------------------------------------------------------------------------------------------/
+ | iOS Notification Mechanism |
+ /---------------------------------------------------------------------------------------------------\
+ \---------------------------------------------------------------------------------------------------/
+
+ ------------------------------------------------------------
+ When UITextField become first responder
+ ------------------------------------------------------------
+ - UITextFieldTextDidBeginEditingNotification (UITextField)
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When UITextView become first responder
+ ------------------------------------------------------------
+ - UIKeyboardWillShowNotification
+ - UITextViewTextDidBeginEditingNotification (UITextView)
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When switching focus from UITextField to another UITextField
+ ------------------------------------------------------------
+ - UITextFieldTextDidEndEditingNotification (UITextField1)
+ - UITextFieldTextDidBeginEditingNotification (UITextField2)
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When switching focus from UITextView to another UITextView
+ ------------------------------------------------------------
+ - UITextViewTextDidEndEditingNotification: (UITextView1)
+ - UIKeyboardWillShowNotification
+ - UITextViewTextDidBeginEditingNotification: (UITextView2)
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When switching focus from UITextField to UITextView
+ ------------------------------------------------------------
+ - UITextFieldTextDidEndEditingNotification (UITextField)
+ - UIKeyboardWillShowNotification
+ - UITextViewTextDidBeginEditingNotification (UITextView)
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When switching focus from UITextView to UITextField
+ ------------------------------------------------------------
+ - UITextViewTextDidEndEditingNotification (UITextView)
+ - UITextFieldTextDidBeginEditingNotification (UITextField)
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ When opening/closing UIKeyboard Predictive bar
+ ------------------------------------------------------------
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+
+ ------------------------------------------------------------
+ On orientation change
+ ------------------------------------------------------------
+ - UIApplicationWillChangeStatusBarOrientationNotification
+ - UIKeyboardWillHideNotification
+ - UIKeyboardDidHideNotification
+ - UIApplicationDidChangeStatusBarOrientationNotification
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+ - UIKeyboardWillShowNotification
+ - UIKeyboardDidShowNotification
+
+ */
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift
new file mode 100755
index 00000000..1e65ac11
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift
@@ -0,0 +1,24 @@
+//
+// IQKeyboardManagerConstantsInternal.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManager.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManager.swift
new file mode 100755
index 00000000..fa64ced6
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManager.swift
@@ -0,0 +1,2277 @@
+//
+// IQKeyboardManager.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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 CoreGraphics
+import UIKit
+import QuartzCore
+
+///---------------------
+/// MARK: IQToolbar tags
+///---------------------
+
+/**
+Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more. A generic version of KeyboardManagement. https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
+*/
+
+@objc public class IQKeyboardManager: NSObject, UIGestureRecognizerDelegate {
+
+ /**
+ Default tag for toolbar with Done button -1002.
+ */
+ private static let kIQDoneButtonToolbarTag = -1002
+
+ /**
+ Default tag for toolbar with Previous/Next buttons -1005.
+ */
+ private static let kIQPreviousNextButtonToolbarTag = -1005
+
+ /**
+ Invalid point value.
+ */
+ private static let kIQCGPointInvalid = CGPoint.init(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude)
+
+ ///---------------------------
+ /// MARK: UIKeyboard handling
+ ///---------------------------
+
+ /**
+ Registered classes list with library.
+ */
+ private var registeredClasses = [UIView.Type]()
+
+ /**
+ Enable/disable managing distance between keyboard and textField. Default is YES(Enabled when class loads in `+(void)load` method).
+ */
+ @objc public var enable = false {
+
+ didSet {
+ //If not enable, enable it.
+ if enable == true &&
+ oldValue == false {
+ //If keyboard is currently showing. Sending a fake notification for keyboardWillHide to retain view's original position.
+ if let notification = _kbShowNotification {
+ keyboardWillShow(notification)
+ }
+ showLog("Enabled")
+ } else if enable == false &&
+ oldValue == true { //If not disable, desable it.
+ keyboardWillHide(nil)
+ showLog("Disabled")
+ }
+ }
+ }
+
+ private func privateIsEnabled() -> Bool {
+
+ var isEnabled = enable
+
+// let enableMode = _textFieldView?.enableMode
+//
+// if enableMode == .enabled {
+// isEnabled = true
+// } else if enableMode == .disabled {
+// isEnabled = false
+// } else {
+
+ if let textFieldViewController = _textFieldView?.viewContainingController() {
+
+ if isEnabled == false {
+
+ //If viewController is kind of enable viewController class, then assuming it's enabled.
+ for enabledClass in enabledDistanceHandlingClasses {
+
+ if textFieldViewController.isKind(of: enabledClass) {
+ isEnabled = true
+ break
+ }
+ }
+ }
+
+ if isEnabled == true {
+
+ //If viewController is kind of disabled viewController class, then assuming it's disabled.
+ for disabledClass in disabledDistanceHandlingClasses {
+
+ if textFieldViewController.isKind(of: disabledClass) {
+ isEnabled = false
+ break
+ }
+ }
+
+ //Special Controllers
+ if isEnabled == true {
+
+ let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
+
+ //_UIAlertControllerTextFieldViewController
+ if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
+ isEnabled = false
+ }
+ }
+ }
+ }
+// }
+
+ return isEnabled
+ }
+
+ /**
+ To set keyboard distance from textField. can't be less than zero. Default is 10.0.
+ */
+ @objc public var keyboardDistanceFromTextField: CGFloat {
+
+ set {
+ _privateKeyboardDistanceFromTextField = max(0, newValue)
+ showLog("keyboardDistanceFromTextField: \(_privateKeyboardDistanceFromTextField)")
+ }
+ get {
+ return _privateKeyboardDistanceFromTextField
+ }
+ }
+
+ /**
+ Boolean to know if keyboard is showing.
+ */
+ @objc public var keyboardShowing: Bool {
+
+ return _privateIsKeyboardShowing
+ }
+
+ /**
+ moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
+ */
+ @objc public var movedDistance: CGFloat {
+
+ return _privateMovedDistance
+ }
+
+ /**
+ Returns the default singleton instance.
+ */
+ @objc public class var shared: IQKeyboardManager {
+ struct Static {
+ //Singleton instance. Initializing keyboard manger.
+ static let kbManager = IQKeyboardManager()
+ }
+
+ /** @return Returns the default singleton instance. */
+ return Static.kbManager
+ }
+
+ ///-------------------------
+ /// MARK: IQToolbar handling
+ ///-------------------------
+
+ /**
+ Automatic add the IQToolbar functionality. Default is YES.
+ */
+ @objc public var enableAutoToolbar = true {
+
+ didSet {
+
+ privateIsEnableAutoToolbar() ? addToolbarIfRequired() : removeToolbarIfRequired()
+
+ let enableToolbar = enableAutoToolbar ? "Yes" : "NO"
+
+ showLog("enableAutoToolbar: \(enableToolbar)")
+ }
+ }
+
+ private func privateIsEnableAutoToolbar() -> Bool {
+
+ var enableToolbar = enableAutoToolbar
+
+ if let textFieldViewController = _textFieldView?.viewContainingController() {
+
+ if enableToolbar == false {
+
+ //If found any toolbar enabled classes then return.
+ for enabledClass in enabledToolbarClasses {
+
+ if textFieldViewController.isKind(of: enabledClass) {
+ enableToolbar = true
+ break
+ }
+ }
+ }
+
+ if enableToolbar == true {
+
+ //If found any toolbar disabled classes then return.
+ for disabledClass in disabledToolbarClasses {
+
+ if textFieldViewController.isKind(of: disabledClass) {
+ enableToolbar = false
+ break
+ }
+ }
+
+ //Special Controllers
+ if enableToolbar == true {
+
+ let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
+
+ //_UIAlertControllerTextFieldViewController
+ if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
+ enableToolbar = false
+ }
+ }
+ }
+ }
+
+ return enableToolbar
+ }
+
+ /**
+ /**
+ IQAutoToolbarBySubviews: Creates Toolbar according to subview's hirarchy of Textfield's in view.
+ IQAutoToolbarByTag: Creates Toolbar according to tag property of TextField's.
+ IQAutoToolbarByPosition: Creates Toolbar according to the y,x position of textField in it's superview coordinate.
+
+ Default is IQAutoToolbarBySubviews.
+ */
+ AutoToolbar managing behaviour. Default is IQAutoToolbarBySubviews.
+ */
+ @objc public var toolbarManageBehaviour = IQAutoToolbarManageBehaviour.bySubviews
+
+ /**
+ If YES, then uses textField's tintColor property for IQToolbar, otherwise tint color is black. Default is NO.
+ */
+ @objc public var shouldToolbarUsesTextFieldTintColor = false
+
+ /**
+ This is used for toolbar.tintColor when textfield.keyboardAppearance is UIKeyboardAppearanceDefault. If shouldToolbarUsesTextFieldTintColor is YES then this property is ignored. Default is nil and uses black color.
+ */
+ @objc public var toolbarTintColor: UIColor?
+
+ /**
+ This is used for toolbar.barTintColor. Default is nil and uses white color.
+ */
+ @objc public var toolbarBarTintColor: UIColor?
+
+ /**
+ IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textField otherwise hide.
+ IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
+ IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
+ */
+ @objc public var previousNextDisplayMode = IQPreviousNextDisplayMode.default
+
+ /**
+ Toolbar previous/next/done button icon, If nothing is provided then check toolbarDoneBarButtonItemText to draw done button.
+ */
+ @objc public var toolbarPreviousBarButtonItemImage: UIImage?
+ @objc public var toolbarNextBarButtonItemImage: UIImage?
+ @objc public var toolbarDoneBarButtonItemImage: UIImage?
+
+ /**
+ Toolbar previous/next/done button text, If nothing is provided then system default 'UIBarButtonSystemItemDone' will be used.
+ */
+ @objc public var toolbarPreviousBarButtonItemText: String?
+ @objc public var toolbarNextBarButtonItemText: String?
+ @objc public var toolbarDoneBarButtonItemText: String?
+
+ /**
+ If YES, then it add the textField's placeholder text on IQToolbar. Default is YES.
+ */
+ @objc public var shouldShowToolbarPlaceholder = true
+
+ /**
+ Placeholder Font. Default is nil.
+ */
+ @objc public var placeholderFont: UIFont?
+
+ /**
+ Placeholder Color. Default is nil. Which means lightGray
+ */
+ @objc public var placeholderColor: UIColor?
+
+ /**
+ Placeholder Button Color when it's treated as button. Default is nil. Which means iOS Blue for light toolbar and Yellow for dark toolbar
+ */
+ @objc public var placeholderButtonColor: UIColor?
+
+ ///--------------------------
+ /// MARK: UITextView handling
+ ///--------------------------
+
+ /** used to adjust contentInset of UITextView. */
+ private var startingTextViewContentInsets = UIEdgeInsets()
+
+ /** used to adjust scrollIndicatorInsets of UITextView. */
+ private var startingTextViewScrollIndicatorInsets = UIEdgeInsets()
+
+ /** used with textView to detect a textFieldView contentInset is changed or not. (Bug ID: #92)*/
+ private var isTextViewContentInsetChanged = false
+
+ ///---------------------------------------
+ /// MARK: UIKeyboard appearance overriding
+ ///---------------------------------------
+
+ /**
+ Override the keyboardAppearance for all textField/textView. Default is NO.
+ */
+ @objc public var overrideKeyboardAppearance = false
+
+ /**
+ If overrideKeyboardAppearance is YES, then all the textField keyboardAppearance is set using this property.
+ */
+ @objc public var keyboardAppearance = UIKeyboardAppearance.default
+
+ ///-----------------------------------------------------------
+ /// MARK: UITextField/UITextView Next/Previous/Resign handling
+ ///-----------------------------------------------------------
+
+ /**
+ Resigns Keyboard on touching outside of UITextField/View. Default is NO.
+ */
+ @objc public var shouldResignOnTouchOutside = false {
+
+ didSet {
+ resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside()
+
+ let shouldResign = shouldResignOnTouchOutside ? "Yes" : "NO"
+
+ showLog("shouldResignOnTouchOutside: \(shouldResign)")
+ }
+ }
+
+ /** TapGesture to resign keyboard on view's touch. It's a readonly property and exposed only for adding/removing dependencies if your added gesture does have collision with this one */
+ @objc lazy public var resignFirstResponderGesture: UITapGestureRecognizer = {
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized(_:)))
+ tapGesture.cancelsTouchesInView = false
+ tapGesture.delegate = self
+
+ return tapGesture
+ }()
+
+ /*******************************************/
+
+ private func privateShouldResignOnTouchOutside() -> Bool {
+
+ var shouldResign = shouldResignOnTouchOutside
+
+ let enableMode = _textFieldView?.shouldResignOnTouchOutsideMode
+
+ if enableMode == .enabled {
+ shouldResign = true
+ } else if enableMode == .disabled {
+ shouldResign = false
+ } else {
+ if let textFieldViewController = _textFieldView?.viewContainingController() {
+
+ if shouldResign == false {
+
+ //If viewController is kind of enable viewController class, then assuming shouldResignOnTouchOutside is enabled.
+ for enabledClass in enabledTouchResignedClasses {
+
+ if textFieldViewController.isKind(of: enabledClass) {
+ shouldResign = true
+ break
+ }
+ }
+ }
+
+ if shouldResign == true {
+
+ //If viewController is kind of disable viewController class, then assuming shouldResignOnTouchOutside is disable.
+ for disabledClass in disabledTouchResignedClasses {
+
+ if textFieldViewController.isKind(of: disabledClass) {
+ shouldResign = false
+ break
+ }
+ }
+
+ //Special Controllers
+ if shouldResign == true {
+
+ let classNameString = NSStringFromClass(type(of: textFieldViewController.self))
+
+ //_UIAlertControllerTextFieldViewController
+ if classNameString.contains("UIAlertController") && classNameString.hasSuffix("TextFieldViewController") {
+ shouldResign = false
+ }
+ }
+ }
+ }
+ }
+
+ return shouldResign
+ }
+
+ /**
+ Resigns currently first responder field.
+ */
+ @objc @discardableResult public func resignFirstResponder() -> Bool {
+
+ if let textFieldRetain = _textFieldView {
+
+ //Resigning first responder
+ let isResignFirstResponder = textFieldRetain.resignFirstResponder()
+
+ // If it refuses then becoming it as first responder again. (Bug ID: #96)
+ if isResignFirstResponder == false {
+ //If it refuses to resign then becoming it first responder again for getting notifications callback.
+ textFieldRetain.becomeFirstResponder()
+
+ showLog("Refuses to resign first responder: \(textFieldRetain)")
+ }
+
+ return isResignFirstResponder
+ }
+
+ return false
+ }
+
+ /**
+ Returns YES if can navigate to previous responder textField/textView, otherwise NO.
+ */
+ @objc public var canGoPrevious: Bool {
+ //Getting all responder view's.
+ if let textFields = responderViews() {
+ if let textFieldRetain = _textFieldView {
+
+ //Getting index of current textField.
+ if let index = textFields.firstIndex(of: textFieldRetain) {
+
+ //If it is not first textField. then it's previous object canBecomeFirstResponder.
+ if index > 0 {
+ return true
+ }
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ Returns YES if can navigate to next responder textField/textView, otherwise NO.
+ */
+ @objc public var canGoNext: Bool {
+ //Getting all responder view's.
+ if let textFields = responderViews() {
+ if let textFieldRetain = _textFieldView {
+ //Getting index of current textField.
+ if let index = textFields.firstIndex(of: textFieldRetain) {
+
+ //If it is not first textField. then it's previous object canBecomeFirstResponder.
+ if index < textFields.count-1 {
+ return true
+ }
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ Navigate to previous responder textField/textView.
+ */
+ @objc @discardableResult public func goPrevious() -> Bool {
+
+ //Getting all responder view's.
+ if let textFieldRetain = _textFieldView {
+ if let textFields = responderViews() {
+ //Getting index of current textField.
+ if let index = textFields.firstIndex(of: textFieldRetain) {
+
+ //If it is not first textField. then it's previous object becomeFirstResponder.
+ if index > 0 {
+
+ let nextTextField = textFields[index-1]
+
+ let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder()
+
+ // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
+ if isAcceptAsFirstResponder == false {
+ //If next field refuses to become first responder then restoring old textField as first responder.
+ textFieldRetain.becomeFirstResponder()
+
+ showLog("Refuses to become first responder: \(nextTextField)")
+ }
+
+ return isAcceptAsFirstResponder
+ }
+ }
+ }
+ }
+
+ return false
+ }
+
+ /**
+ Navigate to next responder textField/textView.
+ */
+ @objc @discardableResult public func goNext() -> Bool {
+
+ //Getting all responder view's.
+ if let textFieldRetain = _textFieldView {
+ if let textFields = responderViews() {
+ //Getting index of current textField.
+ if let index = textFields.firstIndex(of: textFieldRetain) {
+ //If it is not last textField. then it's next object becomeFirstResponder.
+ if index < textFields.count-1 {
+
+ let nextTextField = textFields[index+1]
+
+ let isAcceptAsFirstResponder = nextTextField.becomeFirstResponder()
+
+ // If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
+ if isAcceptAsFirstResponder == false {
+ //If next field refuses to become first responder then restoring old textField as first responder.
+ textFieldRetain.becomeFirstResponder()
+
+ showLog("Refuses to become first responder: \(nextTextField)")
+ }
+
+ return isAcceptAsFirstResponder
+ }
+ }
+ }
+ }
+
+ return false
+ }
+
+ /** previousAction. */
+ @objc internal func previousAction (_ barButton: IQBarButtonItem) {
+
+ //If user wants to play input Click sound.
+ if shouldPlayInputClicks == true {
+ //Play Input Click Sound.
+ UIDevice.current.playInputClick()
+ }
+
+ if canGoPrevious == true {
+
+ if let textFieldRetain = _textFieldView {
+ let isAcceptAsFirstResponder = goPrevious()
+
+ var invocation = barButton.invocation
+ var sender = textFieldRetain
+
+ //Handling search bar special case
+ do {
+ if let searchBar = textFieldRetain.textFieldSearchBar() {
+ invocation = searchBar.keyboardToolbar.previousBarButton.invocation
+ sender = searchBar
+ }
+ }
+
+ if isAcceptAsFirstResponder {
+ invocation?.invoke(from: sender)
+ }
+ }
+ }
+ }
+
+ /** nextAction. */
+ @objc internal func nextAction (_ barButton: IQBarButtonItem) {
+
+ //If user wants to play input Click sound.
+ if shouldPlayInputClicks == true {
+ //Play Input Click Sound.
+ UIDevice.current.playInputClick()
+ }
+
+ if canGoNext == true {
+
+ if let textFieldRetain = _textFieldView {
+ let isAcceptAsFirstResponder = goNext()
+
+ var invocation = barButton.invocation
+ var sender = textFieldRetain
+
+ //Handling search bar special case
+ do {
+ if let searchBar = textFieldRetain.textFieldSearchBar() {
+ invocation = searchBar.keyboardToolbar.nextBarButton.invocation
+ sender = searchBar
+ }
+ }
+
+ if isAcceptAsFirstResponder {
+ invocation?.invoke(from: sender)
+ }
+ }
+ }
+ }
+
+ /** doneAction. Resigning current textField. */
+ @objc internal func doneAction (_ barButton: IQBarButtonItem) {
+
+ //If user wants to play input Click sound.
+ if shouldPlayInputClicks == true {
+ //Play Input Click Sound.
+ UIDevice.current.playInputClick()
+ }
+
+ if let textFieldRetain = _textFieldView {
+ //Resign textFieldView.
+ let isResignedFirstResponder = resignFirstResponder()
+
+ var invocation = barButton.invocation
+ var sender = textFieldRetain
+
+ //Handling search bar special case
+ do {
+ if let searchBar = textFieldRetain.textFieldSearchBar() {
+ invocation = searchBar.keyboardToolbar.doneBarButton.invocation
+ sender = searchBar
+ }
+ }
+
+ if isResignedFirstResponder {
+ invocation?.invoke(from: sender)
+ }
+ }
+ }
+
+ /** Resigning on tap gesture. (Enhancement ID: #14)*/
+ @objc internal func tapRecognized(_ gesture: UITapGestureRecognizer) {
+
+ if gesture.state == .ended {
+
+ //Resigning currently responder textField.
+ resignFirstResponder()
+ }
+ }
+
+ /** Note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES. */
+ @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ return false
+ }
+
+ /** To not detect touch events in a subclass of UIControl, these may have added their own selector for specific work */
+ @objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
+ // Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(=4.2)
+ private var _animationCurve: UIView.AnimationOptions = .curveEaseOut
+ #else
+ private var _animationCurve: UIViewAnimationOptions = .curveEaseOut
+ #endif
+
+ /*******************************************/
+
+ /** Boolean to maintain keyboard is showing or it is hide. To solve rootViewController.view.frame calculations. */
+ private var _privateIsKeyboardShowing = false
+
+ private var _privateMovedDistance: CGFloat = 0.0
+
+ /** To use with keyboardDistanceFromTextField. */
+ private var _privateKeyboardDistanceFromTextField: CGFloat = 10.0
+
+ /** To know if we have any pending request to adjust view position. */
+ private var _privateHasPendingAdjustRequest = false
+
+ /**************************************************************************************/
+
+ ///--------------------------------------
+ /// MARK: Initialization/Deinitialization
+ ///--------------------------------------
+
+ /* Singleton Object Initialization. */
+ override init() {
+
+ super.init()
+
+ self.registerAllNotifications()
+
+ //Creating gesture for @shouldResignOnTouchOutside. (Enhancement ID: #14)
+ resignFirstResponderGesture.isEnabled = shouldResignOnTouchOutside
+
+ //Loading IQToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay (Bug ID: #550)
+ //If you experience exception breakpoint issue at below line then try these solutions https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator
+ let textField = UITextField()
+ textField.addDoneOnKeyboardWithTarget(nil, action: #selector(self.doneAction(_:)))
+ textField.addPreviousNextDoneOnKeyboardWithTarget(nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), doneAction: #selector(self.doneAction(_:)))
+
+ disabledDistanceHandlingClasses.append(UITableViewController.self)
+ disabledDistanceHandlingClasses.append(UIAlertController.self)
+ disabledToolbarClasses.append(UIAlertController.self)
+ disabledTouchResignedClasses.append(UIAlertController.self)
+ toolbarPreviousNextAllowedClasses.append(UITableView.self)
+ toolbarPreviousNextAllowedClasses.append(UICollectionView.self)
+ toolbarPreviousNextAllowedClasses.append(IQPreviousNextView.self)
+ touchResignedGestureIgnoreClasses.append(UIControl.self)
+ touchResignedGestureIgnoreClasses.append(UINavigationBar.self)
+ }
+
+ /** Override +load method to enable KeyboardManager when class loader load IQKeyboardManager. Enabling when app starts (No need to write any code) */
+ /** It doesn't work from Swift 1.2 */
+// override public class func load() {
+// super.load()
+//
+// //Enabling IQKeyboardManager.
+// IQKeyboardManager.shared.enable = true
+// }
+
+ deinit {
+ // Disable the keyboard manager.
+ enable = false
+
+ //Removing notification observers on dealloc.
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ /** Getting keyWindow. */
+ private func keyWindow() -> UIWindow? {
+
+ if let keyWindow = _textFieldView?.window {
+ return keyWindow
+ } else {
+
+ struct Static {
+ /** @abstract Save keyWindow object for reuse.
+ @discussion Sometimes [[UIApplication sharedApplication] keyWindow] is returning nil between the app. */
+ static weak var keyWindow: UIWindow?
+ }
+
+ //If original key window is not nil and the cached keywindow is also not original keywindow then changing keywindow.
+ if let originalKeyWindow = UIApplication.shared.keyWindow,
+ (Static.keyWindow == nil || Static.keyWindow != originalKeyWindow) {
+ Static.keyWindow = originalKeyWindow
+ }
+
+ //Return KeyWindow
+ return Static.keyWindow
+ }
+ }
+
+ ///-----------------------
+ /// MARK: Helper Functions
+ ///-----------------------
+
+ private func optimizedAdjustPosition() {
+ if _privateHasPendingAdjustRequest == false {
+ _privateHasPendingAdjustRequest = true
+ OperationQueue.main.addOperation {
+ self.adjustPosition()
+ self._privateHasPendingAdjustRequest = false
+ }
+ }
+ }
+
+ /* Adjusting RootViewController's frame according to interface orientation. */
+ private func adjustPosition() {
+
+ // We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
+ if _privateHasPendingAdjustRequest == true,
+ let textFieldView = _textFieldView,
+ let rootController = textFieldView.parentContainerViewController(),
+ let window = keyWindow(),
+ let textFieldViewRectInWindow = textFieldView.superview?.convert(textFieldView.frame, to: window),
+ let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) {
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ // Getting RootViewOrigin.
+ var rootViewOrigin = rootController.view.frame.origin
+
+ //Maintain keyboardDistanceFromTextField
+ var specialKeyboardDistanceFromTextField = textFieldView.keyboardDistanceFromTextField
+
+ if let searchBar = textFieldView.textFieldSearchBar() {
+
+ specialKeyboardDistanceFromTextField = searchBar.keyboardDistanceFromTextField
+ }
+
+ let newKeyboardDistanceFromTextField = (specialKeyboardDistanceFromTextField == kIQUseDefaultKeyboardDistance) ? keyboardDistanceFromTextField : specialKeyboardDistanceFromTextField
+
+ var kbSize = _kbFrame.size
+
+ do {
+ var kbFrame = _kbFrame
+
+ kbFrame.origin.y -= newKeyboardDistanceFromTextField
+ kbFrame.size.height += newKeyboardDistanceFromTextField
+
+ //Calculating actual keyboard covered size respect to window, keyboard frame may be different when hardware keyboard is attached (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
+ let intersectRect = kbFrame.intersection(window.frame)
+
+ if intersectRect.isNull {
+ kbSize = CGSize(width: kbFrame.size.width, height: 0)
+ } else {
+ kbSize = intersectRect.size
+ }
+ }
+
+ let statusBarHeight : CGFloat
+
+ #if swift(>=5.1)
+ if #available(iOS 13, *) {
+ statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
+ } else {
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
+ }
+ #else
+ statusBarHeight = UIApplication.shared.statusBarFrame.height
+ #endif
+
+ let navigationBarAreaHeight: CGFloat = statusBarHeight + ( rootController.navigationController?.navigationBar.frame.height ?? 0)
+ let layoutAreaHeight: CGFloat = rootController.view.layoutMargins.bottom
+
+ let topLayoutGuide: CGFloat = max(navigationBarAreaHeight, layoutAreaHeight) + 5
+ let bottomLayoutGuide: CGFloat = (textFieldView is UITextView) ? 0 : rootController.view.layoutMargins.bottom //Validation of textView for case where there is a tab bar at the bottom or running on iPhone X and textView is at the bottom.
+
+ // Move positive = textField is hidden.
+ // Move negative = textField is showing.
+ // Calculating move position.
+ var move: CGFloat = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY-(window.frame.height-kbSize.height)+bottomLayoutGuide)
+
+ showLog("Need to move: \(move)")
+
+ var superScrollView: UIScrollView?
+ var superView = textFieldView.superviewOfClassType(UIScrollView.self) as? UIScrollView
+
+ //Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
+ while let view = superView {
+
+ if view.isScrollEnabled && view.shouldIgnoreScrollingAdjustment == false {
+ superScrollView = view
+ break
+ } else {
+ // Getting it's superScrollView. // (Enhancement ID: #21, #24)
+ superView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
+ }
+ }
+
+ //If there was a lastScrollView. // (Bug ID: #34)
+ if let lastScrollView = _lastScrollView {
+ //If we can't find current superScrollView, then setting lastScrollView to it's original form.
+ if superScrollView == nil {
+
+ if lastScrollView.contentInset != self._startingContentInsets {
+ showLog("Restoring contentInset to: \(_startingContentInsets)")
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ lastScrollView.contentInset = self._startingContentInsets
+ lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
+ })
+ }
+
+ if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(_startingContentOffset) == false {
+ showLog("Restoring contentOffset to: \(_startingContentOffset)")
+
+ var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
+
+ if #available(iOS 9, *) {
+ animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
+ }
+
+ if animatedContentOffset {
+ lastScrollView.setContentOffset(_startingContentOffset, animated: UIView.areAnimationsEnabled)
+ } else {
+ lastScrollView.contentOffset = _startingContentOffset
+ }
+ }
+
+ _startingContentInsets = UIEdgeInsets()
+ _startingScrollIndicatorInsets = UIEdgeInsets()
+ _startingContentOffset = CGPoint.zero
+ _lastScrollView = nil
+ } else if superScrollView != lastScrollView { //If both scrollView's are different, then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
+
+ if lastScrollView.contentInset != self._startingContentInsets {
+ showLog("Restoring contentInset to: \(_startingContentInsets)")
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ lastScrollView.contentInset = self._startingContentInsets
+ lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
+ })
+ }
+
+ if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(_startingContentOffset) == false {
+ showLog("Restoring contentOffset to: \(_startingContentOffset)")
+
+ var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
+
+ if #available(iOS 9, *) {
+ animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
+ }
+
+ if animatedContentOffset {
+ lastScrollView.setContentOffset(_startingContentOffset, animated: UIView.areAnimationsEnabled)
+ } else {
+ lastScrollView.contentOffset = _startingContentOffset
+ }
+ }
+
+ _lastScrollView = superScrollView
+ if let scrollView = superScrollView {
+ _startingContentInsets = scrollView.contentInset
+ _startingContentOffset = scrollView.contentOffset
+
+ #if swift(>=5.1)
+ if #available(iOS 11.1, *) {
+ _startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
+ } else {
+ _startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
+ }
+ #else
+ _startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
+ #endif
+ }
+
+ showLog("Saving ScrollView New contentInset: \(_startingContentInsets) and contentOffset: \(_startingContentOffset)")
+ }
+ //Else the case where superScrollView == lastScrollView means we are on same scrollView after switching to different textField. So doing nothing, going ahead
+ } else if let unwrappedSuperScrollView = superScrollView { //If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
+ _lastScrollView = unwrappedSuperScrollView
+ _startingContentInsets = unwrappedSuperScrollView.contentInset
+ _startingContentOffset = unwrappedSuperScrollView.contentOffset
+
+ #if swift(>=5.1)
+ if #available(iOS 11.1, *) {
+ _startingScrollIndicatorInsets = unwrappedSuperScrollView.verticalScrollIndicatorInsets
+ } else {
+ _startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
+ }
+ #else
+ _startingScrollIndicatorInsets = unwrappedSuperScrollView.scrollIndicatorInsets
+ #endif
+
+ showLog("Saving ScrollView contentInset: \(_startingContentInsets) and contentOffset: \(_startingContentOffset)")
+ }
+
+ // Special case for ScrollView.
+ // If we found lastScrollView then setting it's contentOffset to show textField.
+ if let lastScrollView = _lastScrollView {
+ //Saving
+ var lastView = textFieldView
+ var superScrollView = _lastScrollView
+
+ while let scrollView = superScrollView {
+
+ var shouldContinue = false
+
+ if move > 0 {
+ shouldContinue = move > (-scrollView.contentOffset.y - scrollView.contentInset.top)
+
+ } else if let tableView = scrollView.superviewOfClassType(UITableView.self) as? UITableView {
+
+ shouldContinue = scrollView.contentOffset.y > 0
+
+ if shouldContinue == true, let tableCell = textFieldView.superviewOfClassType(UITableViewCell.self) as? UITableViewCell, let indexPath = tableView.indexPath(for: tableCell), let previousIndexPath = tableView.previousIndexPath(of: indexPath) {
+
+ let previousCellRect = tableView.rectForRow(at: previousIndexPath)
+ if previousCellRect.isEmpty == false {
+ let previousCellRectInRootSuperview = tableView.convert(previousCellRect, to: rootController.view.superview)
+
+ move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
+ }
+ }
+ } else if let collectionView = scrollView.superviewOfClassType(UICollectionView.self) as? UICollectionView {
+
+ shouldContinue = scrollView.contentOffset.y > 0
+
+ if shouldContinue == true, let collectionCell = textFieldView.superviewOfClassType(UICollectionViewCell.self) as? UICollectionViewCell, let indexPath = collectionView.indexPath(for: collectionCell), let previousIndexPath = collectionView.previousIndexPath(of: indexPath), let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
+
+ let previousCellRect = attributes.frame
+ if previousCellRect.isEmpty == false {
+ let previousCellRectInRootSuperview = collectionView.convert(previousCellRect, to: rootController.view.superview)
+
+ move = min(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
+ }
+ }
+ } else {
+
+ shouldContinue = textFieldViewRectInRootSuperview.origin.y < topLayoutGuide
+
+ if shouldContinue {
+ move = min(0, textFieldViewRectInRootSuperview.origin.y - topLayoutGuide)
+ }
+ }
+
+ //Looping in upper hierarchy until we don't found any scrollView in it's upper hirarchy till UIWindow object.
+ if shouldContinue {
+
+ var tempScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
+ var nextScrollView: UIScrollView?
+ while let view = tempScrollView {
+
+ if view.isScrollEnabled && view.shouldIgnoreScrollingAdjustment == false {
+ nextScrollView = view
+ break
+ } else {
+ tempScrollView = view.superviewOfClassType(UIScrollView.self) as? UIScrollView
+ }
+ }
+
+ //Getting lastViewRect.
+ if let lastViewRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
+
+ //Calculating the expected Y offset from move and scrollView's contentOffset.
+ var shouldOffsetY = scrollView.contentOffset.y - min(scrollView.contentOffset.y, -move)
+
+ //Rearranging the expected Y offset according to the view.
+ shouldOffsetY = min(shouldOffsetY, lastViewRect.origin.y)
+
+ //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
+ //nextScrollView == nil If processing scrollView is last scrollView in upper hierarchy (there is no other scrollView upper hierrchy.)
+ //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
+ //shouldOffsetY >= 0 shouldOffsetY must be greater than in order to keep distance from navigationBar (Bug ID: #92)
+ if textFieldView is UITextView == true &&
+ nextScrollView == nil &&
+ shouldOffsetY >= 0 {
+
+ // Converting Rectangle according to window bounds.
+ if let currentTextFieldViewRect = textFieldView.superview?.convert(textFieldView.frame, to: window) {
+
+ //Calculating expected fix distance which needs to be managed from navigation bar
+ let expectedFixDistance = currentTextFieldViewRect.minY - topLayoutGuide
+
+ //Now if expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance) is lower than current shouldOffsetY, which means we're in a position where navigationBar up and hide, then reducing shouldOffsetY with expectedOffsetY (superScrollView.contentOffset.y + expectedFixDistance)
+ shouldOffsetY = min(shouldOffsetY, scrollView.contentOffset.y + expectedFixDistance)
+
+ //Setting move to 0 because now we don't want to move any view anymore (All will be managed by our contentInset logic.
+ move = 0
+ } else {
+ //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
+ move -= (shouldOffsetY-scrollView.contentOffset.y)
+ }
+ } else {
+ //Subtracting the Y offset from the move variable, because we are going to change scrollView's contentOffset.y to shouldOffsetY.
+ move -= (shouldOffsetY-scrollView.contentOffset.y)
+ }
+
+ let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: shouldOffsetY)
+
+ if scrollView.contentOffset.equalTo(newContentOffset) == false {
+
+ showLog("old contentOffset: \(scrollView.contentOffset) new contentOffset: \(newContentOffset)")
+ self.showLog("Remaining Move: \(move)")
+
+ //Getting problem while using `setContentOffset:animated:`, So I used animation API.
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
+
+ if #available(iOS 9, *) {
+ animatedContentOffset = textFieldView.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil
+ }
+
+ if animatedContentOffset {
+ scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
+ } else {
+ scrollView.contentOffset = newContentOffset
+ }
+ }) { _ in
+
+ if scrollView is UITableView || scrollView is UICollectionView {
+ //This will update the next/previous states
+ self.addToolbarIfRequired()
+ }
+ }
+ }
+ }
+
+ // Getting next lastView & superScrollView.
+ lastView = scrollView
+ superScrollView = nextScrollView
+ } else {
+ move = 0
+ break
+ }
+ }
+
+ //Updating contentInset
+ if let lastScrollViewRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window) {
+
+ let bottom: CGFloat = (kbSize.height-newKeyboardDistanceFromTextField)-(window.frame.height-lastScrollViewRect.maxY)
+
+ // Update the insets so that the scroll vew doesn't shift incorrectly when the offset is near the bottom of the scroll view.
+ var movedInsets = lastScrollView.contentInset
+
+ movedInsets.bottom = max(_startingContentInsets.bottom, bottom)
+
+ if lastScrollView.contentInset != movedInsets {
+ showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+ lastScrollView.contentInset = movedInsets
+
+ var newInset : UIEdgeInsets
+
+ #if swift(>=5.1)
+ if #available(iOS 11.1, *) {
+ newInset = lastScrollView.verticalScrollIndicatorInsets
+ } else {
+ newInset = lastScrollView.scrollIndicatorInsets
+ }
+ #else
+ newInset = lastScrollView.scrollIndicatorInsets
+ #endif
+
+ newInset.bottom = movedInsets.bottom
+ lastScrollView.scrollIndicatorInsets = newInset
+ })
+ }
+ }
+ }
+ //Going ahead. No else if.
+
+ //Special case for UITextView(Readjusting textView.contentInset when textView hight is too big to fit on screen)
+ //_lastScrollView If not having inside any scrollView, (now contentInset manages the full screen textView.
+ //[_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
+ if let textView = textFieldView as? UITextView {
+
+// CGRect rootSuperViewFrameInWindow = [_rootViewController.view.superview convertRect:_rootViewController.view.superview.bounds toView:keyWindow];
+//
+// CGFloat keyboardOverlapping = CGRectGetMaxY(rootSuperViewFrameInWindow) - keyboardYPosition;
+//
+// CGFloat textViewHeight = MIN(CGRectGetHeight(_textFieldView.frame), (CGRectGetHeight(rootSuperViewFrameInWindow)-topLayoutGuide-keyboardOverlapping));
+
+ let keyboardYPosition = window.frame.height - (kbSize.height-newKeyboardDistanceFromTextField)
+ var rootSuperViewFrameInWindow = window.frame
+ if let rootSuperview = rootController.view.superview {
+ rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
+ }
+
+ let keyboardOverlapping = rootSuperViewFrameInWindow.maxY - keyboardYPosition
+
+ let textViewHeight = min(textView.frame.height, rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping)
+
+ if textView.frame.size.height-textView.contentInset.bottom>textViewHeight {
+ //_isTextViewContentInsetChanged, If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
+ if self.isTextViewContentInsetChanged == false {
+ self.startingTextViewContentInsets = textView.contentInset
+
+ #if swift(>=5.1)
+ if #available(iOS 11.1, *) {
+ self.startingTextViewScrollIndicatorInsets = textView.verticalScrollIndicatorInsets
+ } else {
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
+ }
+ #else
+ self.startingTextViewScrollIndicatorInsets = textView.scrollIndicatorInsets
+ #endif
+ }
+
+ self.isTextViewContentInsetChanged = true
+
+ var newContentInset = textView.contentInset
+ newContentInset.bottom = textView.frame.size.height-textViewHeight
+
+ if textView.contentInset != newContentInset {
+ self.showLog("\(textFieldView) Old UITextView.contentInset: \(textView.contentInset) New UITextView.contentInset: \(newContentInset)")
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ textView.contentInset = newContentInset
+ textView.scrollIndicatorInsets = newContentInset
+ }, completion: { (_) -> Void in })
+ }
+ }
+ }
+
+ // +Positive or zero.
+ if move >= 0 {
+
+ rootViewOrigin.y = max(rootViewOrigin.y - move, min(0, -(kbSize.height-newKeyboardDistanceFromTextField)))
+
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
+ showLog("Moving Upward")
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ var rect = rootController.view.frame
+ rect.origin = rootViewOrigin
+ rootController.view.frame = rect
+
+ //Animating content if needed (Bug ID: #204)
+ if self.layoutIfNeededOnUpdate == true {
+ //Animating content (Bug ID: #160)
+ rootController.view.setNeedsLayout()
+ rootController.view.layoutIfNeeded()
+ }
+
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
+ })
+ }
+
+ _privateMovedDistance = (_topViewBeginOrigin.y-rootViewOrigin.y)
+ } else { // -Negative
+ let disturbDistance: CGFloat = rootViewOrigin.y-_topViewBeginOrigin.y
+
+ // disturbDistance Negative = frame disturbed.
+ // disturbDistance positive = frame not disturbed.
+ if disturbDistance <= 0 {
+
+ rootViewOrigin.y -= max(move, disturbDistance)
+
+ if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {
+ showLog("Moving Downward")
+ // Setting adjusted rootViewRect
+ // Setting adjusted rootViewRect
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ var rect = rootController.view.frame
+ rect.origin = rootViewOrigin
+ rootController.view.frame = rect
+
+ //Animating content if needed (Bug ID: #204)
+ if self.layoutIfNeededOnUpdate == true {
+ //Animating content (Bug ID: #160)
+ rootController.view.setNeedsLayout()
+ rootController.view.layoutIfNeeded()
+ }
+
+ self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")
+ })
+ }
+
+ _privateMovedDistance = (_topViewBeginOrigin.y-rootViewOrigin.y)
+ }
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+ }
+
+ private func restorePosition() {
+
+ _privateHasPendingAdjustRequest = false
+
+ // Setting rootViewController frame to it's original position. // (Bug ID: #18)
+ if _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false {
+
+ if let rootViewController = _rootViewController {
+
+ if rootViewController.view.frame.origin.equalTo(self._topViewBeginOrigin) == false {
+ //Used UIViewAnimationOptionBeginFromCurrentState to minimize strange animations.
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ self.showLog("Restoring \(rootViewController) origin to: \(self._topViewBeginOrigin)")
+
+ // Setting it's new frame
+ var rect = rootViewController.view.frame
+ rect.origin = self._topViewBeginOrigin
+ rootViewController.view.frame = rect
+
+ //Animating content if needed (Bug ID: #204)
+ if self.layoutIfNeededOnUpdate == true {
+ //Animating content (Bug ID: #160)
+ rootViewController.view.setNeedsLayout()
+ rootViewController.view.layoutIfNeeded()
+ }
+ })
+ }
+
+ self._privateMovedDistance = 0
+
+ if rootViewController.navigationController?.interactivePopGestureRecognizer?.state == .began {
+ self._rootViewControllerWhilePopGestureRecognizerActive = rootViewController
+ self._topViewBeginOriginWhilePopGestureRecognizerActive = self._topViewBeginOrigin
+ }
+
+ _rootViewController = nil
+ }
+ }
+ }
+
+ ///---------------------
+ /// MARK: Public Methods
+ ///---------------------
+
+ /* Refreshes textField/textView position if any external changes is explicitly made by user. */
+ @objc public func reloadLayoutIfNeeded() {
+
+ if privateIsEnabled() == true {
+ if _privateIsKeyboardShowing == true,
+ _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == false,
+ let textFieldView = _textFieldView,
+ textFieldView.isAlertViewTextField() == false {
+ optimizedAdjustPosition()
+ }
+ }
+ }
+
+ ///-------------------------------
+ /// MARK: UIKeyboard Notifications
+ ///-------------------------------
+
+ /* UIKeyboardWillShowNotification. */
+ @objc internal func keyboardWillShow(_ notification: Notification?) {
+
+ _kbShowNotification = notification
+
+ // Boolean to know keyboard is showing/hiding
+ _privateIsKeyboardShowing = true
+
+ let oldKBFrame = _kbFrame
+
+ if let info = notification?.userInfo {
+
+ #if swift(>=4.2)
+ let curveUserInfoKey = UIResponder.keyboardAnimationCurveUserInfoKey
+ let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
+ let frameEndUserInfoKey = UIResponder.keyboardFrameEndUserInfoKey
+ #else
+ let curveUserInfoKey = UIKeyboardAnimationCurveUserInfoKey
+ let durationUserInfoKey = UIKeyboardAnimationDurationUserInfoKey
+ let frameEndUserInfoKey = UIKeyboardFrameEndUserInfoKey
+ #endif
+
+ // Getting keyboard animation.
+ if let curve = info[curveUserInfoKey] as? UInt {
+ _animationCurve = .init(rawValue: curve)
+ } else {
+ _animationCurve = .curveEaseOut
+ }
+
+ // Getting keyboard animation duration
+ if let duration = info[durationUserInfoKey] as? TimeInterval {
+
+ //Saving animation duration
+ if duration != 0.0 {
+ _animationDuration = duration
+ }
+ } else {
+ _animationDuration = 0.25
+ }
+
+ // Getting UIKeyboardSize.
+ if let kbFrame = info[frameEndUserInfoKey] as? CGRect {
+
+ _kbFrame = kbFrame
+ showLog("UIKeyboard Frame: \(_kbFrame)")
+ }
+ }
+
+ if privateIsEnabled() == false {
+ return
+ }
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ // (Bug ID: #5)
+ if let textFieldView = _textFieldView, _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == true {
+
+ // keyboard is not showing(At the beginning only). We should save rootViewRect.
+ _rootViewController = textFieldView.parentContainerViewController()
+ if let controller = _rootViewController {
+
+ if _rootViewControllerWhilePopGestureRecognizerActive == controller {
+ _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive
+ } else {
+ _topViewBeginOrigin = controller.view.frame.origin
+ }
+
+ _rootViewControllerWhilePopGestureRecognizerActive = nil
+ _topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid
+
+ self.showLog("Saving \(controller) beginning origin: \(self._topViewBeginOrigin)")
+ }
+ }
+
+ //If last restored keyboard size is different(any orientation accure), then refresh. otherwise not.
+ if _kbFrame.equalTo(oldKBFrame) == false {
+
+ //If _textFieldView is inside UITableViewController then let UITableViewController to handle it (Bug ID: #37) (Bug ID: #76) See note:- https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html If it is UIAlertView textField then do not affect anything (Bug ID: #70).
+
+ if _privateIsKeyboardShowing == true,
+ let textFieldView = _textFieldView,
+ textFieldView.isAlertViewTextField() == false {
+
+ // keyboard is already showing. adjust position.
+ optimizedAdjustPosition()
+ }
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ /* UIKeyboardDidShowNotification. */
+ @objc internal func keyboardDidShow(_ notification: Notification?) {
+
+ if privateIsEnabled() == false {
+ return
+ }
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ if let textFieldView = _textFieldView,
+ let parentController = textFieldView.parentContainerViewController(), (parentController.modalPresentationStyle == UIModalPresentationStyle.formSheet || parentController.modalPresentationStyle == UIModalPresentationStyle.pageSheet),
+ textFieldView.isAlertViewTextField() == false {
+
+ self.optimizedAdjustPosition()
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ /* UIKeyboardWillHideNotification. So setting rootViewController to it's default frame. */
+ @objc internal func keyboardWillHide(_ notification: Notification?) {
+
+ //If it's not a fake notification generated by [self setEnable:NO].
+ if notification != nil {
+ _kbShowNotification = nil
+ }
+
+ // Boolean to know keyboard is showing/hiding
+ _privateIsKeyboardShowing = false
+
+ if let info = notification?.userInfo {
+
+ #if swift(>=4.2)
+ let durationUserInfoKey = UIResponder.keyboardAnimationDurationUserInfoKey
+ #else
+ let durationUserInfoKey = UIKeyboardAnimationDurationUserInfoKey
+ #endif
+
+ // Getting keyboard animation duration
+ if let duration = info[durationUserInfoKey] as? TimeInterval {
+ if duration != 0 {
+ // Setitng keyboard animation duration
+ _animationDuration = duration
+ }
+ }
+ }
+
+ //If not enabled then do nothing.
+ if privateIsEnabled() == false {
+ return
+ }
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ //Commented due to #56. Added all the conditions below to handle UIWebView's textFields. (Bug ID: #56)
+ // We are unable to get textField object while keyboard showing on UIWebView's textField. (Bug ID: #11)
+ // if (_textFieldView == nil) return
+
+ //Restoring the contentOffset of the lastScrollView
+ if let lastScrollView = _lastScrollView {
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ if lastScrollView.contentInset != self._startingContentInsets {
+ self.showLog("Restoring contentInset to: \(self._startingContentInsets)")
+ lastScrollView.contentInset = self._startingContentInsets
+ lastScrollView.scrollIndicatorInsets = self._startingScrollIndicatorInsets
+ }
+
+ if lastScrollView.shouldRestoreScrollViewContentOffset == true && lastScrollView.contentOffset.equalTo(self._startingContentOffset) == false {
+ self.showLog("Restoring contentOffset to: \(self._startingContentOffset)")
+
+ var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
+
+ if #available(iOS 9, *) {
+ animatedContentOffset = self._textFieldView?.superviewOfClassType(UIStackView.self, belowView: lastScrollView) != nil
+ }
+
+ if animatedContentOffset {
+ lastScrollView.setContentOffset(self._startingContentOffset, animated: UIView.areAnimationsEnabled)
+ } else {
+ lastScrollView.contentOffset = self._startingContentOffset
+ }
+ }
+
+ // TODO: restore scrollView state
+ // This is temporary solution. Have to implement the save and restore scrollView state
+ var superScrollView: UIScrollView? = lastScrollView
+
+ while let scrollView = superScrollView {
+
+ let contentSize = CGSize(width: max(scrollView.contentSize.width, scrollView.frame.width), height: max(scrollView.contentSize.height, scrollView.frame.height))
+
+ let minimumY = contentSize.height - scrollView.frame.height
+
+ if minimumY < scrollView.contentOffset.y {
+
+ let newContentOffset = CGPoint(x: scrollView.contentOffset.x, y: minimumY)
+ if scrollView.contentOffset.equalTo(newContentOffset) == false {
+
+ var animatedContentOffset = false // (Bug ID: #1365, #1508, #1541)
+
+ if #available(iOS 9, *) {
+ animatedContentOffset = self._textFieldView?.superviewOfClassType(UIStackView.self, belowView: scrollView) != nil
+ }
+
+ if animatedContentOffset {
+ scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
+ } else {
+ scrollView.contentOffset = newContentOffset
+ }
+
+ self.showLog("Restoring contentOffset to: \(self._startingContentOffset)")
+ }
+ }
+
+ superScrollView = scrollView.superviewOfClassType(UIScrollView.self) as? UIScrollView
+ }
+ })
+ }
+
+ restorePosition()
+
+ //Reset all values
+ _lastScrollView = nil
+ _kbFrame = CGRect.zero
+ _startingContentInsets = UIEdgeInsets()
+ _startingScrollIndicatorInsets = UIEdgeInsets()
+ _startingContentOffset = CGPoint.zero
+ // topViewBeginRect = CGRectZero //Commented due to #82
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ @objc internal func keyboardDidHide(_ notification: Notification) {
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ _topViewBeginOrigin = IQKeyboardManager.kIQCGPointInvalid
+
+ _kbFrame = CGRect.zero
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ ///-------------------------------------------
+ /// MARK: UITextField/UITextView Notifications
+ ///-------------------------------------------
+
+ /** UITextFieldTextDidBeginEditingNotification, UITextViewTextDidBeginEditingNotification. Fetching UITextFieldView object. */
+ @objc internal func textFieldViewDidBeginEditing(_ notification: Notification) {
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ // Getting object
+ _textFieldView = notification.object as? UIView
+
+ if overrideKeyboardAppearance == true {
+
+ if let textFieldView = _textFieldView as? UITextField {
+ //If keyboard appearance is not like the provided appearance
+ if textFieldView.keyboardAppearance != keyboardAppearance {
+ //Setting textField keyboard appearance and reloading inputViews.
+ textFieldView.keyboardAppearance = keyboardAppearance
+ textFieldView.reloadInputViews()
+ }
+ } else if let textFieldView = _textFieldView as? UITextView {
+ //If keyboard appearance is not like the provided appearance
+ if textFieldView.keyboardAppearance != keyboardAppearance {
+ //Setting textField keyboard appearance and reloading inputViews.
+ textFieldView.keyboardAppearance = keyboardAppearance
+ textFieldView.reloadInputViews()
+ }
+ }
+ }
+
+ //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
+ if privateIsEnableAutoToolbar() == true {
+
+ //UITextView special case. Keyboard Notification is firing before textView notification so we need to resign it first and then again set it as first responder to add toolbar on it.
+ if let textView = _textFieldView as? UITextView,
+ textView.inputAccessoryView == nil {
+
+ UIView.animate(withDuration: 0.00001, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ self.addToolbarIfRequired()
+
+ }, completion: { (_) -> Void in
+
+ //On textView toolbar didn't appear on first time, so forcing textView to reload it's inputViews.
+ textView.reloadInputViews()
+ })
+ } else {
+ //Adding toolbar
+ addToolbarIfRequired()
+ }
+ } else {
+ removeToolbarIfRequired()
+ }
+
+ resignFirstResponderGesture.isEnabled = privateShouldResignOnTouchOutside()
+ _textFieldView?.window?.addGestureRecognizer(resignFirstResponderGesture) // (Enhancement ID: #14)
+
+ if privateIsEnabled() == true {
+ if _topViewBeginOrigin.equalTo(IQKeyboardManager.kIQCGPointInvalid) == true { // (Bug ID: #5)
+
+ _rootViewController = _textFieldView?.parentContainerViewController()
+
+ if let controller = _rootViewController {
+
+ if _rootViewControllerWhilePopGestureRecognizerActive == controller {
+ _topViewBeginOrigin = _topViewBeginOriginWhilePopGestureRecognizerActive
+ } else {
+ _topViewBeginOrigin = controller.view.frame.origin
+ }
+
+ _rootViewControllerWhilePopGestureRecognizerActive = nil
+ _topViewBeginOriginWhilePopGestureRecognizerActive = IQKeyboardManager.kIQCGPointInvalid
+
+ self.showLog("Saving \(controller) beginning origin: \(self._topViewBeginOrigin)")
+ }
+ }
+
+ //If _textFieldView is inside ignored responder then do nothing. (Bug ID: #37, #74, #76)
+ //See notes:- https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html If it is UIAlertView textField then do not affect anything (Bug ID: #70).
+ if _privateIsKeyboardShowing == true,
+ let textFieldView = _textFieldView,
+ textFieldView.isAlertViewTextField() == false {
+
+ // keyboard is already showing. adjust position.
+ optimizedAdjustPosition()
+ }
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ /** UITextFieldTextDidEndEditingNotification, UITextViewTextDidEndEditingNotification. Removing fetched object. */
+ @objc internal func textFieldViewDidEndEditing(_ notification: Notification) {
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ //Removing gesture recognizer (Enhancement ID: #14)
+ _textFieldView?.window?.removeGestureRecognizer(resignFirstResponderGesture)
+
+ // We check if there's a change in original frame or not.
+
+ if let textView = _textFieldView as? UITextView {
+
+ if isTextViewContentInsetChanged == true {
+ self.isTextViewContentInsetChanged = false
+
+ if textView.contentInset != self.startingTextViewContentInsets {
+ self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)")
+
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ //Setting textField to it's initial contentInset
+ textView.contentInset = self.startingTextViewContentInsets
+ textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets
+
+ }, completion: { (_) -> Void in })
+ }
+ }
+ }
+
+ //Setting object to nil
+ _textFieldView = nil
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ ///---------------------------------------
+ /// MARK: UIStatusBar Notification methods
+ ///---------------------------------------
+
+ /** UIApplicationWillChangeStatusBarOrientationNotification. Need to set the textView to it's original position. If any frame changes made. (Bug ID: #92)*/
+ @objc internal func willChangeStatusBarOrientation(_ notification: Notification) {
+
+ let currentStatusBarOrientation : UIInterfaceOrientation
+ #if swift(>=5.1)
+ if #available(iOS 13, *) {
+ currentStatusBarOrientation = keyWindow()?.windowScene?.interfaceOrientation ?? UIInterfaceOrientation.unknown
+ } else {
+ currentStatusBarOrientation = UIApplication.shared.statusBarOrientation
+ }
+ #else
+ currentStatusBarOrientation = UIApplication.shared.statusBarOrientation
+ #endif
+
+ guard let statusBarOrientation = notification.userInfo?[UIApplication.statusBarOrientationUserInfoKey] as? Int, currentStatusBarOrientation.rawValue != statusBarOrientation else {
+ return
+ }
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ //If textViewContentInsetChanged is saved then restore it.
+ if let textView = _textFieldView as? UITextView {
+
+ if isTextViewContentInsetChanged == true {
+
+ self.isTextViewContentInsetChanged = false
+
+ if textView.contentInset != self.startingTextViewContentInsets {
+ UIView.animate(withDuration: _animationDuration, delay: 0, options: _animationCurve.union(.beginFromCurrentState), animations: { () -> Void in
+
+ self.showLog("Restoring textView.contentInset to: \(self.startingTextViewContentInsets)")
+
+ //Setting textField to it's initial contentInset
+ textView.contentInset = self.startingTextViewContentInsets
+ textView.scrollIndicatorInsets = self.startingTextViewScrollIndicatorInsets
+
+ }, completion: { (_) -> Void in })
+ }
+ }
+ }
+
+ restorePosition()
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ ///------------------
+ /// MARK: AutoToolbar
+ ///------------------
+
+ /** Get all UITextField/UITextView siblings of textFieldView. */
+ private func responderViews() -> [UIView]? {
+
+ var superConsideredView: UIView?
+
+ //If find any consider responderView in it's upper hierarchy then will get deepResponderView.
+ for disabledClass in toolbarPreviousNextAllowedClasses {
+
+ superConsideredView = _textFieldView?.superviewOfClassType(disabledClass)
+
+ if superConsideredView != nil {
+ break
+ }
+ }
+
+ //If there is a superConsideredView in view's hierarchy, then fetching all it's subview that responds. No sorting for superConsideredView, it's by subView position. (Enhancement ID: #22)
+ if let view = superConsideredView {
+ return view.deepResponderViews()
+ } else { //Otherwise fetching all the siblings
+
+ if let textFields = _textFieldView?.responderSiblings() {
+
+ //Sorting textFields according to behaviour
+ switch toolbarManageBehaviour {
+ //If autoToolbar behaviour is bySubviews, then returning it.
+ case IQAutoToolbarManageBehaviour.bySubviews: return textFields
+
+ //If autoToolbar behaviour is by tag, then sorting it according to tag property.
+ case IQAutoToolbarManageBehaviour.byTag: return textFields.sortedArrayByTag()
+
+ //If autoToolbar behaviour is by tag, then sorting it according to tag property.
+ case IQAutoToolbarManageBehaviour.byPosition: return textFields.sortedArrayByPosition()
+ }
+ } else {
+ return nil
+ }
+ }
+ }
+
+ /** Add toolbar if it is required to add on textFields and it's siblings. */
+ private func addToolbarIfRequired() {
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ // Getting all the sibling textFields.
+ if let siblings = responderViews(), !siblings.isEmpty {
+
+ showLog("Found \(siblings.count) responder sibling(s)")
+
+ if let textField = _textFieldView {
+ //Either there is no inputAccessoryView or if accessoryView is not appropriate for current situation(There is Previous/Next/Done toolbar).
+ //setInputAccessoryView: check (Bug ID: #307)
+ if textField.responds(to: #selector(setter: UITextField.inputAccessoryView)) {
+
+ if textField.inputAccessoryView == nil ||
+ textField.inputAccessoryView?.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag ||
+ textField.inputAccessoryView?.tag == IQKeyboardManager.kIQDoneButtonToolbarTag {
+
+ let rightConfiguration: IQBarButtonItemConfiguration
+
+ if let doneBarButtonItemImage = toolbarDoneBarButtonItemImage {
+ rightConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.doneAction(_:)))
+ } else if let doneBarButtonItemText = toolbarDoneBarButtonItemText {
+ rightConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.doneAction(_:)))
+ } else {
+ rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: #selector(self.doneAction(_:)))
+ }
+
+ // If only one object is found, then adding only Done button.
+ if (siblings.count == 1 && previousNextDisplayMode == .default) || previousNextDisplayMode == .alwaysHide {
+
+ textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: nil, nextBarButtonConfiguration: nil)
+
+ textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78)
+
+ } else if (siblings.count > 1 && previousNextDisplayMode == .default) || previousNextDisplayMode == .alwaysShow {
+
+ let prevConfiguration: IQBarButtonItemConfiguration
+
+ if let doneBarButtonItemImage = toolbarPreviousBarButtonItemImage {
+ prevConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.previousAction(_:)))
+ } else if let doneBarButtonItemText = toolbarPreviousBarButtonItemText {
+ prevConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.previousAction(_:)))
+ } else {
+ prevConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardPreviousImage() ?? UIImage()), action: #selector(self.previousAction(_:)))
+ }
+
+ let nextConfiguration: IQBarButtonItemConfiguration
+
+ if let doneBarButtonItemImage = toolbarNextBarButtonItemImage {
+ nextConfiguration = IQBarButtonItemConfiguration(image: doneBarButtonItemImage, action: #selector(self.nextAction(_:)))
+ } else if let doneBarButtonItemText = toolbarNextBarButtonItemText {
+ nextConfiguration = IQBarButtonItemConfiguration(title: doneBarButtonItemText, action: #selector(self.nextAction(_:)))
+ } else {
+ nextConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardNextImage() ?? UIImage()), action: #selector(self.nextAction(_:)))
+ }
+
+ textField.addKeyboardToolbarWithTarget(target: self, titleText: (shouldShowToolbarPlaceholder ? textField.drawingToolbarPlaceholder: nil), rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
+
+ textField.inputAccessoryView?.tag = IQKeyboardManager.kIQPreviousNextButtonToolbarTag // (Bug ID: #78)
+ }
+
+ let toolbar = textField.keyboardToolbar
+
+ // Setting toolbar to keyboard.
+ if let textField = textField as? UITextField {
+
+ //Bar style according to keyboard appearance
+ switch textField.keyboardAppearance {
+
+ case .dark:
+ toolbar.barStyle = UIBarStyle.black
+ toolbar.tintColor = UIColor.white
+ toolbar.barTintColor = nil
+ default:
+ toolbar.barStyle = UIBarStyle.default
+ toolbar.barTintColor = toolbarBarTintColor
+
+ //Setting toolbar tintColor // (Enhancement ID: #30)
+ if shouldToolbarUsesTextFieldTintColor {
+ toolbar.tintColor = textField.tintColor
+ } else if let tintColor = toolbarTintColor {
+ toolbar.tintColor = tintColor
+ } else {
+ toolbar.tintColor = UIColor.black
+ }
+ }
+ } else if let textView = textField as? UITextView {
+
+ //Bar style according to keyboard appearance
+ switch textView.keyboardAppearance {
+
+ case .dark:
+ toolbar.barStyle = UIBarStyle.black
+ toolbar.tintColor = UIColor.white
+ toolbar.barTintColor = nil
+ default:
+ toolbar.barStyle = UIBarStyle.default
+ toolbar.barTintColor = toolbarBarTintColor
+
+ if shouldToolbarUsesTextFieldTintColor {
+ toolbar.tintColor = textView.tintColor
+ } else if let tintColor = toolbarTintColor {
+ toolbar.tintColor = tintColor
+ } else {
+ toolbar.tintColor = UIColor.black
+ }
+ }
+ }
+
+ //Setting toolbar title font. // (Enhancement ID: #30)
+ if shouldShowToolbarPlaceholder == true &&
+ textField.shouldHideToolbarPlaceholder == false {
+
+ //Updating placeholder font to toolbar. //(Bug ID: #148, #272)
+ if toolbar.titleBarButton.title == nil ||
+ toolbar.titleBarButton.title != textField.drawingToolbarPlaceholder {
+ toolbar.titleBarButton.title = textField.drawingToolbarPlaceholder
+ }
+
+ //Setting toolbar title font. // (Enhancement ID: #30)
+ if let font = placeholderFont {
+ toolbar.titleBarButton.titleFont = font
+ }
+
+ //Setting toolbar title color. // (Enhancement ID: #880)
+ if let color = placeholderColor {
+ toolbar.titleBarButton.titleColor = color
+ }
+
+ //Setting toolbar button title color. // (Enhancement ID: #880)
+ if let color = placeholderButtonColor {
+ toolbar.titleBarButton.selectableTitleColor = color
+ }
+
+ } else {
+
+ toolbar.titleBarButton.title = nil
+ }
+
+ //In case of UITableView (Special), the next/previous buttons has to be refreshed everytime. (Bug ID: #56)
+ // If firstTextField, then previous should not be enabled.
+ if siblings.first == textField {
+ if siblings.count == 1 {
+ textField.keyboardToolbar.previousBarButton.isEnabled = false
+ textField.keyboardToolbar.nextBarButton.isEnabled = false
+ } else {
+ textField.keyboardToolbar.previousBarButton.isEnabled = false
+ textField.keyboardToolbar.nextBarButton.isEnabled = true
+ }
+ } else if siblings.last == textField { // If lastTextField then next should not be enaled.
+ textField.keyboardToolbar.previousBarButton.isEnabled = true
+ textField.keyboardToolbar.nextBarButton.isEnabled = false
+ } else {
+ textField.keyboardToolbar.previousBarButton.isEnabled = true
+ textField.keyboardToolbar.nextBarButton.isEnabled = true
+ }
+ }
+ }
+ }
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ /** Remove any toolbar if it is IQToolbar. */
+ private func removeToolbarIfRequired() { // (Bug ID: #18)
+
+ let startTime = CACurrentMediaTime()
+ showLog("****** \(#function) started ******", indentation: 1)
+
+ // Getting all the sibling textFields.
+ if let siblings = responderViews() {
+
+ showLog("Found \(siblings.count) responder sibling(s)")
+
+ for view in siblings {
+
+ if let toolbar = view.inputAccessoryView as? IQToolbar {
+
+ //setInputAccessoryView: check (Bug ID: #307)
+ if view.responds(to: #selector(setter: UITextField.inputAccessoryView)) &&
+ (toolbar.tag == IQKeyboardManager.kIQDoneButtonToolbarTag || toolbar.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag) {
+
+ if let textField = view as? UITextField {
+ textField.inputAccessoryView = nil
+ textField.reloadInputViews()
+ } else if let textView = view as? UITextView {
+ textView.inputAccessoryView = nil
+ textView.reloadInputViews()
+ }
+ }
+ }
+ }
+ }
+
+ let elapsedTime = CACurrentMediaTime() - startTime
+ showLog("****** \(#function) ended: \(elapsedTime) seconds ******", indentation: -1)
+ }
+
+ /** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
+ @objc public func reloadInputViews() {
+
+ //If enabled then adding toolbar.
+ if privateIsEnableAutoToolbar() == true {
+ self.addToolbarIfRequired()
+ } else {
+ self.removeToolbarIfRequired()
+ }
+ }
+
+ ///------------------------------------
+ /// MARK: Debugging & Developer options
+ ///------------------------------------
+
+ @objc public var enableDebugging = false
+
+ /**
+ @warning Use below methods to completely enable/disable notifications registered by library internally. Please keep in mind that library is totally dependent on NSNotification of UITextField, UITextField, Keyboard etc. If you do unregisterAllNotifications then library will not work at all. You should only use below methods if you want to completedly disable all library functions. You should use below methods at your own risk.
+ */
+ @objc public func registerAllNotifications() {
+
+ #if swift(>=4.2)
+ let UIKeyboardWillShow = UIResponder.keyboardWillShowNotification
+ let UIKeyboardDidShow = UIResponder.keyboardDidShowNotification
+ let UIKeyboardWillHide = UIResponder.keyboardWillHideNotification
+ let UIKeyboardDidHide = UIResponder.keyboardDidHideNotification
+
+ let UITextFieldTextDidBeginEditing = UITextField.textDidBeginEditingNotification
+ let UITextFieldTextDidEndEditing = UITextField.textDidEndEditingNotification
+
+ let UITextViewTextDidBeginEditing = UITextView.textDidBeginEditingNotification
+ let UITextViewTextDidEndEditing = UITextView.textDidEndEditingNotification
+
+ let UIApplicationWillChangeStatusBarOrientation = UIApplication.willChangeStatusBarOrientationNotification
+ #else
+ let UIKeyboardWillShow = Notification.Name.UIKeyboardWillShow
+ let UIKeyboardDidShow = Notification.Name.UIKeyboardDidShow
+ let UIKeyboardWillHide = Notification.Name.UIKeyboardWillHide
+ let UIKeyboardDidHide = Notification.Name.UIKeyboardDidHide
+
+ let UITextFieldTextDidBeginEditing = Notification.Name.UITextFieldTextDidBeginEditing
+ let UITextFieldTextDidEndEditing = Notification.Name.UITextFieldTextDidEndEditing
+
+ let UITextViewTextDidBeginEditing = Notification.Name.UITextViewTextDidBeginEditing
+ let UITextViewTextDidEndEditing = Notification.Name.UITextViewTextDidEndEditing
+
+ let UIApplicationWillChangeStatusBarOrientation = Notification.Name.UIApplicationWillChangeStatusBarOrientation
+ #endif
+
+ // Registering for keyboard notification.
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIKeyboardWillShow, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIKeyboardDidShow, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIKeyboardWillHide, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIKeyboardDidHide, object: nil)
+
+ // Registering for UITextField notification.
+ registerTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextFieldTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextFieldTextDidEndEditing.rawValue)
+
+ // Registering for UITextView notification.
+ registerTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextViewTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextViewTextDidEndEditing.rawValue)
+
+ // Registering for orientation changes notification
+ NotificationCenter.default.addObserver(self, selector: #selector(self.willChangeStatusBarOrientation(_:)), name: UIApplicationWillChangeStatusBarOrientation, object: UIApplication.shared)
+ }
+
+ @objc public func unregisterAllNotifications() {
+
+ #if swift(>=4.2)
+ let UIKeyboardWillShow = UIResponder.keyboardWillShowNotification
+ let UIKeyboardDidShow = UIResponder.keyboardDidShowNotification
+ let UIKeyboardWillHide = UIResponder.keyboardWillHideNotification
+ let UIKeyboardDidHide = UIResponder.keyboardDidHideNotification
+
+ let UITextFieldTextDidBeginEditing = UITextField.textDidBeginEditingNotification
+ let UITextFieldTextDidEndEditing = UITextField.textDidEndEditingNotification
+
+ let UITextViewTextDidBeginEditing = UITextView.textDidBeginEditingNotification
+ let UITextViewTextDidEndEditing = UITextView.textDidEndEditingNotification
+
+ let UIApplicationWillChangeStatusBarOrientation = UIApplication.willChangeStatusBarOrientationNotification
+ #else
+ let UIKeyboardWillShow = Notification.Name.UIKeyboardWillShow
+ let UIKeyboardDidShow = Notification.Name.UIKeyboardDidShow
+ let UIKeyboardWillHide = Notification.Name.UIKeyboardWillHide
+ let UIKeyboardDidHide = Notification.Name.UIKeyboardDidHide
+
+ let UITextFieldTextDidBeginEditing = Notification.Name.UITextFieldTextDidBeginEditing
+ let UITextFieldTextDidEndEditing = Notification.Name.UITextFieldTextDidEndEditing
+
+ let UITextViewTextDidBeginEditing = Notification.Name.UITextViewTextDidBeginEditing
+ let UITextViewTextDidEndEditing = Notification.Name.UITextViewTextDidEndEditing
+
+ let UIApplicationWillChangeStatusBarOrientation = Notification.Name.UIApplicationWillChangeStatusBarOrientation
+ #endif
+
+ // Unregistering for keyboard notification.
+ NotificationCenter.default.removeObserver(self, name: UIKeyboardWillShow, object: nil)
+ NotificationCenter.default.removeObserver(self, name: UIKeyboardDidShow, object: nil)
+ NotificationCenter.default.removeObserver(self, name: UIKeyboardWillHide, object: nil)
+ NotificationCenter.default.removeObserver(self, name: UIKeyboardDidHide, object: nil)
+
+ // Unregistering for UITextField notification.
+ unregisterTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextFieldTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextFieldTextDidEndEditing.rawValue)
+
+ // Unregistering for UITextView notification.
+ unregisterTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextViewTextDidBeginEditing.rawValue, didEndEditingNotificationName: UITextViewTextDidEndEditing.rawValue)
+
+ // Unregistering for orientation changes notification
+ NotificationCenter.default.removeObserver(self, name: UIApplicationWillChangeStatusBarOrientation, object: UIApplication.shared)
+ }
+
+ private func showLog(_ logString: String, indentation: Int = 0) {
+
+ struct Static {
+ static var indentation = 0
+ }
+
+ if indentation < 0 {
+ Static.indentation = max(0, Static.indentation + indentation)
+ }
+
+ if enableDebugging {
+
+ var preLog = "IQKeyboardManager"
+
+ for _ in 0 ... Static.indentation {
+ preLog += "|\t"
+ }
+ print(preLog + logString)
+ }
+
+ if indentation > 0 {
+ Static.indentation += indentation
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManagerSwift.h b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManagerSwift.h
new file mode 100755
index 00000000..229d4b92
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardManagerSwift.h
@@ -0,0 +1,34 @@
+//
+// IQKeyboardManagerSwift.h
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+//! Project version number for IQKeyboardManagerSwift.
+FOUNDATION_EXPORT double IQKeyboardManagerSwiftVersionNumber;
+
+//! Project version string for IQKeyboardManagerSwift.
+FOUNDATION_EXPORT const unsigned char IQKeyboardManagerSwiftVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift
new file mode 100755
index 00000000..f0ee16e2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift
@@ -0,0 +1,620 @@
+//
+// IQKeyboardReturnKeyHandler.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+private class IQTextFieldViewInfoModal: NSObject {
+
+ fileprivate weak var textFieldDelegate: UITextFieldDelegate?
+ fileprivate weak var textViewDelegate: UITextViewDelegate?
+ fileprivate weak var textFieldView: UIView?
+ fileprivate var originalReturnKeyType = UIReturnKeyType.default
+
+ init(textFieldView: UIView?, textFieldDelegate: UITextFieldDelegate?, textViewDelegate: UITextViewDelegate?, originalReturnKeyType: UIReturnKeyType = .default) {
+ self.textFieldView = textFieldView
+ self.textFieldDelegate = textFieldDelegate
+ self.textViewDelegate = textViewDelegate
+ self.originalReturnKeyType = originalReturnKeyType
+ }
+}
+
+/**
+Manages the return key to work like next/done in a view hierarchy.
+*/
+public class IQKeyboardReturnKeyHandler: NSObject, UITextFieldDelegate, UITextViewDelegate {
+
+ ///---------------
+ /// MARK: Settings
+ ///---------------
+
+ /**
+ Delegate of textField/textView.
+ */
+ @objc public weak var delegate: (UITextFieldDelegate & UITextViewDelegate)?
+
+ /**
+ Set the last textfield return key type. Default is UIReturnKeyDefault.
+ */
+ @objc public var lastTextFieldReturnKeyType: UIReturnKeyType = UIReturnKeyType.default {
+
+ didSet {
+
+ for modal in textFieldInfoCache {
+
+ if let view = modal.textFieldView {
+ updateReturnKeyTypeOnTextField(view)
+ }
+ }
+ }
+ }
+
+ ///--------------------------------------
+ /// MARK: Initialization/Deinitialization
+ ///--------------------------------------
+
+ @objc public override init() {
+ super.init()
+ }
+
+ /**
+ Add all the textFields available in UIViewController's view.
+ */
+ @objc public init(controller: UIViewController) {
+ super.init()
+
+ addResponderFromView(controller.view)
+ }
+
+ deinit {
+
+ for modal in textFieldInfoCache {
+
+ if let textField = modal.textFieldView as? UITextField {
+ textField.returnKeyType = modal.originalReturnKeyType
+
+ textField.delegate = modal.textFieldDelegate
+
+ } else if let textView = modal.textFieldView as? UITextView {
+
+ textView.returnKeyType = modal.originalReturnKeyType
+
+ textView.delegate = modal.textViewDelegate
+ }
+ }
+
+ textFieldInfoCache.removeAll()
+ }
+
+ ///------------------------
+ /// MARK: Private variables
+ ///------------------------
+ private var textFieldInfoCache = [IQTextFieldViewInfoModal]()
+
+ ///------------------------
+ /// MARK: Private Functions
+ ///------------------------
+ private func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModal? {
+
+ for modal in textFieldInfoCache {
+
+ if let view = modal.textFieldView {
+
+ if view == textField {
+ return modal
+ }
+ }
+ }
+
+ return nil
+ }
+
+ private func updateReturnKeyTypeOnTextField(_ view: UIView) {
+ var superConsideredView: UIView?
+
+ //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
+ for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
+
+ superConsideredView = view.superviewOfClassType(disabledClass)
+
+ if superConsideredView != nil {
+ break
+ }
+ }
+
+ var textFields = [UIView]()
+
+ //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
+ if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
+ textFields = unwrappedTableView.deepResponderViews()
+ } else { //Otherwise fetching all the siblings
+
+ textFields = view.responderSiblings()
+
+ //Sorting textFields according to behaviour
+ switch IQKeyboardManager.shared.toolbarManageBehaviour {
+ //If needs to sort it by tag
+ case .byTag: textFields = textFields.sortedArrayByTag()
+ //If needs to sort it by Position
+ case .byPosition: textFields = textFields.sortedArrayByPosition()
+ default: break
+ }
+ }
+
+ if let lastView = textFields.last {
+
+ if let textField = view as? UITextField {
+
+ //If it's the last textField in responder view, else next
+ textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
+ } else if let textView = view as? UITextView {
+
+ //If it's the last textField in responder view, else next
+ textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
+ }
+ }
+ }
+
+ ///----------------------------------------------
+ /// MARK: Registering/Unregistering textFieldView
+ ///----------------------------------------------
+
+ /**
+ Should pass UITextField/UITextView intance. Assign textFieldView delegate to self, change it's returnKeyType.
+
+ @param view UITextField/UITextView object to register.
+ */
+ @objc public func addTextFieldView(_ view: UIView) {
+
+ let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil)
+
+ if let textField = view as? UITextField {
+
+ modal.originalReturnKeyType = textField.returnKeyType
+ modal.textFieldDelegate = textField.delegate
+ textField.delegate = self
+
+ } else if let textView = view as? UITextView {
+
+ modal.originalReturnKeyType = textView.returnKeyType
+ modal.textViewDelegate = textView.delegate
+ textView.delegate = self
+ }
+
+ textFieldInfoCache.append(modal)
+ }
+
+ /**
+ Should pass UITextField/UITextView intance. Restore it's textFieldView delegate and it's returnKeyType.
+
+ @param view UITextField/UITextView object to unregister.
+ */
+ @objc public func removeTextFieldView(_ view: UIView) {
+
+ if let modal = textFieldViewCachedInfo(view) {
+
+ if let textField = view as? UITextField {
+
+ textField.returnKeyType = modal.originalReturnKeyType
+ textField.delegate = modal.textFieldDelegate
+ } else if let textView = view as? UITextView {
+
+ textView.returnKeyType = modal.originalReturnKeyType
+ textView.delegate = modal.textViewDelegate
+ }
+
+ if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) {
+
+ textFieldInfoCache.remove(at: index)
+ }
+ }
+ }
+
+ /**
+ Add all the UITextField/UITextView responderView's.
+
+ @param view UIView object to register all it's responder subviews.
+ */
+ @objc public func addResponderFromView(_ view: UIView) {
+
+ let textFields = view.deepResponderViews()
+
+ for textField in textFields {
+
+ addTextFieldView(textField)
+ }
+ }
+
+ /**
+ Remove all the UITextField/UITextView responderView's.
+
+ @param view UIView object to unregister all it's responder subviews.
+ */
+ @objc public func removeResponderFromView(_ view: UIView) {
+
+ let textFields = view.deepResponderViews()
+
+ for textField in textFields {
+
+ removeTextFieldView(textField)
+ }
+ }
+
+ @discardableResult private func goToNextResponderOrResign(_ view: UIView) -> Bool {
+
+ var superConsideredView: UIView?
+
+ //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
+ for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
+
+ superConsideredView = view.superviewOfClassType(disabledClass)
+
+ if superConsideredView != nil {
+ break
+ }
+ }
+
+ var textFields = [UIView]()
+
+ //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
+ if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
+ textFields = unwrappedTableView.deepResponderViews()
+ } else { //Otherwise fetching all the siblings
+
+ textFields = view.responderSiblings()
+
+ //Sorting textFields according to behaviour
+ switch IQKeyboardManager.shared.toolbarManageBehaviour {
+ //If needs to sort it by tag
+ case .byTag: textFields = textFields.sortedArrayByTag()
+ //If needs to sort it by Position
+ case .byPosition: textFields = textFields.sortedArrayByPosition()
+ default:
+ break
+ }
+ }
+
+ //Getting index of current textField.
+ if let index = textFields.firstIndex(of: view) {
+ //If it is not last textField. then it's next object becomeFirstResponder.
+ if index < (textFields.count - 1) {
+
+ let nextTextField = textFields[index+1]
+ nextTextField.becomeFirstResponder()
+ return false
+ } else {
+
+ view.resignFirstResponder()
+ return true
+ }
+ } else {
+ return true
+ }
+ }
+
+ ///---------------------------------------
+ /// MARK: UITextField/UITextView delegates
+ ///---------------------------------------
+
+ @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) {
+ return unwrapDelegate.textFieldShouldBeginEditing?(textField) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) {
+ return unwrapDelegate.textFieldShouldEndEditing?(textField) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @objc public func textFieldDidBeginEditing(_ textField: UITextField) {
+ updateReturnKeyTypeOnTextField(textField)
+
+ var aDelegate: UITextFieldDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textField) {
+ aDelegate = modal.textFieldDelegate
+ }
+ }
+
+ aDelegate?.textFieldDidBeginEditing?(textField)
+ }
+
+ @objc public func textFieldDidEndEditing(_ textField: UITextField) {
+
+ var aDelegate: UITextFieldDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textField) {
+ aDelegate = modal.textFieldDelegate
+ }
+ }
+
+ aDelegate?.textFieldDidEndEditing?(textField)
+ }
+
+ #if swift(>=4.2)
+ @available(iOS 10.0, *)
+ @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
+
+ var aDelegate: UITextFieldDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textField) {
+ aDelegate = modal.textFieldDelegate
+ }
+ }
+
+ aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
+ }
+ #else
+ @available(iOS 10.0, *)
+ @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
+
+ var aDelegate: UITextFieldDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textField) {
+ aDelegate = modal.textFieldDelegate
+ }
+ }
+
+ aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
+ }
+ #endif
+
+ @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) {
+ return unwrapDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == true
+ }
+ }
+ }
+ return true
+ }
+
+ @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) {
+ return unwrapDelegate.textFieldShouldClear?(textField) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+
+ var shouldReturn = true
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) {
+ shouldReturn = unwrapDelegate.textFieldShouldReturn?(textField) == true
+ }
+ }
+ }
+
+ if shouldReturn == true {
+ goToNextResponderOrResign(textField)
+ return true
+ } else {
+ return goToNextResponderOrResign(textField)
+ }
+ }
+
+ @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) {
+ return unwrapDelegate.textViewShouldBeginEditing?(textView) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) {
+ return unwrapDelegate.textViewShouldEndEditing?(textView) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @objc public func textViewDidBeginEditing(_ textView: UITextView) {
+ updateReturnKeyTypeOnTextField(textView)
+
+ var aDelegate: UITextViewDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textView) {
+ aDelegate = modal.textViewDelegate
+ }
+ }
+
+ aDelegate?.textViewDidBeginEditing?(textView)
+ }
+
+ @objc public func textViewDidEndEditing(_ textView: UITextView) {
+
+ var aDelegate: UITextViewDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textView) {
+ aDelegate = modal.textViewDelegate
+ }
+ }
+
+ aDelegate?.textViewDidEndEditing?(textView)
+ }
+
+ @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+
+ var shouldReturn = true
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))) {
+ shouldReturn = (unwrapDelegate.textView?(textView, shouldChangeTextIn: range, replacementText: text)) == true
+ }
+ }
+ }
+
+ if shouldReturn == true && text == "\n" {
+ shouldReturn = goToNextResponderOrResign(textView)
+ }
+
+ return shouldReturn
+ }
+
+ @objc public func textViewDidChange(_ textView: UITextView) {
+
+ var aDelegate: UITextViewDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textView) {
+ aDelegate = modal.textViewDelegate
+ }
+ }
+
+ aDelegate?.textViewDidChange?(textView)
+ }
+
+ @objc public func textViewDidChangeSelection(_ textView: UITextView) {
+
+ var aDelegate: UITextViewDelegate? = delegate
+
+ if aDelegate == nil {
+
+ if let modal = textFieldViewCachedInfo(textView) {
+ aDelegate = modal.textViewDelegate
+ }
+ }
+
+ aDelegate?.textViewDidChangeSelection?(textView)
+ }
+
+ @available(iOS 10.0, *)
+ @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)) {
+ return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange, interaction: interaction) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @available(iOS 10.0, *)
+ @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) -> Bool)) {
+ return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @available(iOS, deprecated: 10.0)
+ @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) {
+ return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange) == true
+ }
+ }
+ }
+
+ return true
+ }
+
+ @available(iOS, deprecated: 10.0)
+ @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
+
+ if delegate == nil {
+
+ if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
+ if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) {
+ return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange) == true
+ }
+ }
+ }
+
+ return true
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQTextView/IQTextView.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQTextView/IQTextView.swift
new file mode 100755
index 00000000..66913fe3
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQTextView/IQTextView.swift
@@ -0,0 +1,205 @@
+//
+// IQTextView.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+/** @abstract UITextView with placeholder support */
+open class IQTextView: UITextView {
+
+ @objc required public init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+
+ #if swift(>=4.2)
+ let UITextViewTextDidChange = UITextView.textDidChangeNotification
+ #else
+ let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange
+ #endif
+
+ NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self)
+ }
+
+ @objc override public init(frame: CGRect, textContainer: NSTextContainer?) {
+ super.init(frame: frame, textContainer: textContainer)
+
+ #if swift(>=4.2)
+ let notificationName = UITextView.textDidChangeNotification
+ #else
+ let notificationName = Notification.Name.UITextViewTextDidChange
+ #endif
+
+ NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: notificationName, object: self)
+ }
+
+ @objc override open func awakeFromNib() {
+ super.awakeFromNib()
+
+ #if swift(>=4.2)
+ let UITextViewTextDidChange = UITextView.textDidChangeNotification
+ #else
+ let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange
+ #endif
+
+ NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self)
+ }
+
+ deinit {
+ placeholderLabel.removeFromSuperview()
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ private var placeholderInsets: UIEdgeInsets {
+ return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding)
+ }
+
+ private var placeholderExpectedFrame: CGRect {
+ let placeholderInsets = self.placeholderInsets
+ let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right
+ let expectedSize = placeholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom))
+
+ return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height)
+ }
+
+ lazy var placeholderLabel: UILabel = {
+ let label = UILabel()
+
+ label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ label.lineBreakMode = .byWordWrapping
+ label.numberOfLines = 0
+ label.font = self.font
+ label.textAlignment = self.textAlignment
+ label.backgroundColor = UIColor.clear
+ label.textColor = UIColor(white: 0.7, alpha: 1.0)
+ label.alpha = 0
+ self.addSubview(label)
+
+ return label
+ }()
+
+ /** @abstract To set textView's placeholder text color. */
+ @IBInspectable open var placeholderTextColor: UIColor? {
+
+ get {
+ return placeholderLabel.textColor
+ }
+
+ set {
+ placeholderLabel.textColor = newValue
+ }
+ }
+
+ /** @abstract To set textView's placeholder text. Default is nil. */
+ @IBInspectable open var placeholder: String? {
+
+ get {
+ return placeholderLabel.text
+ }
+
+ set {
+ placeholderLabel.text = newValue
+ refreshPlaceholder()
+ }
+ }
+
+ /** @abstract To set textView's placeholder attributed text. Default is nil. */
+ open var attributedPlaceholder: NSAttributedString? {
+ get {
+ return placeholderLabel.attributedText
+ }
+
+ set {
+ placeholderLabel.attributedText = newValue
+ refreshPlaceholder()
+ }
+ }
+
+ @objc override open func layoutSubviews() {
+ super.layoutSubviews()
+
+ placeholderLabel.frame = placeholderExpectedFrame
+ }
+
+ @objc internal func refreshPlaceholder() {
+
+ if !text.isEmpty || !attributedText.string.isEmpty {
+ placeholderLabel.alpha = 0
+ } else {
+ placeholderLabel.alpha = 1
+ }
+ }
+
+ @objc override open var text: String! {
+
+ didSet {
+ refreshPlaceholder()
+ }
+ }
+
+ open override var attributedText: NSAttributedString! {
+
+ didSet {
+ refreshPlaceholder()
+ }
+ }
+
+ @objc override open var font: UIFont? {
+
+ didSet {
+
+ if let unwrappedFont = font {
+ placeholderLabel.font = unwrappedFont
+ } else {
+ placeholderLabel.font = UIFont.systemFont(ofSize: 12)
+ }
+ }
+ }
+
+ @objc override open var textAlignment: NSTextAlignment {
+ didSet {
+ placeholderLabel.textAlignment = textAlignment
+ }
+ }
+
+ @objc override open var delegate: UITextViewDelegate? {
+
+ get {
+ refreshPlaceholder()
+ return super.delegate
+ }
+
+ set {
+ super.delegate = newValue
+ }
+ }
+
+ @objc override open var intrinsicContentSize: CGSize {
+ guard !hasText else {
+ return super.intrinsicContentSize
+ }
+
+ var newSize = super.intrinsicContentSize
+ let placeholderInsets = self.placeholderInsets
+ newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom
+
+ return newSize
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift
new file mode 100755
index 00000000..7e84dadc
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift
@@ -0,0 +1,134 @@
+//
+// IQBarButtonItem.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+import Foundation
+
+open class IQBarButtonItem: UIBarButtonItem {
+
+ private static var _classInitialize: Void = classInitialize()
+
+ @objc public override init() {
+ _ = IQBarButtonItem._classInitialize
+ super.init()
+ }
+
+ @objc public required init?(coder aDecoder: NSCoder) {
+ _ = IQBarButtonItem._classInitialize
+ super.init(coder: aDecoder)
+ }
+
+ private class func classInitialize() {
+
+ let appearanceProxy = self.appearance()
+
+ #if swift(>=4.2)
+ let states: [UIControl.State]
+ #else
+ let states: [UIControlState]
+ #endif
+
+ states = [.normal, .highlighted, .disabled, .selected, .application, .reserved]
+
+ for state in states {
+
+ appearanceProxy.setBackgroundImage(nil, for: state, barMetrics: .default)
+ appearanceProxy.setBackgroundImage(nil, for: state, style: .done, barMetrics: .default)
+ appearanceProxy.setBackgroundImage(nil, for: state, style: .plain, barMetrics: .default)
+ appearanceProxy.setBackButtonBackgroundImage(nil, for: state, barMetrics: .default)
+ }
+
+ appearanceProxy.setTitlePositionAdjustment(UIOffset(), for: .default)
+ appearanceProxy.setBackgroundVerticalPositionAdjustment(0, for: .default)
+ appearanceProxy.setBackButtonBackgroundVerticalPositionAdjustment(0, for: .default)
+ }
+
+ @objc override open var tintColor: UIColor? {
+ didSet {
+
+ #if swift(>=4.2)
+ var textAttributes = [NSAttributedString.Key: Any]()
+ let foregroundColorKey = NSAttributedString.Key.foregroundColor
+ #elseif swift(>=4)
+ var textAttributes = [NSAttributedStringKey: Any]()
+ let foregroundColorKey = NSAttributedStringKey.foregroundColor
+ #else
+ var textAttributes = [String: Any]()
+ let foregroundColorKey = NSForegroundColorAttributeName
+ #endif
+
+ textAttributes[foregroundColorKey] = tintColor
+
+ #if swift(>=4)
+
+ if let attributes = titleTextAttributes(for: .normal) {
+
+ for (key, value) in attributes {
+ #if swift(>=4.2)
+ textAttributes[key] = value
+ #else
+ textAttributes[NSAttributedStringKey.init(key)] = value
+ #endif
+ }
+ }
+
+ #else
+
+ if let attributes = titleTextAttributes(for: .normal) {
+ textAttributes = attributes
+ }
+ #endif
+
+ setTitleTextAttributes(textAttributes, for: .normal)
+ }
+ }
+
+ /**
+ Boolean to know if it's a system item or custom item, we are having a limitation that we cannot override a designated initializer, so we are manually setting this property once in initialization
+ */
+ @objc internal var isSystemItem = false
+
+ /**
+ Additional target & action to do get callback action. Note that setting custom target & selector doesn't affect native functionality, this is just an additional target to get a callback.
+
+ @param target Target object.
+ @param action Target Selector.
+ */
+ @objc open func setTarget(_ target: AnyObject?, action: Selector?) {
+ if let target = target, let action = action {
+ invocation = IQInvocation(target, action)
+ } else {
+ invocation = nil
+ }
+ }
+
+ /**
+ Customized Invocation to be called when button is pressed. invocation is internally created using setTarget:action: method.
+ */
+ @objc open var invocation: IQInvocation?
+
+ deinit {
+ target = nil
+ invocation = nil
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift
new file mode 100755
index 00000000..e75a0977
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift
@@ -0,0 +1,44 @@
+//
+// IQInvocation.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+@objc public class IQInvocation: NSObject {
+ @objc public weak var target: AnyObject?
+ @objc public var action: Selector
+
+ @objc public init(_ target: AnyObject, _ action: Selector) {
+ self.target = target
+ self.action = action
+ }
+
+ @objc public func invoke(from: Any) {
+ if let target = target {
+ UIApplication.shared.sendAction(action, to: target, from: from, for: UIEvent())
+ }
+ }
+
+ deinit {
+ target = nil
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift
new file mode 100755
index 00000000..cae19270
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift
@@ -0,0 +1,28 @@
+//
+// IQPreviousNextView.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+@objc public class IQPreviousNextView: UIView {
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift
new file mode 100755
index 00000000..1d8d1e70
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift
@@ -0,0 +1,177 @@
+//
+// IQTitleBarButtonItem.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+open class IQTitleBarButtonItem: IQBarButtonItem {
+
+ @objc open var titleFont: UIFont? {
+
+ didSet {
+ if let unwrappedFont = titleFont {
+ titleButton?.titleLabel?.font = unwrappedFont
+ } else {
+ titleButton?.titleLabel?.font = UIFont.systemFont(ofSize: 13)
+ }
+ }
+ }
+
+ @objc override open var title: String? {
+ didSet {
+ titleButton?.setTitle(title, for: .normal)
+ }
+ }
+
+ /**
+ titleColor to be used for displaying button text when displaying title (disabled state).
+ */
+ @objc open var titleColor: UIColor? {
+
+ didSet {
+
+ if let color = titleColor {
+ titleButton?.setTitleColor(color, for: .disabled)
+ } else {
+ titleButton?.setTitleColor(UIColor.lightGray, for: .disabled)
+ }
+ }
+ }
+
+ /**
+ selectableTitleColor to be used for displaying button text when button is enabled.
+ */
+ @objc open var selectableTitleColor: UIColor? {
+
+ didSet {
+
+ if let color = selectableTitleColor {
+ titleButton?.setTitleColor(color, for: .normal)
+ } else {
+ titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal)
+ }
+ }
+ }
+
+ /**
+ Customized Invocation to be called on title button action. titleInvocation is internally created using setTitleTarget:action: method.
+ */
+ @objc override open var invocation: IQInvocation? {
+
+ didSet {
+
+ if let target = invocation?.target, let action = invocation?.action {
+ self.isEnabled = true
+ titleButton?.isEnabled = true
+ titleButton?.addTarget(target, action: action, for: .touchUpInside)
+ } else {
+ self.isEnabled = false
+ titleButton?.isEnabled = false
+ titleButton?.removeTarget(nil, action: nil, for: .touchUpInside)
+ }
+ }
+ }
+
+ internal var titleButton: UIButton?
+ private var _titleView: UIView?
+
+ override init() {
+ super.init()
+ }
+
+ @objc public convenience init(title: String?) {
+
+ self.init(title: nil, style: .plain, target: nil, action: nil)
+
+ _titleView = UIView()
+ _titleView?.backgroundColor = UIColor.clear
+
+ titleButton = UIButton(type: .system)
+ titleButton?.isEnabled = false
+ titleButton?.titleLabel?.numberOfLines = 3
+ titleButton?.setTitleColor(UIColor.lightGray, for: .disabled)
+ titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal)
+ titleButton?.backgroundColor = UIColor.clear
+ titleButton?.titleLabel?.textAlignment = .center
+ titleButton?.setTitle(title, for: .normal)
+ titleFont = UIFont.systemFont(ofSize: 13.0)
+ titleButton?.titleLabel?.font = self.titleFont
+ _titleView?.addSubview(titleButton!)
+
+#if swift(>=3.2)
+ if #available(iOS 11, *) {
+
+ var layoutDefaultLowPriority: UILayoutPriority
+ var layoutDefaultHighPriority: UILayoutPriority
+
+ #if swift(>=4.0)
+ let layoutPriorityLowValue = UILayoutPriority.defaultLow.rawValue-1
+ let layoutPriorityHighValue = UILayoutPriority.defaultHigh.rawValue-1
+ layoutDefaultLowPriority = UILayoutPriority(rawValue: layoutPriorityLowValue)
+ layoutDefaultHighPriority = UILayoutPriority(rawValue: layoutPriorityHighValue)
+ #else
+ layoutDefaultLowPriority = UILayoutPriorityDefaultLow-1
+ layoutDefaultHighPriority = UILayoutPriorityDefaultHigh-1
+ #endif
+
+ _titleView?.translatesAutoresizingMaskIntoConstraints = false
+ _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical)
+ _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal)
+ _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical)
+ _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal)
+
+ titleButton?.translatesAutoresizingMaskIntoConstraints = false
+ titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical)
+ titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal)
+ titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical)
+ titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal)
+
+ let top = NSLayoutConstraint.init(item: titleButton!, attribute: .top, relatedBy: .equal, toItem: _titleView, attribute: .top, multiplier: 1, constant: 0)
+ let bottom = NSLayoutConstraint.init(item: titleButton!, attribute: .bottom, relatedBy: .equal, toItem: _titleView, attribute: .bottom, multiplier: 1, constant: 0)
+ let leading = NSLayoutConstraint.init(item: titleButton!, attribute: .leading, relatedBy: .equal, toItem: _titleView, attribute: .leading, multiplier: 1, constant: 0)
+ let trailing = NSLayoutConstraint.init(item: titleButton!, attribute: .trailing, relatedBy: .equal, toItem: _titleView, attribute: .trailing, multiplier: 1, constant: 0)
+
+ _titleView?.addConstraints([top, bottom, leading, trailing])
+ } else {
+ _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ }
+#else
+ _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+#endif
+
+ customView = _titleView
+ }
+
+ @objc required public init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+
+ deinit {
+ customView = nil
+ titleButton?.removeTarget(nil, action: nil, for: .touchUpInside)
+ _titleView = nil
+ titleButton = nil
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift
new file mode 100755
index 00000000..81b85882
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift
@@ -0,0 +1,356 @@
+//
+// IQToolbar.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+/** @abstract IQToolbar for IQKeyboardManager. */
+open class IQToolbar: UIToolbar, UIInputViewAudioFeedback {
+
+ private static var _classInitialize: Void = classInitialize()
+
+ private class func classInitialize() {
+
+ let appearanceProxy = self.appearance()
+
+ appearanceProxy.barTintColor = nil
+
+ let positions: [UIBarPosition] = [.any, .bottom, .top, .topAttached]
+
+ for position in positions {
+
+ appearanceProxy.setBackgroundImage(nil, forToolbarPosition: position, barMetrics: .default)
+ appearanceProxy.setShadowImage(nil, forToolbarPosition: .any)
+ }
+
+ //Background color
+ appearanceProxy.backgroundColor = nil
+ }
+
+ /**
+ Previous bar button of toolbar.
+ */
+ private var privatePreviousBarButton: IQBarButtonItem?
+ @objc open var previousBarButton: IQBarButtonItem {
+ get {
+ if privatePreviousBarButton == nil {
+ privatePreviousBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
+ privatePreviousBarButton?.accessibilityLabel = "Previous"
+ }
+ return privatePreviousBarButton!
+ }
+
+ set (newValue) {
+ privatePreviousBarButton = newValue
+ }
+ }
+
+ /**
+ Next bar button of toolbar.
+ */
+ private var privateNextBarButton: IQBarButtonItem?
+ @objc open var nextBarButton: IQBarButtonItem {
+ get {
+ if privateNextBarButton == nil {
+ privateNextBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
+ privateNextBarButton?.accessibilityLabel = "Next"
+ }
+ return privateNextBarButton!
+ }
+
+ set (newValue) {
+ privateNextBarButton = newValue
+ }
+ }
+
+ /**
+ Title bar button of toolbar.
+ */
+ private var privateTitleBarButton: IQTitleBarButtonItem?
+ @objc open var titleBarButton: IQTitleBarButtonItem {
+ get {
+ if privateTitleBarButton == nil {
+ privateTitleBarButton = IQTitleBarButtonItem(title: nil)
+ privateTitleBarButton?.accessibilityLabel = "Title"
+ }
+ return privateTitleBarButton!
+ }
+
+ set (newValue) {
+ privateTitleBarButton = newValue
+ }
+ }
+
+ /**
+ Done bar button of toolbar.
+ */
+ private var privateDoneBarButton: IQBarButtonItem?
+ @objc open var doneBarButton: IQBarButtonItem {
+ get {
+ if privateDoneBarButton == nil {
+ privateDoneBarButton = IQBarButtonItem(title: nil, style: .done, target: nil, action: nil)
+ privateDoneBarButton?.accessibilityLabel = "Done"
+ }
+ return privateDoneBarButton!
+ }
+
+ set (newValue) {
+ privateDoneBarButton = newValue
+ }
+ }
+
+ /**
+ Fixed space bar button of toolbar.
+ */
+ private var privateFixedSpaceBarButton: IQBarButtonItem?
+ @objc open var fixedSpaceBarButton: IQBarButtonItem {
+ get {
+ if privateFixedSpaceBarButton == nil {
+ privateFixedSpaceBarButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
+ }
+ privateFixedSpaceBarButton!.isSystemItem = true
+
+ if #available(iOS 10, *) {
+ privateFixedSpaceBarButton!.width = 6
+ } else {
+ privateFixedSpaceBarButton!.width = 20
+ }
+
+ return privateFixedSpaceBarButton!
+ }
+
+ set (newValue) {
+ privateFixedSpaceBarButton = newValue
+ }
+ }
+
+ override init(frame: CGRect) {
+ _ = IQToolbar._classInitialize
+ super.init(frame: frame)
+
+ sizeToFit()
+
+ autoresizingMask = .flexibleWidth
+ self.isTranslucent = true
+ }
+
+ @objc required public init?(coder aDecoder: NSCoder) {
+ _ = IQToolbar._classInitialize
+ super.init(coder: aDecoder)
+
+ sizeToFit()
+
+ autoresizingMask = .flexibleWidth
+ self.isTranslucent = true
+ }
+
+ @objc override open func sizeThatFits(_ size: CGSize) -> CGSize {
+ var sizeThatFit = super.sizeThatFits(size)
+ sizeThatFit.height = 44
+ return sizeThatFit
+ }
+
+ @objc override open var tintColor: UIColor! {
+
+ didSet {
+ if let unwrappedItems = items {
+ for item in unwrappedItems {
+ item.tintColor = tintColor
+ }
+ }
+ }
+ }
+
+ @objc override open var barStyle: UIBarStyle {
+ didSet {
+
+ if titleBarButton.selectableTitleColor == nil {
+ if barStyle == .default {
+ titleBarButton.titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal)
+ } else {
+ titleBarButton.titleButton?.setTitleColor(UIColor.yellow, for: .normal)
+ }
+ }
+ }
+ }
+
+ @objc override open func layoutSubviews() {
+
+ super.layoutSubviews()
+
+ //If running on Xcode9 (iOS11) only then we'll validate for iOS version, otherwise for older versions of Xcode (iOS10 and below) we'll just execute the tweak
+#if swift(>=3.2)
+
+ if #available(iOS 11, *) {
+ return
+ } else if let customTitleView = titleBarButton.customView {
+ var leftRect = CGRect.null
+ var rightRect = CGRect.null
+ var isTitleBarButtonFound = false
+
+ let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in
+ if view1.frame.minX != view2.frame.minX {
+ return view1.frame.minX < view2.frame.minX
+ } else {
+ return view1.frame.minY < view2.frame.minY
+ }
+ })
+
+ for barButtonItemView in sortedSubviews {
+
+ if isTitleBarButtonFound == true {
+ rightRect = barButtonItemView.frame
+ break
+ } else if barButtonItemView === customTitleView {
+ isTitleBarButtonFound = true
+ //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem)
+ } else if barButtonItemView.isKind(of: UIControl.self) == true {
+ leftRect = barButtonItemView.frame
+ }
+ }
+
+ let titleMargin: CGFloat = 16
+
+ let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX)
+ let maxHeight = self.frame.height
+
+ let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight))
+
+ var titleRect: CGRect
+
+ if sizeThatFits.width > 0 && sizeThatFits.height > 0 {
+ let width = min(sizeThatFits.width, maxWidth)
+ let height = min(sizeThatFits.height, maxHeight)
+
+ var xPosition: CGFloat
+
+ if leftRect.isNull == false {
+ xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2)
+ } else {
+ xPosition = titleMargin
+ }
+
+ let yPosition = (maxHeight - height)/2
+
+ titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height)
+ } else {
+
+ var xPosition: CGFloat
+
+ if leftRect.isNull == false {
+ xPosition = titleMargin + leftRect.maxX
+ } else {
+ xPosition = titleMargin
+ }
+
+ let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX)
+
+ titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight)
+ }
+
+ customTitleView.frame = titleRect
+ }
+
+#else
+ if let customTitleView = titleBarButton.customView {
+ var leftRect = CGRect.null
+ var rightRect = CGRect.null
+ var isTitleBarButtonFound = false
+
+ let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in
+ if view1.frame.minX != view2.frame.minX {
+ return view1.frame.minX < view2.frame.minX
+ } else {
+ return view1.frame.minY < view2.frame.minY
+ }
+ })
+
+ for barButtonItemView in sortedSubviews {
+
+ if isTitleBarButtonFound == true {
+ rightRect = barButtonItemView.frame
+ break
+ } else if barButtonItemView === titleBarButton.customView {
+ isTitleBarButtonFound = true
+ //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem)
+ } else if barButtonItemView.isKind(of: UIControl.self) == true {
+ leftRect = barButtonItemView.frame
+ }
+ }
+
+ let titleMargin: CGFloat = 16
+ let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX)
+ let maxHeight = self.frame.height
+
+ let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight))
+
+ var titleRect: CGRect
+
+ if sizeThatFits.width > 0 && sizeThatFits.height > 0 {
+ let width = min(sizeThatFits.width, maxWidth)
+ let height = min(sizeThatFits.height, maxHeight)
+
+ var xPosition: CGFloat
+
+ if leftRect.isNull == false {
+ xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2)
+ } else {
+ xPosition = titleMargin
+ }
+
+ let yPosition = (maxHeight - height)/2
+
+ titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height)
+ } else {
+
+ var xPosition: CGFloat
+
+ if leftRect.isNull == false {
+ xPosition = titleMargin + leftRect.maxX
+ } else {
+ xPosition = titleMargin
+ }
+
+ let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX)
+
+ titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight)
+ }
+
+ customTitleView.frame = titleRect
+ }
+#endif
+ }
+
+ @objc open var enableInputClicksWhenVisible: Bool {
+ return true
+ }
+
+ deinit {
+
+ items = nil
+ privatePreviousBarButton = nil
+ privateNextBarButton = nil
+ privateTitleBarButton = nil
+ privateDoneBarButton = nil
+ privateFixedSpaceBarButton = nil
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift
new file mode 100755
index 00000000..9aa2b598
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift
@@ -0,0 +1,608 @@
+//
+// IQUIView+IQKeyboardToolbar.swift
+// https://github.com/hackiftekhar/IQKeyboardManager
+// Copyright (c) 2013-16 Iftekhar Qurashi.
+//
+// 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
+
+private var kIQShouldHideToolbarPlaceholder = "kIQShouldHideToolbarPlaceholder"
+private var kIQToolbarPlaceholder = "kIQToolbarPlaceholder"
+
+private var kIQKeyboardToolbar = "kIQKeyboardToolbar"
+
+/**
+ IQBarButtonItemConfiguration for creating toolbar with bar button items
+ */
+@objc public class IQBarButtonItemConfiguration: NSObject {
+
+ #if swift(>=4.2)
+ @objc public init(barButtonSystemItem: UIBarButtonItem.SystemItem, action: Selector) {
+ self.barButtonSystemItem = barButtonSystemItem
+ self.image = nil
+ self.title = nil
+ self.action = action
+ super.init()
+ }
+ #else
+ @objc public init(barButtonSystemItem: UIBarButtonSystemItem, action: Selector) {
+ self.barButtonSystemItem = barButtonSystemItem
+ self.image = nil
+ self.title = nil
+ self.action = action
+ super.init()
+ }
+ #endif
+
+ @objc public init(image: UIImage, action: Selector) {
+ self.barButtonSystemItem = nil
+ self.image = image
+ self.title = nil
+ self.action = action
+ super.init()
+ }
+
+ @objc public init(title: String, action: Selector) {
+ self.barButtonSystemItem = nil
+ self.image = nil
+ self.title = title
+ self.action = action
+ super.init()
+ }
+
+ #if swift(>=4.2)
+ public let barButtonSystemItem: UIBarButtonItem.SystemItem? //System Item to be used to instantiate bar button.
+ #else
+ public let barButtonSystemItem: UIBarButtonSystemItem? //System Item to be used to instantiate bar button.
+ #endif
+
+ @objc public let image: UIImage? //Image to show on bar button item if it's not a system item.
+
+ @objc public let title: String? //Title to show on bar button item if it's not a system item.
+
+ @objc public let action: Selector? //action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'.
+}
+
+/**
+ UIImage category methods to get next/prev images
+ */
+@objc public extension UIImage {
+
+ @objc static func keyboardPreviousiOS9Image() -> UIImage? {
+
+ struct Static {
+ static var keyboardPreviousiOS9Image: UIImage?
+ }
+
+ if Static.keyboardPreviousiOS9Image == nil {
+ // Get the top level "bundle" which may actually be the framework
+ var bundle = Bundle(for: IQKeyboardManager.self)
+
+ if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") {
+ if let resourcesBundle = Bundle(path: resourcePath) {
+ bundle = resourcesBundle
+ }
+ }
+
+ Static.keyboardPreviousiOS9Image = UIImage(named: "IQButtonBarArrowLeft", in: bundle, compatibleWith: nil)
+
+ //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
+ if #available(iOS 9, *) {
+ Static.keyboardPreviousiOS9Image = Static.keyboardPreviousiOS9Image?.imageFlippedForRightToLeftLayoutDirection()
+ }
+ }
+
+ return Static.keyboardPreviousiOS9Image
+ }
+
+ @objc static func keyboardNextiOS9Image() -> UIImage? {
+
+ struct Static {
+ static var keyboardNextiOS9Image: UIImage?
+ }
+
+ if Static.keyboardNextiOS9Image == nil {
+ // Get the top level "bundle" which may actually be the framework
+ var bundle = Bundle(for: IQKeyboardManager.self)
+
+ if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") {
+ if let resourcesBundle = Bundle(path: resourcePath) {
+ bundle = resourcesBundle
+ }
+ }
+
+ Static.keyboardNextiOS9Image = UIImage(named: "IQButtonBarArrowRight", in: bundle, compatibleWith: nil)
+
+ //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
+ if #available(iOS 9, *) {
+ Static.keyboardNextiOS9Image = Static.keyboardNextiOS9Image?.imageFlippedForRightToLeftLayoutDirection()
+ }
+ }
+
+ return Static.keyboardNextiOS9Image
+ }
+
+ @objc static func keyboardPreviousiOS10Image() -> UIImage? {
+
+ struct Static {
+ static var keyboardPreviousiOS10Image: UIImage?
+ }
+
+ if Static.keyboardPreviousiOS10Image == nil {
+ // Get the top level "bundle" which may actually be the framework
+ var bundle = Bundle(for: IQKeyboardManager.self)
+
+ if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") {
+ if let resourcesBundle = Bundle(path: resourcePath) {
+ bundle = resourcesBundle
+ }
+ }
+
+ Static.keyboardPreviousiOS10Image = UIImage(named: "IQButtonBarArrowUp", in: bundle, compatibleWith: nil)
+
+ //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
+ if #available(iOS 9, *) {
+ Static.keyboardPreviousiOS10Image = Static.keyboardPreviousiOS10Image?.imageFlippedForRightToLeftLayoutDirection()
+ }
+ }
+
+ return Static.keyboardPreviousiOS10Image
+ }
+
+ @objc static func keyboardNextiOS10Image() -> UIImage? {
+
+ struct Static {
+ static var keyboardNextiOS10Image: UIImage?
+ }
+
+ if Static.keyboardNextiOS10Image == nil {
+ // Get the top level "bundle" which may actually be the framework
+ var bundle = Bundle(for: IQKeyboardManager.self)
+
+ if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") {
+ if let resourcesBundle = Bundle(path: resourcePath) {
+ bundle = resourcesBundle
+ }
+ }
+
+ Static.keyboardNextiOS10Image = UIImage(named: "IQButtonBarArrowDown", in: bundle, compatibleWith: nil)
+
+ //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
+ if #available(iOS 9, *) {
+ Static.keyboardNextiOS10Image = Static.keyboardNextiOS10Image?.imageFlippedForRightToLeftLayoutDirection()
+ }
+ }
+
+ return Static.keyboardNextiOS10Image
+ }
+
+ @objc static func keyboardPreviousImage() -> UIImage? {
+
+ if #available(iOS 10, *) {
+ return keyboardPreviousiOS10Image()
+ } else {
+ return keyboardPreviousiOS9Image()
+ }
+ }
+
+ @objc static func keyboardNextImage() -> UIImage? {
+
+ if #available(iOS 10, *) {
+ return keyboardNextiOS10Image()
+ } else {
+ return keyboardNextiOS9Image()
+ }
+ }
+}
+
+/**
+UIView category methods to add IQToolbar on UIKeyboard.
+*/
+@objc public extension UIView {
+
+ ///--------------
+ /// MARK: Toolbar
+ ///--------------
+
+ /**
+ IQToolbar references for better customization control.
+ */
+ @objc var keyboardToolbar: IQToolbar {
+ var toolbar = inputAccessoryView as? IQToolbar
+
+ if toolbar == nil {
+ toolbar = objc_getAssociatedObject(self, &kIQKeyboardToolbar) as? IQToolbar
+ }
+
+ if let unwrappedToolbar = toolbar {
+
+ return unwrappedToolbar
+
+ } else {
+
+ let newToolbar = IQToolbar()
+
+ objc_setAssociatedObject(self, &kIQKeyboardToolbar, newToolbar, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+
+ return newToolbar
+ }
+ }
+
+ ///--------------------
+ /// MARK: Toolbar title
+ ///--------------------
+
+ /**
+ If `shouldHideToolbarPlaceholder` is YES, then title will not be added to the toolbar. Default to NO.
+ */
+ @objc var shouldHideToolbarPlaceholder: Bool {
+ get {
+ let aValue = objc_getAssociatedObject(self, &kIQShouldHideToolbarPlaceholder) as Any?
+
+ if let unwrapedValue = aValue as? Bool {
+ return unwrapedValue
+ } else {
+ return false
+ }
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQShouldHideToolbarPlaceholder, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+
+ self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder
+ }
+ }
+
+ /**
+ `toolbarPlaceholder` to override default `placeholder` text when drawing text on toolbar.
+ */
+ @objc var toolbarPlaceholder: String? {
+ get {
+ let aValue = objc_getAssociatedObject(self, &kIQToolbarPlaceholder) as? String
+
+ return aValue
+ }
+ set(newValue) {
+ objc_setAssociatedObject(self, &kIQToolbarPlaceholder, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+
+ self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder
+ }
+ }
+
+ /**
+ `drawingToolbarPlaceholder` will be actual text used to draw on toolbar. This would either `placeholder` or `toolbarPlaceholder`.
+ */
+ @objc var drawingToolbarPlaceholder: String? {
+
+ if self.shouldHideToolbarPlaceholder {
+ return nil
+ } else if self.toolbarPlaceholder?.isEmpty == false {
+ return self.toolbarPlaceholder
+ } else if self.responds(to: #selector(getter: UITextField.placeholder)) {
+
+ if let textField = self as? UITextField {
+ return textField.placeholder
+ } else if let textView = self as? IQTextView {
+ return textView.placeholder
+ } else {
+ return nil
+ }
+ } else {
+ return nil
+ }
+ }
+
+ ///---------------------
+ /// MARK: Private helper
+ ///---------------------
+
+ private static func flexibleBarButtonItem () -> IQBarButtonItem {
+
+ struct Static {
+
+ static let nilButton = IQBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+ }
+
+ Static.nilButton.isSystemItem = true
+ return Static.nilButton
+ }
+
+ ///-------------
+ /// MARK: Common
+ ///-------------
+
+ @objc func addKeyboardToolbarWithTarget(target: AnyObject?, titleText: String?, rightBarButtonConfiguration: IQBarButtonItemConfiguration?, previousBarButtonConfiguration: IQBarButtonItemConfiguration? = nil, nextBarButtonConfiguration: IQBarButtonItemConfiguration? = nil) {
+
+ //If can't set InputAccessoryView. Then return
+ if self.responds(to: #selector(setter: UITextField.inputAccessoryView)) {
+
+ // Creating a toolBar for phoneNumber keyboard
+ let toolbar = self.keyboardToolbar
+
+ var items: [IQBarButtonItem] = []
+
+ if let prevConfig = previousBarButtonConfiguration {
+
+ var prev = toolbar.previousBarButton
+
+ if prevConfig.barButtonSystemItem == nil && prev.isSystemItem == false {
+ prev.title = prevConfig.title
+ prev.image = prevConfig.image
+ prev.target = target
+ prev.action = prevConfig.action
+ } else {
+ if let systemItem = prevConfig.barButtonSystemItem {
+ prev = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: prevConfig.action)
+ prev.isSystemItem = true
+ } else if let image = prevConfig.image {
+ prev = IQBarButtonItem(image: image, style: .plain, target: target, action: prevConfig.action)
+ } else {
+ prev = IQBarButtonItem(title: prevConfig.title, style: .plain, target: target, action: prevConfig.action)
+ }
+
+ prev.invocation = toolbar.previousBarButton.invocation
+ prev.accessibilityLabel = toolbar.previousBarButton.accessibilityLabel
+ prev.isEnabled = toolbar.previousBarButton.isEnabled
+ prev.tag = toolbar.previousBarButton.tag
+ toolbar.previousBarButton = prev
+ }
+
+ items.append(prev)
+ }
+
+ if previousBarButtonConfiguration != nil && nextBarButtonConfiguration != nil {
+
+ items.append(toolbar.fixedSpaceBarButton)
+ }
+
+ if let nextConfig = nextBarButtonConfiguration {
+
+ var next = toolbar.nextBarButton
+
+ if nextConfig.barButtonSystemItem == nil && next.isSystemItem == false {
+ next.title = nextConfig.title
+ next.image = nextConfig.image
+ next.target = target
+ next.action = nextConfig.action
+ } else {
+ if let systemItem = nextConfig.barButtonSystemItem {
+ next = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: nextConfig.action)
+ next.isSystemItem = true
+ } else if let image = nextConfig.image {
+ next = IQBarButtonItem(image: image, style: .plain, target: target, action: nextConfig.action)
+ } else {
+ next = IQBarButtonItem(title: nextConfig.title, style: .plain, target: target, action: nextConfig.action)
+ }
+
+ next.invocation = toolbar.nextBarButton.invocation
+ next.accessibilityLabel = toolbar.nextBarButton.accessibilityLabel
+ next.isEnabled = toolbar.nextBarButton.isEnabled
+ next.tag = toolbar.nextBarButton.tag
+ toolbar.nextBarButton = next
+ }
+
+ items.append(next)
+ }
+
+ //Title bar button item
+ do {
+ //Flexible space
+ items.append(UIView.flexibleBarButtonItem())
+
+ //Title button
+ toolbar.titleBarButton.title = titleText
+
+ #if swift(>=3.2)
+ if #available(iOS 11, *) {} else {
+ toolbar.titleBarButton.customView?.frame = CGRect.zero
+ }
+ #else
+ toolbar.titleBarButton.customView?.frame = CGRect.zero
+ #endif
+
+ items.append(toolbar.titleBarButton)
+
+ //Flexible space
+ items.append(UIView.flexibleBarButtonItem())
+ }
+
+ if let rightConfig = rightBarButtonConfiguration {
+
+ var done = toolbar.doneBarButton
+
+ if rightConfig.barButtonSystemItem == nil && done.isSystemItem == false {
+ done.title = rightConfig.title
+ done.image = rightConfig.image
+ done.target = target
+ done.action = rightConfig.action
+ } else {
+ if let systemItem = rightConfig.barButtonSystemItem {
+ done = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: rightConfig.action)
+ done.isSystemItem = true
+ } else if let image = rightConfig.image {
+ done = IQBarButtonItem(image: image, style: .plain, target: target, action: rightConfig.action)
+ } else {
+ done = IQBarButtonItem(title: rightConfig.title, style: .plain, target: target, action: rightConfig.action)
+ }
+
+ done.invocation = toolbar.doneBarButton.invocation
+ done.accessibilityLabel = toolbar.doneBarButton.accessibilityLabel
+ done.isEnabled = toolbar.doneBarButton.isEnabled
+ done.tag = toolbar.doneBarButton.tag
+ toolbar.doneBarButton = done
+ }
+
+ items.append(done)
+ }
+
+ // Adding button to toolBar.
+ toolbar.items = items
+
+ // Setting toolbar to keyboard.
+ if let textField = self as? UITextField {
+ textField.inputAccessoryView = toolbar
+
+ switch textField.keyboardAppearance {
+ case .dark:
+ toolbar.barStyle = UIBarStyle.black
+ default:
+ toolbar.barStyle = UIBarStyle.default
+ }
+ } else if let textView = self as? UITextView {
+ textView.inputAccessoryView = toolbar
+
+ switch textView.keyboardAppearance {
+ case .dark:
+ toolbar.barStyle = UIBarStyle.black
+ default:
+ toolbar.barStyle = UIBarStyle.default
+ }
+ }
+ }
+ }
+
+ ///------------
+ /// MARK: Right
+ ///------------
+
+ @objc func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addDoneOnKeyboardWithTarget(target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: action)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration)
+ }
+
+ @objc func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addRightButtonOnKeyboardWithImage(image, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(image: image, action: action)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration)
+ }
+
+ @objc func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addRightButtonOnKeyboardWithText(text, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(title: text, action: action)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration)
+ }
+
+ ///-----------------
+ /// MARK: Right/Left
+ ///-----------------
+
+ @objc func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addCancelDoneOnKeyboardWithTarget(target, cancelAction: cancelAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addRightLeftOnKeyboardWithTarget(target, leftButtonTitle: leftButtonTitle, rightButtonTitle: rightButtonTitle, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addRightLeftOnKeyboardWithTarget(target, leftButtonImage: leftButtonImage, rightButtonImage: rightButtonImage, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, titleText: String?) {
+
+ let leftConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .cancel, action: cancelAction)
+ let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration)
+ }
+
+ @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) {
+
+ let leftConfiguration = IQBarButtonItemConfiguration(title: leftButtonTitle, action: leftButtonAction)
+ let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration)
+ }
+
+ @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) {
+
+ let leftConfiguration = IQBarButtonItemConfiguration(image: leftButtonImage, action: leftButtonAction)
+ let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration)
+ }
+
+ ///--------------------------
+ /// MARK: Previous/Next/Right
+ ///--------------------------
+
+ @objc func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addPreviousNextDoneOnKeyboardWithTarget(target, previousAction: previousAction, nextAction: nextAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addPreviousNextRightOnKeyboardWithTarget(target, rightButtonImage: rightButtonImage, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) {
+
+ addPreviousNextRightOnKeyboardWithTarget(target, rightButtonTitle: rightButtonTitle, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil))
+ }
+
+ @objc func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction)
+ let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction)
+ let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
+ }
+
+ @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction)
+ let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction)
+ let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
+ }
+
+ @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) {
+
+ let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction)
+ let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction)
+ let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction)
+
+ addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@2x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@2x.png
new file mode 100755
index 00000000..81db2ed2
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@2x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@3x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@3x.png
new file mode 100755
index 00000000..dd341229
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@3x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@2x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@2x.png
new file mode 100755
index 00000000..cfc40d6e
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@2x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@3x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@3x.png
new file mode 100755
index 00000000..849b9913
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@3x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@2x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@2x.png
new file mode 100755
index 00000000..c8b9a874
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@2x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@3x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@3x.png
new file mode 100755
index 00000000..95c43973
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@3x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@2x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@2x.png
new file mode 100755
index 00000000..8ec96a9d
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@2x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@3x.png b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@3x.png
new file mode 100755
index 00000000..9304f50f
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Frameworks/IQKeyboardManagerSwift/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@3x.png differ
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/StringExtensions.swift b/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/StringExtensions.swift
new file mode 100644
index 00000000..a8ab73e2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/StringExtensions.swift
@@ -0,0 +1,28 @@
+//
+// StringExtensions.swift
+// GitHub: https://github.com/moraisandre/SwiftMaskText
+//
+// Created by Andre Morais on 3/11/16.
+// Translated to Swift 3 by: André Santana Ferreira on 31/5/17
+// Copyright © 2016 Andre Morais. All rights reserved.
+// Website: http://www.andremorais.com.br
+//
+
+import Foundation
+
+extension String {
+
+ subscript (i: Int) -> Character {
+ return self[self.index(self.startIndex, offsetBy: i)]
+ }
+
+ subscript (i: Int) -> String {
+ return String(self[i] as Character)
+ }
+
+ subscript (r: Range) -> String {
+ let start = index(startIndex, offsetBy: r.lowerBound)
+ let end = index(start, offsetBy: r.upperBound - r.lowerBound)
+ return String(self[(start ..< end)])
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/SwiftMaskField.swift b/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/SwiftMaskField.swift
new file mode 100644
index 00000000..796d3312
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Frameworks/SwiftMaskText/SwiftMaskField.swift
@@ -0,0 +1,188 @@
+//
+// SwiftMaskField.swift
+// GitHub: https://github.com/moraisandre/SwiftMaskText
+//
+// Created by Andre Morais on 3/9/16.
+// Translated to Swift 3 by: André Santana Ferreira on 31/5/17
+// Translated to Swift 4 by: André Morais on 04/11/17
+// Translated to Swift 4.2 by: Rafael Gustavo Gali on 07/01/19
+// Copyright © 2018 Andre Morais. All rights reserved.
+// Website: http://www.moraisandre.com
+//
+
+import UIKit
+
+open class SwiftMaskField: UITextField {
+
+ private var _mask: String!
+
+ @IBInspectable public var maskString: String {
+
+ get {
+ return _mask
+ }
+
+ set {
+ _mask = newValue
+ }
+
+ }
+
+ public func applyFilter(textField: UITextField) {
+
+ if _mask == nil || _mask.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) == "" {
+ return
+ }
+
+ var index = _mask.startIndex
+ var textWithMask: String = ""
+ var i: Int = 0
+ var text: String = textField.text!
+
+ if (text.isEmpty) {
+ return
+ }
+
+ text = removeMaskCharacters(text: text, withMask: maskString)
+
+ while(index != maskString.endIndex) {
+
+ if(i >= text.count) {
+ self.text = textWithMask
+ break
+ }
+
+ if("\(maskString[index])" == "N") { // Only number
+ if (!isNumber(textToValidate: text[i])) {
+ break
+ }
+ textWithMask = textWithMask + text[i]
+ i += 1
+ } else if("\(maskString[index])" == "C") { // Only Characters A-Z, Upper case only
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+
+ if (isNumber(textToValidate: text[i])) {
+ break
+ }
+ textWithMask = textWithMask + String(text[i]).uppercased()
+ i += 1
+ } else if("\(maskString[index])" == "c") { // Only Characters a-z, lower case only
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+
+ if (isNumber(textToValidate: text[i])) {
+ break
+ }
+ textWithMask = textWithMask + String(text[i]).lowercased()
+ i += 1
+ } else if("\(maskString[index])" == "X") { // Only Characters a-Z
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+
+ if (isNumber(textToValidate: text[i])) {
+ break
+ }
+ textWithMask = textWithMask + text[i]
+ i += 1
+ } else if("\(maskString[index])" == "%") { // Characters a-Z + Numbers
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+ textWithMask = textWithMask + text[i]
+ i += 1
+ } else if("\(maskString[index])" == "U") { // Only Characters A-Z + Numbers, Upper case only
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+
+ textWithMask = textWithMask + String(text[i]).uppercased()
+ i += 1
+ } else if("\(maskString[index])" == "u") { // Only Characters a-z + Numbers, lower case only
+ if(hasSpecialCharacter(searchTerm: text[i])) {
+ break
+ }
+
+ textWithMask = textWithMask + String(text[i]).lowercased()
+ i += 1
+ } else if("\(maskString[index])" == "*") { // Any Character
+ textWithMask = textWithMask + text[i]
+ i += 1
+ } else {
+ textWithMask = textWithMask + "\(maskString[index])"
+ }
+
+
+ index = _mask.index(after: index)
+ }
+
+ self.text = textWithMask
+ }
+
+ public func isNumber(textToValidate: String) -> Bool {
+
+ let num = Int(textToValidate)
+
+ if num != nil {
+ return true
+ }
+
+ return false
+ }
+
+ public func hasSpecialCharacter(searchTerm: String) -> Bool {
+ let regex = try! NSRegularExpression(pattern: ".*[^A-Za-z0-9].*", options: NSRegularExpression.Options())
+
+ if regex.firstMatch(in: searchTerm, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, searchTerm.count)) != nil {
+ return true
+ }
+
+ return false
+
+ }
+
+ public func removeMaskCharacters(text: String, withMask mask: String) -> String {
+
+ var mask = mask
+ var text = text
+ mask = mask.replacingOccurrences(of: "X", with: "")
+ mask = mask.replacingOccurrences(of: "N", with: "")
+ mask = mask.replacingOccurrences(of: "C", with: "")
+ mask = mask.replacingOccurrences(of: "c", with: "")
+ mask = mask.replacingOccurrences(of: "U", with: "")
+ mask = mask.replacingOccurrences(of: "u", with: "")
+ mask = mask.replacingOccurrences(of: "*", with: "")
+
+ var index = mask.startIndex
+
+ while(index != mask.endIndex) {
+ text = text.replacingOccurrences(of: "\(mask[index])", with: "")
+ index = mask.index(after: index)
+ }
+
+ return text
+ }
+
+ override open func draw(_ rect: CGRect) {
+ super.draw(rect)
+
+ addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions(), context: nil)
+
+ self.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
+ }
+
+ @objc func textFieldDidChange(textField: UITextField) {
+ applyFilter(textField: textField)
+ }
+
+}
+
+extension SwiftMaskField {
+
+ override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
+
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Info.plist b/SantanderChallenge/SantanderChallenge/Info.plist
new file mode 100644
index 00000000..290b74d6
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Info.plist
@@ -0,0 +1,59 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ ENVIRONMENT_NAME
+ $(ENVIRONMENT_NAME)
+ UIAppFonts
+
+ DINEngschriftStd.otf
+ DINMittelschriftStd.otf
+ DINNeuzeitGroteskStd-BdCond.otf
+ DINNeuzeitGroteskStd-Light.otf
+ DINPro-Black.otf
+ DINPro-Bold.otf
+ DINPro-Light.otf
+ DINPro-Medium.otf
+ DINPro-Regular.otf
+
+
+
diff --git a/SantanderChallenge/SantanderChallenge/Managers/NetworkManager.swift b/SantanderChallenge/SantanderChallenge/Managers/NetworkManager.swift
new file mode 100644
index 00000000..2c8119aa
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Managers/NetworkManager.swift
@@ -0,0 +1,45 @@
+//
+// NetworkManager.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+class NetworkManager {
+
+ private var provider: NetworkProviderProtocol!
+
+ init() {
+ defineProviderBasedOnEnvironment()
+ }
+
+ init(provider: NetworkProviderProtocol) {
+ self.provider = provider
+ }
+
+ private func defineProviderBasedOnEnvironment() {
+ guard let environment = EnvironmentManager.shared.environmentId else {
+ fatalError("Environment not found")
+ }
+
+ switch environment {
+ case .development:
+ provider = DevelopmentNetworkProvider()
+ case .production:
+ provider = ProductionNetworkProvider()
+ }
+ }
+}
+
+extension NetworkManager {
+
+ func fetchFormFields(_ completion: @escaping (NetworkResponse) -> Void) {
+ provider.fetchFormData(completion)
+ }
+
+ func fetchFunds(_ completion: @escaping (NetworkResponse) -> Void) {
+ provider.fetchFundsData(completion)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/CellType.swift b/SantanderChallenge/SantanderChallenge/Models/CellType.swift
new file mode 100644
index 00000000..ebc22602
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/CellType.swift
@@ -0,0 +1,16 @@
+//
+// CellType.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import Foundation
+
+enum CellType: Int, Decodable {
+ case field = 1
+ case text = 2
+ case image = 3
+ case checkbox = 4
+ case send = 5
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/ContactData.swift b/SantanderChallenge/SantanderChallenge/Models/ContactData.swift
new file mode 100644
index 00000000..4ed37d73
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/ContactData.swift
@@ -0,0 +1,20 @@
+//
+// ContactData.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+class ContactData {
+ var name: String
+ var phone: String
+ var email: String?
+
+ init(name: String, phone: String, email: String? = nil) {
+ self.name = name
+ self.phone = phone
+ self.email = email
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/DownInfo.swift b/SantanderChallenge/SantanderChallenge/Models/DownInfo.swift
new file mode 100644
index 00000000..b5cdb909
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/DownInfo.swift
@@ -0,0 +1,13 @@
+//
+// DownInfo.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct DownInfo: Decodable {
+ let name: String
+ let data: String?
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/FieldType.swift b/SantanderChallenge/SantanderChallenge/Models/FieldType.swift
new file mode 100644
index 00000000..db19048c
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/FieldType.swift
@@ -0,0 +1,40 @@
+//
+// FieldType.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import Foundation
+
+enum FieldType: Int, Decodable {
+ case unknown = 0
+ case text = 1
+ case phone = 2
+ case email = 3
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+
+ if let typeAsInt = try? container.decode(Int.self) {
+ self = FieldType(rawValue: typeAsInt) ?? .unknown
+ return
+ }
+
+ if let typeAsString = try? container.decode(String.self) {
+ self = FieldType(string: typeAsString)
+ return
+ }
+
+ self = .unknown
+ }
+
+ init(string: String) {
+ switch string {
+ case "telnumber":
+ self = .phone
+ default:
+ self = .unknown
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/FormCell.swift b/SantanderChallenge/SantanderChallenge/Models/FormCell.swift
new file mode 100644
index 00000000..1c6f4556
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/FormCell.swift
@@ -0,0 +1,30 @@
+//
+// FormCell.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import Foundation
+
+class FormCell: Decodable {
+ let id: Int?
+ let type: CellType?
+ let message: String?
+ let fieldType: FieldType?
+ var hidden: Bool?
+ let topSpacing: Double?
+ let fieldToPresent: Int?
+ let required: Bool?
+
+ private enum CodingKeys: String, CodingKey {
+ case id
+ case type
+ case message
+ case fieldType = "typefield"
+ case hidden
+ case topSpacing
+ case fieldToPresent = "show"
+ case required
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/FundInfo.swift b/SantanderChallenge/SantanderChallenge/Models/FundInfo.swift
new file mode 100644
index 00000000..7230b1e2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/FundInfo.swift
@@ -0,0 +1,13 @@
+//
+// FundInfo.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct FundInfo: Decodable {
+ let name: String
+ let data: String
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/FundMoreInfo.swift b/SantanderChallenge/SantanderChallenge/Models/FundMoreInfo.swift
new file mode 100644
index 00000000..9199c49b
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/FundMoreInfo.swift
@@ -0,0 +1,20 @@
+//
+// FundMoreInfo.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct FundMoreInfo: Decodable {
+ let month: FundPercentages
+ let year: FundPercentages
+ let twelveMonths: FundPercentages
+
+ private enum CodingKeys: String, CodingKey {
+ case month
+ case year
+ case twelveMonths = "12months"
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/FundPercentages.swift b/SantanderChallenge/SantanderChallenge/Models/FundPercentages.swift
new file mode 100644
index 00000000..a7aa8009
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/FundPercentages.swift
@@ -0,0 +1,26 @@
+//
+// FundPercentages.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct FundPercentages: Decodable {
+ let fund: Double
+ let cdi: Double
+
+ var fundPresentable: String {
+ return "\(fund)%"
+ }
+
+ var cdiPresentable: String {
+ return "\(cdi)%"
+ }
+
+ private enum CodingKeys: String, CodingKey {
+ case fund
+ case cdi = "CDI"
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Models/Funds.swift b/SantanderChallenge/SantanderChallenge/Models/Funds.swift
new file mode 100644
index 00000000..61b80e1c
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Models/Funds.swift
@@ -0,0 +1,21 @@
+//
+// Funds.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct Funds: Decodable {
+ let title: String
+ let fundName: String
+ let whatIs: String
+ let definition: String
+ let riskTitle: String
+ let risk: Int
+ let infoTitle: String
+ let moreInfo: FundMoreInfo
+ let info: [FundInfo]
+ let downInfo: [DownInfo]
+}
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Colors.swift b/SantanderChallenge/SantanderChallenge/Resources/Colors.swift
new file mode 100644
index 00000000..da7b22f3
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Resources/Colors.swift
@@ -0,0 +1,18 @@
+//
+// Colors.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class Colors {
+ static let badRed: UIColor = UIColor(red: 255/255, green: 31/255, blue: 31/255, alpha: 1.0)
+ static let goodGreen: UIColor = UIColor(red: 101/255, green: 190/255, blue: 48/255, alpha: 1.0)
+
+ static let unselectedRed: UIColor = UIColor(red: 218/255, green: 1/255, blue: 1/255, alpha: 1.0)
+ static let selectedRed: UIColor = UIColor(red: 180/255, green: 1/255, blue: 1/255, alpha: 1.0)
+
+ static let shallowGray: UIColor = UIColor(red: 216/255, green: 216/255, blue: 216/255, alpha: 1.0)
+}
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINEngschriftStd.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINEngschriftStd.otf
new file mode 100755
index 00000000..ae85f8ee
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINEngschriftStd.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINMittelschriftStd.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINMittelschriftStd.otf
new file mode 100755
index 00000000..9a6e0d4f
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINMittelschriftStd.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf
new file mode 100755
index 00000000..1da42b06
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-BdCond.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-Light.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-Light.otf
new file mode 100755
index 00000000..0cda2e5b
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINNeuzeitGroteskStd-Light.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Black.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Black.otf
new file mode 100755
index 00000000..2092a7bb
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Black.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Bold.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Bold.otf
new file mode 100755
index 00000000..7c839536
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Bold.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Light.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Light.otf
new file mode 100755
index 00000000..8a7f085a
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Light.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Medium.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Medium.otf
new file mode 100755
index 00000000..b4608d06
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Medium.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Regular.otf b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Regular.otf
new file mode 100755
index 00000000..84d57abb
Binary files /dev/null and b/SantanderChallenge/SantanderChallenge/Resources/Fonts/DIN/DINPro-Regular.otf differ
diff --git a/SantanderChallenge/SantanderChallenge/Resources/MockResources/cells.json b/SantanderChallenge/SantanderChallenge/Resources/MockResources/cells.json
new file mode 100644
index 00000000..812cf171
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Resources/MockResources/cells.json
@@ -0,0 +1,64 @@
+{
+ "cells": [
+ {
+ "id": 1,
+ "type": 2,
+ "message": "Hi, first type your name:",
+ "typefield": null,
+ "hidden": false,
+ "topSpacing": 60.0,
+ "show": null,
+ "required": false
+ },
+ {
+ "id": 2,
+ "type": 1,
+ "message": "Full name",
+ "typefield": 1,
+ "hidden": false,
+ "topSpacing": 35.0,
+ "show": null,
+ "required": true
+ },
+ {
+ "id": 4,
+ "type": 1,
+ "message": "Email",
+ "typefield": 3,
+ "hidden": true,
+ "topSpacing": 35.0,
+ "show": null,
+ "required": true
+ },
+ {
+ "id": 6,
+ "type": 1,
+ "message": "Phone",
+ "typefield": "telnumber",
+ "hidden": false,
+ "topSpacing": 10.0,
+ "show": null,
+ "required": true
+ },
+ {
+ "id": 3,
+ "type": 4,
+ "message": "Register my email",
+ "typefield": null,
+ "hidden": false,
+ "topSpacing": 35.0,
+ "show": 4,
+ "required": false
+ },
+ {
+ "id": 7,
+ "type": 5,
+ "message": "Send",
+ "typefield": null,
+ "hidden": false,
+ "topSpacing": 10.0,
+ "show": null,
+ "required": true
+ }
+ ]
+}
diff --git a/SantanderChallenge/SantanderChallenge/Resources/MockResources/fund.json b/SantanderChallenge/SantanderChallenge/Resources/MockResources/fund.json
new file mode 100644
index 00000000..5afa0470
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Resources/MockResources/fund.json
@@ -0,0 +1,78 @@
+{
+ "screen": {
+ "title": "Fundos de investimento",
+ "fundName": "Vinci Valorem FI Multimercado",
+ "whatIs": "O que é?",
+ "definition": "O Fundo tem por objetivo proporcionar aos seus cotistas rentabilidade no longo prazo através de investimentos.",
+ "riskTitle": "Grau de risco do investimento",
+ "risk": 4,
+ "infoTitle": "Mais informações sobre o investimento",
+ "moreInfo": {
+ "month": {
+ "fund": 0.3,
+ "CDI": 0.3
+ },
+ "year": {
+ "fund": 13.01,
+ "CDI": 12.08
+ },
+ "12months": {
+ "fund": 17.9,
+ "CDI": 17.6
+ }
+ },
+ "info": [
+ {
+ "name": "Taxa de administração",
+ "data": "0,50%"
+ },
+ {
+ "name": "Aplicação inicial",
+ "data": "R$ 10.000,00"
+ },
+ {
+ "name": "Movimentação mínima",
+ "data": "R$ 1.000,00"
+ },
+ {
+ "name": "Saldo mínimo",
+ "data": "R$ 5.000,00"
+ },
+ {
+ "name": "Resgate (valor bruto)",
+ "data": "D+0"
+ },
+ {
+ "name": "Cota (valor bruto)",
+ "data": "D+1"
+ },
+ {
+ "name": "Pagamento (valor bruto)",
+ "data": "D+2"
+ }
+ ],
+ "downInfo": [
+ {
+ "name": "Essenciais",
+ "data": null
+ },
+ {
+ "name": "Desempenho",
+ "data": null
+ },
+ {
+ "name": "Complementares",
+ "data": null
+ },
+ {
+ "name": "Regulamento",
+ "data": null
+ },
+ {
+ "name": "Adesão",
+ "data": null
+ }
+ ]
+
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/BaseViewController.swift b/SantanderChallenge/SantanderChallenge/Scenes/BaseViewController.swift
new file mode 100644
index 00000000..a32364a9
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/BaseViewController.swift
@@ -0,0 +1,37 @@
+//
+// BaseViewController.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class BaseViewController: UIViewController {
+
+ lazy var activityIndicator: UIActivityIndicatorView = {
+ let activity = UIActivityIndicatorView(style: .whiteLarge)
+ activity.color = Colors.selectedRed
+ activity.hidesWhenStopped = true
+
+ view.addSubview(activity)
+ activity.center = view.center
+
+ return activity
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ }
+
+ func startLoading() {
+ view.isUserInteractionEnabled = false
+ activityIndicator.startAnimating()
+ }
+
+ func stopLoading() {
+ view.isUserInteractionEnabled = true
+ activityIndicator.stopAnimating()
+ }
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Container/ContainerViewController.swift b/SantanderChallenge/SantanderChallenge/Scenes/Container/ContainerViewController.swift
new file mode 100644
index 00000000..f8c9a79e
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Container/ContainerViewController.swift
@@ -0,0 +1,96 @@
+//
+// ContainerViewController.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class ContainerViewController: UIViewController {
+
+ @IBOutlet weak var containerView: UIView!
+ @IBOutlet weak var fundsButton: UIButton!
+ @IBOutlet weak var formButton: UIButton!
+ @IBOutlet weak var indicatorViewConstraint: NSLayoutConstraint!
+
+ private var currentSelectedButton: UIButton?
+
+ // MARK: - Child controllers
+ lazy var fundsViewController: FundsViewController = {
+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ let controller = storyboard.instantiateViewController(withIdentifier: "FundsViewController")
+ guard let fundsController = controller as? FundsViewController else { return FundsViewController() }
+ return fundsController
+ }()
+
+ lazy var formViewController: FormViewController = {
+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ let controller = storyboard.instantiateViewController(withIdentifier: "FormViewController")
+ guard let formController = controller as? FormViewController else { return FormViewController() }
+ return formController
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ addControllerToContainer(controllerToAdd: formViewController)
+ didTouchAt(formButton)
+ }
+
+ private func addControllerToContainer(controllerToAdd controller: UIViewController) {
+ addChild(controller)
+
+ containerView.addSubview(controller.view)
+
+ controller.view.frame = containerView.bounds
+ controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ controller.didMove(toParent: self)
+ }
+
+ private func removeControllerFromContainer(controllerToRemove controller: UIViewController) {
+ controller.willMove(toParent: nil)
+ controller.view.removeFromSuperview()
+ controller.removeFromParent()
+ }
+
+ private func updateContainerView() {
+ if currentSelectedButton == formButton {
+ if containerView.subviews.contains(fundsViewController.view) {
+ removeControllerFromContainer(controllerToRemove: fundsViewController)
+ addControllerToContainer(controllerToAdd: formViewController)
+ }
+ } else {
+ if containerView.subviews.contains(formViewController.view) {
+ removeControllerFromContainer(controllerToRemove: formViewController)
+ addControllerToContainer(controllerToAdd: fundsViewController)
+ }
+ }
+ }
+
+ private func updateIndicatorView() {
+ view.setNeedsLayout()
+ view.layoutIfNeeded()
+
+ guard let currentButton = currentSelectedButton else { return }
+ indicatorViewConstraint.constant = currentButton.frame.origin.x
+ UIView.animate(withDuration: 0.5) {
+ self.view.layoutIfNeeded()
+ }
+ }
+
+ private func updateButtonsBackgroundColor(button: UIButton) {
+ currentSelectedButton?.backgroundColor = Colors.unselectedRed
+ button.backgroundColor = Colors.selectedRed
+ }
+
+ // MARK: - Actions
+ @IBAction func didTouchAt(_ button: UIButton) {
+ updateButtonsBackgroundColor(button: button)
+
+ currentSelectedButton = button
+ updateIndicatorView()
+ updateContainerView()
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Form/FormInteractor.swift b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormInteractor.swift
new file mode 100644
index 00000000..7457274d
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormInteractor.swift
@@ -0,0 +1,84 @@
+//
+// FormInteractor.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+enum ValidationError: Error {
+ case invalidName
+ case invalidPhone
+ case invalidEmail
+
+ var detail: String {
+ switch self {
+ case .invalidName:
+ return "Check your name"
+ case .invalidPhone:
+ return "Check your phone"
+ case .invalidEmail:
+ return "Check your email"
+ }
+ }
+}
+
+class FormInteractor {
+
+ private let networkManager: NetworkManager
+
+ var presenter: FormPresenterProtocol
+
+ init(presenter: FormPresenterProtocol,
+ networkManager: NetworkManager = NetworkManager()) {
+ self.networkManager = networkManager
+ self.presenter = presenter
+ }
+
+ func fetchForm() {
+ networkManager.fetchFormFields { (result) in
+ switch result {
+ case .success(let form):
+ self.presenter.presentFormData(form)
+ case .failure(let error):
+ self.presenter.presentError(error.localizedDescription)
+ }
+ }
+ }
+}
+
+
+// MARK: - Validators
+extension FormInteractor {
+ func isValid(name: String) -> Bool {
+ return !name.isEmpty
+ }
+
+ func isValid(phone: String) -> Bool {
+ let raw = phone.removingNonDigitCharacters
+ return 10...11 ~= raw.count
+ }
+
+ func isValid(email: String) -> Bool {
+ return email.validEmail
+ }
+
+ func isContactDataValid(_ contactData: ContactData) -> Result {
+
+ if !isValid(name: contactData.name) {
+ return .failure(.invalidName)
+ }
+
+ if !isValid(phone: contactData.phone) {
+ return .failure(.invalidPhone)
+ }
+
+ if let email = contactData.email {
+ if !isValid(email: email) {
+ return .failure(.invalidEmail)
+ }
+ }
+ return .success(true)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Form/FormPresenter.swift b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormPresenter.swift
new file mode 100644
index 00000000..13e90025
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormPresenter.swift
@@ -0,0 +1,40 @@
+//
+// FormPresenter.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+protocol FormPresenterProtocol {
+ func presentFormData(_ formData: Data)
+ func presentError(_ error: String)
+}
+
+class FormPresenter: FormPresenterProtocol {
+ weak var view: FormPresentableProtocol?
+
+ init(view: FormPresentableProtocol) {
+ self.view = view
+ }
+
+ func presentFormData(_ formData: Data) {
+
+ do {
+ let response = try JSONDecoder().decode(FormCellsResponse.self, from: formData)
+ let cells = response.cells
+ DispatchQueue.main.async {
+ self.view?.displayForm(cells)
+ }
+ } catch {
+ self.view?.displayError(error.localizedDescription)
+ }
+ }
+
+ func presentError(_ error: String) {
+ DispatchQueue.main.async {
+ self.view?.displayError(error)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Form/FormViewController.swift b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormViewController.swift
new file mode 100644
index 00000000..e9d0e257
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Form/FormViewController.swift
@@ -0,0 +1,274 @@
+//
+// FormViewController.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import UIKit
+
+protocol FormPresentableProtocol: AnyObject {
+ func displayForm(_ cells: [FormCell])
+ func displayError(_ error: String)
+}
+
+class FormViewController: BaseViewController {
+
+ @IBOutlet weak var tableView: UITableView!
+
+ fileprivate var interactor: FormInteractor?
+ fileprivate var cells: [FormCell] = []
+ fileprivate var visibleCells: [FormCell] {
+ return cells.compactMap { cell -> FormCell? in
+ if cell.hidden == false {
+ return cell
+ }
+ return nil
+ }
+ }
+
+ fileprivate var contactData: ContactData!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ setupTableView()
+ setupInteractor()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(true)
+ contactData = ContactData(name: "", phone: "")
+
+ startLoading()
+ interactor?.fetchForm()
+ }
+
+ private func setupTableView() {
+
+ tableView.registerCellsNib(cellsClass: [
+ TitleTableViewCell.self,
+ InputTextTableViewCell.self,
+ InputEmailTableViewCell.self,
+ InputPhoneTableViewCell.self,
+ CheckboxTableViewCell.self,
+ ActionButtonTableViewCell.self
+ ])
+
+ tableView.tableFooterView = UIViewController.blankView
+ }
+
+ private func setupInteractor() {
+ let presenter = FormPresenter(view: self)
+ interactor = FormInteractor(presenter: presenter)
+ }
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ super.prepare(for: segue, sender: sender)
+
+ let destination = segue.destination as? SuccessViewController
+ destination?.willDismiss = willDismissSuccessController
+ }
+
+ private func willDismissSuccessController() {
+ contactData = ContactData(name: "", phone: "")
+ tableView.reloadData()
+ }
+}
+
+
+extension FormViewController: FormPresentableProtocol {
+ func displayForm(_ cells: [FormCell]) {
+ defer {
+ stopLoading()
+ }
+
+ self.cells = cells
+ tableView.reloadData()
+ }
+
+ func displayError(_ error: String) {
+ stopLoading()
+ let alert = UIAlertController(title: "🤨", message: error, preferredStyle: .alert)
+ let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
+
+ alert.addAction(okAction)
+
+ present(alert, animated: true, completion: nil)
+ }
+}
+
+
+extension FormViewController: UITableViewDataSource {
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ return visibleCells.count
+ }
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return 1
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = visibleCells[indexPath.section]
+ guard let type = cell.type else { return UITableViewCell() }
+
+ switch type {
+ case .text:
+ return generateTextCell(for: tableView, with: cell)
+ case .field:
+ return generateFieldCell(for: tableView, with: cell)
+ case .checkbox:
+ return generateCheckboxCell(for: tableView, with: cell)
+ case .send:
+ return generateActionButtonCell(for: tableView, with: cell)
+ default:
+ return UITableViewCell()
+ }
+ }
+}
+
+extension FormViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ return UITableView.automaticDimension
+ }
+
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ return CGFloat(visibleCells[section].topSpacing ?? 0.0)
+ }
+
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ return UIViewController.blankView
+ }
+}
+
+// MARK: - Cells Generation
+fileprivate extension FormViewController {
+ func generateTextCell(for tableView: UITableView, with cellData: FormCell) -> UITableViewCell {
+ guard let cell: TitleTableViewCell = tableView.dequeueReusableCell(cellType: TitleTableViewCell.self) else { return UITableViewCell()
+ }
+
+ cell.titleLabel.text = cellData.message
+
+ return cell
+ }
+
+ func generateFieldCell(for tableView: UITableView, with cellData: FormCell) -> UITableViewCell {
+
+ guard let fieldType = cellData.fieldType else { return UITableViewCell() }
+ let cell: InputTextFieldTableViewCell?
+
+ switch fieldType {
+ case .text:
+ cell = tableView.dequeueReusableCell(cellType: InputTextTableViewCell.self)
+ cell?.textField.text = contactData.name
+ case .email:
+ cell = tableView.dequeueReusableCell(cellType: InputEmailTableViewCell.self)
+ cell?.textField.text = contactData.email
+ case .phone:
+ cell = tableView.dequeueReusableCell(cellType: InputPhoneTableViewCell.self)
+ cell?.textField.text = contactData.phone
+ default:
+ return UITableViewCell()
+ }
+
+ cell?.setValidIndicatorColor(status: .notChecked)
+ cell?.set(placeholder: cellData.message)
+ cell?.delegate = self
+ return cell ?? UITableViewCell()
+ }
+
+ func generateCheckboxCell(for tableView: UITableView, with cellData: FormCell) -> UITableViewCell {
+ guard let cell: CheckboxTableViewCell = tableView.dequeueReusableCell(cellType: CheckboxTableViewCell.self) else { return UITableViewCell()
+ }
+ cell.cellData = cellData
+ cell.delegate = self
+
+ // Current status
+ guard let targetId = cell.cellData?.fieldToPresent,
+ let index = formCellIndex(withId: targetId) else { return UITableViewCell() }
+ let targetFormCell = cells[index]
+
+ if targetFormCell.hidden == true {
+ cell.checkBoxStatus = .unselected
+ } else {
+ cell.checkBoxStatus = .selected
+ }
+
+ return cell
+ }
+
+ func generateActionButtonCell(for tableView: UITableView, with cellData: FormCell) -> UITableViewCell {
+ guard let cell: ActionButtonTableViewCell = tableView.dequeueReusableCell(cellType: ActionButtonTableViewCell.self) else { return UITableViewCell()
+ }
+
+ cell.actionButton.setTitle(cellData.message, for: .normal)
+ cell.delegate = self
+ return cell
+ }
+}
+
+extension FormViewController: CheckBoxTableViewDelegate {
+ func checkboxUpdated(status: CheckBoxSelectionStatus, atCell cell: CheckboxTableViewCell) {
+ guard let targetId = cell.cellData?.fieldToPresent,
+ let index = formCellIndex(withId: targetId) else { return }
+ let targetFormCell = cells[index]
+
+ switch status {
+ case .selected:
+ targetFormCell.hidden = false
+ contactData.email = ""
+ case .unselected:
+ targetFormCell.hidden = true
+ contactData.email = nil
+ }
+ tableView.reloadData()
+
+ }
+
+ private func formCellIndex(withId targetId: Int) -> Int? {
+ return cells.firstIndex(where: {
+ return $0.id == targetId
+ })
+ }
+}
+
+extension FormViewController: InputTextFieldTableViewCellDelegate {
+ func editingChanged(text: String, atCell cell: InputTextFieldTableViewCell) {
+ let valid: Bool
+
+ switch cell {
+ case is InputTextTableViewCell:
+ valid = interactor?.isValid(name: text) == true
+ contactData.name = text
+ case is InputPhoneTableViewCell:
+ valid = interactor?.isValid(phone: text) == true
+ contactData.phone = text
+ case is InputEmailTableViewCell:
+ valid = interactor?.isValid(email: text) == true
+ contactData.email = text
+ default:
+ return
+ }
+
+ if valid {
+ cell.setValidIndicatorColor(status: .valid)
+ } else {
+ cell.setValidIndicatorColor(status: .invalid)
+ }
+ }
+}
+
+extension FormViewController: ActionButtonTableViewCellDelegate {
+ func didTouchActionButton(atCell cell: ActionButtonTableViewCell) {
+
+ guard let result = interactor?.isContactDataValid(contactData) else { return }
+
+ switch result {
+ case .success:
+ performSegue(withIdentifier: "showSuccess", sender: nil)
+ case .failure(let error):
+ displayError(error.detail)
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsInteractor.swift b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsInteractor.swift
new file mode 100644
index 00000000..53746d5f
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsInteractor.swift
@@ -0,0 +1,31 @@
+//
+// FundsInteractor.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+class FundsInteractor {
+
+ private let networkManager: NetworkManager
+ private let presenter: FundsPresenterProtocol
+
+ init(presenter: FundsPresenterProtocol,
+ networkManager: NetworkManager = NetworkManager()) {
+ self.networkManager = networkManager
+ self.presenter = presenter
+ }
+
+ func fetchFunds() {
+ networkManager.fetchFunds { result in
+ switch result {
+ case .success(let funds):
+ self.presenter.presentFundsData(funds)
+ case .failure(let error):
+ self.presenter.presentError(error.localizedDescription)
+ }
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsPresenter.swift b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsPresenter.swift
new file mode 100644
index 00000000..d3c46a78
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsPresenter.swift
@@ -0,0 +1,92 @@
+//
+// FundsPresenter.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+protocol FundsPresenterProtocol {
+ func presentFundsData(_ fundsData: Data)
+ func presentError(_ error: String)
+}
+
+enum FundContentData {
+ case title(String)
+ case fundName(String)
+ case definition(title: String, description: String)
+ case risk(title: String, level: Int)
+ case infoTitle(String)
+ case moreInfo(FundMoreInfo)
+ case info(FundInfo)
+ case downInfo(DownInfo)
+ case actionButton(String)
+ case separator
+}
+
+class FundsPresenter: FundsPresenterProtocol {
+ weak var view: FundsPresentableProtocol?
+
+ init(view: FundsPresentableProtocol) {
+ self.view = view
+ }
+
+ func presentFundsData(_ fundsData: Data) {
+ guard let funds = parse(fundsData: fundsData) else {
+ self.view?.displayError("Invalid funds")
+ return
+ }
+
+ let content = buildListOfContent(funds: funds)
+
+ DispatchQueue.main.async {
+ self.view?.displayFunds(content)
+ }
+ }
+
+ func presentError(_ error: String) {
+ DispatchQueue.main.async {
+ self.view?.displayError(error)
+ }
+ }
+}
+
+extension FundsPresenter {
+
+ private func parse(fundsData: Data) -> Funds? {
+ do {
+ let response = try JSONDecoder().decode(FundsResponse.self, from: fundsData)
+ return response.screen
+ } catch {
+ print(error)
+ return nil
+ }
+ }
+
+ private func buildListOfContent(funds: Funds) -> [FundContentData] {
+ var content: [FundContentData] = []
+ content.append(.title(funds.title))
+ content.append(.fundName(funds.fundName))
+ content.append(.definition(title: funds.whatIs, description: funds.definition))
+ content.append(.risk(title: funds.riskTitle, level: funds.risk))
+ content.append(.infoTitle(funds.infoTitle))
+ content.append(.moreInfo(funds.moreInfo))
+ content.append(.separator)
+
+ // Convert each info into a content row
+ let infos = funds.info.map {
+ return FundContentData.info($0)
+ }
+ content.append(contentsOf: infos)
+
+ // Convert each down info into a content row
+ let downInfos = funds.downInfo.map {
+ return FundContentData.downInfo($0)
+ }
+ content.append(contentsOf: downInfos)
+
+ content.append(.actionButton("Investir"))
+ return content
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsViewController.swift b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsViewController.swift
new file mode 100644
index 00000000..fe3feef2
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Funds/FundsViewController.swift
@@ -0,0 +1,252 @@
+//
+// FundsViewController.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+import SafariServices
+
+protocol FundsPresentableProtocol: AnyObject {
+ func displayFunds(_ cells: [FundContentData])
+ func displayError(_ error: String)
+}
+
+class FundsViewController: BaseViewController {
+
+ @IBOutlet weak var tableView: UITableView!
+
+ fileprivate var interactor: FundsInteractor?
+ fileprivate var cells: [FundContentData] = []
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ view.superview?.layoutIfNeeded()
+
+ setupTableView()
+ setupInteractor()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ self.view.setNeedsUpdateConstraints()
+
+ startLoading()
+ interactor?.fetchFunds()
+ }
+
+ private func setupTableView() {
+ // Register cells
+ tableView.registerCellsNib(cellsClass: [
+ FundTitleTableViewCell.self,
+ FundNameTableViewCell.self,
+ FundDefinitionTableViewCell.self,
+ FundRiskTableViewCell.self,
+ FundInfoTitleTableViewCell.self,
+ FundMoreInfoTableViewCell.self,
+ SeparatorTableViewCell.self,
+ FundInfoTableViewCell.self,
+ FundDownInfoTableViewCell.self,
+ ActionButtonTableViewCell.self
+ ])
+
+ tableView.tableFooterView = UIViewController.blankView
+ }
+
+ private func setupInteractor() {
+ let presenter = FundsPresenter(view: self)
+ interactor = FundsInteractor(presenter: presenter)
+ }
+}
+
+extension FundsViewController: FundsPresentableProtocol {
+
+ func displayFunds(_ cells: [FundContentData]) {
+
+ defer {
+ stopLoading()
+ }
+
+ self.cells = cells
+ tableView.reloadData()
+ }
+
+ func displayError(_ error: String) {
+ stopLoading()
+
+ func displayError(_ error: String) {
+ let alert = UIAlertController(title: "🤨", message: error, preferredStyle: .alert)
+ let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
+
+ alert.addAction(okAction)
+
+ present(alert, animated: true, completion: nil)
+ }
+ }
+}
+
+extension FundsViewController: UITableViewDataSource {
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return cells.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let content = cells[indexPath.row]
+
+ switch content {
+ case .title:
+ return generateTitleCell(for: tableView, with: content)
+ case .fundName:
+ return generateFundNameCell(for: tableView, with: content)
+ case .definition:
+ return generateFundDefinitionCell(for: tableView, with: content)
+ case .risk:
+ return generateFundRiskCell(for: tableView, with: content)
+ case .infoTitle:
+ return generateFundInfoTitleCell(for: tableView, with: content)
+ case .moreInfo:
+ return generateFundMoreInfoCell(for: tableView, with: content)
+ case .separator:
+ return generateSeparatorCell(for: tableView)
+ case .info:
+ return generateFundInfoCell(for: tableView, with: content)
+ case .downInfo:
+ return generateFundDownInfoCell(for: tableView, with: content)
+ case .actionButton:
+ return generateActionButtonCell(for: tableView, with: content)
+ }
+
+ }
+}
+
+extension FundsViewController: UITableViewDelegate {
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ return UITableView.automaticDimension
+ }
+}
+
+// MARK: - Cells Generation
+extension FundsViewController {
+ func generateTitleCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundTitleTableViewCell = tableView.dequeueReusableCell(cellType: FundTitleTableViewCell.self)
+
+ if case let FundContentData.title(fundTitle) = content {
+ cell.titleLabel.text = fundTitle
+ }
+
+ return cell
+ }
+
+ func generateFundNameCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundNameTableViewCell = tableView.dequeueReusableCell(cellType: FundNameTableViewCell.self)
+
+ if case let FundContentData.fundName(name) = content {
+ cell.nameLabel.text = name
+ }
+
+ return cell
+ }
+
+ func generateFundDefinitionCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundDefinitionTableViewCell = tableView.dequeueReusableCell(cellType: FundDefinitionTableViewCell.self)
+
+ if case let FundContentData.definition(title, description) = content {
+ cell.topLabel.text = title
+ cell.descriptionLabel.text = description
+ }
+
+ return cell
+ }
+
+ func generateFundRiskCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundRiskTableViewCell = tableView.dequeueReusableCell(cellType: FundRiskTableViewCell.self)
+
+ if case let FundContentData.risk(title, level) = content {
+ cell.titleLabel.text = title
+ cell.set(level: level)
+ }
+
+ return cell
+ }
+
+ func generateFundInfoTitleCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundInfoTitleTableViewCell = tableView.dequeueReusableCell(cellType: FundInfoTitleTableViewCell.self)
+
+ if case let FundContentData.infoTitle(title) = content {
+ cell.infoTitleLabel.text = title
+ }
+
+ return cell
+ }
+
+ func generateFundMoreInfoCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundMoreInfoTableViewCell = tableView.dequeueReusableCell(cellType: FundMoreInfoTableViewCell.self)
+
+ if case let FundContentData.moreInfo(moreInfo) = content {
+ cell.dataSource = moreInfo
+ }
+
+ return cell
+ }
+
+ func generateSeparatorCell(for tableView: UITableView) -> UITableViewCell {
+ return tableView.dequeueReusableCell(cellType: SeparatorTableViewCell.self)
+ }
+
+ func generateFundInfoCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundInfoTableViewCell = tableView.dequeueReusableCell(cellType: FundInfoTableViewCell.self)
+
+ if case let FundContentData.info(info) = content {
+ cell.leftLabel.text = info.name
+ cell.rightLabel.text = info.data
+ }
+
+ return cell
+ }
+
+ func generateFundDownInfoCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+
+ let cell: FundDownInfoTableViewCell = tableView.dequeueReusableCell(cellType: FundDownInfoTableViewCell.self)
+
+ if case let FundContentData.downInfo(info) = content {
+ cell.leftLabel.text = info.name
+ }
+ cell.delegate = self
+
+ return cell
+ }
+
+ func generateActionButtonCell(for tableView: UITableView, with content: FundContentData) -> UITableViewCell {
+ let cell: ActionButtonTableViewCell = tableView.dequeueReusableCell(cellType: ActionButtonTableViewCell.self)
+
+ if case let FundContentData.actionButton(title) = content {
+ cell.actionButton.setTitle(title, for: .normal)
+ }
+
+ return cell
+ }
+}
+
+extension FundsViewController: FundDownInfoTableViewCellDelegate {
+ func didTouchDownload(at cell: FundDownInfoTableViewCell) {
+ guard let googleURL = URL(string: "https://google.com") else { return }
+ let safari = SFSafariViewController(url: googleURL)
+ safari.delegate = self
+ self.present(safari, animated: true, completion: nil)
+ }
+}
+
+extension FundsViewController: SFSafariViewControllerDelegate {
+ func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
+ controller.dismiss(animated: true, completion: nil)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Scenes/Success/SuccessViewController.swift b/SantanderChallenge/SantanderChallenge/Scenes/Success/SuccessViewController.swift
new file mode 100644
index 00000000..e6c12744
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Scenes/Success/SuccessViewController.swift
@@ -0,0 +1,25 @@
+//
+// SuccessViewController.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import UIKit
+
+class SuccessViewController: UIViewController {
+
+ var willDismiss: (() -> Void)?
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Do any additional setup after loading the view.
+ }
+
+ @IBAction func didTouchAt(button: UIButton) {
+ willDismiss?()
+ dismiss(animated: true, completion: nil)
+ }
+
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/FormCellsResponse.swift b/SantanderChallenge/SantanderChallenge/Services/FormCellsResponse.swift
new file mode 100644
index 00000000..3d0b35f1
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/FormCellsResponse.swift
@@ -0,0 +1,12 @@
+//
+// FormCellsResponse.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 06/07/19.
+//
+
+import Foundation
+
+struct FormCellsResponse: Decodable {
+ let cells: [FormCell]
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/FundsResponse.swift b/SantanderChallenge/SantanderChallenge/Services/FundsResponse.swift
new file mode 100644
index 00000000..54f89388
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/FundsResponse.swift
@@ -0,0 +1,12 @@
+//
+// FundsResponse.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 07/07/19.
+//
+
+import Foundation
+
+struct FundsResponse: Decodable {
+ let screen: Funds
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/NetworkError.swift b/SantanderChallenge/SantanderChallenge/Services/NetworkError.swift
new file mode 100644
index 00000000..736235f1
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/NetworkError.swift
@@ -0,0 +1,15 @@
+//
+// NetworkError.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+enum NetworkError: Error {
+ case invalidPath
+ case invalidStatusCode
+ case missingResponseData
+ case responseError(String)
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/NetworkProviderProtocol.swift b/SantanderChallenge/SantanderChallenge/Services/NetworkProviderProtocol.swift
new file mode 100644
index 00000000..80ca9acf
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/NetworkProviderProtocol.swift
@@ -0,0 +1,15 @@
+//
+// NetworkProviderProtocol.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+typealias NetworkResponse = Result
+
+protocol NetworkProviderProtocol {
+ func fetchFormData(_ completion: @escaping (NetworkResponse) -> Void)
+ func fetchFundsData(_ completion: @escaping (NetworkResponse) -> Void)
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/Providers/DevelopmentNetworkProvider.swift b/SantanderChallenge/SantanderChallenge/Services/Providers/DevelopmentNetworkProvider.swift
new file mode 100644
index 00000000..2f589a66
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/Providers/DevelopmentNetworkProvider.swift
@@ -0,0 +1,32 @@
+//
+// DevelopmentNetworkProvider.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+class DevelopmentNetworkProvider: NetworkProviderProtocol {
+ func fetchFormData(_ completion: @escaping (NetworkResponse) -> Void) {
+ guard let data = Bundle.loadJSONFromBundle(resourceName: "cells") else {
+ completion(.failure(.missingResponseData))
+ return
+ }
+
+ DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1) {
+ completion(.success(data))
+ }
+ }
+
+ func fetchFundsData(_ completion: @escaping (NetworkResponse) -> Void) {
+ guard let data = Bundle.loadJSONFromBundle(resourceName: "fund") else {
+ completion(.failure(.missingResponseData))
+ return
+ }
+
+ DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1) {
+ completion(.success(data))
+ }
+ }
+}
diff --git a/SantanderChallenge/SantanderChallenge/Services/Providers/ProductionNetworkProvider.swift b/SantanderChallenge/SantanderChallenge/Services/Providers/ProductionNetworkProvider.swift
new file mode 100644
index 00000000..acb66578
--- /dev/null
+++ b/SantanderChallenge/SantanderChallenge/Services/Providers/ProductionNetworkProvider.swift
@@ -0,0 +1,65 @@
+//
+// ProductionNetworkProvider.swift
+// SantanderChallenge
+//
+// Created for SantanderChallenge on 05/07/19.
+//
+
+import Foundation
+
+fileprivate enum Path: String {
+ case form = "https://floating-mountain-50292.herokuapp.com/cells.json"
+ case fund = "https://floating-mountain-50292.herokuapp.com/fund.json"
+}
+
+class ProductionNetworkProvider: NetworkProviderProtocol {
+
+ func fetchFormData(_ completion: @escaping (NetworkResponse) -> Void) {
+ request(at: Path.form.rawValue) { (result) in
+ completion(result)
+ }
+ }
+
+ func fetchFundsData(_ completion: @escaping (NetworkResponse) -> Void) {
+ request(at: Path.fund.rawValue) { (result) in
+ completion(result)
+ }
+ }
+}
+
+extension ProductionNetworkProvider {
+ private func request(at path: String, _ completion: @escaping (Result) -> Void) {
+ let configuration = URLSessionConfiguration.default
+ let session = URLSession(configuration: configuration)
+
+ guard let url = URL(string: path) else {
+ completion(.failure(NetworkError.invalidPath))
+ return
+ }
+
+ let task = session.dataTask(with: url) { (data, response, error) in
+
+ if let error = error {
+ completion(.failure(NetworkError.responseError(error.localizedDescription)))
+ return
+ }
+
+ guard self.statusCode(fromResponse: response) == 200 else {
+ completion(.failure(NetworkError.invalidStatusCode))
+ return
+ }
+
+ guard let data = data else {
+ completion(.failure(NetworkError.missingResponseData))
+ return
+ }
+
+ completion(.success(data))
+ }
+ task.resume()
+ }
+
+ private func statusCode(fromResponse response: URLResponse?) -> Int? {
+ return (response as? HTTPURLResponse)?.statusCode
+ }
+}
diff --git a/SantanderChallenge/SantanderChallengeTests/EnvironmentManagerTests.swift b/SantanderChallenge/SantanderChallengeTests/EnvironmentManagerTests.swift
new file mode 100644
index 00000000..c8c75c74
--- /dev/null
+++ b/SantanderChallenge/SantanderChallengeTests/EnvironmentManagerTests.swift
@@ -0,0 +1,27 @@
+//
+// EnvironmentManagerTests.swift
+// SantanderChallengeTests
+//
+// Created for SantanderChallenge on 08/07/19.
+//
+
+import XCTest
+
+class EnvironmentManagerTests: XCTestCase {
+
+ func testIfIsDetectingEnvironment() {
+ let environmentId = EnvironmentManager.shared.environmentId
+ XCTAssertEqual(environmentId, EnvironmentIdentifier.development)
+ }
+
+ func testIfRetrivingKeyFromPlist() {
+ // Arrange
+ let target = "test_value"
+
+ // Act
+ let value: String = EnvironmentManager.shared.value(forKey: EnvironmentKey.test)!
+
+ // Assert
+ XCTAssertEqual(target, value)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallengeTests/Extension/StringTests.swift b/SantanderChallenge/SantanderChallengeTests/Extension/StringTests.swift
new file mode 100644
index 00000000..9ba485a5
--- /dev/null
+++ b/SantanderChallenge/SantanderChallengeTests/Extension/StringTests.swift
@@ -0,0 +1,40 @@
+//
+// StringTests.swift
+// SantanderChallengeTests
+//
+// Created for SantanderChallenge on 08/07/19.
+//
+
+import XCTest
+
+class StringTests: XCTestCase {
+
+ func testRemoveNonDigitCharacters() {
+ // Arrange
+ let source = "1!2@3#4$5%6^"
+ let target = "123456"
+
+ // Act
+ let result = source.removingNonDigitCharacters
+
+ // Assert
+ XCTAssertEqual(result, target)
+ }
+
+ func testValidEmail() {
+
+ // Arrange
+ let source = "luiz@mail.com"
+
+ // Act & Assert
+ XCTAssertTrue(source.validEmail)
+ }
+
+ func testInvalidEmail() {
+ // Arrange
+ let source = "@invalid_email_format"
+
+ // Act & Assert
+ XCTAssertFalse(source.validEmail)
+ }
+}
diff --git a/SantanderChallenge/SantanderChallengeTests/Info.plist b/SantanderChallenge/SantanderChallengeTests/Info.plist
new file mode 100644
index 00000000..6c40a6cd
--- /dev/null
+++ b/SantanderChallenge/SantanderChallengeTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/SantanderChallenge/SantanderChallengeTests/Interactors/FormInteractorTests.swift b/SantanderChallenge/SantanderChallengeTests/Interactors/FormInteractorTests.swift
new file mode 100644
index 00000000..bbf7fe35
--- /dev/null
+++ b/SantanderChallenge/SantanderChallengeTests/Interactors/FormInteractorTests.swift
@@ -0,0 +1,80 @@
+//
+// FormInteractorTests.swift
+// SantanderChallengeTests
+//
+// Created for SantanderChallenge on 08/07/19.
+//
+
+@testable import SantanderChallenge
+import XCTest
+
+class FormInteractorTests: XCTestCase {
+
+ var interactor: FormInteractor!
+ var view: FakeView!
+
+ override func setUp() {
+ view = FakeView()
+ let presenter = FormPresenter(view: view)
+ interactor = FormInteractor(presenter: presenter)
+ }
+
+ func testFetchFormData() {
+
+ // Arrange
+ let expectation = XCTestExpectation(description: "Fetch form cells")
+
+ view.displayFormCallback = { cells in
+
+ // Assert
+ XCTAssertNotNil(cells)
+ expectation.fulfill()
+ }
+
+ // Act
+ interactor.fetchForm()
+
+ wait(for: [expectation], timeout: 2)
+ }
+
+ func testValidatorsSuccess() {
+
+ // Arrange
+ let nameStatus = interactor.isValid(name: "Joaquim")
+ let phoneStatus = interactor.isValid(phone: "(01) 1234-5678")
+ let longPhoneStatus = interactor.isValid(phone: "(01) 12345-6789")
+ let emailStatus = interactor.isValid(email: "jared@mail.com")
+
+ // Act & Assert
+ XCTAssertTrue(nameStatus)
+ XCTAssertTrue(phoneStatus)
+ XCTAssertTrue(longPhoneStatus)
+ XCTAssertTrue(emailStatus)
+ }
+
+ func testValidatorsFailure() {
+
+ // Arrange
+ let nameStatus = interactor.isValid(name: "")
+ let phoneStatus = interactor.isValid(phone: "(01) 1234-78")
+ let longPhoneStatus = interactor.isValid(phone: "(01) 12345-6724389")
+ let emailStatus = interactor.isValid(email: "@not_valid@mail@")
+
+ // Act & Assert
+ XCTAssertFalse(nameStatus)
+ XCTAssertFalse(phoneStatus)
+ XCTAssertFalse(longPhoneStatus)
+ XCTAssertFalse(emailStatus)
+ }
+
+ class FakeView: FormPresentableProtocol {
+
+ var displayFormCallback: (([FormCell]) -> Void)?
+
+ func displayForm(_ cells: [FormCell]) {
+ displayFormCallback?(cells)
+ }
+
+ func displayError(_ error: String) {}
+ }
+}
diff --git a/SantanderChallenge/SantanderChallengeTests/Interactors/FundInteractorTests.swift b/SantanderChallenge/SantanderChallengeTests/Interactors/FundInteractorTests.swift
new file mode 100644
index 00000000..921680c4
--- /dev/null
+++ b/SantanderChallenge/SantanderChallengeTests/Interactors/FundInteractorTests.swift
@@ -0,0 +1,47 @@
+//
+// FundInteractor.swift
+// SantanderChallengeTests
+//
+// Created for SantanderChallenge on 08/07/19.
+//
+
+@testable import SantanderChallenge
+import XCTest
+
+class FundInteractorTests: XCTestCase {
+
+ var interactor: FundsInteractor!
+ var view: FakeView!
+
+ override func setUp() {
+ view = FakeView()
+ let presenter = FundsPresenter(view: view)
+ interactor = FundsInteractor(presenter: presenter)
+ }
+
+ func testFetchFundsCells() {
+ // Arrange
+ let expectation = XCTestExpectation(description: "Fetch funds cells")
+
+ view.displayFundsCallback = { data in
+ // Assert
+ XCTAssertNotNil(data)
+ expectation.fulfill()
+ }
+
+ // Act
+ interactor.fetchFunds()
+
+ wait(for: [expectation], timeout: 2)
+ }
+
+ class FakeView: FundsPresentableProtocol {
+ var displayFundsCallback: (([FundContentData]) -> Void)?
+
+ func displayFunds(_ cells: [FundContentData]) {
+ displayFundsCallback?(cells)
+ }
+
+ func displayError(_ error: String) { }
+ }
+}
diff --git a/_config.yml b/_config.yml
deleted file mode 100644
index 2f7efbea..00000000
--- a/_config.yml
+++ /dev/null
@@ -1 +0,0 @@
-theme: jekyll-theme-minimal
\ No newline at end of file
diff --git a/font.zip b/font.zip
deleted file mode 100644
index 3f77e099..00000000
Binary files a/font.zip and /dev/null differ
diff --git a/telas.png b/telas.png
deleted file mode 100644
index d696bb2a..00000000
Binary files a/telas.png and /dev/null differ
diff --git a/teste_app.sketch b/teste_app.sketch
deleted file mode 100644
index 1047c7cb..00000000
Binary files a/teste_app.sketch and /dev/null differ