├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── swift.yml ├── .gitignore ├── .swiftlint.yml ├── Example ├── .swiftlint.yml ├── Gemfile ├── Makefile ├── Podfile ├── Rambafile ├── Templates │ ├── surf_mvp_coordinatable_alert │ │ ├── Code │ │ │ ├── Configurator │ │ │ │ └── module_configurator.swift.liquid │ │ │ ├── Presenter │ │ │ │ ├── module_input.swift.liquid │ │ │ │ ├── module_output.swift.liquid │ │ │ │ └── presenter.swift.liquid │ │ │ └── View │ │ │ │ ├── view_controller.swift.liquid │ │ │ │ ├── view_input.swift.liquid │ │ │ │ └── view_output.swift.liquid │ │ └── surf_mvp_coordinatable_alert.rambaspec │ └── surf_mvp_coordinatable_module │ │ ├── Code │ │ ├── Configurator │ │ │ └── module_configurator.swift.liquid │ │ ├── Presenter │ │ │ ├── module_input.swift.liquid │ │ │ ├── module_output.swift.liquid │ │ │ └── presenter.swift.liquid │ │ └── View │ │ │ ├── view_controller.storyboard.liquid │ │ │ ├── view_controller.swift.liquid │ │ │ ├── view_controller.xib.liquid │ │ │ ├── view_input.swift.liquid │ │ │ └── view_output.swift.liquid │ │ ├── Tests │ │ ├── Configurator │ │ │ └── module_configurator_tests.swift.liquid │ │ ├── Presenter │ │ │ └── presenter_tests.swift.liquid │ │ └── View │ │ │ └── view_tests.swift.liquid │ │ └── surf_mvp_coordinatable_module.rambaspec ├── TextFieldsCatalogExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── TextFieldsCatalogExample.xcscheme ├── TextFieldsCatalogExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── TextFieldsCatalogExample │ ├── Application │ │ ├── AppDelegate.swift │ │ ├── ApplicationCoordinator.swift │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── DeepLinkOption.swift │ │ └── ru.lproj │ │ │ └── LaunchScreen.strings │ ├── Flows │ │ ├── Example │ │ │ ├── ExampleCoordinator.swift │ │ │ ├── ExampleCoordinatorOutput.swift │ │ │ └── Examples │ │ │ │ ├── Configurator │ │ │ │ └── ExamplesModuleConfigurator.swift │ │ │ │ ├── Presenter │ │ │ │ ├── ExamplesModuleInput.swift │ │ │ │ ├── ExamplesModuleOutput.swift │ │ │ │ └── ExamplesPresenter.swift │ │ │ │ └── View │ │ │ │ ├── ExamplesViewController.swift │ │ │ │ ├── ExamplesViewController.xib │ │ │ │ ├── ExamplesViewInput.swift │ │ │ │ └── ExamplesViewOutput.swift │ │ ├── Info │ │ │ ├── Info │ │ │ │ ├── Configurator │ │ │ │ │ └── InfoModuleConfigurator.swift │ │ │ │ ├── Presenter │ │ │ │ │ ├── InfoModuleInput.swift │ │ │ │ │ ├── InfoModuleOutput.swift │ │ │ │ │ └── InfoPresenter.swift │ │ │ │ └── View │ │ │ │ │ ├── InfoViewController.swift │ │ │ │ │ ├── InfoViewController.xib │ │ │ │ │ ├── InfoViewInput.swift │ │ │ │ │ └── InfoViewOutput.swift │ │ │ ├── InfoCoordinator.swift │ │ │ └── InfoCoordinatorOutput.swift │ │ ├── Main │ │ │ ├── FieldExample │ │ │ │ ├── Configurator │ │ │ │ │ └── FieldExampleModuleConfigurator.swift │ │ │ │ ├── Presenter │ │ │ │ │ ├── FieldExampleModuleInput.swift │ │ │ │ │ ├── FieldExampleModuleOutput.swift │ │ │ │ │ └── FieldExamplePresenter.swift │ │ │ │ └── View │ │ │ │ │ ├── FieldExampleViewController.swift │ │ │ │ │ ├── FieldExampleViewController.xib │ │ │ │ │ ├── FieldExampleViewInput.swift │ │ │ │ │ └── FieldExampleViewOutput.swift │ │ │ ├── FieldPresets │ │ │ │ ├── Configurator │ │ │ │ │ └── FieldPresetsModuleConfigurator.swift │ │ │ │ ├── Presenter │ │ │ │ │ ├── FieldPresetsModuleInput.swift │ │ │ │ │ ├── FieldPresetsModuleOutput.swift │ │ │ │ │ └── FieldPresetsPresenter.swift │ │ │ │ └── View │ │ │ │ │ ├── Adapter │ │ │ │ │ ├── Cell │ │ │ │ │ │ ├── FieldPresetTableViewCell.swift │ │ │ │ │ │ └── FieldPresetTableViewCell.xib │ │ │ │ │ └── FieldPresetsAdapter.swift │ │ │ │ │ ├── FieldPresetsViewController.swift │ │ │ │ │ ├── FieldPresetsViewController.xib │ │ │ │ │ ├── FieldPresetsViewInput.swift │ │ │ │ │ └── FieldPresetsViewOutput.swift │ │ │ ├── Main │ │ │ │ ├── Configurator │ │ │ │ │ └── MainModuleConfigurator.swift │ │ │ │ ├── Presenter │ │ │ │ │ ├── MainModuleInput.swift │ │ │ │ │ ├── MainModuleOutput.swift │ │ │ │ │ └── MainPresenter.swift │ │ │ │ └── View │ │ │ │ │ ├── Adapter │ │ │ │ │ ├── Cell │ │ │ │ │ │ ├── MainFieldTableViewCell │ │ │ │ │ │ │ ├── MainFieldTableViewCell.swift │ │ │ │ │ │ │ └── MainFieldTableViewCell.xib │ │ │ │ │ │ └── MainMessageTableViewCell │ │ │ │ │ │ │ ├── MainMessageTableViewCell.swift │ │ │ │ │ │ │ └── MainMessageTableViewCell.xib │ │ │ │ │ └── MainAdapter.swift │ │ │ │ │ ├── MainViewController.swift │ │ │ │ │ ├── MainViewController.xib │ │ │ │ │ ├── MainViewInput.swift │ │ │ │ │ ├── MainViewOutput.swift │ │ │ │ │ └── ViewModel │ │ │ │ │ └── MainModuleViewModel.swift │ │ │ ├── MainCoordinator.swift │ │ │ └── MainCoordinatorOutput.swift │ │ └── MainTabBar │ │ │ ├── MainTabBar │ │ │ ├── Configurator │ │ │ │ └── MainTabBarModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ │ ├── MainTabBarModuleInput.swift │ │ │ │ ├── MainTabBarModuleOutput.swift │ │ │ │ └── MainTabBarPresenter.swift │ │ │ └── View │ │ │ │ ├── MainTabBarViewController.swift │ │ │ │ ├── MainTabBarViewInput.swift │ │ │ │ └── MainTabBarViewOutput.swift │ │ │ ├── MainTabBarCoordinator.swift │ │ │ └── MainTabBarCoordinatorOutput.swift │ ├── Info.plist │ ├── Library │ │ ├── BaseClasses │ │ │ ├── BaseCoordinator.swift │ │ │ ├── DesignableView.swift │ │ │ └── StorkContainerViewController │ │ │ │ ├── StorkContainerViewController.swift │ │ │ │ └── StorkContainerViewController.xib │ │ ├── Extensions │ │ │ ├── FormatterMasks.swift │ │ │ ├── Foundation.swift │ │ │ ├── String.swift │ │ │ ├── UIApplication.swift │ │ │ ├── UIColor.swift │ │ │ ├── UIDevice.swift │ │ │ ├── UIImage.swift │ │ │ ├── UITableView.swift │ │ │ ├── UITableViewCell.swift │ │ │ ├── UIView.swift │ │ │ └── UIViewController.swift │ │ ├── Protocols │ │ │ ├── AppliedPreset.swift │ │ │ ├── Coordinator.swift │ │ │ ├── CustomNavigationTitlePresentable.swift │ │ │ ├── Presentable.swift │ │ │ └── Router.swift │ │ ├── Reusable │ │ │ ├── CommonNavigationController.swift │ │ │ ├── CommonTableHeader │ │ │ │ ├── CommonTableHeader.swift │ │ │ │ └── CommonTableHeader.xib │ │ │ ├── CustomTitleView │ │ │ │ ├── CustomTitleView.swift │ │ │ │ └── CustomTitleView.xib │ │ │ └── MainButton.swift │ │ └── Routers │ │ │ └── MainRouter.swift │ ├── Resources │ │ ├── Colors │ │ │ └── Color.swift │ │ ├── Constants │ │ │ ├── Closures.swift │ │ │ ├── MainTab.swift │ │ │ ├── Sex.swift │ │ │ └── SharedRegex.swift │ │ ├── Images │ │ │ ├── Assets.swift │ │ │ └── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon.png │ │ │ │ ├── icon_20pt.png │ │ │ │ ├── icon_20pt@2x-1.png │ │ │ │ ├── icon_20pt@2x.png │ │ │ │ ├── icon_20pt@3x.png │ │ │ │ ├── icon_29pt.png │ │ │ │ ├── icon_29pt@2x-1.png │ │ │ │ ├── icon_29pt@2x.png │ │ │ │ ├── icon_29pt@3x.png │ │ │ │ ├── icon_40pt.png │ │ │ │ ├── icon_40pt@2x-1.png │ │ │ │ ├── icon_40pt@2x.png │ │ │ │ ├── icon_40pt@3x.png │ │ │ │ ├── icon_60pt@2x.png │ │ │ │ ├── icon_60pt@3x.png │ │ │ │ ├── icon_76pt.png │ │ │ │ ├── icon_76pt@2x.png │ │ │ │ └── icon_83.5@2x.png │ │ │ │ ├── Colors │ │ │ │ ├── Contents.json │ │ │ │ ├── active.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── activePress.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── background.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── error.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── fieldNormal.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── highlighted.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── mainButtonText.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── placeholderGray.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── regular.colorset │ │ │ │ │ └── Contents.json │ │ │ │ └── text.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── MainTab │ │ │ │ ├── Contents.json │ │ │ │ ├── catalog.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── icon-cart.pdf │ │ │ │ ├── example.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── icon-catalog.pdf │ │ │ │ └── info.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── icon-profile.pdf │ │ │ │ ├── close.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── ic_close.png │ │ │ │ ├── ic_close@2x.png │ │ │ │ └── ic_close@3x.png │ │ │ │ ├── customEyeOff.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── iconEyeClose.png │ │ │ │ ├── iconEyeClose@2x.png │ │ │ │ └── iconEyeClose@3x.png │ │ │ │ ├── customEyeOn.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── iconEyeShow.png │ │ │ │ ├── iconEyeShow@2x.png │ │ │ │ └── iconEyeShow@3x.png │ │ │ │ ├── info.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Group 12.3.png │ │ │ │ ├── Group 12.3@2x.png │ │ │ │ └── Group 12.3@3x.png │ │ │ │ └── qrCode.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── ic_qr.png │ │ │ │ ├── ic_qr@2x.png │ │ │ │ └── ic_qr@3x.png │ │ └── Strings │ │ │ ├── Strings.swift │ │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ │ └── ru.lproj │ │ │ └── Localizable.strings │ └── TextFields │ │ ├── TextFields │ │ ├── BoxTextField │ │ │ ├── BoxTextField.swift │ │ │ └── BoxTextField.xib │ │ ├── CustomUnderlinedTextField │ │ │ ├── CustomUnderlinedTextField.swift │ │ │ └── CustomUnderlinedTextField.xib │ │ ├── InputViews │ │ │ └── InputViews.swift │ │ ├── SumTextField │ │ │ ├── CurrencyPlaceholderService.swift │ │ │ ├── SumTextField.swift │ │ │ └── SumTextField.xib │ │ └── UnderlinedTextField │ │ │ └── UnderlinedTextField.swift │ │ └── Types and presets │ │ ├── Presets │ │ ├── SumFieldPreset.swift │ │ ├── UnderlinedFieldPreset.swift │ │ └── UnderlinedTextViewPreset.swift │ │ └── TextFieldType.swift ├── TextFieldsCatalogExampleTests │ ├── FieldTests │ │ ├── UnderlinedFieldTests.swift │ │ └── UnderlinedViewTests.swift │ ├── Info.plist │ ├── ReferenceImages_64 │ │ ├── TextFieldsCatalogExampleTests.MainFieldTableViewCellTests │ │ │ └── testCellLayout@2x.png │ │ ├── TextFieldsCatalogExampleTests.UnderlinedFieldTests │ │ │ ├── testDefaultField@2x.png │ │ │ ├── testEmptyDisabledField@2x.png │ │ │ ├── testEmptyErrorField@2x.png │ │ │ ├── testEmptyField@2x.png │ │ │ ├── testFilledDisabledField@2x.png │ │ │ ├── testFilledErrorField@2x.png │ │ │ ├── testFilledField@2x.png │ │ │ ├── testMultilineErrorMessage@2x.png │ │ │ ├── testPasswordEmptyField@2x.png │ │ │ ├── testPasswordSecureField@2x.png │ │ │ └── testPasswordVisibleField@2x.png │ │ └── TextFieldsCatalogExampleTests.UnderlinedViewTests │ │ │ ├── testDefaultField@2x.png │ │ │ ├── testEmptyDisabledField@2x.png │ │ │ ├── testEmptyErrorField@2x.png │ │ │ ├── testEmptyField@2x.png │ │ │ ├── testFilledDisabledField@2x.png │ │ │ ├── testFilledErrorField@2x.png │ │ │ ├── testFilledField@2x.png │ │ │ ├── testMultilineErrorMessage@2x.png │ │ │ └── testMultilineText@2x.png │ └── Support │ │ └── TableViewCellSnapshotContainer.swift └── swiftgen.yml ├── Gemfile ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── Podfile ├── README.md ├── ROADMAP.md ├── TextFieldsCatalog.podspec ├── TextFieldsCatalog.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── TextFieldsCatalog.xcscheme ├── TextFieldsCatalog.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── TextFieldsCatalog ├── Info.plist ├── Library │ ├── BaseClasses │ │ ├── CommonButton.swift │ │ ├── InnerDesignableView.swift │ │ ├── InnerTextField.swift │ │ └── InnerTextView.swift │ ├── Extensions │ │ ├── Foundation.swift │ │ ├── String.swift │ │ └── UIKit │ │ │ ├── UIColor.swift │ │ │ ├── UIImage.swift │ │ │ ├── UITextField.swift │ │ │ └── UITextView.swift │ ├── Protocols │ │ ├── TextField │ │ │ ├── DateTextField.swift │ │ │ ├── GuidedTextField.swift │ │ │ ├── PickerTextField.swift │ │ │ ├── ResetableField.swift │ │ │ └── RespondableField.swift │ │ ├── TextFieldValidation.swift │ │ └── ToolBarInterface.swift │ ├── Reusable │ │ ├── IconButton.swift │ │ └── Pickers │ │ │ ├── DatePickerView.swift │ │ │ ├── PlainPickerView.swift │ │ │ └── TopView │ │ │ ├── PickerTopView.swift │ │ │ ├── PickerTopView.xib │ │ │ └── PickerTopViewConfiguration.swift │ └── Utils │ │ ├── AssetManager.swift │ │ ├── FormatterMasks.swift │ │ ├── MaskTextFieldFormatter.swift │ │ ├── StandardEditActions.swift │ │ └── TextFieldValidator.swift ├── Resources │ ├── Colors │ │ └── Color.swift │ ├── Constants │ │ ├── FieldContainerState.swift │ │ ├── HeightLayoutPolicy.swift │ │ ├── HintVisibleStates.swift │ │ ├── Internal │ │ │ ├── AccessibilityIdentifiers.swift │ │ │ ├── AnimationTime.swift │ │ │ └── FieldState.swift │ │ ├── NativePlaceholderBehavior.swift │ │ ├── PasteOverflowPolicy.swift │ │ ├── SharedRegex.swift │ │ ├── TextFieldMode.swift │ │ ├── TextFieldPasswordModeBehavior.swift │ │ └── ValidationPolicy.swift │ ├── Images │ │ ├── close.png │ │ ├── close@2x.png │ │ ├── close@3x.png │ │ ├── eyeOff.png │ │ ├── eyeOff@2x.png │ │ ├── eyeOff@3x.png │ │ ├── eyeOn.png │ │ ├── eyeOn@2x.png │ │ ├── eyeOn@3x.png │ │ ├── leftArrow.png │ │ ├── leftArrow@2x.png │ │ ├── leftArrow@3x.png │ │ ├── rightArrow.png │ │ ├── rightArrow@2x.png │ │ └── rightArrow@3x.png │ └── Strings │ │ ├── Strings.swift │ │ ├── en.lproj │ │ └── Localizable.strings │ │ └── ru.lproj │ │ └── Localizable.strings ├── TextFields │ ├── Configuration │ │ ├── ConfigurationParameters.swift │ │ ├── FlexibleHeightPolicy.swift │ │ └── TextFieldConfigurations.swift │ ├── Services │ │ ├── AppearanceServices │ │ │ ├── FieldService.swift │ │ │ ├── HintService.swift │ │ │ ├── LineService.swift │ │ │ └── Placeholder │ │ │ │ ├── FloatingPlaceholderService.swift │ │ │ │ ├── NativePlaceholderService.swift │ │ │ │ └── StaticPlaceholderService.swift │ │ └── Support │ │ │ ├── AbstractHintService.swift │ │ │ ├── AbstractPlaceholderService.swift │ │ │ └── InputFieldProtocol.swift │ ├── UnderlinedTextField │ │ ├── UnderlinedTextField.swift │ │ └── UnderlinedTextField.xib │ └── UnderlinedTextView │ │ ├── UnderlinedTextView.swift │ │ └── UnderlinedTextView.xib └── TextFieldsCatalog.h ├── TextFieldsCatalogTests ├── Extensions │ ├── UITextField.swift │ └── UITextView.swift ├── Info.plist ├── TextFields │ ├── Configuration │ │ └── ColorConfiguration.swift │ └── Services │ │ └── FieldService.swift └── Utils │ ├── AssetManager.swift │ ├── MaskTextFieldFormatter.swift │ └── TextFieldValidator.swift ├── docs ├── Classes.html ├── Classes │ ├── ActionButtonConfiguration.html │ ├── BackgroundConfiguration.html │ ├── BaseFieldConfiguration.html │ ├── ColorConfiguration.html │ ├── DatePickerView.html │ ├── FloatingPlaceholderConfiguration.html │ ├── FloatingPlaceholderService.html │ ├── HintConfiguration.html │ ├── HintService.html │ ├── InnerDesignableView.html │ ├── InnerTextField.html │ ├── InnerTextView.html │ ├── LineConfiguration.html │ ├── MaskTextFieldFormatter.html │ ├── NativePlaceholderConfiguration.html │ ├── NativePlaceholderService.html │ ├── PasswordModeConfiguration.html │ ├── PickerTopView.html │ ├── PickerTopViewButtonConfiguration.html │ ├── PickerTopViewConfiguration.html │ ├── PlainPickerView.html │ ├── StaticPlaceholderConfiguration.html │ ├── StaticPlaceholderService.html │ ├── TextFieldConfiguration.html │ ├── TextFieldValidator.html │ ├── UnderlinedTextField.html │ ├── UnderlinedTextFieldConfiguration.html │ ├── UnderlinedTextView.html │ └── UnderlinedTextViewConfiguration.html ├── Enums.html ├── Enums │ ├── FieldContainerState.html │ ├── FieldState.html │ ├── FormatterMasks.html │ ├── HeightLayoutPolicy.html │ ├── NativePlaceholderBehavior.html │ ├── PasteOverflowPolicy.html │ ├── SharedRegex.html │ ├── StandardEditActions.html │ ├── TextFieldMode.html │ ├── TextFieldPasswordModeBehavior.html │ └── ValidationPolicy.html ├── Extensions.html ├── Extensions │ ├── Array.html │ └── UITextView.html ├── Protocols.html ├── Protocols │ ├── AbstractHintService.html │ ├── AbstractPlaceholderService.html │ ├── DateTextField.html │ ├── GuidedTextField.html │ ├── InputField.html │ ├── PickerTextField.html │ ├── ResetableField.html │ ├── RespondableField.html │ ├── TextFieldValidation.html │ └── ToolBarInterface.html ├── Structs.html ├── Structs │ ├── FlexibleHeightPolicy.html │ └── HintVisibleStates.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── TextFieldsCatalog.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── ActionButtonConfiguration.html │ │ │ │ ├── BackgroundConfiguration.html │ │ │ │ ├── BaseFieldConfiguration.html │ │ │ │ ├── ColorConfiguration.html │ │ │ │ ├── DatePickerView.html │ │ │ │ ├── FloatingPlaceholderConfiguration.html │ │ │ │ ├── FloatingPlaceholderService.html │ │ │ │ ├── HintConfiguration.html │ │ │ │ ├── HintService.html │ │ │ │ ├── InnerDesignableView.html │ │ │ │ ├── InnerTextField.html │ │ │ │ ├── InnerTextView.html │ │ │ │ ├── LineConfiguration.html │ │ │ │ ├── MaskTextFieldFormatter.html │ │ │ │ ├── NativePlaceholderConfiguration.html │ │ │ │ ├── NativePlaceholderService.html │ │ │ │ ├── PasswordModeConfiguration.html │ │ │ │ ├── PickerTopView.html │ │ │ │ ├── PickerTopViewButtonConfiguration.html │ │ │ │ ├── PickerTopViewConfiguration.html │ │ │ │ ├── PlainPickerView.html │ │ │ │ ├── StaticPlaceholderConfiguration.html │ │ │ │ ├── StaticPlaceholderService.html │ │ │ │ ├── TextFieldConfiguration.html │ │ │ │ ├── TextFieldValidator.html │ │ │ │ ├── UnderlinedTextField.html │ │ │ │ ├── UnderlinedTextFieldConfiguration.html │ │ │ │ ├── UnderlinedTextView.html │ │ │ │ └── UnderlinedTextViewConfiguration.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── FieldContainerState.html │ │ │ │ ├── FieldState.html │ │ │ │ ├── FormatterMasks.html │ │ │ │ ├── HeightLayoutPolicy.html │ │ │ │ ├── NativePlaceholderBehavior.html │ │ │ │ ├── PasteOverflowPolicy.html │ │ │ │ ├── SharedRegex.html │ │ │ │ ├── StandardEditActions.html │ │ │ │ ├── TextFieldMode.html │ │ │ │ ├── TextFieldPasswordModeBehavior.html │ │ │ │ └── ValidationPolicy.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── Array.html │ │ │ │ └── UITextView.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── AbstractHintService.html │ │ │ │ ├── AbstractPlaceholderService.html │ │ │ │ ├── DateTextField.html │ │ │ │ ├── GuidedTextField.html │ │ │ │ ├── InputField.html │ │ │ │ ├── PickerTextField.html │ │ │ │ ├── ResetableField.html │ │ │ │ ├── RespondableField.html │ │ │ │ ├── TextFieldValidation.html │ │ │ │ └── ToolBarInterface.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── FlexibleHeightPolicy.html │ │ │ │ └── HintVisibleStates.html │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ └── search.json │ │ │ └── docSet.dsidx │ └── TextFieldsCatalog.tgz ├── img │ ├── carat.png │ ├── dash.png │ └── gh.png ├── index.html ├── js │ ├── jazzy.js │ └── jquery.min.js ├── search.json └── undocumented.json ├── fastlane ├── Appfile ├── Fastfile ├── Pluginfile └── README.md ├── swiftgen.yml └── tech_docs ├── Configuration.md ├── DevNotes.md ├── ExampleProject.md ├── Images ├── BorderedTextField.png ├── DatePickerView.png ├── DatePickerViewCustomized.png ├── NativePlaceholderService.png ├── PlainPickerView.png ├── TextFieldsCatalog_video.gif ├── UnderlinedTextField.png ├── UnderlinedTextView.png └── textView_scheme.png ├── PodProject.md └── Usage.md /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: chausovSurfStudio 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Library version and device type** 27 | - iOS version: [e.g. 14.0] 28 | - device type: [e.g. iPhone 6+] 29 | - Version [e.g. 1.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: update 6 | assignees: chausovSurfStudio 7 | 8 | --- 9 | 10 | # General description of the problem 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. 13 | Ex. I'm always frustrated when [...] 14 | 15 | # Playback Steps 16 | **A detailed description of how to reproduce the problem.** 17 | Be sure to specify the settings for the input field - for example, with what parameters the mask for the input field is applied, what is the height policy, etc. 18 | - step 1 19 | - step 2 20 | 21 | # Expected behavior 22 | **Describe the expected behavior that you expect to see by reproducing the problem through the indicated steps** 23 | 24 | # Actual behavior 25 | **What is really happening and why it seems wrong to you** 26 | 27 | **Describe the solution you'd like** 28 | A clear and concise description of what you want to happen. 29 | 30 | **Describe alternatives you've considered** 31 | A clear and concise description of any alternative solutions or features you've considered. 32 | 33 | **Additional context** 34 | Add any other context or screenshots about the feature request here. 35 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '**' 10 | types: [ opened, edited, synchronize ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: CocoaPods 20 | run: | 21 | gem install xcpretty -N 22 | make init 23 | - name: Force xcode 12 24 | run: | 25 | sudo xcode-select -switch /Applications/Xcode_13.1.app 26 | - name: Build 27 | run: | 28 | make build 29 | - name: Test 30 | run: | 31 | make test 32 | - name: Package build 33 | run: | 34 | swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios12.1-simulator" -Xswiftc "-lswiftUIKit" 35 | - name: documentation 36 | if: github.ref == 'refs/heads/master' 37 | run: | 38 | make doc 39 | - name: Commit changes 40 | uses: EndBug/add-and-commit@v4 41 | with: 42 | author_name: chausovSurfStudio 43 | author_email: chausov@surfstudio.ru 44 | message: "Updates for documentation" 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.gcno 20 | *.xcscmblueprint 21 | *dSYM 22 | *dSYM.zip 23 | *.mobileprovision 24 | .build/ 25 | .swiftpm/ 26 | 27 | # CocoaPods 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 32 | # 33 | Example/Podfile.lock 34 | Example/Pods/ 35 | Pods 36 | Podfile.lock 37 | 38 | # Exported environment variables for XcodeCoverage 39 | env.sh 40 | 41 | # OS X Finder stuff 42 | .DS_Store 43 | 44 | # Fastlane Stuff 45 | fastlane/local_config.sh 46 | fastlane/test_output 47 | fastlane/Provisioning 48 | fastlane/Build 49 | fastlane/report.xml 50 | buildData 51 | 52 | # Bundler 53 | Example/.bundle/ 54 | Example/Gemfile.lock 55 | Example/vendor/ 56 | .bundle/ 57 | Gemfile.lock 58 | vendor/ -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Ensure github repositories are fetched using HTTPS 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | puts(repo_name) 7 | "https://github.com/#{repo_name}.git" 8 | end if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('2') 9 | 10 | gem 'cocoapods', "1.8.4" 11 | gem 'generamba', github: 'surfstudio/Generamba', branch: 'develop', :ref => '91957270f4bc0092305ce6dbf016be5259720d33' -------------------------------------------------------------------------------- /Example/Makefile: -------------------------------------------------------------------------------- 1 | # COLORS 2 | GREEN := $(shell tput -Txterm setaf 2) 3 | YELLOW := $(shell tput -Txterm setaf 3) 4 | WHITE := $(shell tput -Txterm setaf 7) 5 | RESET := $(shell tput -Txterm sgr0) 6 | 7 | 8 | TARGET_MAX_CHAR_NUM=20 9 | ## Show help 10 | help: 11 | @echo '' 12 | @echo 'Usage:' 13 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' 14 | @echo '' 15 | @echo 'Targets:' 16 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 17 | helpMessage = match(lastLine, /^## (.*)/); \ 18 | if (helpMessage) { \ 19 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 20 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 21 | printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \ 22 | } \ 23 | } \ 24 | { lastLine = $$0 }' $(MAKEFILE_LIST) 25 | 26 | ## Initialization of the working environment. 27 | init: 28 | # Install bundler if not installed 29 | if ! gem spec bundler > /dev/null 2>&1; then\ 30 | echo "bundler gem is not installed!";\ 31 | -sudo gem install bundler;\ 32 | fi 33 | -bundle install --path .bundle 34 | -bundle exec pod repo update 35 | -bundle exec pod install 36 | 37 | ## Used to create a new module. Example: make screen modName= flow= 38 | screen: 39 | bundle exec generamba gen $(modName) surf_mvp_coordinatable_module --module_path 'TextFieldsCatalogExample/Flows/$(flow)' 40 | 41 | ## Used to create a new alert. Example: make alert modName= flow= 42 | alert: 43 | bundle exec generamba gen $(modName) surf_mvp_coordinatable_alert --module_path 'TextFieldsCatalogExample/Flows/$(flow)' 44 | 45 | ## Allows you to perfrom swiftlint lint command. 46 | lint: 47 | ./Pods/SwiftLint/swiftlint lint --config .swiftlint.yml 48 | 49 | ## Allows you to perfrom swiftlint autocorrect command. 50 | format: 51 | ./Pods/SwiftLint/swiftlint autocorrect --config .swiftlint.yml 52 | 53 | ## Allows you to perform pod install command via bundler settings. Use it instead plain pod install command. 54 | pod: 55 | bundle exec pod install -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | inhibit_all_warnings! 4 | 5 | def utils 6 | pod 'SwiftGen', '6.4.0' 7 | pod 'SwiftLint', '0.41.0' 8 | end 9 | 10 | def common_pods 11 | utils 12 | pod 'SurfUtils/StringAttributes', :git => "https://github.com/surfstudio/iOS-Utils.git", :tag => "10.0.0" 13 | pod 'SurfUtils/CommonButton', :git => "https://github.com/surfstudio/iOS-Utils.git", :tag => "10.0.0" 14 | pod 'SPStorkController', '1.7.9' 15 | 16 | pod 'TextFieldsCatalog', :path => '../' 17 | end 18 | 19 | target 'TextFieldsCatalogExample' do 20 | use_frameworks! 21 | common_pods 22 | 23 | target 'TextFieldsCatalogExampleTests' do 24 | inherit! :search_paths 25 | common_pods 26 | pod 'iOSSnapshotTestCase', '6.2.0' 27 | end 28 | 29 | end 30 | 31 | post_install do |installer| 32 | installer.pods_project.targets.each do |target| 33 | target.build_configurations.each do |config| 34 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /Example/Rambafile: -------------------------------------------------------------------------------- 1 | ### Headers settings 2 | company: Surf 3 | 4 | ### Xcode project settings 5 | project_name: TextFieldsCatalogExample 6 | xcodeproj_path: TextFieldsCatalogExample.xcodeproj 7 | 8 | ### Code generation settings section 9 | # The main project target name 10 | project_targets: [TextFieldsCatalogExample] 11 | 12 | # The file path for new modules 13 | project_file_path: TextFieldsCatalogExample/Screens 14 | 15 | # The Xcode group path to new modules 16 | project_group_path: TextFieldsCatalogExample/Screens 17 | 18 | 19 | ### Dependencies settings section 20 | podfile_path: Podfile 21 | 22 | ### Catalogs 23 | # catalogs: 24 | # - 'https://github.com/surfstudio/generamba-templates' 25 | 26 | ### Templates 27 | templates: 28 | - {name: surf_mvp_coordinatable_module} 29 | - {name: surf_mvp_coordinatable_alert} -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/Configurator/module_configurator.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class {{ module_info.name }}AlertConfigurator { 12 | 13 | func configure() -> ({{ module_info.name }}Controller, {{ module_info.name }}ModuleOutput) { 14 | 15 | // TODO: Change title and message 16 | let view = {{ module_info.name }}Controller(title: nil, message: nil, preferredStyle: .alert) 17 | let presenter = {{ module_info.name }}Presenter() 18 | 19 | presenter.view = view 20 | view.output = presenter 21 | 22 | return (view, presenter) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/Presenter/module_input.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/Presenter/module_output.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/Presenter/presenter.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | final class {{ module_info.name }}Presenter: {{ module_info.name }}ModuleOutput { 10 | 11 | // MARK: - {{ module_info.name }}ModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: {{ module_info.name }}ViewInput? 16 | 17 | } 18 | 19 | // MARK: - {{ module_info.name }}ModuleInput 20 | 21 | extension {{ module_info.name }}Presenter: {{ module_info.name }}ModuleInput { 22 | } 23 | 24 | // MARK: - {{ module_info.name }}ViewOutput 25 | 26 | extension {{ module_info.name }}Presenter: {{ module_info.name }}ViewOutput { 27 | } 28 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/View/view_controller.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class {{ module_info.name }}Controller: UIAlertController { 12 | 13 | // MARK: - Properties 14 | 15 | var output: {{ module_info.name }}ViewOutput? 16 | 17 | } 18 | 19 | // MARK: - {{ module_info.name }}ViewInput 20 | 21 | extension {{ module_info.name }}Controller: {{ module_info.name }}ViewInput { 22 | } 23 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/View/view_input.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ViewInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/Code/View/view_output.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ViewOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_alert/surf_mvp_coordinatable_alert.rambaspec: -------------------------------------------------------------------------------- 1 | # Template information section 2 | name: "surf_mvp_coordinatable_alert" 3 | summary: "Template for creating MVP alert modules with coordinator." 4 | author: "Serge Nanaev" 5 | version: "1.0.0" 6 | license: "MIT" 7 | 8 | # The declarations for code files 9 | 10 | code_files: 11 | # Configurator 12 | - {name: Configurator/ModuleConfigurator.swift, path: Code/Configurator/module_configurator.swift.liquid} 13 | 14 | # Presenter layer 15 | - {name: Presenter/Presenter.swift, path: Code/Presenter/presenter.swift.liquid} 16 | - {name: Presenter/ModuleInput.swift, path: Code/Presenter/module_input.swift.liquid} 17 | - {name: Presenter/ModuleOutput.swift, path: Code/Presenter/module_output.swift.liquid} 18 | 19 | # View layer 20 | - {name: View/Controller.swift, path: Code/View/view_controller.swift.liquid} 21 | - {name: View/ViewInput.swift, path: Code/View/view_input.swift.liquid} 22 | - {name: View/ViewOutput.swift, path: Code/View/view_output.swift.liquid} 23 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/Configurator/module_configurator.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class {{ module_info.name }}ModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, {{ module_info.name }}ModuleOutput) { 14 | let view = {{ module_info.name }}ViewController() 15 | let presenter = {{ module_info.name }}Presenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/Presenter/module_input.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/Presenter/module_output.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/Presenter/presenter.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | final class {{ module_info.name }}Presenter: {{ module_info.name }}ModuleOutput { 10 | 11 | // MARK: - {{ module_info.name }}ModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: {{ module_info.name }}ViewInput? 16 | 17 | } 18 | 19 | // MARK: - {{ module_info.name }}ModuleInput 20 | 21 | extension {{ module_info.name }}Presenter: {{ module_info.name }}ModuleInput { 22 | } 23 | 24 | // MARK: - {{ module_info.name }}ViewOutput 25 | 26 | extension {{ module_info.name }}Presenter: {{ module_info.name }}ViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/View/view_controller.storyboard.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/View/view_controller.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class {{ module_info.name }}ViewController: UIViewController { 12 | 13 | // MARK: - Properties 14 | 15 | var output: {{ module_info.name }}ViewOutput? 16 | 17 | // MARK: - UIViewController 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | output?.viewLoaded() 22 | } 23 | 24 | } 25 | 26 | // MARK: - {{ module_info.name }}ViewInput 27 | 28 | extension {{ module_info.name }}ViewController: {{ module_info.name }}ViewInput { 29 | 30 | func setupInitialState() { 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/View/view_controller.xib.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/View/view_input.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Code/View/view_output.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | protocol {{ module_info.name }}ViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Tests/Configurator/module_configurator_tests.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import {{ module_info.product_module_name }} 11 | 12 | final class {{ module_info.name }}ModuleConfiguratorTests: XCTestCase { 13 | 14 | // MARK: - Main tests 15 | 16 | func testThatViewControllerLoadsCorrectly() { 17 | if UIStoryboard(name: String(describing: {{ module_info.name }}ViewController.self), 18 | bundle: Bundle.main).instantiateInitialViewController() == nil { 19 | XCTFail("Can't load {{ module_info.name }}ViewController from storyboard, check that controller is initial view controller") 20 | } 21 | } 22 | 23 | func testThatScreenConfiguresCorrectly() { 24 | // when 25 | let (viewController, output) = {{ module_info.name }}ModuleConfigurator().configure() 26 | 27 | // then 28 | XCTAssertNotNil(viewController.output, "{{ module_info.name }}ViewController is nil after configuration") 29 | XCTAssertTrue(viewController.output is {{ module_info.name }}Presenter, "output is not {{ module_info.name }}Presenter") 30 | XCTAssertTrue(output is {{ module_info.name }}Presenter, "module output is not {{ module_info.name }}Presenter") 31 | 32 | guard let presenter: {{ module_info.name }}Presenter = viewController.output as? {{ module_info.name }}Presenter else { 33 | XCTFail("Cannot assign viewController.output as {{ module_info.name }}Presenter") 34 | return 35 | } 36 | 37 | XCTAssertNotNil(presenter.view, "view in {{ module_info.name }}Presenter is nil after configuration") 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Tests/Presenter/presenter_tests.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import {{ module_info.product_module_name }} 11 | 12 | final class {{ module_info.name }}PresenterTest: XCTestCase { 13 | 14 | // MARK: - Properties 15 | 16 | private var presenter: {{ module_info.name }}Presenter? 17 | private var view: MockViewController? 18 | private var output: MockModuleOutput? 19 | 20 | // MARK: - XCTestCase 21 | 22 | override func setUp() { 23 | super.setUp() 24 | presenter = {{ module_info.name }}Presenter() 25 | output = MockModuleOutput() 26 | view = MockViewController() 27 | presenter?.view = view 28 | } 29 | 30 | override func tearDown() { 31 | super.tearDown() 32 | presenter = nil 33 | view = nil 34 | } 35 | 36 | // MARK: - Main tests 37 | 38 | func testThatPresenterHandlesViewLoadedEvent() { 39 | // when 40 | presenter?.viewLoaded() 41 | // then 42 | XCTAssertTrue((presenter?.view as? MockViewController)?.setupInitialStateWasCalled == true) 43 | } 44 | 45 | // MARK: - Mocks 46 | 47 | private final class MockViewController: {{ module_info.name }}ViewInput { 48 | var setupInitialStateWasCalled: Bool = false 49 | 50 | func setupInitialState() { 51 | setupInitialStateWasCalled = true 52 | } 53 | } 54 | 55 | private final class MockModuleOutput { 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/Tests/View/view_tests.swift.liquid: -------------------------------------------------------------------------------- 1 | // 2 | // {{ module_info.file_name }} 3 | // {{ module_info.project_name }} 4 | // 5 | // Created by {{ developer.name }} on {{ date }}. 6 | // Copyright © {{ year }} {{ developer.company }}. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import {{ module_info.product_module_name }} 11 | 12 | final class {{ module_info.name }}ViewTests: XCTestCase { 13 | 14 | // MARK: - Properties 15 | 16 | private var view: {{ module_info.name }}ViewController? 17 | private var output: {{ module_info.name }}ViewOutputMock? 18 | 19 | // MARK: - XCTestCase 20 | 21 | override func setUp() { 22 | super.setUp() 23 | view = {{ module_info.name }}ViewController() 24 | output = {{ module_info.name }}ViewOutputMock() 25 | view?.output = output 26 | } 27 | 28 | override func tearDown() { 29 | super.tearDown() 30 | view = nil 31 | output = nil 32 | } 33 | 34 | // MARK: - Main tests 35 | 36 | func testThatViewNotifiesPresenterOnDidLoad() { 37 | // when 38 | self.view?.viewDidLoad() 39 | // then 40 | XCTAssert(self.output?.viewLoadedWasCalled == true) 41 | } 42 | 43 | // MARK: - Mocks 44 | 45 | final class {{ module_info.name }}ViewOutputMock: {{ module_info.name }}ViewOutput { 46 | var viewLoadedWasCalled: Bool = false 47 | 48 | func viewLoaded() { 49 | viewLoadedWasCalled = true 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Example/Templates/surf_mvp_coordinatable_module/surf_mvp_coordinatable_module.rambaspec: -------------------------------------------------------------------------------- 1 | # Template information section 2 | name: "surf_mvp_module" 3 | summary: "Template for creating MVP modules with coordinator." 4 | author: "Serge Nanaev" 5 | version: "1.0.0" 6 | license: "MIT" 7 | 8 | # The declarations for code files 9 | 10 | code_files: 11 | # Configurator 12 | - {name: Configurator/ModuleConfigurator.swift, path: Code/Configurator/module_configurator.swift.liquid} 13 | 14 | # Presenter layer 15 | - {name: Presenter/Presenter.swift, path: Code/Presenter/presenter.swift.liquid} 16 | - {name: Presenter/ModuleInput.swift, path: Code/Presenter/module_input.swift.liquid} 17 | - {name: Presenter/ModuleOutput.swift, path: Code/Presenter/module_output.swift.liquid} 18 | 19 | # View layer 20 | - {name: View/ViewController.swift, path: Code/View/view_controller.swift.liquid} 21 | - {name: View/ViewController.xib, path: Code/View/view_controller.xib.liquid} 22 | - {name: View/ViewInput.swift, path: Code/View/view_input.swift.liquid} 23 | - {name: View/ViewOutput.swift, path: Code/View/view_output.swift.liquid} 24 | 25 | # The declarations for test files 26 | 27 | test_files: 28 | 29 | # Configurators tests 30 | - {name: Configurator/ModuleConfiguratorTests.swift, path: Tests/Configurator/module_configurator_tests.swift.liquid} 31 | 32 | # Presenter tests 33 | - {name: Presenter/PresenterTests.swift, path: Tests/Presenter/presenter_tests.swift.liquid} 34 | 35 | # View tests 36 | - {name: View/ViewTests.swift, path: Tests/View/view_tests.swift.liquid} 37 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | // MARK: - Properties 15 | 16 | var window: UIWindow? 17 | 18 | // MARK: - Private Properties 19 | 20 | private lazy var applicationCoordinator: Coordinator = self.makeCoordinator() 21 | 22 | // MARK: - UIApplicationDelegate 23 | 24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 25 | initializeRootView() 26 | applicationCoordinator.start() 27 | return true 28 | } 29 | 30 | } 31 | 32 | // MARK: - Private Methods 33 | 34 | private extension AppDelegate { 35 | 36 | private func initializeRootView() { 37 | window = UIWindow(frame: UIScreen.main.bounds) 38 | window?.rootViewController = UIViewController() 39 | window?.makeKeyAndVisible() 40 | } 41 | 42 | private func makeCoordinator() -> Coordinator { 43 | return ApplicationCoordinator() 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Application/ApplicationCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationCoordinator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ApplicationCoordinator: BaseCoordinator { 12 | 13 | // MARK: - Coordinator 14 | 15 | override func start(with deepLinkOption: DeepLinkOption?) { 16 | runMainTabBarFlow() 17 | } 18 | 19 | } 20 | 21 | // MARK: - Private Methods 22 | 23 | private extension ApplicationCoordinator { 24 | 25 | func runMainTabBarFlow(deepLinkOption: DeepLinkOption? = nil) { 26 | let router = MainRouter() 27 | let coordinator = MainTabBarCoordinator(router: router) 28 | self.addDependency(coordinator) 29 | coordinator.start() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Application/DeepLinkOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeepLinkOption.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum DeepLinkOption { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Application/ru.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "TextFieldsCatalog"; ObjectID = "M9R-m1-uE6"; */ 3 | "M9R-m1-uE6.text" = "TextFieldsCatalog"; 4 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/ExampleCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleCoordinator.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | final class ExampleCoordinator: BaseCoordinator, ExampleCoordinatorOutput { 10 | 11 | // MARK: - ExampleCoordinatorOutput 12 | 13 | // MARK: - Private Properties 14 | 15 | private let router: Router 16 | 17 | // MARK: - Initialization 18 | 19 | init(router: Router) { 20 | self.router = router 21 | } 22 | 23 | // MARK: - Coordinator 24 | 25 | override func start(with deepLinkOption: DeepLinkOption?) { 26 | showExamples() 27 | } 28 | 29 | } 30 | 31 | // MARK: - Private Methods 32 | 33 | private extension ExampleCoordinator { 34 | 35 | func showExamples() { 36 | let (view, _) = ExamplesModuleConfigurator().configure() 37 | router.setNavigationControllerRootModule(view, animated: false, hideBar: false) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/ExampleCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleCoordinatorOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | protocol ExampleCoordinatorOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/Configurator/ExamplesModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesModuleConfigurator.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ExamplesModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, ExamplesModuleOutput) { 14 | let view = ExamplesViewController() 15 | let presenter = ExamplesPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/Presenter/ExamplesModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesModuleInput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol ExamplesModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/Presenter/ExamplesModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesModuleOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol ExamplesModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/Presenter/ExamplesPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesPresenter.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | final class ExamplesPresenter: ExamplesModuleOutput { 10 | 11 | // MARK: - ExamplesModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: ExamplesViewInput? 16 | 17 | } 18 | 19 | // MARK: - ExamplesModuleInput 20 | 21 | extension ExamplesPresenter: ExamplesModuleInput { 22 | } 23 | 24 | // MARK: - ExamplesViewOutput 25 | 26 | extension ExamplesPresenter: ExamplesViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState(with: L10n.Example.title) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/View/ExamplesViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesViewInput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol ExamplesViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState(with title: String) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Example/Examples/View/ExamplesViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesViewOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol ExamplesViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/Configurator/InfoModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoModuleConfigurator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class InfoModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, InfoModuleOutput) { 14 | let view = InfoViewController() 15 | let presenter = InfoPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/Presenter/InfoModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoModuleInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol InfoModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/Presenter/InfoModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoModuleOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol InfoModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/Presenter/InfoPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoPresenter.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | final class InfoPresenter: InfoModuleOutput { 10 | 11 | // MARK: - InfoModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: InfoViewInput? 16 | 17 | } 18 | 19 | // MARK: - InfoModuleInput 20 | 21 | extension InfoPresenter: InfoModuleInput { 22 | } 23 | 24 | // MARK: - InfoViewOutput 25 | 26 | extension InfoPresenter: InfoViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState(with: L10n.Info.description, 30 | title: L10n.Info.title) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/View/InfoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoViewController.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SurfUtils 11 | 12 | final class InfoViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var descriptionLabel: UILabel! 17 | 18 | // MARK: - Properties 19 | 20 | var output: InfoViewOutput? 21 | 22 | // MARK: - UIViewController 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | output?.viewLoaded() 27 | } 28 | 29 | } 30 | 31 | // MARK: - InfoViewInput 32 | 33 | extension InfoViewController: InfoViewInput { 34 | 35 | func setupInitialState(with description: String, title: String) { 36 | view.backgroundColor = Color.Main.background 37 | navigationItem.title = title 38 | descriptionLabel.numberOfLines = 0 39 | descriptionLabel.attributedText = description.with(attributes: [.lineHeight(20, font: UIFont.systemFont(ofSize: 14, weight: .regular)), 40 | .foregroundColor(Color.Text.white)]) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/View/InfoViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoViewInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol InfoViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState(with description: String, title: String) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/Info/View/InfoViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoViewOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 25/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol InfoViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/InfoCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoCoordinator.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | final class InfoCoordinator: BaseCoordinator, InfoCoordinatorOutput { 10 | 11 | // MARK: - InfoCoordinatorOutput 12 | 13 | // MARK: - Private Properties 14 | 15 | private let router: Router 16 | 17 | // MARK: - Initialization 18 | 19 | init(router: Router) { 20 | self.router = router 21 | } 22 | 23 | // MARK: - Coordinator 24 | 25 | override func start(with deepLinkOption: DeepLinkOption?) { 26 | showInfo() 27 | } 28 | 29 | } 30 | 31 | // MARK: - Private Methods 32 | 33 | private extension InfoCoordinator { 34 | 35 | func showInfo() { 36 | let (view, _) = InfoModuleConfigurator().configure() 37 | router.setNavigationControllerRootModule(view, animated: false, hideBar: false) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Info/InfoCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoCoordinatorOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | protocol InfoCoordinatorOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/Configurator/FieldExampleModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExampleModuleConfigurator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FieldExampleModuleConfigurator { 12 | 13 | func configure(with fieldType: TextFieldType) -> (UIViewController, FieldExampleModuleOutput, FieldExampleModuleInput) { 14 | let view = FieldExampleViewController() 15 | let presenter = FieldExamplePresenter(with: fieldType) 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/Presenter/FieldExampleModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExampleModuleInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldExampleModuleInput: class { 10 | func applyPreset(_ preset: AppliedPreset) 11 | } 12 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/Presenter/FieldExampleModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExampleModuleOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldExampleModuleOutput: class { 10 | var onChangePreset: ((TextFieldType) -> Void)? { get set } 11 | } 12 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/Presenter/FieldExamplePresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExamplePresenter.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | final class FieldExamplePresenter: FieldExampleModuleOutput { 10 | 11 | // MARK: - FieldExampleModuleOutput 12 | 13 | var onChangePreset: ((TextFieldType) -> Void)? 14 | 15 | // MARK: - Properties 16 | 17 | weak var view: FieldExampleViewInput? 18 | 19 | // MARK: - Private Properties 20 | 21 | private let fieldType: TextFieldType 22 | 23 | // MARK: - Initialization 24 | 25 | init(with fieldType: TextFieldType) { 26 | self.fieldType = fieldType 27 | } 28 | 29 | } 30 | 31 | // MARK: - FieldExampleModuleInput 32 | 33 | extension FieldExamplePresenter: FieldExampleModuleInput { 34 | 35 | func applyPreset(_ preset: AppliedPreset) { 36 | view?.applyPreset(preset) 37 | } 38 | 39 | } 40 | 41 | // MARK: - FieldExampleViewOutput 42 | 43 | extension FieldExamplePresenter: FieldExampleViewOutput { 44 | 45 | func viewLoaded() { 46 | view?.setupInitialState(with: fieldType, preset: fieldType.presets.first) 47 | } 48 | 49 | func changePreset() { 50 | onChangePreset?(fieldType) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/View/FieldExampleViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExampleViewInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldExampleViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState(with fieldType: TextFieldType, preset: AppliedPreset?) 12 | 13 | /// Method for apply some preset 14 | func applyPreset(_ preset: AppliedPreset) 15 | } 16 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldExample/View/FieldExampleViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldExampleViewOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldExampleViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | 13 | /// notify presenter that user wants to change preset 14 | func changePreset() 15 | } 16 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/Configurator/FieldPresetsModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsModuleConfigurator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FieldPresetsModuleConfigurator { 12 | 13 | func configure(with presets: [AppliedPreset]) -> (UIViewController, FieldPresetsModuleOutput) { 14 | let view = FieldPresetsViewController() 15 | let presenter = FieldPresetsPresenter(with: presets) 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | let navigationController = CommonNavigationController(rootViewController: view) 21 | let container = StorkContainerViewController(navController: navigationController) 22 | 23 | return (container, presenter) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/Presenter/FieldPresetsModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsModuleInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldPresetsModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/Presenter/FieldPresetsModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsModuleOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldPresetsModuleOutput: class { 10 | var onClose: EmptyClosure? { get set } 11 | var onSelectPreset: FieldPresetClosure? { get set } 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/Presenter/FieldPresetsPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsPresenter.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | final class FieldPresetsPresenter: FieldPresetsModuleOutput { 10 | 11 | // MARK: - FieldPresetsModuleOutput 12 | 13 | var onClose: EmptyClosure? 14 | var onSelectPreset: FieldPresetClosure? 15 | 16 | // MARK: - Properties 17 | 18 | weak var view: FieldPresetsViewInput? 19 | 20 | // MARK: - Private Properties 21 | 22 | private let presets: [AppliedPreset] 23 | 24 | // MARK: - Initialization 25 | 26 | init(with presets: [AppliedPreset]) { 27 | self.presets = presets 28 | } 29 | 30 | } 31 | 32 | // MARK: - FieldPresetsModuleInput 33 | 34 | extension FieldPresetsPresenter: FieldPresetsModuleInput { 35 | } 36 | 37 | // MARK: - FieldPresetsViewOutput 38 | 39 | extension FieldPresetsPresenter: FieldPresetsViewOutput { 40 | 41 | func viewLoaded() { 42 | view?.setupInitialState(with: presets) 43 | } 44 | 45 | func close() { 46 | onClose?() 47 | } 48 | 49 | func selectPreset(_ preset: AppliedPreset) { 50 | onSelectPreset?(preset) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/View/Adapter/Cell/FieldPresetTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetTableViewCell.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 24/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class FieldPresetTableViewCell: UITableViewCell { 12 | 13 | // MARK: - Constants 14 | 15 | private enum Constants { 16 | static let animationDuration: TimeInterval = 0.3 17 | } 18 | 19 | // MARK: - IBOutlets 20 | 21 | @IBOutlet private weak var nameLabel: UILabel! 22 | 23 | // MARK: - UITableViewCell 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | setupInitialState() 28 | } 29 | 30 | override func setSelected(_ selected: Bool, animated: Bool) { 31 | UIView.animate(withDuration: Constants.animationDuration) { [weak self] in 32 | self?.contentView.backgroundColor = selected ? Color.Cell.container : Color.Cell.background 33 | } 34 | } 35 | 36 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 37 | UIView.animate(withDuration: Constants.animationDuration) { [weak self] in 38 | self?.contentView.backgroundColor = highlighted ? Color.Cell.container : Color.Cell.background 39 | } 40 | } 41 | 42 | // MARK: - Internal Methods 43 | 44 | func configure(with preset: AppliedPreset) { 45 | nameLabel.text = preset.name 46 | } 47 | 48 | } 49 | 50 | // MARK: - Configure 51 | 52 | private extension FieldPresetTableViewCell { 53 | 54 | func setupInitialState() { 55 | contentView.backgroundColor = Color.Cell.background 56 | nameLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium) 57 | nameLabel.textColor = Color.Text.white 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/View/FieldPresetsViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsViewInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldPresetsViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState(with presets: [AppliedPreset]) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/FieldPresets/View/FieldPresetsViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldPresetsViewOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 24/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol FieldPresetsViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | 13 | /// Notify presenter that user wants to close module 14 | func close() 15 | 16 | /// Notify presenter that user select some preset 17 | func selectPreset(_ preset: AppliedPreset) 18 | } 19 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/Configurator/MainModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleConfigurator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class MainModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, MainModuleOutput) { 14 | let view = MainViewController() 15 | let presenter = MainPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/Presenter/MainModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/Presenter/MainModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainModuleOutput: class { 10 | var onFieldOpen: TextFieldTypeClosure? { get set } 11 | } 12 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/Presenter/MainPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainPresenter.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | final class MainPresenter: MainModuleOutput { 10 | 11 | // MARK: - MainModuleOutput 12 | 13 | var onFieldOpen: TextFieldTypeClosure? 14 | 15 | // MARK: - Properties 16 | 17 | weak var view: MainViewInput? 18 | 19 | } 20 | 21 | // MARK: - MainModuleInput 22 | 23 | extension MainPresenter: MainModuleInput { 24 | } 25 | 26 | // MARK: - MainViewOutput 27 | 28 | extension MainPresenter: MainViewOutput { 29 | 30 | func viewLoaded() { 31 | let models = TextFieldType.allCases.map { MainModuleViewModel.field($0) } 32 | view?.setupInitialState(with: models, title: L10n.Main.title) 33 | } 34 | 35 | func openField(with type: TextFieldType) { 36 | onFieldOpen?(type) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/View/Adapter/Cell/MainMessageTableViewCell/MainMessageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainMessageTableViewCell.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SurfUtils 11 | 12 | final class MainMessageTableViewCell: UITableViewCell { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var messageLabel: UILabel! 17 | 18 | // MARK: - UITableViewCell 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | setupInitialState() 23 | } 24 | 25 | // MARK: - Internal Methods 26 | 27 | func configure(with message: String) { 28 | messageLabel.attributedText = message.with(attributes: [.lineHeight(18, font: UIFont.systemFont(ofSize: 14, weight: .regular)), 29 | .foregroundColor(Color.Text.white)]) 30 | } 31 | 32 | } 33 | 34 | // MARK: - Configure 35 | 36 | private extension MainMessageTableViewCell { 37 | 38 | func setupInitialState() { 39 | selectionStyle = .none 40 | contentView.backgroundColor = Color.Cell.background 41 | messageLabel.numberOfLines = 0 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/View/MainViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewInput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState(with models: [MainModuleViewModel], title: String) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/View/MainViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexander Chausov on 23/01/2019. 6 | // Copyright © 2019 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | 13 | /// Notify presenter that user wants to see some field 14 | func openField(with type: TextFieldType) 15 | } 16 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/Main/View/ViewModel/MainModuleViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleViewModel.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum MainModuleViewModel { 12 | case message(String) 13 | case field(TextFieldType) 14 | } 15 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/Main/MainCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainCoordinatorOutput.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MainCoordinatorOutput: class { 12 | var finishFlow: EmptyClosure? { get set } 13 | } 14 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/Configurator/MainTabBarModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarModuleConfigurator.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class MainTabBarModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, MainTabBarModuleOutput) { 14 | let view = MainTabBarViewController() 15 | let presenter = MainTabBarPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | view.viewControllers = configureControllers() 21 | 22 | return (view, presenter) 23 | } 24 | 25 | } 26 | 27 | // MARK: - Private Methods 28 | 29 | private extension MainTabBarModuleConfigurator { 30 | 31 | func configureControllers() -> [UIViewController] { 32 | var controllers: [UIViewController] = [] 33 | for tab in MainTab.allCases { 34 | let tabBarItem = UITabBarItem(title: tab.title, 35 | image: tab.image, 36 | selectedImage: tab.selectedImage) 37 | tabBarItem.tag = tab.rawValue 38 | 39 | let navigationController = tab.navigationController 40 | navigationController.tabBarItem = tabBarItem 41 | controllers.append(navigationController) 42 | } 43 | return controllers 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/Presenter/MainTabBarModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarModuleInput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainTabBarModuleInput: class { 10 | /// Method allows you to change selected tab 11 | func selectTab(_ tab: MainTab) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/Presenter/MainTabBarModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarModuleOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | public typealias TabSelectClosure = (_ isInitial: Bool, _ isChanging: Bool, _ isStackEmpty: Bool) -> Void 10 | 11 | protocol MainTabBarModuleOutput: class { 12 | var onCatalogFlowSelect: TabSelectClosure? { get set } 13 | var onExampleFlowSelect: TabSelectClosure? { get set } 14 | var onInfoFlowSelect: TabSelectClosure? { get set } 15 | } 16 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/Presenter/MainTabBarPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarPresenter.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | final class MainTabBarPresenter: MainTabBarModuleOutput { 10 | 11 | // MARK: - MainTabBarModuleOutput 12 | 13 | var onCatalogFlowSelect: TabSelectClosure? 14 | var onExampleFlowSelect: TabSelectClosure? 15 | var onInfoFlowSelect: TabSelectClosure? 16 | 17 | // MARK: - Properties 18 | 19 | weak var view: MainTabBarViewInput? 20 | 21 | // MARK: - Private Properties 22 | 23 | private var currentTab: MainTab? = .catalog 24 | 25 | } 26 | 27 | // MARK: - MainTabBarModuleInput 28 | 29 | extension MainTabBarPresenter: MainTabBarModuleInput { 30 | 31 | func selectTab(_ tab: MainTab) { 32 | view?.selectTab(tab) 33 | } 34 | 35 | } 36 | 37 | // MARK: - MainTabBarViewOutput 38 | 39 | extension MainTabBarPresenter: MainTabBarViewOutput { 40 | 41 | func selectTab(with tab: MainTab, isInitial: Bool, isStackEmpty: Bool) { 42 | let isChanging = currentTabIsChanged(newTab: tab) 43 | switch tab { 44 | case .catalog: 45 | onCatalogFlowSelect?(isInitial, isChanging, isStackEmpty) 46 | case .example: 47 | onExampleFlowSelect?(isInitial, isChanging, isStackEmpty) 48 | case .info: 49 | onInfoFlowSelect?(isInitial, isChanging, isStackEmpty) 50 | } 51 | currentTab = tab 52 | } 53 | 54 | } 55 | 56 | // MARK: - Private Methods 57 | 58 | private extension MainTabBarPresenter { 59 | 60 | func currentTabIsChanged(newTab: MainTab) -> Bool { 61 | return currentTab != newTab 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/View/MainTabBarViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarViewInput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainTabBarViewInput: class { 10 | /// Method for changing current selected tab 11 | func selectTab(_ tab: MainTab) 12 | } 13 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBar/View/MainTabBarViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarViewOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Surf. All rights reserved. 7 | // 8 | 9 | protocol MainTabBarViewOutput { 10 | /// Notify presenter that user selects some tab 11 | /// 12 | /// - Parameter tab: which tab user did select 13 | /// - Parameter isInitial: flag, indicating that controller was created before 14 | /// - Parameter isStackEmpty: flag, indicating that controllers stack is empty and have only root controller 15 | func selectTab(with tab: MainTab, isInitial: Bool, isStackEmpty: Bool) 16 | } 17 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Flows/MainTabBar/MainTabBarCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarCoordinatorOutput.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | protocol MainTabBarCoordinatorOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIStatusBarStyle 30 | UIStatusBarStyleDefault 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/BaseClasses/BaseCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCoordinator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Provides base realisation of coordinator's methods 12 | /// Subclass from this base class for making new flow coordinator 13 | class BaseCoordinator: Coordinator { 14 | 15 | // MARK: - Properties 16 | var childCoordinators: [Coordinator] = [] 17 | 18 | func start() { 19 | start(with: nil) 20 | } 21 | 22 | func start(with deepLinkOption: DeepLinkOption?) { } 23 | 24 | // add only unique object 25 | func addDependency(_ coordinator: Coordinator) { 26 | guard !haveDependency(coordinator) else { 27 | return 28 | } 29 | childCoordinators.append(coordinator) 30 | } 31 | 32 | func removeDependency(_ coordinator: Coordinator?) { 33 | guard 34 | !childCoordinators.isEmpty, 35 | let coordinator = coordinator 36 | else { return } 37 | 38 | for (index, element) in childCoordinators.enumerated() { 39 | if element === coordinator { 40 | childCoordinators.remove(at: index) 41 | break 42 | } 43 | } 44 | } 45 | 46 | func removeAllChilds() { 47 | guard 48 | !childCoordinators.isEmpty 49 | else { return } 50 | 51 | for coordinator in childCoordinators { 52 | if let coordinator = coordinator as? BaseCoordinator { 53 | coordinator.removeAllChilds() 54 | } 55 | } 56 | 57 | childCoordinators.removeAll() 58 | } 59 | 60 | // MARK: - Private methods 61 | 62 | private func haveDependency(_ coordinator: Coordinator) -> Bool { 63 | for element in childCoordinators { 64 | if element === coordinator { 65 | return true 66 | } 67 | } 68 | return false 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/BaseClasses/DesignableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DesignableView.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Alexandr Olferuk on 21/07/16. 6 | // Copyright © 2016 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Inherit custom subview from this class instead of UIView, 12 | /// mark it as @IBDesignable, 13 | /// set the file's owner and do not set the View's class, 14 | /// => 15 | /// It renders in the IB! 16 | class DesignableView: UIView { 17 | 18 | var view: UIView { 19 | return subviews.first ?? UIView() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | _ = setup() 25 | } 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | _ = setup() 30 | } 31 | 32 | func setup() -> UIView? { 33 | let view = Bundle(for: type(of: self)).loadNibNamed(self.nameOfClass, owner: self, options: nil)?.first as? UIView 34 | if let v = view { 35 | addSubview(v) 36 | v.frame = self.bounds 37 | } 38 | return view 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/BaseClasses/StorkContainerViewController/StorkContainerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/FormatterMasks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormatterMasks.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 27/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import TextFieldsCatalog 11 | import InputMask 12 | 13 | extension FormatterMasks { 14 | 15 | static let name = "[R…]" 16 | 17 | private enum Notations { 18 | enum RussianSymbolsAndSpaces { 19 | static let character: Character = "R" 20 | static let set = CharacterSet(charactersIn: "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ ") 21 | } 22 | } 23 | 24 | /// Method returns all custom notations for this application 25 | static func customNotations() -> [Notation] { 26 | return [ 27 | Notation(character: FormatterMasks.Notations.RussianSymbolsAndSpaces.character, 28 | characterSet: FormatterMasks.Notations.RussianSymbolsAndSpaces.set, 29 | isOptional: false) 30 | ] 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/Foundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSObject { 12 | 13 | @objc class var nameOfClass: String { 14 | if let name = NSStringFromClass(self).components(separatedBy: ".").last { 15 | return name 16 | } 17 | return "" 18 | } 19 | 20 | @objc var nameOfClass: String { 21 | if let name = NSStringFromClass(type(of: self)).components(separatedBy: ".").last { 22 | return name 23 | } 24 | return "" 25 | } 26 | 27 | } 28 | 29 | extension Array { 30 | 31 | subscript (safe index: Int) -> Element? { 32 | return indices ~= index ? self[index] : nil 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | 13 | func height(forWidth width: CGFloat, attributes: [NSAttributedString.Key: Any]) -> CGFloat { 14 | let rect = CGSize(width: width, height: .greatestFiniteMagnitude) 15 | let boundingBox = self.boundingRect(with: rect, 16 | options: .usesLineFragmentOrigin, 17 | attributes: attributes, 18 | context: nil) 19 | return ceil(boundingBox.height) 20 | } 21 | 22 | func width(forHeight height: CGFloat, attributes: [NSAttributedString.Key: Any]) -> CGFloat { 23 | let rect = CGSize(width: .greatestFiniteMagnitude, height: height) 24 | let boundingBox = self.boundingRect(with: rect, 25 | options: .usesLineFragmentOrigin, 26 | attributes: attributes, 27 | context: nil) 28 | return ceil(boundingBox.width) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UIApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIApplication { 12 | 13 | class func topViewController(_ controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 14 | if let navigationController = controller as? UINavigationController { 15 | return topViewController(navigationController.visibleViewController) 16 | } 17 | if let tabController = controller as? UITabBarController { 18 | if let selected = tabController.selectedViewController { 19 | return topViewController(selected) 20 | } 21 | } 22 | if let presented = controller?.presentedViewController { 23 | return topViewController(presented) 24 | } 25 | return controller 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// Instantiates UIColor with hex string 14 | convenience init(hexString: String) { 15 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 16 | var int = UInt32() 17 | Scanner(string: hex).scanHexInt32(&int) 18 | let a, r, g, b: UInt32 19 | switch hex.count { 20 | case 3: // RGB (12-bit) 21 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 22 | case 6: // RGB (24-bit) 23 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 24 | case 8: // ARGB (32-bit) 25 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 26 | default: 27 | (a, r, g, b) = (255, 0, 0, 0) 28 | } 29 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UIDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 07/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIDevice { 12 | 13 | /// Returns 'true' if iOS 13 is available 14 | static var isAvailableIos13: Bool { 15 | if #available(iOS 13.0, *) { 16 | return true 17 | } else { 18 | return false 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UITableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableView { 12 | 13 | func registerNib(_ cellType: UITableViewCell.Type) { 14 | register(UINib(nibName: cellType.nameOfClass, bundle: nil), forCellReuseIdentifier: cellType.nameOfClass) 15 | } 16 | 17 | func dequeueReusableCell(_ type: Cell.Type = Cell.self, indexPath: IndexPath) -> Cell? { 18 | return dequeueReusableCell(withIdentifier: Cell.identifier(), for: indexPath) as? Cell 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UITableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewCell.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableViewCell { 12 | 13 | class func identifier() -> String { 14 | return self.nameOfClass 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Extensions/UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 17.01.2021. 6 | // Copyright © 2021 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | func stretch(_ subview: UIView, translatesAutoresizingMaskIntoConstraints: Bool = false) { 14 | subview.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints 15 | 16 | NSLayoutConstraint.activate([ 17 | subview.topAnchor.constraint(equalTo: self.topAnchor), 18 | subview.bottomAnchor.constraint(equalTo: self.bottomAnchor), 19 | subview.leadingAnchor.constraint(equalTo: self.leadingAnchor), 20 | subview.trailingAnchor.constraint(equalTo: self.trailingAnchor) 21 | ]) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Protocols/AppliedPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppliedPreset.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 24/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Describes preset type which can be applied to the text field 12 | protocol AppliedPreset { 13 | var name: String { get } 14 | var description: String { get } 15 | func apply(for field: Any) 16 | } 17 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Protocols/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinator.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Base protocol for coordinator 12 | protocol Coordinator: class { 13 | /// Notifies coordinator that it can start itself 14 | func start() 15 | /// Notifies coordinator that it should start itself with deeplink option 16 | /// 17 | /// - parameter deepLinkOption: deeplink option such as Dynamic Link, push-notification, etc. 18 | func start(with deepLinkOption: DeepLinkOption?) 19 | /// Notifies coordinator that it should remove all child coordinators 20 | func removeAllChilds() 21 | } 22 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Protocols/Presentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Presentable.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Describes object that can be presented in view hierarchy 12 | protocol Presentable { 13 | func toPresent() -> UIViewController? 14 | } 15 | 16 | extension UIViewController: Presentable { 17 | 18 | func toPresent() -> UIViewController? { 19 | return self 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Protocols/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Router.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | /// Describes object that handles all navigation operations 10 | protocol Router { 11 | func present(_ module: Presentable?) 12 | func present(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) 13 | 14 | func push(_ module: Presentable?) 15 | func push(_ module: Presentable?, animated: Bool) 16 | 17 | func popModule() 18 | func popModule(animated: Bool) 19 | func popPreviousView() 20 | 21 | func dismissModule() 22 | func dismissModule(animated: Bool, completion: (() -> Void)?) 23 | 24 | func setNavigationControllerRootModule(_ module: Presentable?, animated: Bool, hideBar: Bool) 25 | func setRootModule(_ module: Presentable?) 26 | 27 | func setTab(_ index: Int) 28 | } 29 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Reusable/CommonNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonNavigationController.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Default navigation controller in application with customized bar 12 | final class CommonNavigationController: UINavigationController { 13 | 14 | // MARK: - UIViewController 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | configureAppearance() 19 | self.delegate = self 20 | } 21 | 22 | } 23 | 24 | // MARK: - Private Methods 25 | 26 | private extension CommonNavigationController { 27 | 28 | func configureAppearance() { 29 | navigationBar.barTintColor = Color.NavBar.background 30 | navigationBar.tintColor = Color.NavBar.tint 31 | navigationBar.titleTextAttributes = [.foregroundColor: Color.NavBar.text, 32 | .font: UIFont.systemFont(ofSize: 17, weight: .medium)] 33 | navigationBar.shadowImage = UIImage() 34 | navigationBar.setBackgroundImage(UIImage(), for: .default) 35 | navigationBar.isTranslucent = false 36 | } 37 | 38 | } 39 | 40 | // MARK: - UINavigationControllerDelegate 41 | 42 | extension CommonNavigationController: UINavigationControllerDelegate { 43 | 44 | func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { 45 | // removing "Back" word from back navigation bar button 46 | navigationController.topViewController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Reusable/CustomTitleView/CustomTitleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTitleView.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Chausov Alexander on 23/11/2018. 6 | // Copyright © 2018 Surf. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomTitleView: DesignableView { 12 | 13 | // MARK: - IBOutlets 14 | 15 | @IBOutlet private weak var headerLabel: UILabel! 16 | 17 | // MARK: - Private Properties 18 | 19 | private var threshold: CGFloat = 0 20 | 21 | // MARK: - Internal methods 22 | 23 | func configure(with title: String, titleColor: UIColor) { 24 | headerLabel.textColor = titleColor 25 | headerLabel.text = title 26 | headerLabel.alpha = 0 27 | } 28 | 29 | func updateThreshold(_ threshold: CGFloat) { 30 | self.threshold = threshold 31 | } 32 | 33 | func updateForContentOffset(_ offset: CGFloat) { 34 | let supposedAlpha: CGFloat = offset > threshold ? 1 : 0 35 | guard supposedAlpha != headerLabel.alpha else { 36 | return 37 | } 38 | UIView.animate(withDuration: 0.2) { [weak self] in 39 | guard let `self` = self else { 40 | return 41 | } 42 | self.headerLabel.alpha = offset > self.threshold ? 1 : 0 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Library/Reusable/MainButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainButton.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 25/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SurfUtils 11 | 12 | final class MainButton: CommonButton { 13 | 14 | // MARK: - Initialization 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | setupInitialState() 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | setupInitialState() 24 | } 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | setupInitialState() 29 | } 30 | 31 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 32 | super.traitCollectionDidChange(previousTraitCollection) 33 | setupInitialState() 34 | } 35 | 36 | } 37 | 38 | private extension MainButton { 39 | 40 | func setupInitialState() { 41 | set(backgroundColor: Color.Button.active, for: .normal) 42 | set(backgroundColor: Color.Button.pressed, for: [.highlighted, .selected, .disabled]) 43 | set(titleColor: Color.Button.text, for: [.normal, .highlighted]) 44 | cornerRadius = 12 45 | titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .medium) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Constants/Closures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Closures.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 23/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | typealias EmptyClosure = () -> Void 12 | typealias CGFloatClosure = (CGFloat) -> Void 13 | typealias TextFieldTypeClosure = (TextFieldType) -> Void 14 | typealias FieldPresetClosure = (AppliedPreset) -> Void 15 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Constants/MainTab.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTab.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum MainTab: Int, CaseIterable { 12 | 13 | case catalog 14 | case example 15 | case info 16 | 17 | // MARK: - Properties 18 | 19 | var image: UIImage { 20 | switch self { 21 | case .catalog: 22 | return UIImage(asset: Asset.MainTab.catalog) 23 | case .example: 24 | return UIImage(asset: Asset.MainTab.example) 25 | case .info: 26 | return UIImage(asset: Asset.MainTab.info) 27 | } 28 | } 29 | 30 | var selectedImage: UIImage { 31 | return image 32 | } 33 | 34 | var title: String { 35 | switch self { 36 | case .catalog: 37 | return L10n.Constants.MainTab.catalog 38 | case .example: 39 | return L10n.Constants.MainTab.example 40 | case .info: 41 | return L10n.Constants.MainTab.info 42 | } 43 | } 44 | 45 | var navigationController: UINavigationController { 46 | return CommonNavigationController() 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Constants/Sex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sex.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 13/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Sex: CaseIterable { 12 | case male 13 | case female 14 | 15 | var value: String { 16 | switch self { 17 | case .male: 18 | return L10n.Constants.Sex.male 19 | case .female: 20 | return L10n.Constants.Sex.female 21 | } 22 | } 23 | 24 | static func sex(by value: String?) -> Sex? { 25 | guard let value = value else { 26 | return nil 27 | } 28 | for item in Sex.allCases.map({ ($0, $0.value) }) { 29 | if item.1 == value { 30 | return item.0 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Constants/SharedRegex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SharedRegex.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 27/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import TextFieldsCatalog 10 | 11 | extension SharedRegex { 12 | static let name = "^[а-яА-ЯёЁ ]{1,}" 13 | 14 | } 15 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_76pt.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/active.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.875", 13 | "alpha" : "1.000", 14 | "blue" : "0.150", 15 | "green" : "0.537" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "1.000", 31 | "alpha" : "1.000", 32 | "blue" : "0.169", 33 | "green" : "0.616" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/activePress.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.935", 13 | "alpha" : "1.000", 14 | "blue" : "0.160", 15 | "green" : "0.574" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.831", 31 | "alpha" : "1.000", 32 | "blue" : "0.024", 33 | "green" : "0.459" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.929", 13 | "alpha" : "1.000", 14 | "blue" : "0.969", 15 | "green" : "0.945" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.122", 31 | "alpha" : "1.000", 32 | "blue" : "0.196", 33 | "green" : "0.125" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/error.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.886", 13 | "alpha" : "1.000", 14 | "blue" : "0.102", 15 | "green" : "0.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.781", 31 | "alpha" : "1.000", 32 | "blue" : "0.118", 33 | "green" : "0.083" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/fieldNormal.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.122", 13 | "alpha" : "1.000", 14 | "blue" : "0.325", 15 | "green" : "0.161" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.600", 31 | "alpha" : "1.000", 32 | "blue" : "0.600", 33 | "green" : "0.600" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/highlighted.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "gray-gamma-22", 11 | "components" : { 12 | "white" : "242", 13 | "alpha" : "1.000" 14 | } 15 | } 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "appearances" : [ 20 | { 21 | "appearance" : "luminosity", 22 | "value" : "dark" 23 | } 24 | ], 25 | "color" : { 26 | "color-space" : "srgb", 27 | "components" : { 28 | "red" : "0.169", 29 | "alpha" : "1.000", 30 | "blue" : "0.263", 31 | "green" : "0.176" 32 | } 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/mainButtonText.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "1.000", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "1.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "1.000", 31 | "alpha" : "1.000", 32 | "blue" : "1.000", 33 | "green" : "1.000" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/placeholderGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x6C", 13 | "alpha" : "1.000", 14 | "blue" : "0x88", 15 | "green" : "0x75" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0x9F", 31 | "alpha" : "1.000", 32 | "blue" : "0xC7", 33 | "green" : "0xAC" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/regular.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "1.000", 13 | "alpha" : "1.000", 14 | "blue" : "1.000", 15 | "green" : "1.000" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.204", 31 | "alpha" : "1.000", 32 | "blue" : "0.306", 33 | "green" : "0.212" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Colors/text.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x09", 13 | "alpha" : "1.000", 14 | "blue" : "0x47", 15 | "green" : "0x11" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.996", 31 | "alpha" : "1.000", 32 | "blue" : "0.996", 33 | "green" : "0.996" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/MainTab/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/MainTab/catalog.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-cart.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/MainTab/example.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-catalog.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/MainTab/info.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-profile.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_close.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ic_close@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ic_close@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/close.imageset/ic_close@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "iconEyeClose.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "iconEyeClose@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "iconEyeClose@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOff.imageset/iconEyeClose@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "iconEyeShow.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "iconEyeShow@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "iconEyeShow@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/customEyeOn.imageset/iconEyeShow@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group 12.3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Group 12.3@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Group 12.3@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/info.imageset/Group 12.3@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_qr.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ic_qr@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ic_qr@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExample/Resources/Images/Assets.xcassets/qrCode.imageset/ic_qr@3x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/TextFields/TextFields/UnderlinedTextField/UnderlinedTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnderlinedTextField.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 28/04/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import TextFieldsCatalog 10 | import UIKit 11 | 12 | extension UnderlinedTextField { 13 | 14 | func setup(supportPlaceholder: String) { 15 | let config = NativePlaceholderConfiguration(font: UIFont.systemFont(ofSize: 16, weight: .regular), 16 | height: 19, 17 | insets: UIEdgeInsets(top: 23, left: 16, bottom: 0, right: 16), 18 | colors: ColorConfiguration(color: Color.UnderlineTextField.placeholder), 19 | behavior: .hideOnInput, 20 | useAsMainPlaceholder: false, 21 | increasedRightPadding: 16) 22 | let service = NativePlaceholderService(configuration: config) 23 | add(placeholderService: service) 24 | service.setup(placeholder: supportPlaceholder) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExample/TextFields/Types and presets/Presets/SumFieldPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SumFieldPreset.swift 3 | // TextFieldsCatalogExample 4 | // 5 | // Created by Александр Чаусов on 19/05/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TextFieldsCatalog 11 | 12 | enum SumFieldPreset: CaseIterable, AppliedPreset { 13 | case sum 14 | 15 | var name: String { 16 | switch self { 17 | case .sum: 18 | return L10n.Presets.Sum.name 19 | } 20 | } 21 | 22 | var description: String { 23 | switch self { 24 | case .sum: 25 | return L10n.Presets.Sum.description 26 | } 27 | } 28 | 29 | func apply(for field: Any) { 30 | guard let field = field as? SumTextField else { 31 | return 32 | } 33 | apply(for: field) 34 | } 35 | 36 | } 37 | 38 | // MARK: - Tune 39 | 40 | private extension SumFieldPreset { 41 | 42 | func apply(for textField: SumTextField) { 43 | switch self { 44 | case .sum: 45 | tuneFieldForSum(textField) 46 | } 47 | } 48 | 49 | func tuneFieldForSum(_ textField: SumTextField) { 50 | textField.placeholder = L10n.Presets.Sum.placeholder 51 | textField.field.autocorrectionType = .no 52 | textField.field.keyboardType = .decimalPad 53 | textField.maxLength = 14 54 | textField.configure(supportPlaceholder: "1\u{2009}000\u{2009}₽") 55 | textField.configure(currencyPlaceholder: "₽") 56 | 57 | let validator = TextFieldValidator(minLength: 1, maxLength: 14, regex: nil) 58 | textField.validator = validator 59 | 60 | textField.configureForSum() 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.MainFieldTableViewCellTests/testCellLayout@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.MainFieldTableViewCellTests/testCellLayout@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testDefaultField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testDefaultField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyDisabledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyDisabledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyErrorField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyErrorField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testEmptyField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledDisabledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledDisabledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledErrorField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledErrorField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testFilledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testMultilineErrorMessage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testMultilineErrorMessage@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordEmptyField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordEmptyField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordSecureField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordSecureField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordVisibleField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedFieldTests/testPasswordVisibleField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testDefaultField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testDefaultField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyDisabledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyDisabledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyErrorField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyErrorField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testEmptyField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledDisabledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledDisabledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledErrorField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledErrorField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledField@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testFilledField@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testMultilineErrorMessage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testMultilineErrorMessage@2x.png -------------------------------------------------------------------------------- /Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testMultilineText@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/Example/TextFieldsCatalogExampleTests/ReferenceImages_64/TextFieldsCatalogExampleTests.UnderlinedViewTests/testMultilineText@2x.png -------------------------------------------------------------------------------- /Example/swiftgen.yml: -------------------------------------------------------------------------------- 1 | xcassets: 2 | inputs: 3 | - TextFieldsCatalogExample/Resources/Images/Assets.xcassets 4 | outputs: 5 | - templateName: swift4 6 | output: TextFieldsCatalogExample/Resources/Images/Assets.swift 7 | 8 | strings: 9 | inputs: TextFieldsCatalogExample/Resources/Strings/en.lproj/Localizable.strings 10 | outputs: 11 | - templateName: structured-swift4 12 | output: TextFieldsCatalogExample/Resources/Strings/Strings.swift 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Ensure github repositories are fetched using HTTPS 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | puts(repo_name) 7 | "https://github.com/#{repo_name}.git" 8 | end if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('2') 9 | 10 | gem 'cocoapods', "1.8.4" 11 | gem "fastlane", "~> 2.133.0" 12 | gem "jazzy", "0.13.4" 13 | 14 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 15 | eval_gemfile(plugins_path) if File.exist?(plugins_path) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 chausovSurfStudio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # COLORS 2 | GREEN := $(shell tput -Txterm setaf 2) 3 | YELLOW := $(shell tput -Txterm setaf 3) 4 | WHITE := $(shell tput -Txterm setaf 7) 5 | RESET := $(shell tput -Txterm sgr0) 6 | 7 | 8 | TARGET_MAX_CHAR_NUM=20 9 | ## Show help 10 | help: 11 | @echo '' 12 | @echo 'Usage:' 13 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' 14 | @echo '' 15 | @echo 'Targets:' 16 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 17 | helpMessage = match(lastLine, /^## (.*)/); \ 18 | if (helpMessage) { \ 19 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 20 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 21 | printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \ 22 | } \ 23 | } \ 24 | { lastLine = $$0 }' $(MAKEFILE_LIST) 25 | 26 | ## Initialization of the working environment. 27 | init: 28 | # Install bundler if not installed 29 | if ! gem spec bundler > /dev/null 2>&1; then\ 30 | echo "bundler gem is not installed!";\ 31 | -sudo gem install bundler;\ 32 | fi 33 | -bundle install --path .bundle 34 | -bundle exec pod repo update 35 | -bundle exec pod install 36 | 37 | ## Used to build target. Usually, it is not called manually, it is necessary for the CI to work. 38 | build: 39 | bundle exec fastlane build clean:true 40 | 41 | ## Run tests 42 | test: 43 | bundle exec fastlane tests 44 | 45 | ## Allows you to perfrom swiftlint lint command. 46 | lint: 47 | ./Pods/SwiftLint/swiftlint lint --config .swiftlint.yml 48 | 49 | ## Allows you to perfrom swiftlint autocorrect command. 50 | format: 51 | ./Pods/SwiftLint/swiftlint autocorrect --config .swiftlint.yml 52 | 53 | ## Allows you to perform pod install command via bundler settings. Use it instead plain pod install command. 54 | pod: 55 | bundle exec pod install 56 | 57 | ## Generate framework documentation 58 | doc: 59 | bundle exec jazzy --clean --build-tool-arguments -scheme,TextFieldsCatalog,-workspace,TextFieldsCatalog.xcworkspace,-sdk,iphonesimulator --output "docs" -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "InputMask", 6 | "repositoryURL": "https://github.com/RedMadRobot/input-mask-ios.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f78e7c8fe6f0f2b6512b69006ec595aa1224592f", 10 | "version": "6.0.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | inhibit_all_warnings! 4 | 5 | def utils 6 | pod 'SwiftGen', '6.4.0' 7 | pod 'SwiftLint', '0.41.0' 8 | end 9 | 10 | def common_pods 11 | utils 12 | pod 'InputMask', '6.0.0' 13 | end 14 | 15 | target 'TextFieldsCatalog' do 16 | use_frameworks! 17 | common_pods 18 | 19 | target 'TextFieldsCatalogTests' do 20 | inherit! :search_paths 21 | common_pods 22 | end 23 | 24 | end 25 | 26 | post_install do |installer| 27 | installer.pods_project.targets.each do |target| 28 | target.build_configurations.each do |config| 29 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # TextFieldsCatalog - Roadmap 2 | 3 | В данном документе описан текущий статус задач, которые необходимо выполнить для достижения определенных целей на пути развития библиотеки. 4 | 5 | ## Version 1.0.0 6 | 7 | Необходимо решить внутренние архитектурные проблемы, обновить документацию, добавить более подробные примеры работы с полями - это позволит стороннему наблюдателю быстро разобраться с тем, как именно лучше применять разработанные поля ввода на практике, а также существенно улучшит внутреннюю структуру полей ввода. 8 | 9 | 🚀  **Открыто**   📉   **2 / 7** целей выполнено **(28%)** 10 | 11 | | Завершенность | Цель | Статус | 12 | | :---: | :--- | --- | 13 | | ✅ | [Swift 5](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/24) |`done`| 14 | | ✅ | [Swift 5 Example проект](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/25) |`done`| 15 | | ❌ | [Глобальный рефакторинг внутренней архитектуры](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/9) |`ready to start`| 16 | | ❌ | [Добавить примеры работы с полями](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/7) |`ready to start`| 17 | | ❌ | [Установка текста без анимации](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/26) |`ready to start`| 18 | | ❌ | [Обновление документации](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/27) | | 19 | | ❌ | [Релиз v1.0.0](https://github.com/chausovSurfStudio/TextFieldsCatalog/issues/28) | | -------------------------------------------------------------------------------- /TextFieldsCatalog.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "TextFieldsCatalog" 4 | s.version = "0.17.0" 5 | s.summary = "This is catalog of various input field with great opportunities for validation and formatting." 6 | s.homepage = "https://github.com/chausovSurfStudio/TextFieldsCatalog" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | 9 | s.author = { "Alexander Chausov" => "chausov@surfstudio.ru" } 10 | s.ios.deployment_target = "10.0" 11 | s.swift_version = '5.0' 12 | 13 | s.source = { :git => "https://github.com/chausovSurfStudio/TextFieldsCatalog.git", :tag => "#{s.version}" } 14 | s.source_files = 'TextFieldsCatalog/**/*.{swift,xib,strings}' 15 | s.resource_bundles = { 'TextFieldsCatalog' => ['TextFieldsCatalog/Resources/Images/*.{png}'] } 16 | 17 | 18 | s.framework = "UIKit" 19 | s.dependency "InputMask", '6.0.0' 20 | 21 | end 22 | -------------------------------------------------------------------------------- /TextFieldsCatalog.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TextFieldsCatalog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TextFieldsCatalog.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TextFieldsCatalog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/BaseClasses/InnerTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InnerTextView.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Никита Гагаринов on 03.08.2021. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Class for UITextView with some extra features, it uses inside custom textViews in the project 11 | public final class InnerTextView: UITextView { 12 | 13 | // MARK: - Private Properties 14 | 15 | private var disabledActions: [StandardEditActions]? 16 | 17 | // MARK: - UITextView 18 | 19 | override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { 20 | guard disabledActions?.first(where: { $0.selector == action }) != nil else { 21 | return super.canPerformAction(action, withSender: sender) 22 | } 23 | return false 24 | } 25 | 26 | // MARK: - Internal Methods 27 | 28 | func disableEditActions(only actions: [StandardEditActions]?) { 29 | disabledActions = actions 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/Foundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSObject { 12 | 13 | @objc class var nameOfClass: String { 14 | if let name = NSStringFromClass(self).components(separatedBy: ".").last { 15 | return name 16 | } 17 | return "" 18 | } 19 | 20 | @objc var nameOfClass: String { 21 | if let name = NSStringFromClass(type(of: self)).components(separatedBy: ".").last { 22 | return name 23 | } 24 | return "" 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 31/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | 13 | func height(forWidth width: CGFloat, font: UIFont, lineHeight: CGFloat?) -> CGFloat { 14 | var attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font] 15 | if let lineHeight = lineHeight { 16 | let paragraphStyle = NSMutableParagraphStyle() 17 | paragraphStyle.lineSpacing = lineHeight - font.lineHeight 18 | attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle 19 | } 20 | let rect = CGSize(width: width, height: .greatestFiniteMagnitude) 21 | let boundingBox = self.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) 22 | return ceil(boundingBox.height) 23 | } 24 | 25 | func with(lineHeight: CGFloat, font: UIFont, color: UIColor) -> NSAttributedString { 26 | let paragraphStyle = NSMutableParagraphStyle() 27 | paragraphStyle.lineSpacing = lineHeight - font.lineHeight 28 | let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font, 29 | NSAttributedString.Key.paragraphStyle: paragraphStyle, 30 | NSAttributedString.Key.foregroundColor: color] 31 | return NSAttributedString(string: self, attributes: attributes) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/UIKit/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | /// Instantiates UIColor with hex string 14 | convenience init(hexString: String) { 15 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 16 | var int = UInt32() 17 | Scanner(string: hex).scanHexInt32(&int) 18 | let a, r, g, b: UInt32 19 | switch hex.count { 20 | case 3: // RGB (12-bit) 21 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 22 | case 6: // RGB (24-bit) 23 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 24 | case 8: // ARGB (32-bit) 25 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 26 | default: 27 | (a, r, g, b) = (255, 0, 0, 0) 28 | } 29 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/UIKit/UIImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | 13 | /// Init method for creating UIImage of a given color with 1*1 size 14 | convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { 15 | let rect = CGRect(origin: .zero, size: size) 16 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) 17 | color.setFill() 18 | UIRectFill(rect) 19 | let image = UIGraphicsGetImageFromCurrentImageContext() 20 | UIGraphicsEndImageContext() 21 | 22 | guard let cgImage = image?.cgImage else { 23 | return nil 24 | } 25 | self.init(cgImage: cgImage) 26 | } 27 | 28 | /// Method returns UIImage with given tint color 29 | func mask(with color: UIColor) -> UIImage { 30 | UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) 31 | defer { UIGraphicsEndImageContext() } 32 | 33 | guard let context = UIGraphicsGetCurrentContext() else { 34 | return self 35 | } 36 | context.translateBy(x: 0, y: self.size.height) 37 | context.scaleBy(x: 1.0, y: -1.0) 38 | context.setBlendMode(.normal) 39 | 40 | let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) 41 | guard let mask = self.cgImage else { 42 | return self 43 | } 44 | context.clip(to: rect, mask: mask) 45 | 46 | color.setFill() 47 | context.fill(rect) 48 | 49 | guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { 50 | return self 51 | } 52 | return newImage 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/UIKit/UITextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 10/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITextField { 12 | 13 | /// Returns `true` if input text is empty 14 | var isEmpty: Bool { 15 | return text?.isEmpty ?? true 16 | } 17 | 18 | func moveCursorPosition(text: String, pasteLocation: Int, replacementString: String) { 19 | let maxOffset = (text as NSString).length 20 | let offset = min(maxOffset, pasteLocation + (replacementString as NSString).length) 21 | 22 | DispatchQueue.main.async { [weak self] in 23 | guard let self = self else { 24 | return 25 | } 26 | if let newPosition = self.position(from: self.beginningOfDocument, offset: offset) { 27 | self.selectedTextRange = self.textRange(from: newPosition, to: newPosition) 28 | } 29 | } 30 | } 31 | 32 | func fixCursorPosition(pasteLocation: Int) { 33 | moveCursorPosition(text: self.text ?? "", pasteLocation: pasteLocation, replacementString: "") 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Extensions/UIKit/UITextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 10/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITextView { 12 | 13 | /// Returns `true` if input text is empty 14 | var isEmpty: Bool { 15 | return text.isEmpty 16 | } 17 | 18 | func moveCursorPosition(text: String, pasteLocation: Int, replacementString: String) { 19 | let maxOffset = (text as NSString).length 20 | let offset = min(maxOffset, pasteLocation + (replacementString as NSString).length) 21 | 22 | DispatchQueue.main.async { [weak self] in 23 | guard let self = self else { 24 | return 25 | } 26 | 27 | let contentOffset = self.contentOffset 28 | if let newPosition = self.position(from: self.beginningOfDocument, offset: offset) { 29 | self.selectedTextRange = self.textRange(from: newPosition, to: newPosition) 30 | } 31 | self.setContentOffset(contentOffset, animated: false) 32 | 33 | if let start = self.selectedTextRange?.start { 34 | let caret = self.caretRect(for: start) 35 | self.scrollRectToVisible(caret, animated: true) 36 | } 37 | } 38 | } 39 | 40 | func fixCursorPosition(pasteLocation: Int) { 41 | moveCursorPosition(text: self.text ?? "", pasteLocation: pasteLocation, replacementString: "") 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextField/DateTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTextField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 12/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Protocols for text field which can communicate with DatePickerView as its inputView 12 | public protocol DateTextField: GuidedTextField { 13 | func processDateChange(_ date: Date, text: String) 14 | } 15 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextField/GuidedTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuidedTextField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 13/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Protocols for manageable text field, which can switch responder or resign it 12 | public protocol GuidedTextField: AnyObject { 13 | var havePreviousInput: Bool { get } 14 | var haveNextInput: Bool { get } 15 | 16 | func processReturnAction() 17 | func switchToPreviousInput() 18 | func switchToNextInput() 19 | } 20 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextField/PickerTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerTextField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 13/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Protocols for text field which can communicate with PlainPickerView as its inputView 12 | public protocol PickerTextField: GuidedTextField { 13 | func processValueChange(_ value: String) 14 | } 15 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextField/ResetableField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResetableField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Describes type of field which can reset its state 12 | public protocol ResetableField { 13 | func reset(animated: Bool) 14 | } 15 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextField/RespondableField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RespondableField.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 22/05/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Abstract protocol that give you ability to control under responder chain for text fields. 13 | */ 14 | public protocol RespondableField { 15 | /** 16 | Next responder, next field which will be activated with `next` button on keyboard or toolbar. 17 | 18 | You can setup any UIResponder object and it will be activated when user tap on `next` keyboard button. 19 | 20 | - Important: 21 | - When you setup non-nill object - your `returnKeyType` will set with `.next` value. 22 | - If you provide nill object - `returnKeyType` will set with `.default` value. 23 | - This rules doesn't apply into the TextView. 24 | - This property will be used separatly for your needs, but also it can be used with picker views from library (which have custom toolbar view with next/previous arrow buttons). 25 | */ 26 | var nextInput: UIResponder? { get set } 27 | /** 28 | Previous responder, previous field which will be activated with `previous` button on custom tollbar view from this library. 29 | 30 | Originally, this property is useless, because default keyboard doesn't have any button for switching on previous field. But you can use this property for your custom toolbar or predefined toolbar from this library. 31 | */ 32 | var previousInput: UIResponder? { get set } 33 | /// Returns a Boolean value indicating whether this object is the first responder. 34 | var isFirstResponder: Bool { get } 35 | /// Returns a Boolean value indicating whether this object can become the first responder. 36 | var canBecomeFirstResponder: Bool { get } 37 | 38 | /// Asks UIKit to make this object the first responder in its window. 39 | func becomeFirstResponder() -> Bool 40 | } 41 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/TextFieldValidation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldValidation.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol TextFieldValidation: AnyObject { 12 | func validate(_ text: String?) -> (isValid: Bool, errorMessage: String?) 13 | } 14 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Protocols/ToolBarInterface.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolBarInterface.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 09/10/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Abstract protocol for fields input accessory views. 12 | /// You can add buttons to switch between input fields, guidedField will help you with this. 13 | /// Or you can ignore this field and create your own custom toolbar! 14 | /// Because protocol have default implementation and all of it's components is optional. 15 | public protocol ToolBarInterface: UIView { 16 | /// Field that allows you to manage responder chain from your views 17 | var guidedField: GuidedTextField? { get set } 18 | /// In this method you have to update your toolbars's appearance via field states 19 | func updateNavigationButtons() 20 | /// Invokes when text in connected field did change 21 | func textDidChange(text: String) 22 | } 23 | 24 | public extension ToolBarInterface { 25 | var guidedField: GuidedTextField? { 26 | get { 27 | return nil 28 | } 29 | set { 30 | } 31 | } 32 | 33 | func updateNavigationButtons() {} 34 | 35 | func textDidChange(text: String) {} 36 | } 37 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Reusable/IconButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconButton.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class IconButton: CommonButton { 12 | 13 | // MARK: - Initialization 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | setupInitialState() 18 | } 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | setupInitialState() 23 | } 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | setupInitialState() 28 | } 29 | 30 | // MARK: - Internal Methods 31 | 32 | func setImageForAllState(_ image: UIImage?, normalColor: UIColor, pressedColor: UIColor) { 33 | guard let image = image else { 34 | return 35 | } 36 | setImage(image.mask(with: normalColor), for: .normal) 37 | setImage(image.mask(with: pressedColor), for: .selected) 38 | setImage(image.mask(with: pressedColor), for: .highlighted) 39 | } 40 | 41 | } 42 | 43 | private extension IconButton { 44 | 45 | func setupInitialState() { 46 | activeBackgroundColor = UIColor.clear 47 | highlightedBackgroundColor = UIColor.clear 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Utils/AssetManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetManager.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 29/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class AssetManager { 13 | 14 | func getImage(_ name: String) -> UIImage { 15 | let traitCollection = UITraitCollection(displayScale: UIScreen.main.scale) 16 | var bundle: Bundle 17 | #if SWIFT_PACKAGE 18 | bundle = Bundle.module 19 | #else 20 | bundle = Bundle(for: AssetManager.self) 21 | #endif 22 | if let resource = bundle.resourcePath, let resourceBundle = Bundle(path: resource + "/TextFieldsCatalog.bundle") { 23 | bundle = resourceBundle 24 | } 25 | 26 | return UIImage(named: name, in: bundle, compatibleWith: traitCollection) ?? UIImage() 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Utils/FormatterMasks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormatterMasks.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import InputMask 11 | 12 | /// Masks for text field formatters. Rules for this masks you can see in https://github.com/RedMadRobot/input-mask-ios 13 | /// As additional notations for this masks you can use "s" character for all characters except newlines and whitespaces 14 | public enum FormatterMasks { 15 | public static let password = "[ssssssss][s…]" 16 | public static let phone = "+7 ([000]) [000]-[00]-[00]" 17 | public static let cardExpirationDate = "[00]/[00]" 18 | public static let cvc = "[000]" 19 | public static let cardNumber = "[0000] [0000] [0000] [0000] [999]" 20 | } 21 | 22 | // MARK: - Custom Notations 23 | 24 | extension FormatterMasks { 25 | 26 | private enum Notations { 27 | enum ExcludeNewlinesAndWhitespaces { 28 | static let character: Character = "s" 29 | static let set = CharacterSet.whitespacesAndNewlines.inverted 30 | } 31 | } 32 | 33 | /// Method returns all custom notations for this application 34 | static func notations() -> [Notation] { 35 | return [ 36 | Notation(character: FormatterMasks.Notations.ExcludeNewlinesAndWhitespaces.character, 37 | characterSet: FormatterMasks.Notations.ExcludeNewlinesAndWhitespaces.set, 38 | isOptional: false) 39 | ] 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Library/Utils/StandardEditActions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardEditActions.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Никита Гагаринов on 03.08.2021. 6 | // Copyright © 2021 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Contains all standard edit actions for disabling it in fields 12 | public enum StandardEditActions { 13 | 14 | case cut 15 | case copy 16 | case paste 17 | case select 18 | case selectAll 19 | case delete 20 | 21 | var selector: Selector { 22 | switch self { 23 | case .cut: 24 | return #selector(UIResponderStandardEditActions.cut) 25 | case .copy: 26 | return #selector(UIResponderStandardEditActions.copy) 27 | case .paste: 28 | return #selector(UIResponderStandardEditActions.paste) 29 | case .select: 30 | return #selector(UIResponderStandardEditActions.select) 31 | case .selectAll: 32 | return #selector(UIResponderStandardEditActions.selectAll) 33 | case .delete: 34 | return #selector(UIResponderStandardEditActions.delete) 35 | } 36 | } 37 | 38 | } 39 | 40 | public extension Array where Element == StandardEditActions { 41 | 42 | static var all: [StandardEditActions] { 43 | return [.cut, .copy, .paste, .select, .selectAll, .delete] 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Colors/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Contains all projects color constants 12 | enum Color { 13 | /// Main color with its names from Figma 14 | private enum Figma { 15 | static let bold = UIColor(hexString: "1F2032") 16 | static let regular = UIColor(hexString: "34364E") 17 | static let light = UIColor(hexString: "9FACC7") 18 | static let active = UIColor(hexString: "FF9D2B") 19 | static let activePress = UIColor(hexString: "D47506") 20 | static let red = UIColor(hexString: "FF4747") 21 | static let black = UIColor(hexString: "000000") 22 | static let white = UIColor(hexString: "FEFEFE") 23 | } 24 | /// Main colors of application 25 | enum Main { 26 | static let background = Color.Figma.bold 27 | static let container = Color.Figma.regular 28 | static let active = Color.Figma.active 29 | static let red = Color.Figma.red 30 | } 31 | /// Colors for labels and button text 32 | enum Text { 33 | static let white = Color.Figma.white 34 | static let black = Color.Figma.black 35 | static let gray = Color.Figma.light 36 | static let red = Color.Figma.red 37 | static let active = Color.Figma.active 38 | } 39 | /// Colors for buttons 40 | enum Button { 41 | static let active = Color.Figma.active 42 | static let pressed = Color.Figma.activePress 43 | static let disabled = Color.Figma.regular 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/FieldContainerState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldContainerState.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 06/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | /// Possible textField/textView container states, 10 | /// includes FieldState cases plus `error` state case 11 | public enum FieldContainerState { 12 | /// field not in focus 13 | case normal 14 | /// state for active field 15 | case active 16 | /// state for disabled field 17 | case disabled 18 | /// state for text field in error state 19 | case error 20 | } 21 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/HeightLayoutPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeightLayoutPolicy.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 02/02/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum HeightLayoutPolicy { 12 | /// Fixed height of text field 13 | case fixed 14 | /** 15 | Flexible height policy for text field. 16 | Also allows you to configure minimal height for text field and bottom space value under hint label. 17 | */ 18 | case elastic(policy: FlexibleHeightPolicy) 19 | } 20 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/HintVisibleStates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HintMessageState.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 05.01.2021. 6 | // Copyright © 2021 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | /// Option set for managing possible visible states for fields hint message. 10 | /// 11 | /// You can provide needed set to hint service init method or 12 | /// change this set for specific field with appropriate method. 13 | /// Hint or error messages will be shown only for specified states. 14 | public struct HintVisibleStates: OptionSet { 15 | 16 | // MARK: - Public Properties 17 | 18 | public let rawValue: Int 19 | 20 | public static let error = HintVisibleStates(rawValue: 1 << 0) 21 | public static let disabled = HintVisibleStates(rawValue: 1 << 1) 22 | public static let normal = HintVisibleStates(rawValue: 1 << 2) 23 | public static let active = HintVisibleStates(rawValue: 1 << 3) 24 | 25 | public static let all: HintVisibleStates = [.error, .disabled, .normal, .active] 26 | 27 | // MARK: - Initialization 28 | 29 | public init(rawValue: Int) { 30 | self.rawValue = rawValue 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/Internal/AccessibilityIdentifiers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityIdentifiers.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 17/05/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Reusable constants that are added to the user-specified identifier when setting the identifier for the internal field elements 12 | enum AccessibilityIdentifiers { 13 | static let field = "_input" 14 | static let button = "_button" 15 | static let hint = "_hint" 16 | } 17 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/Internal/AnimationTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationTime.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 08/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum AnimationTime { 12 | static let `default`: TimeInterval = 0.25 13 | } 14 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/Internal/FieldState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldState.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 05/09/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Possible textField/textView states 12 | public enum FieldState { 13 | /// field not in focus 14 | case normal 15 | /// state for active field 16 | case active 17 | /// state for disabled field 18 | case disabled 19 | 20 | // MARK: - Properties 21 | 22 | var containerState: FieldContainerState { 23 | switch self { 24 | case .normal: 25 | return .normal 26 | case .active: 27 | return .active 28 | case .disabled: 29 | return .disabled 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/NativePlaceholderBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NativePlaceholderBehavior.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/04/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | public enum NativePlaceholderBehavior { 10 | /// placeholder will hide when user tap on the field, 11 | /// recommend to use with 'useAsMainPlaceholder' == true case 12 | case hideOnFocus 13 | /// placeholder will hide when user enter at least one character in field 14 | case hideOnInput 15 | } 16 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/PasteOverflowPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasteOverflowPolicy.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Olesya Tranina on 26.04.2021. 6 | // Copyright © 2021 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | /// Allows you to manage paste behaviors for text fields that use maxLength 10 | public enum PasteOverflowPolicy { 11 | /// Pastes nothing if the text cannot fit completely 12 | case noChanges 13 | /// Pastes part of text that can fit 14 | case textThatFits 15 | } 16 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/SharedRegex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SharedRegex.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum SharedRegex { 12 | public static let email = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 13 | public static let everything = "(.*)" 14 | public static let password = "^((?=(.*\\d))(?=.*[A-Z])(?=.*[a-z]))(.{8,})" 15 | } 16 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/TextFieldMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldMode.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 05/09/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Possible mode for textFields 12 | public enum TextFieldMode { 13 | /// normal textField mode without any action buttons 14 | case plain 15 | /// mode for password textField 16 | case password(TextFieldPasswordModeBehavior) 17 | /// mode for textField with custom action button 18 | case custom(ActionButtonConfiguration) 19 | } 20 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/TextFieldPasswordModeBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldPasswordModeBehavior.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 08/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | public enum TextFieldPasswordModeBehavior { 10 | /// password button always visible 11 | case alwaysVisible 12 | /// password button visible only if text is not empty 13 | case visibleOnNotEmptyText 14 | /// password button becomes visible after the user enters the first character, 15 | /// and never disappears again. 16 | case visibleAfterFirstEntry 17 | } 18 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Constants/ValidationPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidationPolicy.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 13/10/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Allows you to manage validation behaviors after text field end editing 12 | public enum ValidationPolicy { 13 | /// Validation always performs after text field end editing 14 | case always 15 | /// Validation performs if current text is not empty 16 | case notEmptyText 17 | /// Validation performs if user make some changes into the text 18 | /// (entered at least one character, set the text, or manually validate the field) 19 | case afterChanges 20 | /// Validation is never performed 21 | case never 22 | } 23 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/close.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/close@2x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/close@3x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOff.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOff@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOff@2x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOff@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOff@3x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOn.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOn@2x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/eyeOn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/eyeOn@3x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/leftArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/leftArrow.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/leftArrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/leftArrow@2x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/leftArrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/leftArrow@3x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/rightArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/rightArrow.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/rightArrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/rightArrow@2x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Images/rightArrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/TextFieldsCatalog/Resources/Images/rightArrow@3x.png -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Strings/Strings.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | import Foundation 5 | 6 | // swiftlint:disable superfluous_disable_command file_length implicit_return 7 | 8 | // MARK: - Strings 9 | 10 | // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length 11 | // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces 12 | internal enum L10n { 13 | 14 | internal enum Button { 15 | /// Done 16 | internal static let done = L10n.tr("Localizable", "Button.Done") 17 | } 18 | 19 | internal enum Errors { 20 | internal enum TextField { 21 | /// Field must be filled 22 | internal static let empty = L10n.tr("Localizable", "Errors.TextField.empty") 23 | /// Wrong format 24 | internal static let notValid = L10n.tr("Localizable", "Errors.TextField.notValid") 25 | /// The field must contain at least %@ characters. 26 | internal static func short(_ p1: Any) -> String { 27 | return L10n.tr("Localizable", "Errors.TextField.short", String(describing: p1)) 28 | } 29 | } 30 | } 31 | } 32 | // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length 33 | // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces 34 | 35 | // MARK: - Implementation Details 36 | 37 | extension L10n { 38 | private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { 39 | let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table) 40 | return String(format: format, locale: Locale.current, arguments: args) 41 | } 42 | } 43 | 44 | // swiftlint:disable convenience_type 45 | private final class BundleToken { 46 | static let bundle: Bundle = { 47 | #if SWIFT_PACKAGE 48 | return Bundle.module 49 | #else 50 | return Bundle(for: BundleToken.self) 51 | #endif 52 | }() 53 | } 54 | // swiftlint:enable convenience_type 55 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Strings/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TextFieldsCatalog 4 | 5 | Created by Александр Чаусов on 28/01/2019. 6 | Copyright © 2019 Александр Чаусов. All rights reserved. 7 | */ 8 | 9 | // MARK: - Errors 10 | 11 | "Errors.TextField.empty" = "Field must be filled"; 12 | "Errors.TextField.short" = "The field must contain at least %@ characters."; 13 | "Errors.TextField.notValid" = "Wrong format"; 14 | 15 | // MARK: - Button 16 | 17 | "Button.Done" = "Done"; 18 | -------------------------------------------------------------------------------- /TextFieldsCatalog/Resources/Strings/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | TextFieldsCatalog 4 | 5 | Created by Александр Чаусов on 28/01/2019. 6 | Copyright © 2019 Александр Чаусов. All rights reserved. 7 | */ 8 | 9 | // MARK: - Errors 10 | 11 | "Errors.TextField.empty" = "Поле должно быть заполнено"; 12 | "Errors.TextField.short" = "Поле должно содержать минимум %@ символов"; 13 | "Errors.TextField.notValid" = "Неверный формат"; 14 | 15 | // MARK: - Button 16 | 17 | "Button.Done" = "Готово"; 18 | -------------------------------------------------------------------------------- /TextFieldsCatalog/TextFields/Configuration/FlexibleHeightPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlexibleHeightPolicy.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 08/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Flexible height policy for text field/view. 13 | Also allows you to configure minimal height for input field and bottom space value under hint label. 14 | */ 15 | public struct FlexibleHeightPolicy { 16 | 17 | // MARK: - Properties 18 | 19 | let minHeight: CGFloat 20 | /// additional offset (usually used for offset between hintLabel and textField/textView) 21 | let bottomOffset: CGFloat 22 | /** 23 | Final height depends on `ignoreEmptyHint` value: 24 | - if `ignoreEmptyHint` == true, then the algorithm will add bottomOffset to the final height only if the height of the tooltip is not zero 25 | - if `ignoreEmptyHint` == false, then resulting height is obtained always as sum of all elements heights and offsets between it 26 | */ 27 | let ignoreEmptyHint: Bool 28 | 29 | // MARK: - Initialization 30 | 31 | public init(minHeight: CGFloat, 32 | bottomOffset: CGFloat, 33 | ignoreEmptyHint: Bool) { 34 | self.minHeight = minHeight 35 | self.bottomOffset = bottomOffset 36 | self.ignoreEmptyHint = ignoreEmptyHint 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /TextFieldsCatalog/TextFields/Services/Support/AbstractHintService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AbstractHintService.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 05.01.2021. 6 | // Copyright © 2021 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Abstract protocl for hint service. 13 | 14 | You can use existed `HintService` from this library, this service will do all work for supporting hint messages, 15 | or you can provide your own service which have to implement this protocol. 16 | */ 17 | public protocol AbstractHintService { 18 | /** 19 | This method provides your service with a label, where you have to place hint message 20 | */ 21 | func provide(label: UILabel) 22 | /** 23 | Method where you have to setup initial state for hint label 24 | */ 25 | func configureAppearance() 26 | /** 27 | Method invokes when field wants to update UI elements. 28 | You have to update visibility of you hint, it's color, etc. 29 | */ 30 | func updateContent(containerState: FieldContainerState, 31 | heightLayoutPolicy: HeightLayoutPolicy, 32 | animated: Bool) 33 | /** 34 | Method allows to calculate current hint message height, 35 | or returns zero if hint message doesn't exist or invisible 36 | */ 37 | func hintHeight(containerState: FieldContainerState) -> CGFloat 38 | 39 | /** 40 | Method allows setup hint message 41 | */ 42 | func setup(plainHint: String?) 43 | /** 44 | Method allows setup error hint message. 45 | 46 | If errorHint is nil - you have to save previous plain hint message in label, if it exists. 47 | */ 48 | func setup(errorHint: String?) 49 | /** 50 | Method to drop all error hint messages and present plain hint, if it exists. 51 | */ 52 | func showHint() 53 | /** 54 | Service method, which allows you to change current visible hint states. 55 | */ 56 | func setup(visibleHintStates: HintVisibleStates) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /TextFieldsCatalog/TextFields/Services/Support/InputFieldProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputFieldProtocol.swift 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 07/01/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Abstraction under UITextField/InnerTextField and UITextView 13 | 14 | The need is due to the desire to close two different classes with one protocol for use in places where both classes will be used in the same way. 15 | 16 | - Important: Protocol have two default extension with implementation for InnerTextField and UITextView. 17 | */ 18 | public protocol InputField: UIView { 19 | /// Text in textField/textView 20 | var inputText: String? { get } 21 | var textColor: UIColor? { get set } 22 | var backgroundColor: UIColor? { get set } 23 | } 24 | 25 | extension InnerTextField: InputField { 26 | public var inputText: String? { 27 | return text 28 | } 29 | } 30 | 31 | extension UITextView: InputField { 32 | public var inputText: String? { 33 | return text 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TextFieldsCatalog/TextFieldsCatalog.h: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldsCatalog.h 3 | // TextFieldsCatalog 4 | // 5 | // Created by Александр Чаусов on 28/01/2019. 6 | // Copyright © 2019 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TextFieldsCatalog. 12 | FOUNDATION_EXPORT double TextFieldsCatalogVersionNumber; 13 | 14 | //! Project version string for TextFieldsCatalog. 15 | FOUNDATION_EXPORT const unsigned char TextFieldsCatalogVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /TextFieldsCatalogTests/Extensions/UITextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField.swift 3 | // TextFieldsCatalogTests 4 | // 5 | // Created by Александр Чаусов on 01/03/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TextFieldsCatalog 11 | 12 | final class UITextFieldTests: XCTestCase { 13 | 14 | // MARK: - Tests 15 | 16 | func testEmptyTextField() { 17 | // given 18 | let firstTextField = UITextField() 19 | let secondTextField = UITextField() 20 | secondTextField.text = "" 21 | 22 | // when 23 | let firstIsEmpty = firstTextField.isEmpty 24 | let secondIsEmpty = secondTextField.isEmpty 25 | 26 | // then 27 | XCTAssertTrue(firstIsEmpty) 28 | XCTAssertTrue(secondIsEmpty) 29 | } 30 | 31 | func testNotEmptyTextField() { 32 | // given 33 | let textField = UITextField() 34 | textField.text = "test" 35 | 36 | // when 37 | let fieldIsEmpty = textField.isEmpty 38 | 39 | // then 40 | XCTAssertFalse(fieldIsEmpty) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /TextFieldsCatalogTests/Extensions/UITextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView.swift 3 | // TextFieldsCatalogTests 4 | // 5 | // Created by Александр Чаусов on 01/03/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TextFieldsCatalog 11 | 12 | final class UITextViewTests: XCTestCase { 13 | 14 | // MARK: - Tests 15 | 16 | func testEmptyTextView() { 17 | // given 18 | let textView = UITextView() 19 | 20 | // when 21 | let viewIsEmpty = textView.isEmpty 22 | 23 | // then 24 | XCTAssertTrue(viewIsEmpty) 25 | } 26 | 27 | func testNotEmptyTextView() { 28 | // given 29 | let textView = UITextField() 30 | textView.text = "test" 31 | 32 | // when 33 | let viewIsEmpty = textView.isEmpty 34 | 35 | // then 36 | XCTAssertFalse(viewIsEmpty) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /TextFieldsCatalogTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TextFieldsCatalogTests/Utils/AssetManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetManager.swift 3 | // TextFieldsCatalogTests 4 | // 5 | // Created by Александр Чаусов on 24/04/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TextFieldsCatalog 11 | 12 | final class AssetManagerTests: XCTestCase { 13 | 14 | // MARK: - Tests 15 | 16 | func testImageGeneration() { 17 | // given 18 | let imageName = "close" 19 | 20 | // when 21 | let image = AssetManager().getImage(imageName) 22 | 23 | // then 24 | XCTAssertNotNil(image) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /TextFieldsCatalogTests/Utils/MaskTextFieldFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaskTextFieldFormatter.swift 3 | // TextFieldsCatalogTests 4 | // 5 | // Created by Александр Чаусов on 24/04/2020. 6 | // Copyright © 2020 Александр Чаусов. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TextFieldsCatalog 11 | 12 | final class MaskTextFieldFormatterTests: XCTestCase { 13 | 14 | // MARK: - Tests 15 | 16 | func testFormatting() { 17 | // given 18 | typealias TestData = (mask: String, value: String?, result: String?) 19 | let testData: [TestData] = [ 20 | (FormatterMasks.phone, "79005553333", "+7 (900) 555-33-33"), 21 | (FormatterMasks.cardExpirationDate, "asd3265", "32/65"), 22 | (FormatterMasks.cvc, "!654", "654"), 23 | (FormatterMasks.cardNumber, "12345678901234567", "1234 5678 9012 3456 7"), 24 | (FormatterMasks.cardNumber, nil, nil), 25 | (FormatterMasks.cardNumber, "", "") 26 | ] 27 | 28 | for data in testData { 29 | // when 30 | let result = MaskTextFieldFormatter(mask: data.mask).format(string: data.value) 31 | 32 | // then 33 | XCTAssertEqual(result, data.result) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 51% 23 | 24 | 25 | 51% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.textfieldscatalog 7 | CFBundleName 8 | TextFieldsCatalog 9 | DocSetPlatformFamily 10 | textfieldscatalog 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/docsets/TextFieldsCatalog.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/docsets/TextFieldsCatalog.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/TextFieldsCatalog.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/docsets/TextFieldsCatalog.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier "[[APP_IDENTIFIER]]" # The bundle identifier of your app 2 | apple_id "APPLE_ID" # Your Apple email address 3 | 4 | team_id "TEAM_ID" # Developer Portal Team ID 5 | 6 | # For more information about the Appfile, see: 7 | # https://docs.fastlane.tools/advanced/#appfile 8 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customise this file, documentation can be found here: 2 | # https://docs.fastlane.tools/actions/ 3 | # All available actions: https://docs.fastlane.tools/actions 4 | # can also be listed using the `fastlane actions` command 5 | 6 | # Change the syntax highlighting to Ruby 7 | # All lines starting with a # are ignored when running `fastlane` 8 | 9 | # If you want to automatically update fastlane if a new version is available: 10 | # update_fastlane 11 | 12 | prod_bundle_id = "surf.TextFieldsCatalog" 13 | prod_scheme = "TextFieldsCatalog" 14 | prod_target = "TextFieldsCatalog" 15 | 16 | derived_data_path = "./buildData" 17 | 18 | default_platform :ios 19 | 20 | platform :ios do 21 | 22 | ENV["FASTLANE_HIDE_CHANGELOG"] = "true" 23 | ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "true" 24 | 25 | before_all do |lane, options| 26 | # ensure_git_status_clean 27 | end 28 | 29 | desc "Build Main scheme of Xcode target in Debug configuration" 30 | desc "Parameters: 31 | - clean: Pass **true** if you need to clean project before build 32 | " 33 | lane :build do |options| 34 | xcodebuild( 35 | scheme: prod_scheme, 36 | configuration: "Debug", 37 | clean: options[:clean], 38 | build: true, 39 | destination: "generic/platform=iOS\" CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=\"", 40 | xcargs: "-sdk iphonesimulator ONLY_ACTIVE_ARCH=NO build-for-testing", 41 | derivedDataPath: derived_data_path 42 | ) 43 | end 44 | 45 | desc "Run tests" 46 | lane :tests do 47 | run_tests( 48 | devices: ["iPhone SE"], 49 | scheme: prod_scheme, 50 | code_coverage: false, 51 | test_without_building: true, 52 | skip_build: true, 53 | derived_data_path: derived_data_path 54 | ) 55 | end 56 | 57 | end -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-git_tags' 6 | gem 'fastlane-plugin-versioning' 7 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios build 20 | ``` 21 | fastlane ios build 22 | ``` 23 | Build Main scheme of Xcode target in Debug configuration 24 | 25 | Parameters: 26 | - clean: Pass **true** if you need to clean project before build 27 | 28 | ### ios tests 29 | ``` 30 | fastlane ios tests 31 | ``` 32 | Run tests 33 | 34 | ---- 35 | 36 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 37 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 38 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 39 | -------------------------------------------------------------------------------- /swiftgen.yml: -------------------------------------------------------------------------------- 1 | strings: 2 | inputs: TextFieldsCatalog/Resources/Strings/en.lproj/Localizable.strings 3 | outputs: 4 | - templateName: structured-swift4 5 | params: 6 | publicAccess: false 7 | output: TextFieldsCatalog/Resources/Strings/Strings.swift 8 | -------------------------------------------------------------------------------- /tech_docs/DevNotes.md: -------------------------------------------------------------------------------- 1 | # Заметки разработчика 2 | 3 | Данный документ содержит вспомогательную информацию, которая поможет в будущем понять разработчикам тот или иной кусок логики. Документ для внутреннего использования. 4 | 5 | ## NativePlaceholderService 6 | 7 | Сервис содержит метод `func updatePlaceholderVisibility(fieldState: FieldState)` с довольно странной логикой внутри. 8 | 9 | Чтобы понять, почему именно так, а не как-то иначе - ниже предоставлено фото, которое объясняет взаимосвязь жизненных кейсов и логику внутри этого метода: 10 | 11 |

12 | 13 |

-------------------------------------------------------------------------------- /tech_docs/Images/BorderedTextField.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/BorderedTextField.png -------------------------------------------------------------------------------- /tech_docs/Images/DatePickerView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/DatePickerView.png -------------------------------------------------------------------------------- /tech_docs/Images/DatePickerViewCustomized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/DatePickerViewCustomized.png -------------------------------------------------------------------------------- /tech_docs/Images/NativePlaceholderService.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/NativePlaceholderService.png -------------------------------------------------------------------------------- /tech_docs/Images/PlainPickerView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/PlainPickerView.png -------------------------------------------------------------------------------- /tech_docs/Images/TextFieldsCatalog_video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/TextFieldsCatalog_video.gif -------------------------------------------------------------------------------- /tech_docs/Images/UnderlinedTextField.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/UnderlinedTextField.png -------------------------------------------------------------------------------- /tech_docs/Images/UnderlinedTextView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/UnderlinedTextView.png -------------------------------------------------------------------------------- /tech_docs/Images/textView_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chausovSurfStudio/TextFieldsCatalog/bf12b4a43105cedcfdc8bc6966f57b021c3b95b7/tech_docs/Images/textView_scheme.png -------------------------------------------------------------------------------- /tech_docs/PodProject.md: -------------------------------------------------------------------------------- 1 | # Проект с каталогом полей ввода 2 | 3 | **Эта документ для тех, кто хочет добавить новое поле ввода/изменить поведение существующего.** 4 | 5 | ## Содержание 6 | 7 | - [Конфигурация проекта](#Конфигурация-проекта) 8 | 9 | ## Конфигурация проекта 10 | 11 | Для установки всех необходимых утилит достаточно выполнить команду `make init`. 12 | 13 | Скрипт установит все необходимые зависимости нужных версий при помощи `bundler`, а также выполнит команду `pod install`. 14 | 15 | После выполнения скрипта можно открывать .xcworkspace файл проекта и начинать над ним работу. 16 | 17 | **Важно** - возможны проблемы с версией `bundler`, если текущая установленная версия конфликтует с необходимыми для работы проекта гемами. Рекомендуется использовать версию **1.17.1** 18 | 19 | Дальнейшее использование Gem зависимостей необходимо выполнять при помощи версий, установленных через `bundler`. Для облегчения работы - на все основные команды написаны обертки, вызывающие их через `bundler`. 20 | 21 | Например, CocoaPods зависимости устанавливаются при помощи команды: `make pod`. 22 | 23 | Для вывода в консоль всех возможных команд вместе с их описанием - достаточно вызывать команду `make help`. --------------------------------------------------------------------------------