Compare commits

..

No commits in common. "master" and "0.3.5" have entirely different histories.

1074 changed files with 3222 additions and 74827 deletions

View File

@ -1,2 +0,0 @@
---
BUNDLE_PATH: ".gem"

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
import sys, re, os
from subprocess import check_output
from sys import getdefaultencoding
getdefaultencoding() # utf-8
valid_commit_style = '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style)(\(\S+\))?\!?: .+'
merge_commit_style = '^(m|M)erge .+'
success_title = 'SUCCESS'
success_color = '92m'
error_title = 'ERROR'
error_message = 'Incorrect commit message style!\nThe commit pattern:'
error_commit_pattern = ' type(scope): message | type: message \n'
error_color = '91m'
breaking_changes_message = 'If commit include Breaking changes use ! after type or scope:'
colored_breaking_changes_message = 'If commit include \033[91mBreaking changes\033[00m use \033[91m!\033[00m after type or scope:'
breaking_changes_commit_pattern = ' type(scope)!: message | type!: message \n'
available_types_message = 'Available commit types:'
available_commit_types = ['build: Changes that affect the build system or external dependencies',
'ci: Changes to our CI configuration files and scripts',
'docs: Documentation only changes',
'feat: A new feature. Correlates with MINOR in SemVer',
'fix: A bug fix. Correlates with PATCH in SemVer',
'perf: A code change that improves performance',
'refactor: A code change that neither fixes',
'revert: A revert to previous commit',
'style: Changes that do not affect the meaning of the code (white-space, formatting, etc)']
is_GUI_client = False
def print_result_header(result_title, color):
if not is_GUI_client:
print("[\033[96mcommit lint\033[00m] [\033[{}{}\033[00m]\n".format(color, result_title))
def print_pattern(pattern):
if is_GUI_client:
print(pattern)
else:
print("\033[96m{}\033[00m".format(pattern))
def print_error_message():
print_result_header(error_title, error_color)
print(error_message)
print_pattern(error_commit_pattern)
if is_GUI_client:
print(breaking_changes_message)
else:
print(colored_breaking_changes_message)
print_pattern(breaking_changes_commit_pattern)
print_available_commit_types()
def print_available_commit_types():
print(available_types_message)
for commit_type in available_commit_types:
print(" - %s" %commit_type)
def write_commit_message(fh, commit_msg):
fh.seek(0, 0)
fh.write(commit_msg)
def lint_commit_message(fh, commit_msg):
is_merge_commit = re.findall(merge_commit_style, commit_msg)
is_valid_commit = re.findall(valid_commit_style, commit_msg)
if is_valid_commit or is_merge_commit:
print_result_header(success_title, success_color)
write_commit_message(fh, commit_msg)
sys.exit(os.EX_OK)
else:
print_error_message()
sys.exit(os.EX_DATAERR)
def run_script():
commit_msg_filepath = sys.argv[1]
with open(commit_msg_filepath, 'r+') as fh:
commit_msg = fh.read()
lint_commit_message(fh, commit_msg)
try:
sys.stdin = open("/dev/tty", "r")
is_GUI_client = False
except:
is_GUI_client = True
run_script()

83
.gitignore vendored
View File

@ -1,18 +1,16 @@
# ================
# Swift.gitignore
# ================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
## Build generated
build/
DerivedData/
*.moved-aside
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
@ -21,14 +19,17 @@ DerivedData/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
@ -38,14 +39,6 @@ playground.xcworkspace
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
.swiftpm
.build/
# CocoaPods
@ -55,56 +48,28 @@ playground.xcworkspace
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
# https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/screenshots
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
# AppCode
# https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems
# homebrew-bundle
Brewfile.lock.json
.idea/workspace.xml
.idea/tasks.xml
# Node.js
# Dependency directories
node_modules/
# Touch Instinct custom
Downloads/
fastlane/README.md
Templates/
cpd-output.xml
*.swp
*IDEWorkspaceChecks.plist
# Gem
.gem/
.DS_Store

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "build-scripts"]
path = build-scripts
url = https://git.svc.touchin.ru/TouchInstinct/BuildScripts.git

1
.swift-version Normal file
View File

@ -0,0 +1 @@
3.0

View File

@ -1,941 +0,0 @@
# Changelog
### 1.56.0
- **Update**: `ViewSkeletonsConfiguration`. It's possible to enable or disable animation for specific skeletons now.
- **Added**: `HolderViewSkeletonsConfiguration` for skeleton root view configuration
- **Added**: `DashedBoundsLayer` can now be applied to `CALayer`
### 1.55.1
- **Update**: revert `TextSkeletonsConfiguration` line height calculation
### 1.55.0
- **Update**: use TouchInstinct `TableKit` fork instead of original one
- **Update**: remove default value from `BoolValueDefaultsStorage`
### 1.54.6
- **Added**: `xcprivacy` files
- **Update**: Correctly detect app reinstall in `AppInstallLifetimeSingleValueStorage`
- **Update**: use `xHeight` instead of `pointSize` for default skeleton line height calculation
- **Update**: update `linkTextAttributes` in `UITextView` when setting interactive url parts
### 1.54.5
- **Update**: Сhange `StatefulButton` event propogation avoidance method.
### 1.54.4
- **Update**: Fix `StatefulButton` state configuration for iOS 15+.
### 1.54.3
- **Update**: Set reasonable defaults for `SkeletonConfiguration`.
### 1.54.2
- **Update**: Changed access level from internal to public of title and subtitle view in `BaseTitleSubtitleView`.
### 1.54.1
- **Added**: `BaseTitleSubtitleView` which can be inherited for fine-tuning skeletons and other behavior.
- **Update**: Changed lines number calculation method in `TextSkeletonsConfiguration`.
### 1.54.0
- **Added**: `maxWidth` parameter to `BaseViewSkeletonsConfiguration`.
- **Added**: custom `SkeletonConfigurations` for nested `SkeletonPresenters`.
- **Update**: Many fixes and improvenments to `TextSkeletonsConfiguration`.
### 1.53.3
- **Update**: `Skeletonable` can now control custom geometry change notification.
- **Update**: Filter hidden views from skeletonable views by default.
### 1.53.2
- **Update**: `DefaultTitleSubtitleView` support for separated configuration of title and subtitle labels layout.
- **Update**: `BaseListItemView` fixed trailing insets when trailing view is hidden.
### 1.53.1
- **Update**: Insets layout heuristics for `WrappedViewHodler` implementations
### 1.53.0
- **Added**: Custom string attributes to `BaseTextAttributes`
- **Added**: Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
- **Added**: Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
- **Update**: Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
### 1.52.0
- **Added**: `TIApplication` module with core dependencies of main application and its extension targets
- **Added**: `DefaultHomogeneousItemsCollectionView` default collection view implementation with configurable identical-type cells
- **Update**: Changed implementation of `AppInstallLifetimeSingleValueStorage`. Now it uses `SingleValueStorage<Bool>` to be able to migrate stored UserDefaults values
- **Added**: `UserLocationFetcher.OnLocationFetchFailureCallback` and `ItemDistanceTo` in `TIMapUtils`
- **Added**: Tap handler closure to `DefaultConfigurableStatefulButton.ViewModel`
- **Added**: Universal DSL
### 1.51.0
- **Added**: `BaseModalViewController` implementing `PanModalPresentable` with additional functionality
- **Added**: `BaseModalWrapperViewController` for wrapping `UIViewController`s with `BaseModalViewController` functionality
### 1.50.0
- **Updated**: Fix activity indicator positioning for `StatefulButton` on iOS 15+ and disabled state touch handling
- **Added**: iOS 15+ activity indicator placement support in `StatefulButton`
- **Added**: `TICoreGraphicsUtils` module for drawing operations and other CoreGraphics related functionality
- **Update**: `MarkerIconFactory` can now return optional `UIImage`. In this case MapManagers will show the default marker icon.
### 1.49.0
- **Added**: `BaseMigratingSingleValueKeychainStorage` and `BaseMigratingSingleValueDefaultsStorage` implementations for migrating keys from one storage to another.
### 1.48.0
- **Added**: `BaseStackView` with configurable items appearance
- **Fixed**: `CollectionTableViewCell` self-sizing
- **Added**: `ViewAppearance.WrappedViewLayout` support for all `WrappedViewHolders`
- **Added**: `ViewCallbacks` support for all `BaseInitializeableViews`
### 1.47.0
- **Added**: `flatMap` operator for `AsyncOperation`
- **Update**: `CodableKeyValueStorage` now returns `Swift.Result` with typed errors.
- **Added**: `SingleValueExpirationStorage` for time aware entries (expirable api tokens, etc.)
- **Added**: `AsyncOperation` variants of process methods in NetworkServices.
### 1.46.0
- **Added**: `AsyncSingleValueStorage` and `SingleValueStorageAsyncWrapper<SingleValueStorage>` for async access to SingleValue storages wtih swift concurrency support
- **Added**: `BaseMapUISettings` used to configure map view of different providers + user location icon rendering for yandex maps
- **Added**: `UserLocationFetcher` helper that requests authorization and subscribes to user location updates
- **Update**: add `DEVELOPMENT_INSTALL` support for all podspecs and fix playground compilation issues
### 1.45.0
- **Added**: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
- **Added**: `TILogging` with error logging types
- **Update**: `DefaultRecoverableJsonNetworkService` supports iOS 12.
- **Update**: `DefaultFingerprintsProvider` now uses `SingleValueStorage`
### 1.44.0
- **Added**: HTTP status codes to `EndpointErrorResult.apiError` responses
- **Added**: SwiftLint pre-build SPM step to TINetworking module
### 1.43.1
- **Fixed**: build scripts submodule url
### 1.43.0
- **Added**: `TITextProcessing` for regex and text formatting added
### 1.42.1
- **Fixed**: Podspecs source and homepage urls
### 1.42.0
- **Added**: TIDeeplink to support deeplink API
### 1.41.0
- **Update**: added callbacks for views while skeletons change status to presented or hidden
### 1.40.0
- **Added**: `PlaceholderFactory` for creating `DefaultPlaceholderView` views
- **Added**: `DefaultPlaceholderImageView`
### 1.39.0
- **Added**: UIButton Appearance model
- **Added**: `SpacedWrappedViewLayout` for spacing configurations
- **Update**: UIView appearance model with border configurations
### 1.38.0
- **Added**: Placemarks states for icon updating
- **Added**: Selecting / deselecting markers through cluster manager
### 1.37.0
- **Added**: API for converting view hierarchy to skeletons
### 1.36.1
- **Update**: `YandexMapsMobile` version updated
- **Fix**: Map manager memory leak removed
### 1.36.0
- **Removed**: `TILogger`module
- **Updated**: moved `LoggingPresenter` to `TIDeveloperUtils` module.
### 1.35.1
- **Added**: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
- **Updated**: `AsyncOperation` fixed ordering of chain operations execution
### 1.35.0
- **Added**: `TIDeveloperUtils` framework, that contains different utils for development
- **Added**: `UIView` and `UIViewController` extensions for showing SwiftUI previews
- **Added**: `DashedBoundsLayer` for debugging views' frames visually
### 1.34.0
- **Added**: `BaseListItemView` for displaying three views horizontally
- **Added**: `DefaultTitleSubtitleView` for displaying one or two labels vertically
- **Update**: `StatefulButton` now can be configured with `ViewAppearance` model for each state
### 1.33.0
- **Added**: `ViewAppearance` and `ViewLayout` models for setting up Views' appearance and layout
- **Added**: `TableKit.Row` extension for configuration inner View's appearance and layout
- **Added**: `WrappableView` with typealiases for creating wrapped in the container views
- **Added**: `CollectionTableViewCell` and `ContainerView`
- **Update**: Separator appearance configureation for table views
### 1.32.0
- **Added**: `BaseInitializableWebView` with navigation and error handling api.
### 1.31.0
- **Added**: `URLInteractiveTextView` for terms and conditions hints in login flow
### 1.30.0
- **Added**: Base classes for encryption and decryption user token with pin code or biometry
- **Added**: Pin code validators
### 1.29.1
- **Updated**: `BaseTextAttributes` correct detection of the necessity of using attributed string
### 1.29.0
- **Added**: `BaseTextAttributes`can now measure text size and provides paragraph style configuration API.
- **Removed**: `ViewText`. Was fully replaced with `BaseTextAttributes`
- **Fixed**: `Operation.flattenDependencies` used in `Operation.add(to:waitUntilFinished:)` now works correctly.
- **Added**: Now it's possible to add dependent operation to start of the queue.
### 1.28.0
- **Add**: `LoggingPresenter`to present list of logs with ability of sharing it
- **Add**: `TILogger` wrapper object to log events.
### 1.27.1
- **Fix**: Weak target reference in `RefreshControl`
### 1.27.0
- **Add**: Tag like filter collection view
- **Add**: List like filter table view
- **Add**: Range like filter view
### 1.26.3
- **Update**: Add @escaping in `RequestExecutor.ExecutionClosure`
### 1.26.2
- **Update**: Add failureCompletion in `RequestExecutor`
### 1.26.1
- **Fix**: Use OperationQueue instead of NSLock in `DefaultTokenInterceptor`
- **Update**: AsyncOperation refactoring
### 1.26.0
- **Add**: `TIEcommerce` module with Cart, products, promocodes, bonuses and other related actions.
### 1.25.0
- **Update**: `RequestError` cases now contain additional url assotiated value
- **Update**: Network requests error catching now throws `RequestError` with url
### 1.24.0
- **Add**: `AlertFactory` for presenting alerts in SwiftUI and UIKit.
### 1.23.0
- **Update**: `UITextView` now support configuration with `BaseTextAttributes`
- **Add**: `ReconfigurableView` & `ChangeableViewModel` for non-destructing state update
- **Add**: `WrappedViewHolder` protocol with table/collection view cell implementations
- **Add**: `UIViewPresenter` and `ReusableUIViewPresenter` protocols with default implementation for proper handling view/cells reuse
### 1.22.0
- **Update**: Asynchronous request preprocessing
### 1.21.0
- **Update**: `AsyncEventHandler` was replaced with `EndpointRequestRetrier`
- **Add**: `FingerprintsTrustEvaluator` and `FingerprintsProvider` for fingerprint-based host trust evaluation
- **Add**: `DefaultTokenInterceptor` for queue-based token refresh across all requests of single api interactor (network service).
- **Update**: `DefaultRecoverableJsonNetworkService` now returns collection of errors in result
- **Update**: `CancellableTask` was renamed to `Cancellable`. Cancellable implementations has been moved from `TIMoyaNetworking` to `TIFoundationUtils`.
- **Add**: `ApiInteractor` protocol with basic request/response methods
### 1.20.0
- **Add**: OpenAPI security schemes support for EndpointRequest's.
- **Update**: Replace `AdditionalHeadersPlugin` with `SecuritySchemePreprocessor` and `EndpointRequestPreprocessor` (with default implementations)
### 1.19.0
- **Add**: Add presenter protocols to `TISwiftUICore` and `TIUIKitCore` modules
- **Add**: `CodeConfirmPresenter` protocol and `DefaultCodeConfirmPresenter` implementation in `TIAuth` module
### 1.18.0
- **Add**: add MapManagers for routine maps configuration
### 1.17.0
- **Add**: add smooth CameraUpdate actions for supported maps
### 1.16.2
- **Update**: `DefaultRecoverableJsonNetworkService` now supports error forwarding from its error handlers to initial requests.
### 1.16.1
- **Update**: `DateFormattersReusePool` and `ISO8601DateFormattersReusePool` are now thread safe.
### 1.16.0
- **Add**: `TIMapUtils`, `TIAppleMapUtils`, `TIGoogleMapUtils` and `TIYandexMapUtils` modules for map items clustering and interacting with them.
### 1.15.0
- **Update**: Network services in TIMoyaNetworking now passes MoyaError in result of EnpointRequest execution.
- **Add**: `TINetworkingCache` module - caching results of EndpointRequests.
- **Important Note**: `TINetworkingCache` added via SPM may require you to add `DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC=YES` flag to build settings of project target (see [probably related problem](https://forums.swift.org/t/adding-a-package-to-two-targets-in-one-projects-results-in-an-error/35007/18))
### 1.14.3
- **Fix**: Creating headerView and footerView when initializing a section with rows in `TITableKitUtils`.
- **Add**: Empty table section initialization method in `TITableKitUtils`.
### 1.14.2
- **Update**: DateFormatters properties preset in reuse pools
### 1.14.1
- **Fix**: Array encoding for `QueryStringParameterEncoding`
### 1.14.0
- **Add**: [Date] coding methods
### 1.13.0
- **Update**: Change access modifiers in `DefaultJsonNetworkService` from `public` to `open`, added additional Moya plugins processing
- **Add**: `DisplayDecodingErrorPlugin` for showing developer-frendly decoding error messages
- **Add**: Gemfile for cocoapods versioning
### 1.12.3
- **Fix**: Try parse date in ISO8601 format appending `.withFractionalSeconds` if `.withInternetDateTime` fails
### 1.12.2
- **Fix**: HeaderParameterEncoding encodes array correctly
### 1.12.1
- **Update**: DefaultRecoverableNetworkService `request` parameter was renamed to prevent ambgious reference
### 1.12.0
- **Update**: EndpointRequest Body can take a nil value
- **Update**: Parameter value can be nil as well
- **Update**: observe operator of AsyncOperation now accepts callback queue parameter
### 1.11.1
- **Fix**: `timeoutIntervalForRequest` parameter for `URLSessionConfiguration` in `NetworkServiceConfiguration` added.
### 1.11.0
- **Breaking changes**: many method signatures was changes in `TIMoyaNetworking`.
- **Add**: `ISO8601DateFormattersReusePool` and codable helpers for ISO8601 date (de)coding.
- **Add**: Moya plugin protocol for adding HTTP headers with default implementation.
### 1.10.0
- **Add**: `DefaultRecoverableJsonNetworkService` with error handling chain.
### 1.9.0
- **Add**: `TIMoyaNetworking` target - Moya + Swagger network service.
- **Update**: `TISwiftUtils` - added async closure typealiases.
- **Update**: `TIFoundationUtils` - added date formatting & decoding helpers.
### 1.8.0
- **Add**: `TIFoundationUtils.AsyncOperation` - generic subclass of Operation with chaining and result observation support
### 1.7.0
- **Add**: `TINetworking` - Swagger-frendly networking layer helpers
### 1.6.0
- **Add**: the pretty timer - TITimer.
### 1.5.0
- **Add**: `HeaderTransitionDelegate` - Helper for transition of TableView header and navigationBar title view
### 1.4.0
- **Update**: update minor dependencies.
- **Fix**: project's scripts.
### 1.3.0
- **Add**: `TIPaginator` - realisation of paginating items from a data source.
### 1.2.0
- **Add**: `TIKeychainUtils` - Set of helpers for Keychain classes.
### 1.1.1
- **Fix**: `StatefullButton` propagation
### 1.1.0
- **Add**: `BaseInitializeableViewController`, `BaseCustomViewController` and `BaseViewController` to TIUIKitCore.
- **Add**: `TableKitTableView` and `TableDirectorHolder` to TITableKitUtils.
### 1.0.0
- **API BreakingChanges**: up swift version to 5.1.
- **Update**: build scripts.
- **Update**: code with new swiftlint rules.
- **Update**: RxSwift to 6.0.0.
### 0.13.1
- **Fix**: LeadKit.podspec file.
### 0.13.0
- **Add**: Githook `prepare-commit-msg` to check commit's style.
- **Add**: Setup script.
### 0.12.0
- **Add**: StatefulButton & RoundedStatefulButton to TIUIElements.
- **Add**: added CACornerMask rounding extension to TIUIElements.
- **Add**: UIControl.State dictionary extensions to TIUIKitCore.
- **Add**: UIFont and CTFont extensions to TIUIKitCore.
- **Breaking change**: reworked BaseTextAttributes & ViewText. Removed ViewTextConfigurable protocol & conformances.
### 0.11.0
- **Add**: Cocoapods support for TI-family libraries.
- **Add**: `SeparatorConfigurable` and all helper types for separator configuration.
- **Add**: `BaseSeparatorCell` - `BaseInitializeableCell` subclass with separators support.
- **Add**: `TITableKitUtils` - set of helpers for TableKit classes.
- **Add**: `BaseTextAttributes` and `ViewText` implementation form LeadKit.
- **Update**: `BaseInitializableView` and `BaseInitializableControl` are moved to `TIUIElements` from `TIUIKitCore`.
### 0.10.9
- **Fix**: `change presentedOrTopViewController to open`.
### 0.10.8
- **Fix**: `Add presentedOrTopViewController`.
### 0.10.7
- **Fix**: `Add BaseOrientationController`.
- **Fix**: `Add videoOrientation extension`.
### 0.10.6
- **Fix**: `Add tvos exclude files`.
### 0.10.5
- **Add**: `OrientationNavigationController` .
- **Add**: `Forced Interface Orientation logic to BaseConfigurableController` .
- **Fix**: `Exclude files to watchos and tvos`.
### 0.10.4
- **Fix**: `noConnection` error.
### 0.10.3
- **Fix**: `mappingQueue` of `SessionManager`.
### 0.10.2
- **Add**: `RefreshControl` - a basic UIRefreshControl with fixed refresh action.
### 0.10.1
- **Update**: Third party dependencies: `Alamofire` 5.2.2, `RxAlamofire` 5.6.1
### 0.10.0
- **Update**: Third party dependencies: `RxSwift` (and all sub-dependencies) to 5.1.0, `Alamofire` 5.0, `SnapKit` 5.0
- **Refactored**: NetworkManager to use new Alamofire API
- **API BreakingChanges**: NetworkServiceConfiguration no longer accepts `ServerTrustPolicy`, it is now replaced by an instance of a `ServerTrustEvaluating` protocol. Full description and default implementations can be found at Alamofire [sources](https://github.com/Alamofire/Alamofire/blob/master/Source/ServerTrustEvaluation.swift). Since new evaluation is used, evaluation against self-signed certificates will now throw an AfError and abort any outcoming request. To support self-signed certificates use `DisabledTrustEvaluator` for specified host in configuration.
- **Removed**: UIImage+SupportExtensions, UIScrollView+Support
### 0.9.44
- **Add**: `TIFoundationUtils` - set of helpers for Foundation framework classes.
#### TISwiftUtils
- **Add**: `BackingStore` - a property wrapper that wraps storage and defines getter and setter for accessing value from it.
#### TIFoundationUtils
- **Add**: `CodableKeyValueStorage` - storage that can get and set codable objects by the key.
### 0.9.43
- **Fix**: `OTPSwiftView`'s dependencies.
### 0.9.42
- **Fix**: Logic bugs of `PaginationWrapper`.
### 0.9.41
- **Add**: `OTPSwiftView` - a fully customizable OTP view.
- **Add**: `BaseInitializableControl` UIControl conformance to InitializableView.
- **Add**: `TISwiftUtils` a bunch of useful helpers for development.
### 0.9.40
- **Fix**: Load more request repetion in `PaginationWrapper`.
### 0.9.39
- **Add**: `Animatable` protocol to TIUIKitCore.
- **Add**: `ActivityIndicator` protocol to TIUIKitCore.
- **Add**: `ActivityIndicatorHolder` protocol to TIUIKitCore.
- **Add**: `TIUIElements` for ui elements.
### 0.9.38
- **Add**: `BaseRxTableViewCell` is subclass of `UITableViewCell` class with support `InitializableView` and `DisposeBagHolder` protocols.
- **Add**: `ContainerTableCell` is container class that provides wrapping any `UIView` into `UITableViewCell`.
- **Add**: `BaseTappableViewModel` is simplifies interaction between view and viewModel for events of tapping.
- **Add**: `VoidTappableViewModel` is subclass of `BaseTappableViewModel` class with void payload type.
### 0.9.37
- **Fix**: ScrollView content offset of `PaginationWrapper` for iOS 13.
- **Fix**: Load more request crash of `PaginationWrapper`.
### 0.9.36
- **Add**: SPM Package.swift.
- **Add**: TITransitions via SPM.
- **Add**: TIUIKitCore via SPM.
- **Update**: Readme.
### 0.9.35
- **Add**: Selector `refreshAction()` for refresh control of `PaginationWrapper`.
### 0.9.34
- **Add**: `ButtonHolder` - protocol that contains button property.
- **Add**: `ButtonHolderView` - view which contains button.
- **Add**: Conformance `UIButton` to `ButtonHolder`.
- **Add**: Conformance `BasePlaceholderView` to `ButtonHolderView`.
- **[Breaking change]**: Replace functions `footerRetryButton() -> UIButton?` to `footerRetryView() -> ButtonHolderView?` and `footerRetryButtonHeight() -> CGFloat` to `footerRetryViewHeight() -> CGFloat` for `PaginationWrapperUIDelegate`.
- **[Breaking change]**: Replace functions `footerRetryButtonWillAppear()` to `footerRetryViewWillAppear()` and `footerRetryButtonWillDisappear()` to `footerRetryViewWillDisappear()` for `PaginationWrapperUIDelegate`.
### 0.9.33
- **Fix**: `CustomizableButtonView` container class that provides great customization.
- **Fix**: `CustomizableButtonViewModel` viewModel class for `CustomizableButtonView` configuration.
### 0.9.32
- **Fix**: `CustomizableButtonView` container class that provides great customization.
### 0.9.31
- **Add**: `@discardableResult` to function - `replace(with:at:with:manualBeginEndUpdates)` in `TableDirector`.
### 0.9.30
- **Add**: character `*` into a valid set of characters in the extension `telpromptURL` of String.
### 0.9.29
- **Update**: remove Carthage binary dependencies, update build scripts.
### 0.9.28
- **Add**: method `presentFullScreen(_ viewController:presentationStyle:animated:completion:)` for `UIViewController` that present any `viewController` modally in full screen mode by default (avoid problems with *iOS13* default presentation mode changed to `.automatic` stork)
### 0.9.27
- **Add**: method `date(from string:formats:parsedIn:)` method for `DateFormattingService` that parses date from string in one of the given formats with current region.
### 0.9.26
- **Add**: method `processResultFromConfigurationSingle` for `TotalCountCursor` that allows to get server response.
- **Add**: possibility to inherit from `TotalCountCursor`.
### 0.9.25
- **Add**: `queryItems` parameter for `ApiRequestParameters`.
- **Add**: `asQueryItems` method for `Encodable` that converts model to query items array.
### 0.9.24
- **Fix**: Make `ApiRequestParameters` properties public.
### 0.9.23
- **Add**: Rounding for `Decimal`.
- **Add**: `doubleValue` property for `Decimal`.
- **Add**: `intValue` property for `Decimal`.
- **Fix**: Rounding for `Double`.
### 0.9.22
- **Fix**: Make `Initializable` protocol public.
### 0.9.21
- **Add**: `Initializable` - common protocol for object types that can be initialized without params.
- **Add**: `instantiateArray(count:)` function in `Initializable` extension to initialize an array of instances.
### 0.9.20
- **Fix**: `bindBottomInsetBinding(from bottomInsetDriver:)` in `BaseScrollContentController` works correctly now.
### 0.9.19
- **Add**: `hexString` property for `UIColor` that returns hex representation of color as string.
### 0.9.18
- **Add**: `CustomizableButtonView` container class that provides great customization.
- **Add**: `CustomizableButtonViewModel` viewModel class for `CustomizableButtonView` configuration.
- **Add**: `CustomizableButton` class that is a `CustomizableButtonView` subview and gives it a button functionality.
### 0.9.17
- **Fix**: SpinnerView infinity animation.
### 0.9.16
- **Add**: `LabelTableViewCell` moved from `LeadKitAdditions`.
- **Add**: `SnapKit` dependency.
### 0.9.15
- **Add**: `BaseSearchViewController` class that allows to enter text for search and then displays search results in table view.
- **Add**: `BaseSearchViewModel` class that loads data from a given data source and performs search among the results.
- **Add**: `SearchResultsController` protocol that represent a controller able to display search results.
- **Add**: `SearchResultsControllerState` enum that represents `SearchResultsController` state.
### 0.9.14
- **Update**: SwiftDate dependency (~> 6).
### 0.9.13
- **Add**: `ApiUploadRequestParameters` struct that defines upload data request parameters.
- **Add**: `rxUploadRequest` method to `NetworkService` class that performs reactive request to upload data.
- **Add**: `uploadResponseModel` method to `SessionManager` extension that executes upload request and serializes response.
- **Add**: `handleMappingError` method to `Error` extension that tries to serialize response from a mapping request error to a model.
- **Add**: `handleMappingError` method to `ObservableType`, `Single`, `Completable` extensions that handles a mapping error and serialize response to a model.
- **Add**: `validate` method to `DataRequest` observable extension that validates status codes and catch network errors.
- **Add**: `dataApiResponse` method to `DataRequest` reactive extension that serializes response into data.
- **Update**: `validStatusCodes` parameter in network methods renamed to `additionalValidStatusCodes`.
### 0.9.12
- **Update**: Swift 5 support
### 0.9.11
- **[Breaking change]**: Renamed `NumberFormat`'s `allOptions` to `allCases`
- **Fix**: Closure syntax fix. New closure naming.
- **Fix**: Added missing `BasePlaceholderView` protocol function.
### 0.9.10
- **Remove**: Removed unused scheme & target
- **Remove**: Cocoapods deintegrated
- **Update**: New closure typealiases
### 0.9.9
- **Add**: `validStatusCodes` parameter to request methods in `NetworkService` class, that expands valid status codes for request.
- **Add**: `validStatusCodes` parameter to response methods in `SessionManager` extension, that expands valid status codes for request.
### 0.9.8
- **Add**: `rxDataRequest` method to `NetworkService` class, that performs reactive request to get data and http response.
- **Add**: `responseData` method to `SessionManager` extension, that executes request and returns data.
### 0.9.7
- **Add**: Carthage support.
### 0.9.6
- **Add**: Add new `configureSeparators` method to `SeparatorRowBox` array.
### 0.9.5
- **Add**: `TitleType` enum, that defines `UIViewController`'s title type.
- **Add**: `UINavigationItem.largeTitleDisplayMode` property, that defines `UINavigationItem`'s large title display mode.
- **Add**: `UIViewController.updateNavigationItemTitle` method, that takes `TitleType` as a parameter and updates `UIViewController`'s title.
### 0.9.4
- **Add**: initialization of `ApiRequestParameters`, that takes an array as a request parameter.
- **Add**: `NetworkServiceConfiguration.apiRequestParameters` method, that creates `ApiRequestParameters` with array request parameter.
- **Add**: `SessionManager.request` method, that takes an array as a request parameter.
- **Add**: `RequestUsageError` error, that represents wrong usage of requset parameters.
### 0.9.3
- **Add**: `Insert`/`Remove` section with animation functions to `TableKit`. Also make new function `Replace` that uses new `Insert`/`Remove` to animate section replace.
### 0.9.2
- **Update**: Add response to `RequestError`.
- **Fix**: Update `SessionManager+Extensions` to catch network connection error.
### 0.9.1
- **Update**: `DataRequest+Extensions` time out as network error
### 0.9.0
- **Update**: version update.
### 0.8.13
- **Add**: `configureLayout` method to `InitializeableView` protocol and all implementations.
- **Update**: `GeneralDataLoadingViewModel` now can handle state changes and result of data source. Previously it was possible only in view controller.
- **Add**: `GeneralDataLoadingHandler` protocol, that defines methods for common data loading states handling.
- **Add**: `resultObservable` and `resultDriver` properties to `GeneralDataLoadingViewModel`.
- **Add**: `hidesWhenStopped` option to `SpinnerView`, so you can stop animation without hiding image inside it.
- **Update**: Migrate to Swift 4.2 & Xcode 10. Update dependencies.
### 0.8.12
- **Add**: `UserDefaults+Codable` is back. Now with generic subscript support.
### 0.8.11
- **Change**: `NumberFormattingService.computedFormatters` computed var reverted to static.
### 0.8.10
- **[Breaking change]**: `NumberFormattingService` methods is not static anymore.
- **Add**: `NSNumberConvertible` protocol for `NumberFormattingService` use cases.
- **Add**: `TableDirector` methods for rows insertion and removal without reload a whole table.
- **Add**: `UIImageView` binder for disclosure indicator rotation.
- **Add**: `UIView.addSubviews(:)` methods with variable number of arguments and array of views.
- **Add**: `PlaceholderConfigurable` that defines attributes and methods for view with placeholder and regular state.
- **Add**: `ContentLoadingViewModel` enum that describes possible `PlaceholderConfigurable` view states.
### 0.8.9
- **Add**: Methods `replace(with:)`, `asVoid()`, `asOptional()` to `ObservableType`, `SharedSequence` (aka `Driver`) and `Single`.
- **Add**: `Completable.deferredJust(:)` static method.
- **Add**: `ViewTextConfigurable` protocol. Conform `UILabel`, `UITextField` and `UIButton` to this protocol.
- **Add**: `BaseTextAttributes` with base text appearance attributes.
- **Update**: `ViewText.string` now uses `BaseTextAttributes` instead of separate properties.
- **Add**: `BasePlaceholderView` and `BasePlaceholderViewModel` classes used to create your own placeholder.
- **Add**: `TableKitViewModel` protocol that adds convenient extensions to cell view models that implements it.
### 0.8.8
- **Update**: Update `DateFormat` protocol. Add `dateToStringFormat` and `stringToDateFormat` according to SwiftDate 5.0.
- **Update**: Replace `String` with `DateFormat` in `DataFormattingService` date parsing methods.
- **Update**: Replace `DateInRegion` with `DateRepresentable` in `DataFormattingService` string formatting methods.
- **Add**: `parsedIn` optional parameter to date parsing method in `DataFormattingService`.
### 0.8.7
- **Add**: Base configurable controllers hierarchy with generic custom view argument (`BaseConfigurableController`, `BaseCustomViewController`, `BaseScrollContentController`, `BaseTableContentController` and `BaseCollectionContentController`).
- **Add**: `ScrollViewHolder`, `TableViewHolder` and `CollectionViewHolder` protocols.
- **Update**: Update dependencies.
- **[Breaking change]**: Update `SwiftDate` to 5.0.x.
- **[Breaking change]**: Update `DateFormattingService`. Change `format` argument from `DateFormatType` to `String`.
- **Update**: Add compile time debug messages. Improve compile time for some pieces of code.
### 0.8.6
- **Fix**: Add `trustPolicies` param to `NetworkServiceConfiguration` initialization.
- **Fix**: Update `serverTrustPolicies` to save host instead of the whole URL as a key.
- **Add**: String extension that extracts host.
### 0.8.5
- **Add**: `replaceDataSource` method to `RxNetworkOperationModel`.
- **Add**: `customErrorHandler` constructor parameter to `RxNetworkOperationModel` and it heirs.
### 0.8.4
- **Fix**: Add `SeparatorCell` to `Core-iOS-Extension`.
- **Fix**: `UIApplication` extensions path for `Core-iOS-Extension` exclusions.
### 0.8.3
- **Fix**: `SpinnerView` animation freezing
### 0.8.2
- **Add**: `acceptableStatusCodes` property to `NetworkServiceConfiguration`
### 0.8.1
- **Add**: Support for `localizedComponent` for `FixedWidthInteger`
### 0.8.0
- **Add**: tests for `NetworkService`
- **Add**: `toJSON(with encoder: JSONEncoder)` method to `Encodable`
- **Add**: `failedToDecode` error case to `LeadKitError`
- **Add**: `SessionManager` class
- **Remove**: occurrences `ObjectMapper` pod and its occurrences in code
- **Update**: replace `ObjectMapper` mapping with `Decodable`
### 0.7.19
- **Fix**: `PaginationWrapper` retry button showing.
### 0.7.18
- **Update**: default implementation of `PaginationWrapperUIDelegate`.
### 0.7.17
- **Add**: `RxNetworkOperationModel` base class, `NetworkOperationState` and `NetworkOperationStateType` protocols.
### 0.7.16
- **[Breaking Change]**: Remove `ModuleConfigurator`, change type of `ConfigurableController.viewModel` property from `IUO` to plain `ViewModelT`.
- **Add**: `InitializableView` protocol with default implementation.
- **Update**: `ConfigurableController` protocol now inherit `InitializableView`.
- **[Breaking Change]**: `setAppearance` of `ConfigurableController` replaced with `configureAppearance` of `InitializableView`.
### 0.7.15
- **Fix**: `Double.roundValue(withPrecision:)` rounding issue
- **Add**: `Double+Rounding` test case
### 0.7.14
- **[Breaking Change]**: `PaginationWrapper` separating state views from data loading.
### 0.7.13
- **Update**: Migrate from `Variable` to `BehaviorRelay`.
- **Fix**: `PaginationWrapper` retry load more after fail.
- **Fix**: `safeClear` method of `TableDirector` now creates section without header and footer.
- **Add**: `TableSection` convenience initializer for section without footer and header.
### 0.7.12
- **Add**: `UniversalMappable` protocol to have ability generate generic mapping models
### 0.7.11
- **Fix**: `addHeaderBackground` cells overlapping.
### 0.7.10
- **Fix**: `wtihInsets` renamed to `with insets`
### 0.7.9
- **Fix**: timeoutInterval is set to another URLSessionConfiguration property in NetworkServiceConfiguration
### 0.7.8
- **Remove**: `App`, `Log` and `LogFormatter`.
- **Remove**: `CocoaLumberjack` dependency.
- **Add**: Rotate operation for image drawing.
- **Add**: `mapViewEvents` overload with closure that returns array of disposables.
- **Update**: Update `ObjectMapper` to 3.1.
- **Add**: `apiRequestParameters` method to `NetworkServiceConfiguration` extension.
- **Update**: Rename setToCenter(withInsets:) to pintToSuperview(withInsets:excluding:)
- **Update**: Added parameter "edges" with label "excluding" to aforementioned method
### 0.7.7
- **Fix**: Fix doubling separator line issue
### 0.7.6
- **Add**: `NetworkServiceConfiguration` to configure NetworkService instance
- **Remove**: `ConfigurableNetworkSevice` protocol
- **Update**: Acceptable status codes in SessionManager become `Set<Int>`
### 0.7.5
- **Add**: `topConfiguration` and `bottomConfiguration` properties, methods to configure top and bottom separators in `CellSeparatorType` extension.
- **Add**: `totalHeight` property in `SeparatorConfiguration` extension.
### 0.7.4
- **Update**: Exclude UIApplication extensions from iOS-Extension subspec.
### 0.7.3
- **Update**: Xcode 9.3 migration.
- **Remove**: Default initializer for Network service that conforms to `ConfigurableNetworkService` protocol.
- **[Breaking Change]**: `DateFormattingService` class replaced with protocol.
- **Add**: `SwiftDate` dependency for `DateFormattingService`.
- **Add**: `ViewBackground` enum that describes possible view backgrounds.
- **Add**: `ViewText` enum that describes text with appearance options.
- **Removed**: `String+SizeCalculation` extension.
### 0.7.2
- **Fixed**: Change root controller for window
### 0.7.1
- **Add**: Extension for comparing optional arrays (`[T]?`) with `Equatable` elements.
- **Add**: `additionalHttpHeaders` static field in `ConfigurableNetworkService` protocol.
- **Add**: Default initializer for Network service that conforms to `ConfigurableNetworkService` protocol.
## 0.7.0
- **Add**: `TotalCountCursor` for total count based pagination and related stuff.
- **[Breaking Change]**: `PaginationTableViewWrapper` and `PaginationTableViewWrapperDelegate` was renamed to `PaginationWrapper` and `PaginationWrapperDelegate `. Also there is significant changes in api
- **Add**: `GeneralDataLoadingModel` and `PaginationDataLoadingModel` for regular and paginated data loading with state handling.
- **Add**: `GeneralDataLoadingViewModel` and `GeneralDataLoadingController` for regular data loading and state handling in UI.
- **Add**: `ConfigurableNetworkService` - replacement of `DefaultNetworkService` from LeadKitAdditions.
- **Add**: `NumberFormattingService` and `NumberFormat` protocols with default implementation for creating per-project number formatters.
- **Add**: Very flexible in configuration `TextFieldViewModel` with build-in two-side data model binding.
- **Add**: `SingleLoadCursorConfiguration` as a replacement of `SingleLoadCursor`.
- **Add**: `UIApplication` extensions for making phone calls.
- **Add**: `NSAttributedString` extensions for appending attributed strings using `+` operator.
- **Change**: Lots of fixes and enhancements.
- **Update**: Update dependecies versions.
### 0.6.7
- **Add**: UITableView extension to add colored background for tableview bounce area.
### 0.6.6
- **Add**: Ability to map primitive response types (`String`, `Int`, `[String]`, etc.).
### 0.6.5
- **Add**: Ability to handle responses with non-default status codes.
### 0.6.4
- **Fix**: SpinnerView bug(no animation) in Swift 4.
### 0.6.3
- **Fix**: SeparatorCell updates constraints after setting separator insets
### 0.6.2
- **Fix**: AlamofireManager extension no longer performs requests with default manager
### 0.6.1
- **New**: `RequestError`. Represents general api request errors
- **Change**: All api methods now throws `RequestError` when fails.
## 0.6.0
- **New**: Swift 4 support & dependencies update.
- **Remove**: `Mutex`
- **Remove**: `IndexPath+ImmutableIndexPath`
- **Remove**: `StoryboardProtocol`, `StoryboardProtocol+Extensions`, `StoryboardProtocol+DefaultBundle`
- **Remove**: `String+Extensions` image property
- **Remove**: `UICollectionView+CellRegistration`
- **Remove**: `UIStoryboard+InstantiateViewController`
- **Remove**: `NetworkService` extension for loading images
- **Remove**: `Observable` creation for `ImmutableMappable`
- **Remove**: `UIView` and `UsedDefaults` extensions, `EstimatedViewHeightProtocol`, `StaticEstimatedViewHeightProtocol`, `StoryboardIdentifierProtocol`
### 0.5.18
- **Fix**: EmptyCell first appearance setup fix
### 0.5.17
- **Fix**: EmptyCell reusing appearance fix
- **Fix**: SeparatorCell reusing separators fix
### 0.5.16
- **Change**: Rename `AppearanceProtocol` to `AppearanceConfigurable`
- **Add**: `subscript(safe:)` subscript to `Array` extension for safe access to element by index
### 0.5.15
- **Add**: `AppearanceProtocol` which ensures that specific type can apply appearance to itself
- **Add**: `with(appearance:)`, `set(appearance:)` methods to TableRow extension
- **Add**: `Appearance` to `EmptyCell`
- **Remove**: `SeparatorCellViewModel`.
### 0.5.13
- **Change**: Remove type erasure behavior from `AnyBaseTableRow`
- **Change**: Rename `AnyBaseTableRow` class to `SeparatorRowBox`
- **Change**: Move `anyRow` property from `EmptyCellRow` to `TableRow` extension and rename it to `separatorRowBox`.
- **Change**: Move `configure(extreme: middle:)` method from `TableDirector` extension to `Array` extension and rename it to `configureSeparators(extreme: middle:)`
### 0.5.12
- **Fix**: Update type of `viewModel` in `ConfigurableController` to `ImplicitlyUwrappedOptional<ViewModelT>` instead of `ViewModelT`
### 0.5.11
- **[Breaking Change]**: rename initializer from `init(initialFrom:)` to `init(resetFrom:)` in `ResettableType`
- **Add**: `SeparatorCell` with `SeparatorCellViewModel`
- **Add**: `AnyBaseTableRow` for type-erasure
- **Add**: `EmptyCellRow` for empty cell with static height
### 0.5.10
- **Fix**: `Public` modifier for `SpinnerView`
### 0.5.9
- **Fix**: One-two-many fixed for values more than 99
### 0.5.8
- **Fix**: Synchronization over `NSRecursiveLock` for request count tracker in NetworkService
### 0.5.7
- **Add**: String extension `localizedComponent(value:stringOne:stringTwo:stringMany:)`
### 0.5.6
- **Fix**: Clear tableview if placeholder is shown

View File

@ -1,7 +1,4 @@
github "malcommac/SwiftDate"
github "Alamofire/Alamofire"
github "RxSwiftCommunity/RxAlamofire" ~> 6.1
github "TouchInstinct/TableKit"
github "ReactiveX/RxSwift" ~> 6.2
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"
github "SnapKit/SnapKit" ~> 5.0
github "CocoaLumberjack/CocoaLumberjack" ~> 3.0.0
github "ReactiveX/RxSwift" "3.0.1"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"
github "Hearst-DD/ObjectMapper" ~> 2.1

View File

@ -1,7 +1,5 @@
github "Alamofire/Alamofire" "5.4.3"
github "ReactiveX/RxSwift" "6.2.0"
github "RxSwiftCommunity/RxAlamofire" "v6.1.2"
github "SnapKit/SnapKit" "5.0.1"
github "TouchInstinct/TableKit" "2.10008.1"
github "malcommac/SwiftDate" "6.3.1"
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"
github "Alamofire/Alamofire" "4.2.0"
github "CocoaLumberjack/CocoaLumberjack" "3.0.0"
github "Hearst-DD/ObjectMapper" "2.2.1"
github "ReactiveX/RxSwift" "3.0.1"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"

View File

@ -1,5 +0,0 @@
# frozen_string_literal: true
source "https://rubygems.org"
gem "cocoapods", "~> 1.11"

View File

@ -1,98 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
rexml
activesupport (6.1.5)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
claide (1.1.0)
cocoapods (1.11.3)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.11.3)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 1.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.11.3)
activesupport (>= 5.0, < 7)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (1.6.2)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.1.10)
escape (0.0.4)
ethon (0.15.0)
ffi (>= 1.15.0)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
json (2.6.1)
minitest (5.15.0)
molinillo (0.8.0)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
public_suffix (4.0.6)
rexml (3.2.5)
ruby-macho (2.5.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
zeitwerk (2.5.4)
PLATFORMS
x86_64-darwin-20
x86_64-darwin-21
DEPENDENCIES
cocoapods (~> 1.11)
BUNDLED WITH
2.3.26

View File

@ -1,122 +1,17 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "1.35.0"
s.version = "0.3.2"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://git.svc.touchin.ru/TouchInstinct/LeadKit"
s.homepage = "https://github.com/TouchInstinct/LeadKit"
s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct"
s.source = { :git => "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", :tag => s.version }
s.platform = :ios, '10.0'
s.swift_versions = ['5.1']
s.subspec 'UIColorHex' do |ss|
ss.ios.deployment_target = '10.0'
ss.tvos.deployment_target = '10.0'
ss.watchos.deployment_target = '3.0'
ss.source_files = "Sources/Extensions/UIColor/UIColor+Hex.swift"
end
s.subspec 'Core' do |ss|
ss.ios.deployment_target = '10.0'
ss.tvos.deployment_target = '10.0'
ss.watchos.deployment_target = '3.0'
ss.source_files = "Sources/**/*.swift"
ss.watchos.exclude_files = [
"Sources/Classes/Controllers/**/*",
"Sources/Classes/Views/SeparatorRowBox/*",
"Sources/Classes/Views/BaseRxTableViewCell/*",
"Sources/Classes/Views/ContainerTableCell/*",
"Sources/Classes/Views/SeparatorCell/*",
"Sources/Classes/Views/EmptyCell/*",
"Sources/Classes/Views/LabelTableViewCell/*",
"Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift",
"Sources/Classes/Views/XibView/*",
"Sources/Classes/Views/SpinnerView/*",
"Sources/Classes/Views/DefaultPlaceholders/*",
"Sources/Classes/Views/CollectionViewWrapperView/*",
"Sources/Classes/Views/TableViewWrapperView/*",
"Sources/Classes/Views/BasePlaceholderView/*",
"Sources/Classes/Views/CustomizableButton/*",
"Sources/Classes/Search/*",
"Sources/Enums/Search/*",
"Sources/Extensions/CABasicAnimation/*",
"Sources/Extensions/CGFloat/CGFloat+Pixels.swift",
"Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift",
"Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingController+DefaultImplementation.swift",
"Sources/Extensions/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/Support/UINavigationItem+Support.swift",
"Sources/Extensions/TableKit/**/*.swift",
"Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift",
"Sources/Extensions/Array/Array+RowExtensions.swift",
"Sources/Extensions/Drawing/UIImage/*",
"Sources/Extensions/UIKit/**/*.swift",
"Sources/Extensions/Views/ViewBackground/*",
"Sources/Extensions/Views/SeparatorCell/*",
"Sources/Extensions/Views/ConfigurableView/*",
"Sources/Extensions/Views/PlaceholderConfigurable/*",
"Sources/Protocols/UIKit/**/*.swift",
"Sources/Protocols/LoadingIndicator.swift",
"Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift",
"Sources/Protocols/DataLoading/GeneralDataLoading/GeneralDataLoadingController.swift",
"Sources/Protocols/Views/SeparatorCell/*",
"Sources/Protocols/Views/PlaceholderConfigurable/*",
"Sources/Protocols/TableKit/**/*",
"Sources/Protocols/Controllers/SearchResultsViewController.swift",
"Sources/Structures/Views/AnyLoadingIndicator.swift",
"Sources/Structures/DrawingOperations/CALayerDrawingOperation.swift",
"Sources/Structures/DrawingOperations/RoundDrawingOperation.swift",
"Sources/Structures/DrawingOperations/BorderDrawingOperation.swift",
"Sources/Structures/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/UIInterfaceOrientation/*"
]
ss.tvos.exclude_files = [
"Sources/Classes/Controllers/BaseConfigurableController.swift",
"Sources/Classes/Controllers/BaseCollectionContentController.swift",
"Sources/Classes/Views/TableViewWrapperView/TableViewWrapperView.swift",
"Sources/Classes/Views/CollectionViewWrapperView/CollectionViewWrapperView.swift",
"Sources/Classes/Controllers/BaseScrollContentController.swift",
"Sources/Classes/Controllers/BaseCustomViewController.swift",
"Sources/Classes/Controllers/BaseOrientationNavigationController.swift",
"Sources/Extensions/UIKit/UIDevice/UIDevice+ScreenOrientation.swift",
"Sources/Classes/Controllers/BaseTableContentController.swift",
"Sources/Classes/Views/BaseRxTableViewCell/*",
"Sources/Classes/Views/ContainerTableCell/*",
"Sources/Classes/Views/SeparatorRowBox/*",
"Sources/Classes/Views/SeparatorCell/*",
"Sources/Classes/Views/EmptyCell/*",
"Sources/Classes/Views/LabelTableViewCell/*",
"Sources/Classes/Views/CustomizableButton/*",
"Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift",
"Sources/Classes/Search/*",
"Sources/Structures/Drawing/CALayerDrawingOperation.swift",
"Sources/Enums/Search/*",
"Sources/Extensions/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/Support/UINavigationItem+Support.swift",
"Sources/Extensions/TableKit/**/*.swift",
"Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift",
"Sources/Extensions/Array/Array+RowExtensions.swift",
"Sources/Extensions/Views/SeparatorCell/*",
"Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift",
"Sources/Protocols/Views/SeparatorCell/*",
"Sources/Protocols/TableKit/**/*",
"Sources/Protocols/Controllers/SearchResultsViewController.swift",
"Sources/Structures/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/UIInterfaceOrientation/*",
"Sources/Classes/Controllers/BaseOrientationController.swift"
]
ss.dependency "RxSwift", '~> 6.2'
ss.dependency "RxCocoa", '~> 6.2'
ss.dependency "RxAlamofire", '~> 6.1'
ss.dependency "SwiftDate", '~> 6'
ss.ios.dependency "TableKit", '~> 2.11'
ss.ios.dependency "SnapKit", '~> 5.0.1'
ss.ios.dependency "UIScrollView-InfiniteScroll", '~> 1.1.0'
end
s.default_subspec = 'Core'
s.platform = :ios, "9.0"
s.source = { :git => "https://github.com/TouchInstinct/LeadKit.git", :tag => s.version }
s.source_files = "LeadKit/LeadKit/**/*.swift"
s.dependency "CocoaLumberjack/Swift", '~> 3.0.0'
s.dependency "RxSwift", '3.0.1'
s.dependency "RxCocoa", '3.0.1'
s.dependency "RxAlamofire", '3.0.0'
s.dependency "ObjectMapper", '~> 2.1'
end

File diff suppressed because it is too large Load Diff

View File

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6782BB9F1EB31D590086E0B8"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit tvOS"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6782BB9F1EB31D590086E0B8"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit tvOS"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6782BBA71EB31D5A0086E0B8"
BuildableName = "LeadKit tvOSTests.xctest"
BlueprintName = "LeadKit tvOSTests"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6782BB9F1EB31D590086E0B8"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit tvOS"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6782BB9F1EB31D590086E0B8"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit tvOS"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

BIN
LeadKit/.DS_Store vendored Normal file

Binary file not shown.

8
LeadKit/.swiftlint.yml Normal file
View File

@ -0,0 +1,8 @@
disabled_rules:
- force_cast
- trailing_whitespace
excluded:
- Carthage
- Pods
- RxAlamofire
line_length: 128

4
LeadKit/.tailor.yml Normal file
View File

@ -0,0 +1,4 @@
exclude:
- 'Pods'
- 'Carthage'
- 'RxAlamofire'

View File

@ -0,0 +1,966 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */; };
78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */; };
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23421DA412470084620D /* CGImage+Alpha.swift */; };
780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23451DA416F80084620D /* CGContext+Initializers.swift */; };
780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */; };
780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */; };
7827C9341DE4ADB2009DA4E6 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */; };
7827C9351DE4ADB2009DA4E6 /* CocoaLumberjack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */; };
7827C9361DE4ADB2009DA4E6 /* ObjectMapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */; };
7827C9371DE4ADB2009DA4E6 /* RxAlamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9311DE4ADB2009DA4E6 /* RxAlamofire.framework */; };
7827C9381DE4ADB2009DA4E6 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9321DE4ADB2009DA4E6 /* RxCocoa.framework */; };
7827C9391DE4ADB2009DA4E6 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */; };
7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 783423691DB8D0E100A79643 /* StoryboardProtocol.swift */; };
7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */; };
786D78E81D53C378006B2CEA /* AlamofireRequest+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */; };
786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */; };
7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7873D14E1E1127BC001816EB /* LeadKitError.swift */; };
7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7873D1501E112B0D001816EB /* Any+Cast.swift */; };
78753E241DE58A5D006BC0FB /* CursorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E231DE58A5D006BC0FB /* CursorError.swift */; };
78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */; };
78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */; };
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2F1DE594B4006BC0FB /* MapCursor.swift */; };
787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787609211E1403830093CE36 /* Observable+DeferredJust.swift */; };
787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */; };
787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */; };
787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */; };
787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */; };
7884DB9C1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */; };
788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */; };
789CC6081DE5835600F789D3 /* CursorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789CC6071DE5835600F789D3 /* CursorType.swift */; };
789CC60B1DE584F800F789D3 /* CursorType+Slice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */; };
78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC51DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift */; };
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */; };
78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A74EA81C6B373700FE9724 /* UIView+DefaultNibName.swift */; };
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */; };
78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */; };
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036441DA561D00021D5CC /* CGImage+Utils.swift */; };
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036461DA5624D0021D5CC /* CGImage+Creation.swift */; };
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B036481DA562C30021D5CC /* CGImage+Template.swift */; };
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */; };
78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */; };
78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC7E1C6B2C4D00358B64 /* Log.swift */; };
78B0FC811C6B2CD500358B64 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B0FC801C6B2CD500358B64 /* App.swift */; };
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C36F7D1D801E3E00E7EBEA /* Double+Rounding.swift */; };
78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C36F801D8021DD00E7EBEA /* UIColor+Hex.swift */; };
78CFEE2E1C5C456B00F50370 /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 78CFEE2D1C5C456B00F50370 /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
78CFEE351C5C456B00F50370 /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78CFEE2A1C5C456B00F50370 /* LeadKit.framework */; };
78CFEE3A1C5C456B00F50370 /* LeadKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE391C5C456B00F50370 /* LeadKitTests.swift */; };
78CFEE541C5C45E500F50370 /* UIView+LoadFromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE481C5C45E500F50370 /* UIView+LoadFromNib.swift */; };
78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4A1C5C45E500F50370 /* NibNameProtocol.swift */; };
78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4B1C5C45E500F50370 /* ReuseIdentifierProtocol.swift */; };
78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4C1C5C45E500F50370 /* StaticNibNameProtocol.swift */; };
78CFEE581C5C45E500F50370 /* StaticViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4D1C5C45E500F50370 /* StaticViewHeightProtocol.swift */; };
78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4E1C5C45E500F50370 /* StoryboardIdentifierProtocol.swift */; };
78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE4F1C5C45E500F50370 /* ViewHeightProtocol.swift */; };
78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78CFEE501C5C45E500F50370 /* ViewModelProtocol.swift */; };
78D4B5461DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D4B5451DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift */; };
78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */; };
95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B39A851D9D51250057BD54 /* String+Localization.swift */; };
CA1FE7091E27D7DE00968901 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1FE7081E27D7DE00968901 /* UIDevice+Extensions.swift */; };
CAA707D51E2E614E0022D732 /* ModuleConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA707D41E2E614E0022D732 /* ModuleConfigurator.swift */; };
CAA707D71E2E616D0022D732 /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA707D61E2E616D0022D732 /* BaseViewModel.swift */; };
CAA707D91E2E61A50022D732 /* ConfigurableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA707D81E2E61A50022D732 /* ConfigurableController.swift */; };
E126CBB31DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126CBB21DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift */; };
EF2921A61E165DF400E8F43B /* TimeInterval+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2921A51E165DF400E8F43B /* TimeInterval+DateComponents.swift */; };
EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF5FB5681E0141610030E4BE /* UIView+Rotation.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
78CFEE361C5C456B00F50370 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 78CFEE211C5C456B00F50370 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 78CFEE291C5C456B00F50370;
remoteInfo = LeadKit;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+DefaultReuseIdentifier.swift"; sourceTree = "<group>"; };
78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequestParameters.swift; sourceTree = "<group>"; };
780D23421DA412470084620D /* CGImage+Alpha.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Alpha.swift"; sourceTree = "<group>"; };
780D23451DA416F80084620D /* CGContext+Initializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGContext+Initializers.swift"; sourceTree = "<group>"; };
780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = "<group>"; };
780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableMappable.swift; sourceTree = "<group>"; };
7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../../../Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaLumberjack.framework; path = ../../../Carthage/Build/iOS/CocoaLumberjack.framework; sourceTree = "<group>"; };
7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjectMapper.framework; path = ../../../Carthage/Build/iOS/ObjectMapper.framework; sourceTree = "<group>"; };
7827C9311DE4ADB2009DA4E6 /* RxAlamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxAlamofire.framework; path = ../../../Carthage/Build/iOS/RxAlamofire.framework; sourceTree = "<group>"; };
7827C9321DE4ADB2009DA4E6 /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = ../../../Carthage/Build/iOS/RxCocoa.framework; sourceTree = "<group>"; };
7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = ../../../Carthage/Build/iOS/RxSwift.framework; sourceTree = "<group>"; };
783423691DB8D0E100A79643 /* StoryboardProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardProtocol.swift; sourceTree = "<group>"; };
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EstimatedViewHeightProtocol.swift; sourceTree = "<group>"; };
786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireRequest+Extensions.swift"; sourceTree = "<group>"; };
786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireManager+Extensions.swift"; sourceTree = "<group>"; };
7873D14E1E1127BC001816EB /* LeadKitError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadKitError.swift; sourceTree = "<group>"; };
7873D1501E112B0D001816EB /* Any+Cast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Any+Cast.swift"; sourceTree = "<group>"; };
78753E231DE58A5D006BC0FB /* CursorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorError.swift; sourceTree = "<group>"; };
78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticCursor.swift; sourceTree = "<group>"; };
78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FixedPageCursor.swift; sourceTree = "<group>"; };
78753E2F1DE594B4006BC0FB /* MapCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapCursor.swift; sourceTree = "<group>"; };
787609211E1403830093CE36 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.swift"; sourceTree = "<group>"; };
787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticEstimatedViewHeightProtocol.swift; sourceTree = "<group>"; };
787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexPath+ImmutableIndexPath.swift"; sourceTree = "<group>"; };
787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SizeCalculation.swift"; sourceTree = "<group>"; };
787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImmutableMappable+ObservableMappable.swift"; sourceTree = "<group>"; };
7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+MappableDataTypes.swift"; sourceTree = "<group>"; };
788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+InstantiateViewController.swift"; sourceTree = "<group>"; };
789CC6071DE5835600F789D3 /* CursorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorType.swift; sourceTree = "<group>"; };
789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CursorType+Slice.swift"; sourceTree = "<group>"; };
78A0FCC51DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+DefaultBundle.swift"; sourceTree = "<group>"; };
78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+Extensions.swift"; sourceTree = "<group>"; };
78A74EA81C6B373700FE9724 /* UIView+DefaultNibName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+DefaultNibName.swift"; path = "LeadKit/Extensions/UIView/UIView+DefaultNibName.swift"; sourceTree = SOURCE_ROOT; };
78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Transform.swift"; sourceTree = "<group>"; };
78B036441DA561D00021D5CC /* CGImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Utils.swift"; sourceTree = "<group>"; };
78B036461DA5624D0021D5CC /* CGImage+Creation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Creation.swift"; sourceTree = "<group>"; };
78B036481DA562C30021D5CC /* CGImage+Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Template.swift"; sourceTree = "<group>"; };
78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Crop.swift"; sourceTree = "<group>"; };
78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = "<group>"; };
78B0FC7E1C6B2C4D00358B64 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
78B0FC801C6B2CD500358B64 /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
78C36F7D1D801E3E00E7EBEA /* Double+Rounding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Rounding.swift"; sourceTree = "<group>"; };
78C36F801D8021DD00E7EBEA /* UIColor+Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = "<group>"; };
78CFEE2A1C5C456B00F50370 /* LeadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78CFEE2D1C5C456B00F50370 /* LeadKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LeadKit.h; sourceTree = "<group>"; };
78CFEE2F1C5C456B00F50370 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78CFEE341C5C456B00F50370 /* LeadKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LeadKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
78CFEE391C5C456B00F50370 /* LeadKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeadKitTests.swift; sourceTree = "<group>"; };
78CFEE3B1C5C456B00F50370 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78CFEE481C5C45E500F50370 /* UIView+LoadFromNib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+LoadFromNib.swift"; path = "LeadKit/Extensions/UIView/UIView+LoadFromNib.swift"; sourceTree = SOURCE_ROOT; };
78CFEE4A1C5C45E500F50370 /* NibNameProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibNameProtocol.swift; sourceTree = "<group>"; };
78CFEE4B1C5C45E500F50370 /* ReuseIdentifierProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReuseIdentifierProtocol.swift; sourceTree = "<group>"; };
78CFEE4C1C5C45E500F50370 /* StaticNibNameProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticNibNameProtocol.swift; sourceTree = "<group>"; };
78CFEE4D1C5C45E500F50370 /* StaticViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticViewHeightProtocol.swift; sourceTree = "<group>"; };
78CFEE4E1C5C45E500F50370 /* StoryboardIdentifierProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardIdentifierProtocol.swift; sourceTree = "<group>"; };
78CFEE4F1C5C45E500F50370 /* ViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewHeightProtocol.swift; sourceTree = "<group>"; };
78CFEE501C5C45E500F50370 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = "<group>"; };
78D4B5451DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+DefaultStoryboardIdentifier.swift"; sourceTree = "<group>"; };
78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Any+TypeName.swift"; sourceTree = "<group>"; };
95B39A851D9D51250057BD54 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
CA1FE7081E27D7DE00968901 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIDevice+Extensions.swift"; path = "UIDevice/UIDevice+Extensions.swift"; sourceTree = "<group>"; };
CAA707D41E2E614E0022D732 /* ModuleConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModuleConfigurator.swift; sourceTree = "<group>"; };
CAA707D61E2E616D0022D732 /* BaseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = "<group>"; };
CAA707D81E2E61A50022D732 /* ConfigurableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableController.swift; sourceTree = "<group>"; };
E126CBB21DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UICollectionView+CellRegistration.swift"; path = "UICollectionView/UICollectionView+CellRegistration.swift"; sourceTree = "<group>"; };
EF2921A51E165DF400E8F43B /* TimeInterval+DateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+DateComponents.swift"; sourceTree = "<group>"; };
EF5FB5681E0141610030E4BE /* UIView+Rotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Rotation.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
78CFEE261C5C456B00F50370 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7827C9361DE4ADB2009DA4E6 /* ObjectMapper.framework in Frameworks */,
7827C9351DE4ADB2009DA4E6 /* CocoaLumberjack.framework in Frameworks */,
7827C9391DE4ADB2009DA4E6 /* RxSwift.framework in Frameworks */,
7827C9341DE4ADB2009DA4E6 /* Alamofire.framework in Frameworks */,
7827C9381DE4ADB2009DA4E6 /* RxCocoa.framework in Frameworks */,
7827C9371DE4ADB2009DA4E6 /* RxAlamofire.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
78CFEE311C5C456B00F50370 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78CFEE351C5C456B00F50370 /* LeadKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0A3BB96A1D5DCFBD00B03CBD /* Frameworks */ = {
isa = PBXGroup;
children = (
7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */,
7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */,
7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */,
7827C9311DE4ADB2009DA4E6 /* RxAlamofire.framework */,
7827C9321DE4ADB2009DA4E6 /* RxCocoa.framework */,
7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */,
);
path = Frameworks;
sourceTree = "<group>";
};
78011A651D47AF3000EA16A2 /* Enums */ = {
isa = PBXGroup;
children = (
78753E231DE58A5D006BC0FB /* CursorError.swift */,
7873D14E1E1127BC001816EB /* LeadKitError.swift */,
);
path = Enums;
sourceTree = "<group>";
};
78011AAE1D48B46100EA16A2 /* Structures */ = {
isa = PBXGroup;
children = (
78011AB11D48B53600EA16A2 /* Api */,
);
path = Structures;
sourceTree = "<group>";
};
78011AB11D48B53600EA16A2 /* Api */ = {
isa = PBXGroup;
children = (
78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */,
);
path = Api;
sourceTree = "<group>";
};
780D23411DA412330084620D /* CGImage */ = {
isa = PBXGroup;
children = (
780D23421DA412470084620D /* CGImage+Alpha.swift */,
78B036421DA4FEC90021D5CC /* CGImage+Transform.swift */,
78B036441DA561D00021D5CC /* CGImage+Utils.swift */,
78B036461DA5624D0021D5CC /* CGImage+Creation.swift */,
78B036481DA562C30021D5CC /* CGImage+Template.swift */,
78B0364A1DA61EDE0021D5CC /* CGImage+Crop.swift */,
);
path = CGImage;
sourceTree = "<group>";
};
780D23441DA416E80084620D /* CGContext */ = {
isa = PBXGroup;
children = (
780D23451DA416F80084620D /* CGContext+Initializers.swift */,
);
path = CGContext;
sourceTree = "<group>";
};
780F56C81E0D76A5004530B6 /* Sequence */ = {
isa = PBXGroup;
children = (
780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */,
);
path = Sequence;
sourceTree = "<group>";
};
786D78E61D53C355006B2CEA /* Alamofire */ = {
isa = PBXGroup;
children = (
786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */,
786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */,
);
path = Alamofire;
sourceTree = "<group>";
};
78753E2A1DE58BED006BC0FB /* Cursors */ = {
isa = PBXGroup;
children = (
78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */,
78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */,
78753E2F1DE594B4006BC0FB /* MapCursor.swift */,
);
path = Cursors;
sourceTree = "<group>";
};
787609201E1403460093CE36 /* Observable */ = {
isa = PBXGroup;
children = (
787609211E1403830093CE36 /* Observable+DeferredJust.swift */,
);
path = Observable;
sourceTree = "<group>";
};
787783611CA03C84001CDC9B /* IndexPath */ = {
isa = PBXGroup;
children = (
787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */,
);
path = IndexPath;
sourceTree = "<group>";
};
787783651CA04D14001CDC9B /* String */ = {
isa = PBXGroup;
children = (
787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */,
95B39A851D9D51250057BD54 /* String+Localization.swift */,
);
path = String;
sourceTree = "<group>";
};
787D87481E10E19000D6015C /* ObjectMapper */ = {
isa = PBXGroup;
children = (
787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */,
);
path = ObjectMapper;
sourceTree = "<group>";
};
7884DB9A1DC1432B00E52A63 /* UserDefaults */ = {
isa = PBXGroup;
children = (
7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */,
);
path = UserDefaults;
sourceTree = "<group>";
};
789CC6091DE584C000F789D3 /* CursorType */ = {
isa = PBXGroup;
children = (
789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */,
);
path = CursorType;
sourceTree = "<group>";
};
78A0FCC41DC366A10070B5E1 /* StoryboardProtocol */ = {
isa = PBXGroup;
children = (
78A0FCC51DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift */,
78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */,
);
path = StoryboardProtocol;
sourceTree = "<group>";
};
78A74EAA1C6B401800FE9724 /* Classes */ = {
isa = PBXGroup;
children = (
78B0FC7B1C6B2BAE00358B64 /* Logging */,
78753E2A1DE58BED006BC0FB /* Cursors */,
);
path = Classes;
sourceTree = "<group>";
};
78B0FC7B1C6B2BAE00358B64 /* Logging */ = {
isa = PBXGroup;
children = (
78B0FC7C1C6B2BE200358B64 /* LogFormatter.swift */,
78B0FC7E1C6B2C4D00358B64 /* Log.swift */,
78B0FC801C6B2CD500358B64 /* App.swift */,
);
path = Logging;
sourceTree = "<group>";
};
78C36F7C1D801E2F00E7EBEA /* Double */ = {
isa = PBXGroup;
children = (
78C36F7D1D801E3E00E7EBEA /* Double+Rounding.swift */,
);
path = Double;
sourceTree = "<group>";
};
78C36F7F1D8021D100E7EBEA /* UIColor */ = {
isa = PBXGroup;
children = (
78C36F801D8021DD00E7EBEA /* UIColor+Hex.swift */,
);
path = UIColor;
sourceTree = "<group>";
};
78CFEE201C5C456B00F50370 = {
isa = PBXGroup;
children = (
78CFEE2C1C5C456B00F50370 /* LeadKit */,
78CFEE381C5C456B00F50370 /* LeadKitTests */,
78CFEE2B1C5C456B00F50370 /* Products */,
);
sourceTree = "<group>";
};
78CFEE2B1C5C456B00F50370 /* Products */ = {
isa = PBXGroup;
children = (
78CFEE2A1C5C456B00F50370 /* LeadKit.framework */,
78CFEE341C5C456B00F50370 /* LeadKitTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
78CFEE2C1C5C456B00F50370 /* LeadKit */ = {
isa = PBXGroup;
children = (
0A3BB96A1D5DCFBD00B03CBD /* Frameworks */,
78A74EAA1C6B401800FE9724 /* Classes */,
78011AAE1D48B46100EA16A2 /* Structures */,
78CFEE441C5C45E500F50370 /* Extensions */,
78011A651D47AF3000EA16A2 /* Enums */,
78CFEE491C5C45E500F50370 /* Protocols */,
78D4B54B1DA650FC005B0764 /* Functions */,
78CFEE2D1C5C456B00F50370 /* LeadKit.h */,
78CFEE2F1C5C456B00F50370 /* Info.plist */,
);
path = LeadKit;
sourceTree = "<group>";
};
78CFEE381C5C456B00F50370 /* LeadKitTests */ = {
isa = PBXGroup;
children = (
78CFEE391C5C456B00F50370 /* LeadKitTests.swift */,
78CFEE3B1C5C456B00F50370 /* Info.plist */,
);
path = LeadKitTests;
sourceTree = "<group>";
};
78CFEE441C5C45E500F50370 /* Extensions */ = {
isa = PBXGroup;
children = (
786D78E61D53C355006B2CEA /* Alamofire */,
780D23441DA416E80084620D /* CGContext */,
780D23411DA412330084620D /* CGImage */,
789CC6091DE584C000F789D3 /* CursorType */,
78C36F7C1D801E2F00E7EBEA /* Double */,
787783611CA03C84001CDC9B /* IndexPath */,
787D87481E10E19000D6015C /* ObjectMapper */,
787609201E1403460093CE36 /* Observable */,
780F56C81E0D76A5004530B6 /* Sequence */,
78A0FCC41DC366A10070B5E1 /* StoryboardProtocol */,
787783651CA04D14001CDC9B /* String */,
EF2921A41E16595100E8F43B /* TimeInterval */,
E126CBB11DB68D9A00E1B2F8 /* UICollectionView */,
78C36F7F1D8021D100E7EBEA /* UIColor */,
CA1FE7071E27D79C00968901 /* UIDevice */,
C37210711ACDF1042F70C2EB /* UIImage */,
C372153938A7B7D327F55124 /* UIStoryboard */,
78E59B2C1C786CD500C6BFE9 /* UIView */,
78D4B5441DA64D31005B0764 /* UIViewController */,
7884DB9A1DC1432B00E52A63 /* UserDefaults */,
);
path = Extensions;
sourceTree = "<group>";
};
78CFEE491C5C45E500F50370 /* Protocols */ = {
isa = PBXGroup;
children = (
78CFEE4C1C5C45E500F50370 /* StaticNibNameProtocol.swift */,
78CFEE4A1C5C45E500F50370 /* NibNameProtocol.swift */,
78CFEE4B1C5C45E500F50370 /* ReuseIdentifierProtocol.swift */,
78CFEE4E1C5C45E500F50370 /* StoryboardIdentifierProtocol.swift */,
78CFEE4D1C5C45E500F50370 /* StaticViewHeightProtocol.swift */,
78CFEE4F1C5C45E500F50370 /* ViewHeightProtocol.swift */,
78CFEE501C5C45E500F50370 /* ViewModelProtocol.swift */,
787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */,
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */,
783423691DB8D0E100A79643 /* StoryboardProtocol.swift */,
789CC6071DE5835600F789D3 /* CursorType.swift */,
780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */,
CAA707D41E2E614E0022D732 /* ModuleConfigurator.swift */,
CAA707D61E2E616D0022D732 /* BaseViewModel.swift */,
CAA707D81E2E61A50022D732 /* ConfigurableController.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
78D4B5441DA64D31005B0764 /* UIViewController */ = {
isa = PBXGroup;
children = (
78D4B5451DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift */,
);
path = UIViewController;
sourceTree = "<group>";
};
78D4B54B1DA650FC005B0764 /* Functions */ = {
isa = PBXGroup;
children = (
78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */,
7873D1501E112B0D001816EB /* Any+Cast.swift */,
);
path = Functions;
sourceTree = "<group>";
};
78E59B2C1C786CD500C6BFE9 /* UIView */ = {
isa = PBXGroup;
children = (
78A74EA81C6B373700FE9724 /* UIView+DefaultNibName.swift */,
78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */,
78CFEE481C5C45E500F50370 /* UIView+LoadFromNib.swift */,
EF5FB5681E0141610030E4BE /* UIView+Rotation.swift */,
);
path = UIView;
sourceTree = "<group>";
};
C37210711ACDF1042F70C2EB /* UIImage */ = {
isa = PBXGroup;
children = (
78B036401DA4D7060021D5CC /* UIImage+Extensions.swift */,
);
path = UIImage;
sourceTree = "<group>";
};
C372153938A7B7D327F55124 /* UIStoryboard */ = {
isa = PBXGroup;
children = (
788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */,
);
path = UIStoryboard;
sourceTree = "<group>";
};
CA1FE7071E27D79C00968901 /* UIDevice */ = {
isa = PBXGroup;
children = (
CA1FE7081E27D7DE00968901 /* UIDevice+Extensions.swift */,
);
name = UIDevice;
sourceTree = "<group>";
};
E126CBB11DB68D9A00E1B2F8 /* UICollectionView */ = {
isa = PBXGroup;
children = (
E126CBB21DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift */,
);
name = UICollectionView;
sourceTree = "<group>";
};
EF2921A41E16595100E8F43B /* TimeInterval */ = {
isa = PBXGroup;
children = (
EF2921A51E165DF400E8F43B /* TimeInterval+DateComponents.swift */,
);
path = TimeInterval;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
78CFEE271C5C456B00F50370 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
78CFEE2E1C5C456B00F50370 /* LeadKit.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
78CFEE291C5C456B00F50370 /* LeadKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = 78CFEE3E1C5C456B00F50370 /* Build configuration list for PBXNativeTarget "LeadKit" */;
buildPhases = (
782B1B3D1C7343CD003F8A95 /* Tailor */,
782B1B3E1C7343E0003F8A95 /* SwiftLint */,
78CFEE251C5C456B00F50370 /* Sources */,
78CFEE261C5C456B00F50370 /* Frameworks */,
78CFEE271C5C456B00F50370 /* Headers */,
78CFEE281C5C456B00F50370 /* Resources */,
78B0FC871C6B314B00358B64 /* Carthage copy-frameworks */,
);
buildRules = (
);
dependencies = (
);
name = LeadKit;
productName = LeadKit;
productReference = 78CFEE2A1C5C456B00F50370 /* LeadKit.framework */;
productType = "com.apple.product-type.framework";
};
78CFEE331C5C456B00F50370 /* LeadKitTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 78CFEE411C5C456B00F50370 /* Build configuration list for PBXNativeTarget "LeadKitTests" */;
buildPhases = (
78CFEE301C5C456B00F50370 /* Sources */,
78CFEE311C5C456B00F50370 /* Frameworks */,
78CFEE321C5C456B00F50370 /* Resources */,
);
buildRules = (
);
dependencies = (
78CFEE371C5C456B00F50370 /* PBXTargetDependency */,
);
name = LeadKitTests;
productName = LeadKitTests;
productReference = 78CFEE341C5C456B00F50370 /* LeadKitTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
78CFEE211C5C456B00F50370 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Touch Instinct";
TargetAttributes = {
78CFEE291C5C456B00F50370 = {
CreatedOnToolsVersion = 7.2;
LastSwiftMigration = 0800;
};
78CFEE331C5C456B00F50370 = {
CreatedOnToolsVersion = 7.2;
LastSwiftMigration = 0800;
};
};
};
buildConfigurationList = 78CFEE241C5C456B00F50370 /* Build configuration list for PBXProject "LeadKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 78CFEE201C5C456B00F50370;
productRefGroup = 78CFEE2B1C5C456B00F50370 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
78CFEE291C5C456B00F50370 /* LeadKit */,
78CFEE331C5C456B00F50370 /* LeadKitTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
78CFEE281C5C456B00F50370 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
78CFEE321C5C456B00F50370 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
782B1B3D1C7343CD003F8A95 /* Tailor */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = Tailor;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if hash tailor 2>/dev/null; then\n tailor --except=trailing-whitespace,forced-type-cast\nelse\n echo \"warning: Please install Tailor from https://tailor.sh\"\nfi";
};
782B1B3E1C7343E0003F8A95 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = SwiftLint;
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi";
};
78B0FC871C6B314B00358B64 /* Carthage copy-frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(SRCROOT)/../Carthage/Build/iOS/CocoaLumberjack.framework",
"$(SRCROOT)/../Carthage/Build/iOS/RxSwift.framework",
"$(SRCROOT)/../Carthage/Build/iOS/RxCocoa.framework",
"$(SRCROOT)/../Carthage/Build/iOS/Alamofire.framework",
"$(SRCROOT)/../Carthage/Build/iOS/ObjectMapper.framework",
"$(SRCROOT)/../Carthage/Build/iOS/RxAlamofire.framework",
);
name = "Carthage copy-frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/usr/local/bin/carthage copy-frameworks";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
78CFEE251C5C456B00F50370 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */,
780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */,
7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */,
78CFEE541C5C45E500F50370 /* UIView+LoadFromNib.swift in Sources */,
EF2921A61E165DF400E8F43B /* TimeInterval+DateComponents.swift in Sources */,
78D4B5461DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */,
7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */,
78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */,
78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */,
789CC60B1DE584F800F789D3 /* CursorType+Slice.swift in Sources */,
78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */,
78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */,
78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */,
788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */,
787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */,
7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */,
78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */,
78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */,
786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */,
CAA707D51E2E614E0022D732 /* ModuleConfigurator.swift in Sources */,
78B0FC811C6B2CD500358B64 /* App.swift in Sources */,
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */,
7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */,
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */,
780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */,
95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */,
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */,
78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */,
787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */,
78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */,
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */,
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */,
78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */,
78753E241DE58A5D006BC0FB /* CursorError.swift in Sources */,
786D78E81D53C378006B2CEA /* AlamofireRequest+Extensions.swift in Sources */,
78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */,
78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */,
EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */,
780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */,
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */,
78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */,
787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */,
CA1FE7091E27D7DE00968901 /* UIDevice+Extensions.swift in Sources */,
78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */,
CAA707D91E2E61A50022D732 /* ConfigurableController.swift in Sources */,
7884DB9C1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift in Sources */,
CAA707D71E2E616D0022D732 /* BaseViewModel.swift in Sources */,
E126CBB31DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift in Sources */,
78CFEE581C5C45E500F50370 /* StaticViewHeightProtocol.swift in Sources */,
787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */,
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */,
789CC6081DE5835600F789D3 /* CursorType.swift in Sources */,
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */,
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */,
78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */,
78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */,
78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
78CFEE301C5C456B00F50370 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
78CFEE3A1C5C456B00F50370 /* LeadKitTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
78CFEE371C5C456B00F50370 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 78CFEE291C5C456B00F50370 /* LeadKit */;
targetProxy = 78CFEE361C5C456B00F50370 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
78CFEE3C1C5C456B00F50370 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
78CFEE3D1C5C456B00F50370 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
78CFEE3F1C5C456B00F50370 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/LeadKit",
"$(PROJECT_DIR)/LeadKit/Frameworks",
"$(PROJECT_DIR)/../Carthage/Build/iOS",
);
INFOPLIST_FILE = LeadKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = ru.touchin.LeadKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
78CFEE401C5C456B00F50370 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/LeadKit",
"$(PROJECT_DIR)/LeadKit/Frameworks",
"$(PROJECT_DIR)/../Carthage/Build/iOS",
);
INFOPLIST_FILE = LeadKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = ru.touchin.LeadKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
78CFEE421C5C456B00F50370 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = LeadKitTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = ru.touchin.LeadKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
78CFEE431C5C456B00F50370 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = LeadKitTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = ru.touchin.LeadKitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
78CFEE241C5C456B00F50370 /* Build configuration list for PBXProject "LeadKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78CFEE3C1C5C456B00F50370 /* Debug */,
78CFEE3D1C5C456B00F50370 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
78CFEE3E1C5C456B00F50370 /* Build configuration list for PBXNativeTarget "LeadKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78CFEE3F1C5C456B00F50370 /* Debug */,
78CFEE401C5C456B00F50370 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
78CFEE411C5C456B00F50370 /* Build configuration list for PBXNativeTarget "LeadKitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
78CFEE421C5C456B00F50370 /* Debug */,
78CFEE431C5C456B00F50370 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 78CFEE211C5C456B00F50370 /* Project object */;
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit iOS"
BlueprintName = "LeadKit"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -27,27 +27,29 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit iOS"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "67186B2F1EB248F100CFAFFB"
BuildableName = "LeadKit iOSTests.xctest"
BlueprintName = "LeadKit iOSTests"
BlueprintIdentifier = "78CFEE331C5C456B00F50370"
BuildableName = "LeadKitTests.xctest"
BlueprintName = "LeadKitTests"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -62,12 +64,14 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit iOS"
BlueprintName = "LeadKit"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -78,9 +82,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
BuildableName = "LeadKit.framework"
BlueprintName = "LeadKit iOS"
BlueprintName = "LeadKit"
ReferencedContainer = "container:LeadKit.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@ -23,13 +23,13 @@
import RxSwift
/// Paging cursor implementation with enclosed cursor for fetching results
public class FixedPageCursor<Cursor: CursorType>: CursorType, RxDataSource {
public class FixedPageCursor<Cursor: CursorType>: CursorType where Cursor.LoadResultType == CountableRange<Int> {
public typealias ResultType = [Element]
public typealias LoadResultType = CountableRange<Int>
fileprivate let cursor: Cursor
private let cursor: Cursor
fileprivate let pageSize: Int
private let pageSize: Int
/// Initializer with enclosed cursor
///
@ -42,19 +42,19 @@ public class FixedPageCursor<Cursor: CursorType>: CursorType, RxDataSource {
}
public var exhausted: Bool {
cursor.exhausted && cursor.count == count
return cursor.exhausted && cursor.count == count
}
public private(set) var count: Int = 0
public subscript(index: Int) -> Cursor.Element {
cursor[index]
return cursor[index]
}
public func loadNextBatch() -> Single<[Cursor.Element]> {
Single.deferred {
public func loadNextBatch() -> Observable<LoadResultType> {
return Observable.deferred {
if self.exhausted {
return .error(CursorError.exhausted)
throw CursorError.exhausted
}
let restOfLoaded = self.cursor.count - self.count
@ -63,27 +63,12 @@ public class FixedPageCursor<Cursor: CursorType>: CursorType, RxDataSource {
let startIndex = self.count
self.count += min(restOfLoaded, self.pageSize)
return .just(self.cursor[startIndex..<self.count])
return Observable.just(startIndex..<self.count)
}
return self.cursor.loadNextBatch()
.flatMap { _ in
self.loadNextBatch()
}
.flatMap { _ in self.loadNextBatch() }
}
}
}
/// FixedPageCursor subclass with implementation of ResettableType
public class ResettableFixedPageCursor<Cursor: ResettableCursorType>: FixedPageCursor<Cursor>, ResettableType {
public typealias ResultType = [Element]
public override init(cursor: Cursor, pageSize: Int) {
super.init(cursor: cursor, pageSize: pageSize)
}
public required init(resetFrom other: ResettableFixedPageCursor) {
super.init(cursor: other.cursor.reset(), pageSize: other.pageSize)
}
}

View File

@ -22,37 +22,30 @@
import RxSwift
public extension CursorType {
public typealias MapCursorLoadResultType = CountableRange<Int>
public extension CursorType where Self.LoadResultType == MapCursorLoadResultType {
/// Creates MapCursor with current cursor
///
/// - Parameter transform: closure to transform elements
/// - Returns: new MapCursor instance
/// - Returns: new MapCursorInstance
func flatMap<T>(transform: @escaping MapCursor<Self, T>.Transform) -> MapCursor<Self, T> {
MapCursor(cursor: self, transform: transform)
return MapCursor(cursor: self, transform: transform)
}
/// Creates ResettableMapCursor with current cursor
///
/// - Parameter transform: closure to transform elements
/// - Returns: new ResettableMapCursor instance
func flatMap<T>(transform: @escaping ResettableMapCursor<Self, T>.Transform)
-> ResettableMapCursor<Self, T> where Self: ResettableCursorType {
ResettableMapCursor(cursor: self, transform: transform)
}
}
/// Map cursor implementation with enclosed cursor for fetching results
public class MapCursor<Cursor: CursorType, T>: CursorType, RxDataSource {
public class MapCursor<Cursor: CursorType, T>: CursorType where Cursor.LoadResultType == MapCursorLoadResultType {
public typealias ResultType = [Element]
public typealias LoadResultType = Cursor.LoadResultType
public typealias Transform = (Cursor.Element) -> T?
fileprivate let cursor: Cursor
private let cursor: Cursor
fileprivate let transform: Transform
private let transform: Transform
private var elements: [T] = []
@ -67,37 +60,24 @@ public class MapCursor<Cursor: CursorType, T>: CursorType, RxDataSource {
}
public var exhausted: Bool {
cursor.exhausted
return cursor.exhausted
}
public var count: Int {
elements.count
return elements.count
}
public subscript(index: Int) -> T {
elements[index]
return elements[index]
}
public func loadNextBatch() -> Single<[T]> {
cursor.loadNextBatch().map { newItems in
let transformedNewItems = newItems.compactMap(self.transform)
self.elements += transformedNewItems
public func loadNextBatch() -> Observable<LoadResultType> {
return cursor.loadNextBatch().map { loadedRange in
let startIndex = self.elements.count
self.elements += self.cursor[loadedRange].flatMap(self.transform)
return transformedNewItems
return startIndex..<self.elements.count
}
}
}
/// MapCursor subclass with implementation of ResettableType
public class ResettableMapCursor<Cursor: ResettableCursorType, T>: MapCursor<Cursor, T>, ResettableType {
public typealias ResultType = [Cursor.Element]
public override init(cursor: Cursor, transform: @escaping Transform) {
super.init(cursor: cursor, transform: transform)
}
public required init(resetFrom other: ResettableMapCursor) {
super.init(cursor: other.cursor.reset(), transform: other.transform)
}
}

View File

@ -23,42 +23,39 @@
import RxSwift
/// Stub cursor implementation for array content type
public class StaticCursor<Element>: ResettableRxDataSourceCursor {
public class StaticCursor<Element>: CursorType {
public typealias ResultType = [Element]
public typealias LoadResultType = CountableRange<Int>
private let content: [Element]
/// Initializer for array content type
///
/// - Parameter content: array with elements of Element type
/// - Parameter content: array with elements of Elemet type
public init(content: [Element]) {
self.content = content
}
public required init(resetFrom other: StaticCursor) {
self.content = other.content
}
public private(set) var exhausted = false
public private(set) var count = 0
public subscript(index: Int) -> Element {
content[index]
return content[index]
}
public func loadNextBatch() -> Single<[Element]> {
Single.deferred {
public func loadNextBatch() -> Observable<LoadResultType> {
return Observable.deferred {
if self.exhausted {
return .error(CursorError.exhausted)
throw CursorError.exhausted
}
self.count = self.content.count
self.exhausted = true
return .just(self.content)
return Observable.just(0..<self.count)
}
}
}

View File

@ -0,0 +1,59 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
open class App {
fileprivate static let stringVendorIdentifierKey = "stringIdentifierForVendor"
/// The value of CFBundleName
open static let bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
/// The value of CFBundleShortVersionString
open static let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
/// The value of CFBundleVersion
open static let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
/**
Return app's version
- returns: shortBundleVersion.bundleVersion
*/
open static var version: String {
return App.shortVersion + "." + App.bundleVersion
}
/**
Return device identifier
- returns: UUIDString
*/
open static var deviceUniqueIdentifier: String {
if let vendorIdentifier = UserDefaults.standard.string(forKey: App.stringVendorIdentifierKey) {
return vendorIdentifier
}
let vendorIdentifier = UUID().uuidString
UserDefaults.standard.set(vendorIdentifier, forKey: App.stringVendorIdentifierKey)
UserDefaults.standard.synchronize()
return vendorIdentifier
}
}

View File

@ -0,0 +1,59 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import CocoaLumberjack
open class Log {
/// Logger for CocoaLumberJack
open let fileLogger = DDFileLogger()
init() {
DDLog.add(fileLogger)
DDLog.add(DDASLLogger.sharedInstance())
DDLog.add(DDTTYLogger.sharedInstance())
let logFormatter = LogFormatter()
DDASLLogger.sharedInstance().logFormatter = logFormatter
DDTTYLogger.sharedInstance().logFormatter = logFormatter
let assertionHandler = NSAssertionHandler()
Thread.current.threadDictionary.setValue(assertionHandler, forKey: NSAssertionHandlerKey)
}
/**
Add start message for your application
- returns: Return value looks like "AppName 1.0.1 session started on version 9.2 (build 13c75)"
*/
open static var startMessage: String {
let startMessage = App.bundleName + " " + App.shortVersion + "."
+ App.bundleVersion + " session started on "
+ ProcessInfo.processInfo.operatingSystemVersionString.lowercased()
return startMessage
}
}

View File

@ -21,35 +21,38 @@
//
import Foundation
import CocoaLumberjack
import CocoaLumberjack.DDDispatchQueueLogFormatter
public extension String {
class LogFormatter: DDDispatchQueueLogFormatter {
fileprivate let dateFormatter: DateFormatter
/// Pass value and proper naming string in russian
static func localizedComponent<T: FixedWidthInteger>(value: T,
stringOne: String,
stringTwo: String,
stringMany: String) -> String {
override init() {
dateFormatter = DateFormatter()
dateFormatter.formatterBehavior = .behavior10_4
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"
let lastTwoDigits = value % 100
if (11...14).contains(lastTwoDigits) {
return stringMany
} else {
let lastDigit = lastTwoDigits % 10
switch lastDigit {
case 1:
return stringOne
case 2...4:
return stringTwo
case 5...9, 0:
return stringMany
default:
return ""
}
}
super.init()
}
override func format(message logMessage: DDLogMessage) -> String {
let level: String
switch logMessage.flag {
case DDLogFlag.error:
level = "ERR"
case DDLogFlag.warning:
level = "WRN"
case DDLogFlag.info:
level = "INF"
case DDLogFlag.debug:
level = "DBG"
default:
level = "VRB"
}
let dateAndTime = dateFormatter.string(from: logMessage.timestamp)
return "\(level) \(dateAndTime) [\(logMessage.fileName):\(logMessage.line)]: \(logMessage.message)"
}
}

View File

@ -24,8 +24,11 @@ import Foundation
/// A type representing an possible errors that can be thrown during working with cursor object
///
/// - busy: cursor is currently processing another request
/// - exhausted: cursor did load all available results
public enum CursorError: Error {
case busy
case exhausted
}

View File

@ -22,14 +22,11 @@
import Foundation
/// Enum that represents common errors in LeadKit framework
/// Enum which represents common errors in LeadKit framework
///
/// - failedToCastValue: attempt to cast was failed
/// - failedToDecode: attempt to decoding was failed
/// - failedToEncodeQueryItems: attempt to encoding to query items was failed
public enum LeadKitError: Error {
case failedToCastValue(expectedType: Any.Type, givenType: Any.Type)
case failedToDecode(reason: String)
case failedToEncodeQueryItems
}

View File

@ -0,0 +1,66 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import RxSwift
import RxAlamofire
import ObjectMapper
public extension Reactive where Base: Alamofire.SessionManager {
/// Method which executes request with given api parameters
///
/// - Parameter requestParameters: api parameters to pass Alamofire
/// - Returns: Observable with request
func apiRequest(requestParameters: ApiRequestParameters) -> Observable<DataRequest> {
return RxAlamofire.request(requestParameters.method,
requestParameters.url,
parameters: requestParameters.parameters,
encoding: requestParameters.encoding,
headers: requestParameters.headers)
}
/// Method which executes request and serializes response into target object
///
/// - Parameter requestParameters: api parameters to pass Alamofire
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func responseModel<T: ImmutableMappable>(requestParameters: ApiRequestParameters,
mappingQueue: DispatchQueue = DispatchQueue.global()) -> Observable<(HTTPURLResponse, T)> {
return apiRequest(requestParameters: requestParameters)
.flatMap { $0.rx.apiResponse(mappingQueue: mappingQueue) }
}
/// Method which executes request and serializes response into target object
///
/// - Parameter requestParameters: api parameters to pass Alamofire
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func responseObservableModel<T: ObservableMappable>(requestParameters: ApiRequestParameters,
mappingQueue: DispatchQueue = DispatchQueue.global()) -> Observable<(HTTPURLResponse, T)>
where T.ModelType == T {
return apiRequest(requestParameters: requestParameters)
.flatMap { $0.rx.apiResponse(mappingQueue: mappingQueue) }
}
}

View File

@ -0,0 +1,65 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Alamofire
import RxSwift
import ObjectMapper
import RxAlamofire
public extension Reactive where Base: DataRequest {
/// Method which serializes response into target object
///
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func apiResponse<T: ImmutableMappable>(mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(HTTPURLResponse, T)> {
return responseJSONOnQueue(mappingQueue)
.map { resp, value in
let json = try cast(value) as [String: Any]
return (resp, try T(JSON: json))
}
}
/// Method which serializes response into target object
///
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func apiResponse<T: ObservableMappable>(mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(HTTPURLResponse, T)> where T.ModelType == T {
return responseJSONOnQueue(mappingQueue)
.flatMap { resp, value -> Observable<(HTTPURLResponse, T)> in
let json = try cast(value) as [String: Any]
return T.createFrom(map: Map(mappingType: .fromJSON, JSON: json))
.map { (resp, $0) }
}
}
internal func responseJSONOnQueue(_ queue: DispatchQueue) -> Observable<(HTTPURLResponse, Any)> {
return responseResult(queue: queue, responseSerializer: DataRequest.jsonResponseSerializer(options: .allowFragments))
}
}

View File

@ -0,0 +1,69 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public extension CGContext {
/**
method which creates an instance of CGContext with parameters taken from a given image
- parameter forCGImage: CGImage instance from which the parameters will be taken
- parameter fallbackColorSpace: fallback color space if image doesn't have it
*/
public static func create(forCGImage cgImage: CGImage,
fallbackColorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()) -> CGContext? {
return create(width: cgImage.width,
height: cgImage.height,
bitmapInfo: cgImage.bitmapInfo,
colorSpace: cgImage.colorSpace ?? fallbackColorSpace,
bitsPerComponent: cgImage.bitsPerComponent)
}
/**
method which creates an instance of CGContext
- parameter width: The width, in pixels, of the required bitmap.
- parameter height: The height, in pixels, of the required bitmap.
- parameter bitmapInfo: Constants that specify whether the bitmap should contain an alpha channel,
the alpha channels relative location in a pixel,
and information about whether the pixel components are floating-point or integer values.
- parameter colorSpace: The color space to use for the bitmap context.
- parameter bitsPerComponent: The number of bits to use for each component of a pixel in memory.
*/
public static func create(width: Int,
height: Int,
bitmapInfo: CGBitmapInfo = alphaBitmapInfo,
colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: Int = 8) -> CGContext? {
return CGContext(data: nil,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)
}
}

View File

@ -24,14 +24,30 @@ import CoreGraphics
public extension CGImage {
/// A Boolean value indicating whether the image data has an alpha channel.
var hasAlpha: Bool {
/**
- returns: true if the image has an alpha layer.
*/
public var hasAlpha: Bool {
switch alphaInfo {
case .first, .last, .premultipliedFirst, .premultipliedLast:
return true
default:
return false
}
}
/**
- returns: a copy of the given image, adding an alpha channel if it doesn't already have one.
*/
public func applyAlpha() -> CGImage? {
guard !hasAlpha else {
return self
}
let ctx = CGContext.create(width: width, height: height, bitmapInfo: alphaBitmapInfo)
ctx?.draw(self, in: bounds)
return ctx?.makeImage()
}
}

View File

@ -0,0 +1,78 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public extension CGImage {
/**
method which creates new CGImage instance filled by given color
- parameter color: color to fill
- parameter width: width of new image
- parameter height: height of new image
- parameter opaque: a flag indicating whether the bitmap is opaque (default: False)
- returns: new instanse of UIImage with given size and color
*/
public static func create(color: CGColor,
width: Int,
height: Int,
opaque: Bool = false) -> CGImage? {
let context = CGContext.create(width: width,
height: height,
bitmapInfo: opaque ? opaqueBitmapInfo : alphaBitmapInfo)
guard let ctx = context else {
return nil
}
ctx.setFillColor(color)
ctx.fill(CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height)))
return ctx.makeImage()
}
/**
creates an image from a UIView.
- parameter fromView: The source view.
- returns A new image
*/
public static func create(fromView view: UIView) -> CGImage? {
let size = view.bounds.size
let ctxWidth = Int(ceil(size.width))
let ctxHeight = Int(ceil(size.height))
guard let ctx = CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
view.layer.render(in: ctx)
return ctx.makeImage()
}
}

View File

@ -24,10 +24,12 @@ import CoreGraphics
public extension CGImage {
/// Crop image to square from center.
///
/// - Returns: A new cropped image or nil if something goes wrong.
func cropFromCenterToSquare() -> CGImage? {
/**
crop image to square from center
- returns: cropped image
*/
public func cropFromCenterToSquare() -> CGImage? {
let shortest = min(width, height)
let widthCropSize = width > shortest ? (width - shortest) : 0
@ -43,25 +45,5 @@ public extension CGImage {
return cropping(to: cropRect)
}
/// Crop image with given margin values.
///
/// - Parameters:
/// - top: Top margin.
/// - left: Left margin.
/// - bottom: Bottom margin.
/// - right: Right margin.
/// - Returns: A new CGImage cropped with given paddings or nil if something goes wrong.
func crop(top: CGFloat = 0,
left: CGFloat = 0,
bottom: CGFloat = 0,
right: CGFloat = 0) -> CGImage? {
let rect = CGRect(x: left,
y: top,
width: CGFloat(width) - left - right,
height: CGFloat(height) - top - bottom)
return cropping(to: rect)
}
}

View File

@ -20,24 +20,34 @@
// THE SOFTWARE.
//
import RxSwift
import CoreGraphics
public extension Single {
public extension CGImage {
/// Returns an single that invokes the specified factory function whenever a new observer subscribes.
///
/// - Parameter elementFactory: Element factory function to invoke for each observer
/// that subscribes to the resulting sequence.
/// - Returns: A single whose observers trigger an invocation of the given element factory function.
static func deferredJust(_ elementFactory: @escaping () throws -> Element) -> Single<Element> {
.create { observer in
do {
observer(.success(try elementFactory()))
} catch {
observer(.failure(error))
}
/**
method which render current template CGImage into new image using given color
return Disposables.create()
- parameter withColor: color which used to fill template image
- returns: new CGImage rendered with given color or nil if something goes wrong
*/
public func renderTemplate(withColor color: CGColor) -> CGImage? {
guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else {
return nil
}
let imageRect = bounds
ctx.setFillColor(color)
ctx.translateBy(x: 0, y: CGFloat(height))
ctx.scaleBy(x: 1.0, y: -1.0)
ctx.clip(to: imageRect, mask: self)
ctx.fill(imageRect)
ctx.setBlendMode(.multiply)
return ctx.makeImage()
}
}

View File

@ -0,0 +1,203 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public extension CGImage {
/**
creates a new image with rounded corners.
- parameter withRadius: The corner radius.
- returns: A new image
*/
public func round(withRadius radius: CGFloat) -> CGImage? {
guard let ctx = CGContext.create(forCGImage: self) ?? CGContext.create(width: width, height: height) else {
return nil
}
ctx.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath)
ctx.clip()
ctx.draw(self, in: bounds)
return ctx.makeImage()
}
/**
creates a new image with a border.
- parameter width: The size of the border.
- parameter color: The color of the border.
- parameter radius: The corner radius.
- parameter extendSize: Extend result image size and don't overlap source image by border.
- returns: A new image
*/
public func applyBorder(width border: CGFloat,
color: CGColor,
radius: CGFloat = 0,
extendSize: Bool = false) -> CGImage? {
let offset = extendSize ? border : 0
let newWidth = CGFloat(width) + offset * 2
let newHeight = CGFloat(height) + offset * 2
let ctxWidth = Int(ceil(newWidth))
let ctxHeight = Int(ceil(newHeight))
let ctxRect: CGRect = CGRect(origin: CGPoint.zero, size: CGSize(width: newWidth, height: newHeight))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: width, height: height) else {
return nil
}
ctx.draw(self, in: CGRect(x: offset, y: offset, width: CGFloat(width), height: CGFloat(height)))
ctx.setStrokeColor(color)
let widthDiff = CGFloat(ctxWidth) - newWidth // difference between context width and real width
let heightDiff = CGFloat(ctxWidth) - newWidth // difference between context height and real height
let inset = ctxRect.insetBy(dx: border / 2 + widthDiff, dy: border / 2 + heightDiff)
if radius != 0 {
ctx.setLineWidth(border)
ctx.addPath(UIBezierPath(roundedRect: inset, cornerRadius: radius).cgPath)
ctx.strokePath()
} else {
ctx.stroke(inset, width: border)
}
return ctx.makeImage()
}
/**
creates a resized copy of an image.
- parameter newSize: the new size of the image.
- parameter contentMode: the way to handle the content in the new size.
- returns: a new image
*/
public func resize(newSize: CGSize, contentMode: ImageContentMode = .scaleToFill) -> CGImage? {
let ctxWidth = Int(ceil(newSize.width))
let ctxHeight = Int(ceil(newSize.height))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
let horizontalRatio = newSize.width / CGFloat(width)
let verticalRatio = newSize.height / CGFloat(height)
let ratio: CGFloat
switch contentMode {
case .scaleToFill:
ratio = 1
case .scaleAspectFill:
ratio = max(horizontalRatio, verticalRatio)
case .scaleAspectFit:
ratio = min(horizontalRatio, verticalRatio)
}
let newImageWidth = contentMode == .scaleToFill ? newSize.width : CGFloat(width) * ratio
let newImageHeight = contentMode == .scaleToFill ? newSize.height : CGFloat(height) * ratio
let originX: CGFloat
let originY: CGFloat
if newImageWidth > newSize.width {
originX = (newSize.width - newImageWidth) / 2
} else if newImageWidth < newSize.width {
originX = newSize.width / 2 - newImageWidth / 2
} else {
originX = 0
}
if newImageHeight > newSize.height {
originY = (newSize.height - newImageHeight) / 2
} else if newImageHeight < newSize.height {
originY = newSize.height / 2 - newImageHeight / 2
} else {
originY = 0
}
let rect = CGRect(origin: CGPoint(x: originX, y: originY),
size: CGSize(width: newImageWidth, height: newImageHeight))
ctx.interpolationQuality = .high
ctx.draw(self, in: rect)
return ctx.makeImage()
}
/**
returns a copy of the image with border of the given size added around its edges.
- parameter padding: The padding amount.
- returns: A new image.
*/
public func applyPadding(_ padding: CGFloat) -> CGImage? {
let ctxWidth = Int(ceil(CGFloat(width) + padding * 2))
let ctxHeight = Int(ceil(CGFloat(height) + padding * 2))
let context = CGContext.create(width: ctxWidth,
height: ctxHeight,
bitmapInfo: bitmapInfo,
colorSpace: colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitsPerComponent: bitsPerComponent)
guard let ctx = context ?? CGContext.create(width: ctxWidth, height: ctxHeight) else {
return nil
}
// Draw the image in the center of the context, leaving a gap around the edges
let imageLocation = CGRect(x: padding,
y: padding,
width: CGFloat(width),
height: CGFloat(height))
ctx.addRect(imageLocation)
ctx.clip()
ctx.draw(self, in: imageLocation)
return ctx.makeImage()
}
}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
// The bitmapInfo value are hard-coded to prevent an "unsupported parameter combination" error
public let alphaBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
public let opaqueBitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.none.rawValue)
public enum ImageContentMode {
case scaleToFill, scaleAspectFit, scaleAspectFill
}
public extension CGImage {
/**
- returns: bounds of image.
*/
var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height))
}
}

View File

@ -0,0 +1,47 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public extension CursorType where LoadResultType == CountableRange<Int> {
subscript(range: LoadResultType) -> [Self.Element] {
return range.map { self[$0] }
}
var loadedElements: [Self.Element] {
return self[0..<count]
}
}
public extension CursorType where LoadResultType == CountableClosedRange<Int> {
subscript(range: LoadResultType) -> [Self.Element] {
return range.map { self[$0] }
}
var loadedElements: [Self.Element] {
return self[0...count - 1]
}
}

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public extension Double {
/**
Type of rounding double value
- Normal: From 167.567 you will get 167.6
- Down: From 167.567 you will get 167.5
*/
public enum RoundingType {
case normal
case down
}
/**
Rounding of double value
- parameter persicion: important number of digits after comma
- parameter roundType: rounding type
- returns: rounded value
*/
public func roundValue(withPersicion persicion: UInt,
roundType: RoundingType = .normal) -> Double {
let divider = pow(10.0, Double(persicion))
switch roundType {
case .normal:
return (self * divider).rounded(.up) / divider
case .down:
return (self * divider).rounded(.down) / divider
}
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
// http://stackoverflow.com/a/21686163
extension IndexPath {
/// return immutable copy if class is not NSIndexPath or return self
var immutableIndexPath: IndexPath {
if Mirror(reflecting: self).subjectType == IndexPath.self { // check for UIMutableIndexPath
return self
}
return IndexPath(item: item, section: section)
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import ObjectMapper
import RxSwift
public extension ObservableMappable where Self: ImmutableMappable, ModelType == Self {
/// Default implementation of ObservableMappable protocol for ImmutableMappable protocol
///
/// - Parameter map: ObjectMapper.Map object
/// - Returns: Observable with value of ModelType(map: ObjectMapper.Map)
/// - Throws: error of ModelType(map: ObjectMapper.Map)
static func createFrom(map: Map) -> Observable<Self> {
return Observable.deferredJust { try ModelType(map: map) }
}
}

View File

@ -22,7 +22,7 @@
import RxSwift
public extension ObservableType {
public extension Observable {
/// Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes.
///
@ -30,7 +30,7 @@ public extension ObservableType {
/// that subscribes to the resulting sequence.
/// - Returns: An observable sequence whose observers trigger an invocation of the given element factory function.
static func deferredJust(_ elementFactory: @escaping () throws -> Element) -> Observable<Element> {
.create { observer in
return create { (observer) -> Disposable in
do {
observer.onNext(try elementFactory())
observer.onCompleted()
@ -41,4 +41,5 @@ public extension ObservableType {
return Disposables.create()
}
}
}

View File

@ -33,8 +33,8 @@ public extension Sequence {
/// - transform: Transform closure
/// - Returns: Observable of array which contains transform return type
func concurrentRxMap<R>(concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount,
qos: DispatchQoS = .default,
transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> {
qos: DispatchQoS = .default,
transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> {
let operationsCount = Swift.max(1, concurrentOperationCount)
@ -54,15 +54,12 @@ public extension Sequence {
return Observable.from(indexedRanges)
.flatMap { indexedRange -> Observable<(idx: Int, results: [R])> in
Observable.just(indexedRange)
.observe(on: scheduler)
return Observable.just(indexedRange)
.observeOn(scheduler)
.map { (idx: $0.idx, results: try array[$0.range].map(transform)) }
}
.toArray()
.map { array -> [R] in
array.sorted { $0.idx < $1.idx }
.flatMap { $0.results }
}
.asObservable()
.map { $0.sorted { $0.0.idx < $0.1.idx }.flatMap { $0.results } }
}
}

View File

@ -22,9 +22,13 @@
import UIKit
extension UIViewController: XibNameProtocol {
public extension StoryboardProtocol {
open class var xibName: String {
typeName(of: self)
/**
- returns: default bundle for storyboard initialization
*/
public static var bundle: Bundle? {
return Bundle.main
}
}

View File

@ -0,0 +1,47 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension StoryboardProtocol where
StoryboardIdentifier: RawRepresentable, StoryboardIdentifier.RawValue == String,
ViewControllerIdentifier: RawRepresentable, ViewControllerIdentifier.RawValue == String {
/**
- returns: UIStoryboradInstance with StoryboardIdentifier name
*/
public static var uiStoryboard: UIStoryboard {
return UIStoryboard(name: storyboardIdentifier.rawValue, bundle: bundle)
}
/**
method for instantiating UIViewController from storyboard using view controller identifier
- parameter _: object which represents view controller identifier
- returns: UIViewController instance
*/
public static func instantiateViewController(_ viewController: ViewControllerIdentifier) -> UIViewController {
return uiStoryboard.instantiateViewController(withIdentifier: viewController.rawValue)
}
}

View File

@ -29,7 +29,8 @@ public extension String {
- returns: localized string
*/
func localized() -> String {
NSLocalizedString(self, comment: "")
public func localized() -> String {
return NSLocalizedString(self, comment: "")
}
}

View File

@ -0,0 +1,82 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
/**
* Struct for holding result of string size calculation
*/
public struct StringSizeCalculationResult {
public let size: CGSize
public let fontLineHeight: CGFloat?
}
public extension StringSizeCalculationResult {
public var height: CGFloat { return size.height }
public var width: CGFloat { return size.width }
public var numberOfLines: UInt? {
if let lineHeight = fontLineHeight {
let lineHeightRounded = Double(lineHeight).roundValue(withPersicion: 2)
let heightRounded = Double(height).roundValue(withPersicion: 2)
let numberOfLines = ceil(heightRounded / lineHeightRounded)
return UInt(numberOfLines)
}
return nil
}
}
public extension String {
/**
method which calculates string size based on given character attributes and (optional) max width and height
- parameter attributes: dictionary with string character attributes
- parameter maxWidth: maximum width of text
- parameter maxHeight: maximum height of text
- returns: string size calculation result
*/
public func size(withAttributes attributes: [String: AnyObject]?,
maxWidth: CGFloat = CGFloat.greatestFiniteMagnitude,
maxHeight: CGFloat = CGFloat.greatestFiniteMagnitude) -> StringSizeCalculationResult {
let size = self.boundingRect(with: CGSize(width: maxWidth, height: maxHeight),
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: attributes,
context: nil).size
let fontLineHeight = (attributes?[NSFontAttributeName] as? UIFont)?.lineHeight
return StringSizeCalculationResult(size: size, fontLineHeight: fontLineHeight)
}
}

View File

@ -25,12 +25,10 @@ import Foundation
extension TimeInterval {
private static let secondsInMinute = 60
private static let minutesInHour = 60
private static let hoursInDay = 24
private static let secondsInHour = secondsInMinute * minutesInHour
private static let secondsInDay = secondsInHour * hoursInDay
public typealias TimeComponents = (days: Int, hours: Int, minutes: Int, seconds: Int)
private static let minutesInHour = 60
private static let hoursInDay = 24
private static let secondsInHour = secondsInMinute * minutesInHour
private static let secondsInDay = secondsInHour * hoursInDay
/**
Deserialize TimeInterval from string
@ -42,22 +40,15 @@ extension TimeInterval {
*/
public init(timeString: String, timeSeparator: String = ":", daySeparator: String = ".") {
let timeComponents = timeString.components(separatedBy: daySeparator)
let fullDays = Double(timeComponents.first ?? "") ?? 0
let dayComponent = timeComponents.first ?? ""
let fullDays = Double(dayComponent) ?? 0
let timeComponent = timeComponents.last ?? ""
let timeValue = timeComponent
let timeValue = (timeComponents.last ?? "")
.components(separatedBy: timeSeparator)
.reversed()
.enumerated()
.reduce(0.0) { interval, part in
let partElement = Double(part.element) ?? 0
return interval + partElement * pow(Double(TimeInterval.secondsInMinute), Double(part.offset))
}
.reduce(0) { interval, part in
interval + (Double(part.element) ?? 0) * pow(Double(TimeInterval.secondsInMinute), Double(part.offset))
}
self = (fullDays * Double(TimeInterval.secondsInDay)) + timeValue
}
@ -66,15 +57,16 @@ extension TimeInterval {
Returns a tuple with date components, contained in TimeInterval value
Supported components: days, hours, minutes, seconds
*/
public var timeComponents: TimeComponents {
var timeInterval = Int(self)
let days = (timeInterval / TimeInterval.secondsInDay) % TimeInterval.secondsInDay
timeInterval -= days * TimeInterval.secondsInDay
public var timeComponents: (days: Int, hours: Int, minutes: Int, seconds: Int) {
var ti = Int(self)
let days = (ti / TimeInterval.secondsInDay) % TimeInterval.secondsInDay
ti -= days * TimeInterval.secondsInDay
return (
days,
(timeInterval / TimeInterval.secondsInHour) % TimeInterval.secondsInHour,
(timeInterval / TimeInterval.secondsInMinute) % TimeInterval.secondsInMinute,
timeInterval % TimeInterval.secondsInMinute
(ti / TimeInterval.secondsInHour) % TimeInterval.secondsInHour,
(ti / TimeInterval.secondsInMinute) % TimeInterval.secondsInMinute,
ti % TimeInterval.secondsInMinute
)
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension UICollectionView {
/**
method which register UICollectionViewCell subclass for reusing in UICollectionView with reuse identifier
provided by ReuseIdentifierProtocol protocol implementation and nib name
provided by StaticNibNameProtocol protocol implementation
- parameter cellClass: UICollectionViewCell subclass which implements ReuseIdentifierProtocol and StaticNibNameProtocol
- see: ReuseIdentifierProtocol, StaticNibNameProtocol
*/
public func registerNib<T>(forCellClass cellClass: T.Type)
where T: ReuseIdentifierProtocol, T: UICollectionViewCell, T: StaticNibNameProtocol {
register(UINib(nibName: T.nibName), forCellWithReuseIdentifier: T.reuseIdentifier)
}
/**
method which register UICollectionViewCell subclass for reusing in UICollectionView with reuse identifier
provided by ReuseIdentifierProtocol protocol implementation and nib name
provided by NibNameProtocol protocol implementation
- parameter cellClass: UICollectionViewCell subclass which implements ReuseIdentifierProtocol and NibNameProtocol
- parameter interfaceIdiom: UIUserInterfaceIdiom value for NibNameProtocol
- see: ReuseIdentifierProtocol, NibNameProtocol
*/
public func registerNib<T>(forCellClass cellClass: T.Type,
forUserInterfaceIdiom interfaceIdiom: UIUserInterfaceIdiom)
where T: ReuseIdentifierProtocol, T: UICollectionViewCell, T: NibNameProtocol {
let nib = UINib(nibName: T.nibName(forConfiguration: interfaceIdiom))
register(nib, forCellWithReuseIdentifier: T.reuseIdentifier)
}
}

View File

@ -31,10 +31,10 @@ public extension UIColor {
- parameter hex3: Three-digit hexadecimal value.
- parameter alpha: 0.0 - 1.0. The default is 1.0.
*/
convenience init(hex3: UInt16, alpha: CGFloat = 1) {
let red = CGFloat((hex3 & 0xF00) >> 8) / 0xF
let green = CGFloat((hex3 & 0x0F0) >> 4) / 0xF
let blue = CGFloat((hex3 & 0x00F) >> 0) / 0xF
public convenience init(hex3: UInt16, alpha: CGFloat = 1) {
let red = CGFloat((hex3 & 0xF00) >> 8) / 0xF
let green = CGFloat((hex3 & 0x0F0) >> 4) / 0xF
let blue = CGFloat((hex3 & 0x00F) >> 0) / 0xF
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
@ -44,11 +44,11 @@ public extension UIColor {
- parameter hex4: Four-digit hexadecimal value.
*/
convenience init(hex4: UInt16) {
let red = CGFloat((hex4 & 0xF000) >> 12) / 0xF
let green = CGFloat((hex4 & 0x0F00) >> 8) / 0xF
let blue = CGFloat((hex4 & 0x00F0) >> 4) / 0xF
let alpha = CGFloat((hex4 & 0x000F) >> 0) / 0xF
public convenience init(hex4: UInt16) {
let red = CGFloat((hex4 & 0xF000) >> 12) / 0xF
let green = CGFloat((hex4 & 0x0F00) >> 8) / 0xF
let blue = CGFloat((hex4 & 0x00F0) >> 4) / 0xF
let alpha = CGFloat((hex4 & 0x000F) >> 0) / 0xF
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
@ -59,10 +59,10 @@ public extension UIColor {
- parameter hex6: Six-digit hexadecimal value.
- parameter alpha: alpha: 0.0 - 1.0. The default is 1.0.
*/
convenience init(hex6: UInt32, alpha: CGFloat = 1) {
let red = CGFloat((hex6 & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((hex6 & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat((hex6 & 0x0000FF) >> 0) / 0xFF
public convenience init(hex6: UInt32, alpha: CGFloat = 1) {
let red = CGFloat((hex6 & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((hex6 & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat((hex6 & 0x0000FF) >> 0) / 0xFF
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
@ -72,11 +72,11 @@ public extension UIColor {
- parameter hex8: Eight-digit hexadecimal value.
*/
convenience init(hex8: UInt32) {
let red = CGFloat((hex8 & 0xFF000000) >> 24) / 0xFF
let green = CGFloat((hex8 & 0x00FF0000) >> 16) / 0xFF
let blue = CGFloat((hex8 & 0x0000FF00) >> 8) / 0xFF
let alpha = CGFloat((hex8 & 0x000000FF) >> 0) / 0xFF
public convenience init(hex8: UInt32) {
let red = CGFloat((hex8 & 0xFF000000) >> 24) / 0xFF
let green = CGFloat((hex8 & 0x00FF0000) >> 16) / 0xFF
let blue = CGFloat((hex8 & 0x0000FF00) >> 8) / 0xFF
let alpha = CGFloat((hex8 & 0x000000FF) >> 0) / 0xFF
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
@ -87,16 +87,16 @@ public extension UIColor {
- parameter hexString: hex string with red green and blue values (can have `#` sign)
- parameter alpha: alpha component used if not given in hexString
*/
convenience init?(hexString: String, alpha: CGFloat = 1) {
public convenience init?(hexString: String, alpha: CGFloat = 1) {
let hexStr: String
if hexString.hasPrefix("#") {
hexStr = String(hexString[hexString.index(hexString.startIndex, offsetBy: 1)...])
hexStr = hexString.substring(from: hexString.characters.index(hexString.startIndex, offsetBy: 1))
} else {
hexStr = hexString
}
let charactersCount = hexStr.count
let charactersCount = hexStr.characters.count
switch charactersCount {
case 3, 4:
@ -109,7 +109,6 @@ public extension UIColor {
} else {
return nil
}
case 6, 8:
if let hex = UInt32(hexStr, radix: 16) {
if charactersCount == 6 {
@ -120,32 +119,9 @@ public extension UIColor {
} else {
return nil
}
default:
return nil
}
}
}
public extension UIColor {
/// Hex representation of UIColor as String
var hexString: String {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
let intRepresentation: Int
if alpha == 1 {
intRepresentation = Int(red * 255) << 16 | Int(green * 255) << 8 | Int(blue * 255)
} else {
intRepresentation = Int(red * 255) << 24 | Int(green * 255) << 16 | Int(blue * 255) << 8 | Int(alpha * 255)
}
return String(intRepresentation, radix: 16)
}
}

View File

@ -20,15 +20,13 @@
// THE SOFTWARE.
//
import Foundation
import UIKit
public protocol ConfigurableController: InitializableView {
public extension UIDevice {
associatedtype ViewModelT
var viewModel: ViewModelT { get }
func configureBarButtons()
func initialLoadView()
/// Returns true if the current device is simulator
public static var isSimulator: Bool {
return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}
}

View File

@ -0,0 +1,169 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension UIImage {
/**
method which creates new UIImage instance filled by given color
- parameter color: color to fill
- parameter size: size of new image
- returns: new instanse of UIImage with given size and color
*/
public convenience init?(color: UIColor, size: CGSize) {
let cgImage = CGImage.create(color: color.cgColor,
width: Int(ceil(size.width)),
height: Int(ceil(size.height)))
guard let image = cgImage else {
return nil
}
self.init(cgImage: image)
}
/**
creates an image from a UIView.
- parameter fromView: The source view.
- returns A new image or nil if something goes wrong.
*/
public convenience init?(fromView view: UIView) {
guard let cgImage = CGImage.create(fromView: view) else {
return nil
}
self.init(cgImage: cgImage)
}
/**
method which render current template CGImage into new image using given color
- parameter withColor: color which used to fill template image
- returns: new CGImage rendered with given color or nil if something goes wrong
*/
public func renderTemplate(withColor color: UIColor) -> UIImage? {
return cgImage?.renderTemplate(withColor: color.cgColor)?.uiImage
}
/**
creates a new image with rounded corners and border.
- parameter cornerRadius: The corner radius.
- parameter border: The size of the border.
- parameter color: The color of the border.
- parameter extendSize: Extend result image size and don't overlap source image by border.
- returns: A new image
*/
public func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> UIImage? {
let rounded = cgImage?.round(withRadius: cornerRadius)
return rounded?.applyBorder(width: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)?.uiImage
}
/**
creates a new circle image.
- returns: A new image
*/
public func roundCornersToCircle() -> UIImage? {
return cgImage?.round(withRadius: CGFloat(min(size.width, size.height) / 2))?.uiImage
}
/**
creates a new circle image with a border.
- parameter border: CGFloat The size of the border.
- parameter color: UIColor The color of the border.
- parameter extendSize: Extend result image size and don't overlap source image by border.
- returns: UIImage?
*/
public func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> UIImage? {
let radius = CGFloat(min(size.width, size.height) / 2)
let rounded = cgImage?.round(withRadius: radius)
return rounded?.applyBorder(width: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)?.uiImage
}
/**
creates a resized copy of an image.
- parameter newSize: the new size of the image.
- parameter contentMode: the way to handle the content in the new size.
- returns: a new image
*/
public func resize(newSize: CGSize, contentMode: ImageContentMode = .scaleToFill) -> UIImage? {
return cgImage?.resize(newSize: newSize, contentMode: contentMode)?.uiImage
}
/**
creates a cropped copy of an image.
- parameter to: The bounds of the rectangle inside the image.
- returns: A new image
*/
public func crop(to bounds: CGRect) -> UIImage? {
return cgImage?.cropping(to: bounds)?.uiImage
}
/**
crop image to square from center
- returns: cropped image
*/
public func cropFromCenterToSquare() -> UIImage? {
return cgImage?.cropFromCenterToSquare()?.uiImage
}
}
public extension CGImage {
public var uiImage: UIImage {
return UIImage(cgImage: self)
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UIStoryboard {
/**
method for instantiating new UIViewController subclass from storyboard using storyboard identifier
provided by StoryboardIdentifierProtocol protocol implementation
- returns: UIViewController subclass instance
*/
public func instantiateViewController<T>() -> T where T: UIViewController, T: StoryboardIdentifierProtocol {
return self.instantiateViewController(withIdentifier: T.storyboardIdentifier) as! T
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UIView: StaticNibNameProtocol {
/**
default implementation of StaticNibNameProtocol
- returns: class name string
*/
open class var nibName: String {
return className(of: self)
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UIView: ReuseIdentifierProtocol {
/**
default implementation of ReuseIdentifierProtocol
- returns: type name string
*/
open class var reuseIdentifier: String {
return className(of: self)
}
}

View File

@ -0,0 +1,66 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension UINib {
convenience public init(nibName name: String) {
self.init(nibName: name, bundle: nil)
}
}
public extension UIView {
/**
method which return UIView subclass instance loaded from nib using nib name provided by NibNameProtocol implementation
- parameter interfaceIdiom: UIUserInterfaceIdiom value for passing into NibNameProtocol
- returns: UIView subclass instance
*/
public static func loadFromNib<T>
(forUserInterfaceIdiom interfaceIdiom: UIUserInterfaceIdiom) -> T where T: NibNameProtocol, T: UIView {
return loadFromNib(named: T.nibName(forConfiguration: interfaceIdiom))
}
/**
method which return UIView subclass instance loaded from nib using nib name
provided by StaticNibNameProtocol implementation
- returns: UIView subclass instance
*/
public static func loadFromNib<T>() -> T where T: StaticNibNameProtocol, T: UIView {
return loadFromNib(named: T.nibName)
}
/**
method which loads UIView (or subclass) instance from nib using given nib name parameter
- parameter nibName: nib name
- returns: UIView subclass instance
*/
public static func loadFromNib<T>(named nibName: String) -> T {
return UINib(nibName: nibName).instantiate(withOwner: nil, options: nil).first as! T
}
}

View File

@ -20,9 +20,11 @@
// THE SOFTWARE.
//
import UIKit
import Foundation
public extension UIView {
extension UIView {
private static let rotationKeyPath = "transform.rotation.z"
/**
Starts rotating the view around Z axis.
@ -31,15 +33,19 @@ public extension UIView {
- parameter repeatCount: How many times the spin should be done. If not provided, the view will spin forever.
- parameter clockwise: Direction of the rotation. Default is clockwise (true).
*/
func startZRotation(duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true) {
let animation = CABasicAnimation.zRotationAnimationWith(duration: duration,
repeatCount: repeatCount,
clockwise: clockwise)
layer.add(animation, forKey: CABasicAnimation.rotationKeyPath)
public func startZRotation(duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true) {
let animation = CABasicAnimation(keyPath: UIView.rotationKeyPath)
let direction = clockwise ? 1.0 : -1.0
animation.toValue = NSNumber(value: M_PI * 2 * direction)
animation.duration = duration
animation.isCumulative = true
animation.repeatCount = repeatCount
layer.add(animation, forKey: UIView.rotationKeyPath)
}
/// Stop rotating the view around Z axis.
func stopZRotation() {
layer.removeAnimation(forKey: CABasicAnimation.rotationKeyPath)
public func stopZRotation() {
layer.removeAnimation(forKey: UIView.rotationKeyPath)
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UIViewController: StoryboardIdentifierProtocol {
/**
default implementation of StoryboardIdentifierProtocol
- returns: type name string
*/
open class var storyboardIdentifier: String {
return className(of: self)
}
}

View File

@ -0,0 +1,249 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import ObjectMapper
import RxSwift
/// A type representing an possible errors that can be thrown during fetching
/// model or array of specified type from UserDefaults.
///
/// - noSuchValue: there is no such value for given key
/// - unableToMap: the value cannot be mapped to given type for some reason
public enum UserDefaultsError: Error {
case noSuchValue(key: String)
case unableToMap(mappingError: Error)
}
fileprivate typealias JSONObject = [String: Any]
public extension UserDefaults {
fileprivate func storedValue<ST>(forKey key: String) throws -> ST {
guard let objectForKey = object(forKey: key) else {
throw UserDefaultsError.noSuchValue(key: key)
}
return try cast(objectForKey) as ST
}
/// Returns the object with specified type associated with the first occurrence of the specified default.
///
/// - parameter key: A key in the current user's defaults database.
///
/// - throws: One of cases in UserDefaultsError
///
/// - returns: The object with specified type associated with the specified key,
/// or throw exception if the key was not found.
public func object<T>(forKey key: String) throws -> T where T: ImmutableMappable {
let jsonObject = try storedValue(forKey: key) as JSONObject
do {
return try T(JSON: jsonObject)
} catch {
throw UserDefaultsError.unableToMap(mappingError: error)
}
}
/// Returns the array of objects with specified type associated with the first occurrence of the specified default.
///
/// - parameter key: A key in the current user's defaults database.
///
/// - throws: One of cases in UserDefaultsError
///
/// - returns: The array of objects with specified type associated with the specified key,
/// or throw exception if the key was not found.
public func object<T>(forKey key: String) throws -> [T] where T: ImmutableMappable {
let jsonArray = try storedValue(forKey: key) as [JSONObject]
do {
return try jsonArray.map { try T(JSON: $0) }
} catch {
throw UserDefaultsError.unableToMap(mappingError: error)
}
}
/// Returns the object with specified type associated with the first occurrence of the specified default.
///
/// - parameter key: A key in the current user's defaults database.
/// - parameter defaultValue: A default value which will be used if there is no such value for specified key,
/// or if error occurred during mapping
///
/// - returns: The object with specified type associated with the specified key, or passed default value
/// if there is no such value for specified key or if error occurred during mapping.
public func object<T>(forKey key: String, defaultValue: T) -> T where T: ImmutableMappable {
return (try? object(forKey: key)) ?? defaultValue
}
/// Returns the array of objects with specified type associated with the first occurrence of the specified default.
///
/// - parameter key: A key in the current user's defaults database.
/// - parameter defaultValue: A default value which will be used if there is no such value for specified key,
/// or if error occurred during mapping
///
/// - returns: The array of objects with specified type associated with the specified key, or passed default value
/// if there is no such value for specified key or if error occurred during mapping.
public func object<T>(forKey key: String, defaultValue: [T]) -> [T] where T: ImmutableMappable {
return (try? object(forKey: key)) ?? defaultValue
}
/// Sets or removes the value of the specified default key in the standard application domain.
///
/// - Parameters:
/// - model: The object with specified type to store or nil to remove it from the defaults database.
/// - key: The key with which to associate with the value.
public func set<T>(_ model: T?, forKey key: String) where T: ImmutableMappable {
if let model = model {
set(model.toJSON(), forKey: key)
} else {
set(nil, forKey: key)
}
}
/// Sets or removes the value of the specified default key in the standard application domain.
///
/// - Parameters:
/// - models: The array of object with specified type to store or nil to remove it from the defaults database.
/// - key: The key with which to associate with the value.
public func set<T, S>(_ models: S?, forKey key: String) where T: ImmutableMappable, S: Sequence, S.Iterator.Element == T {
if let models = models {
set(models.map { $0.toJSON() }, forKey: key)
} else {
set(nil, forKey: key)
}
}
}
public extension Reactive where Base: UserDefaults {
/// Reactive version of object<T>(forKey:) -> T.
///
/// - parameter key: A key in the current user's defaults database.
///
/// - returns: Observable of specified model type.
func object<T>(forKey key: String) -> Observable<T> where T: ImmutableMappable {
return Observable.create { observer in
do {
observer.onNext(try self.base.object(forKey: key))
observer.onCompleted()
} catch {
observer.onError(error)
}
return Disposables.create()
}
}
/// Reactive version of object<T>(forKey:defaultValue:) -> T.
///
/// Will never call onError(:) on observer.
///
/// - parameter key: A key in the current user's defaults database.
/// - parameter defaultValue: A default value which will be used if there is no such value for specified key,
/// or if error occurred during mapping
///
/// - returns: Observable of specified model type.
func object<T>(forKey key: String, defaultValue: T) -> Observable<T> where T: ImmutableMappable {
return Observable.create { observer in
observer.onNext(self.base.object(forKey: key, defaultValue: defaultValue))
observer.onCompleted()
return Disposables.create()
}
}
/// Reactive version of object<T>(forKey:) -> [T].
///
/// - parameter key: A key in the current user's defaults database.
///
/// - returns: Observable of specified array type.
func object<T>(forKey key: String) -> Observable<[T]> where T: ImmutableMappable {
return Observable.create { observer in
do {
observer.onNext(try self.base.object(forKey: key))
observer.onCompleted()
} catch {
observer.onError(error)
}
return Disposables.create()
}
}
/// Reactive version of object<T>(forKey:defaultValue:) -> [T].
///
/// Will never call onError(:) on observer.
///
/// - parameter key: A key in the current user's defaults database.
/// - parameter defaultValue: A default value which will be used if there is no such value for specified key,
/// or if error occurred during mapping
///
/// - returns: Observable of specified array type.
func object<T>(forKey key: String, defaultValue: [T]) -> Observable<[T]> where T: ImmutableMappable {
return Observable.create { observer in
observer.onNext(self.base.object(forKey: key, defaultValue: defaultValue))
observer.onCompleted()
return Disposables.create()
}
}
/// Reactive version of set<T>(_:forKey:).
///
/// Will never call onError(:) on observer.
///
/// - parameter model: The object with specified type to store in the defaults database.
/// - parameter key: The key with which to associate with the value.
///
/// - returns: Observable of Void type.
func set<T>(_ model: T?, forKey key: String) -> Observable<Void> where T: ImmutableMappable {
return Observable.create { observer in
observer.onNext(self.base.set(model, forKey: key))
observer.onCompleted()
return Disposables.create()
}
}
/// Reactive version of set<T, S>(_:forKey:).
///
/// Will never call onError(:) on observer.
///
/// - parameter models: The array of object with specified type to store in the defaults database.
/// - parameter key: The key with which to associate with the value.
///
/// - returns: Observable of Void type.
func set<T, S>(_ models: S?, forKey key: String) -> Observable<Void>
where T: ImmutableMappable, S: Sequence, S.Iterator.Element == T {
return Observable.create { observer in
observer.onNext(self.base.set(models, forKey: key))
observer.onCompleted()
return Disposables.create()
}
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Function which returns string representation of type without ".Type" suffix
///
/// - Parameter type: a type
/// - Returns: string representation of type without ".Type" suffix
public func className<T>(of type: T) -> String {
let clsName = String(describing: type(of: type))
if let typeRange = clsName.range(of: ".Type") {
return clsName.substring(to: typeRange.lowerBound)
} else {
return clsName
}
}

View File

@ -15,7 +15,9 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>0.3.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@ -22,6 +22,6 @@
import Foundation
public protocol BaseViewModel: AnyObject {
public protocol BaseViewModel {
// Nothing
}

View File

@ -21,20 +21,56 @@
//
import Foundation
import UIKit
public extension Comparable {
public protocol ConfigurableController {
/// Use this function to restrict comparable with lower and upper values
/// - parameter bounds: Lower and uppper bounds tuple
/// - returns: Current value if it fits range, otherwise lower if value is too small or upper if value is too big
func `in`(bounds: (lower: Self, upper: Self)) -> Self {
min(max(bounds.lower, self), bounds.upper)
}
associatedtype ViewModelT
var viewModel: ViewModelT { get }
func bindViews()
func addViews()
func setAppearance()
func configureBarButtons()
func localize()
func initialLoadView()
/// Use this function to restrict comparable with lower and upper values
/// - parameter range: ClosedRange representing bounds
/// - returns: Current value if it fits range, otherwise lower if value is too small or upper if value is too big
func `in`(range: ClosedRange<Self>) -> Self {
`in`(bounds: (lower: range.lowerBound, upper: range.upperBound))
}
}
extension ConfigurableController where Self: UIViewController {
public func bindViews() {
// nothing
}
public func addViews() {
// nothing
}
public func setAppearance() {
// nothing
}
public func configureBarButtons() {
// nothing
}
public func localize() {
// nothing
}
public func initialLoadView() {
addViews()
setAppearance()
configureBarButtons()
localize()
bindViews()
}
}

View File

@ -27,6 +27,8 @@ public protocol CursorType {
associatedtype Element
associatedtype LoadResultType
/// Indicates that cursor load all available results
var exhausted: Bool { get }
@ -38,5 +40,6 @@ public protocol CursorType {
/// Loads next batch of results
///
/// - Returns: Observable of LoadResultType
func loadNextBatch() -> Single<[Element]>
func loadNextBatch() -> Observable<LoadResultType>
}

View File

@ -22,16 +22,19 @@
import Foundation
public extension Double {
/**
* protocol which ensures that specific type can return estimated height of view for view model
*/
public protocol EstimatedViewHeightProtocol {
associatedtype ViewModelType
/**
method which returns estimated view height for specific view model
- parameter viewModel: object which represents view model of view
- returns: estimatedViewHeight view height
*/
static func estimatedViewHeight(forViewModel viewModel: ViewModelType) -> CGFloat
/// Rounding of double value
///
/// - Parameters:
/// - precision: significant digits after decimal point
/// - roundingMode: mode of rounding
/// - Returns: rounded value
func roundValue(precision: UInt, roundingMode: NSDecimalNumber.RoundingMode) -> Double {
// Using decimal here because of precision problem
return Decimal(self).roundValue(precision: precision, roundingMode: roundingMode).doubleValue
}
}

View File

@ -20,11 +20,9 @@
// THE SOFTWARE.
//
import UIKit
public protocol ModuleConfigurator {
extension UIView: XibNameProtocol {
associatedtype ViewControllerT
open class var xibName: String {
typeName(of: self)
}
func configure(input: ViewControllerT)
}

View File

@ -0,0 +1,55 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/**
* protocol which ensures that specific type can return nib name of view for specific configuration
*/
public protocol AbstractNibNameProtocol {
associatedtype ConfigurationType
/**
static method which returns nib name for specific configuration
- parameter configuration: object which represents configuration
- returns: nib name string
*/
static func nibName(forConfiguration configuration: ConfigurationType) -> String
}
/**
* protocol which ensures that specific type can return nib name of view
for specified UserInterfaceIdiom (iPhone, iPad, AppleTV)
*/
public protocol NibNameProtocol: AbstractNibNameProtocol {
/**
static method which returns nib name for specific UIUserInterfaceIdiom value
- parameter configuration: object which represents configuration in terms of user interface idiom
- returns: nib name string
*/
static func nibName(forConfiguration configuration: UIUserInterfaceIdiom) -> String
}

View File

@ -20,10 +20,14 @@
// THE SOFTWARE.
//
import ObjectMapper
import RxSwift
/// Protocol for concurrent model mapping
public protocol ObservableMappable where Self: Decodable {
public protocol ObservableMappable {
associatedtype ModelType
static func createFrom(map: Map) -> Observable<ModelType>
static func create(from jsonObject: Any, with decoder: JSONDecoder) -> Observable<Self>
}

View File

@ -27,7 +27,7 @@ import Foundation
*/
public protocol AbstractReuseIdentifierProtocol {
associatedtype IdentifierType
/**
- returns: reuse identifier with protocol associated type
*/

View File

@ -22,10 +22,14 @@
import Foundation
/// Function which returns string representation of class type
///
/// - Parameter type: an class type
/// - Returns: string representation of class type
public func typeName<T>(of type: T.Type) -> String {
String(describing: type)
/**
* protocol which ensures that specific type can return estimated height of view
*/
public protocol StaticEstimatedViewHeightProtocol {
/**
method which return estimated view height
- returns: estimated view height
*/
static func estimatedViewHeight() -> CGFloat
}

View File

@ -22,9 +22,12 @@
import Foundation
/// Protocol that ensures that specific type can return it's xib name
public protocol XibNameProtocol {
/// Name of related xib
static var xibName: String { get }
/**
* protocol which ensures that specific type can return nib name of view
*/
public protocol StaticNibNameProtocol {
/**
- returns: nib name string
*/
static var nibName: String { get }
}

View File

@ -20,7 +20,7 @@
// THE SOFTWARE.
//
import CoreGraphics
import Foundation
/**
* protocol which ensures that specific type can return height of view

View File

@ -22,17 +22,14 @@
import Foundation
public extension CursorType {
subscript(range: CountableRange<Int>) -> [Self.Element] {
range.map { self[$0] }
}
subscript(range: CountableClosedRange<Int>) -> [Self.Element] {
range.map { self[$0] }
}
var loadedElements: [Self.Element] {
self[0..<count]
}
/**
* protocol which ensures that specific type can return storyboard identifier of view controller
*/
public protocol StoryboardIdentifierProtocol {
/**
method which returns storyboard identifier of view controller
- returns: storyboard identifier string
*/
static var storyboardIdentifier: String { get }
}

View File

@ -0,0 +1,64 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
/**
* protocol which helps us organize storyboards and view controllers creation
*/
public protocol StoryboardProtocol {
associatedtype StoryboardIdentifier
associatedtype ViewControllerIdentifier
/**
- returns: storyboard identifier with associatedtype type
*/
static var storyboardIdentifier: StoryboardIdentifier { get }
/**
- returns: bundle for storyboard initialization
*/
static var bundle: Bundle? { get }
/**
method which instantiate UIViewControlle instance for specific view controller identifier
- parameter _: object which represents view controller identifier
- returns: UIViewController instance
*/
static func instantiateViewController(_: ViewControllerIdentifier) -> UIViewController
}
public extension StoryboardProtocol {
public static func instantiate<T: UIViewController>(_ identificator: Self.ViewControllerIdentifier) -> T {
guard let controller = instantiateViewController(identificator) as? T else {
assertionFailure("\(T.self) not created")
return T()
}
return controller
}
}

View File

@ -20,14 +20,14 @@
// THE SOFTWARE.
//
import CoreGraphics
import Foundation
/**
* protocol which ensures that specific type can return height of view for view model
*/
public protocol ViewHeightProtocol {
associatedtype ViewModelType
/**
method which returns view height for specific view model
@ -35,5 +35,5 @@ public protocol ViewHeightProtocol {
- returns: view height
*/
static func viewHeight(for viewModel: ViewModelType) -> CGFloat
static func viewHeight(forViewModel viewModel: ViewModelType) -> CGFloat
}

View File

@ -23,17 +23,17 @@
import Foundation
/**
* Protocol that ensures that specific type can can apply new view state with view model
* protocol which ensures that specific type can create view model and can apply new view state with view model
*/
public protocol ConfigurableView {
public protocol AbstractViewModelProtocol {
associatedtype ViewModelType
/**
Applies new view state with view model object
method which applies new view state with view model object
- parameter viewModel: view model to apply new view state
- returns: nothing
*/
func configure(with _: ViewModelType)
func setViewModel(_ viewModel: ViewModelType)
}

View File

@ -20,21 +20,30 @@
// THE SOFTWARE.
//
import UIKit
import Alamofire
public extension UIViewController {
/**
* Struct which keeps base parameters required for api request
*/
public struct ApiRequestParameters {
/// Return top visible controller even if we have inner UI(Navigation/TabBar)Controller's inside
var topVisibleViewController: UIViewController {
switch self {
case let navController as UINavigationController:
return navController.visibleViewController?.topVisibleViewController ?? navController
let method: HTTPMethod
let url: URLConvertible
let parameters: Parameters?
let encoding: ParameterEncoding
let headers: HTTPHeaders?
case let tabController as UITabBarController:
return tabController.selectedViewController?.topVisibleViewController ?? tabController
public init(url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil) {
default:
return self.presentedViewController?.topVisibleViewController ?? self
}
self.method = method
self.url = url
self.parameters = parameters
self.encoding = encoding
self.headers = headers
}
}

View File

@ -16,6 +16,8 @@
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>

View File

@ -0,0 +1,35 @@
//
// LeadKitTests.swift
// LeadKitTests
//
// Created by Иван Смолин on 30/01/16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import XCTest
class LeadKitTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

107
Makefile
View File

@ -1,107 +0,0 @@
export SRCROOT := $(shell pwd)
push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target TIApplication.target
$(call clean)
TISwiftUtils.target:
MODULE_NAME="TISwiftUtils" ./project-scripts/push_to_podspecs.sh
touch TISwiftUtils.target
TIFoundationUtils.target: TISwiftUtils.target TILogging.target
MODULE_NAME="TIFoundationUtils" ./project-scripts/push_to_podspecs.sh
touch TIFoundationUtils.target
TICoreGraphicsUtils.target:
MODULE_NAME="TICoreGraphicsUtils" ./project-scripts/push_to_podspecs.sh
touch TICoreGraphicsUtils.target
TIKeychainUtils.target: TIFoundationUtils.target
MODULE_NAME="TIKeychainUtils" ./project-scripts/push_to_podspecs.sh
touch TIKeychainUtils.target
TIUIKitCore.target: TISwiftUtils.target
MODULE_NAME="TIUIKitCore" ./project-scripts/push_to_podspecs.sh
touch TIUIKitCore.target
TIUIElements.target: TIUIKitCore.target TILogging.target
MODULE_NAME="TIUIElements" ./project-scripts/push_to_podspecs.sh
touch TIUIElements.target
TIWebView.target: TIUIKitCore.target
MODULE_NAME="TIWebView" ./project-scripts/push_to_podspecs.sh
touch TIWebView.target
TIBottomSheet.target: TIUIElements.target
MODULE_NAME="TIBottomSheet" ./project-scripts/push_to_podspecs.sh
touch TIBottomSheet.target
TISwiftUICore.target: TIUIKitCore.target
MODULE_NAME="TISwiftUICore" ./project-scripts/push_to_podspecs.sh
touch TISwiftUICore.target
TITableKitUtils.target: TIUIElements.target
MODULE_NAME="TITableKitUtils" ./project-scripts/push_to_podspecs.sh
touch TITableKitUtils.target
TIDeeplink.target: TIFoundationUtils.target
MODULE_NAME="TIDeeplink" ./project-scripts/push_to_podspecs.sh
touch TIDeeplink.target
TIDeveloperUtils.target: TIUIElements.target
MODULE_NAME="TIDeveloperUtils" ./project-scripts/push_to_podspecs.sh
touch TIDeveloperUtils.target
TINetworking.target: TIFoundationUtils.target
MODULE_NAME="TINetworking" ./project-scripts/push_to_podspecs.sh
touch TINetworking.target
TILogging.target:
MODULE_NAME="TILogging" ./project-scripts/push_to_podspecs.sh
touch TILogging.target
TIMoyaNetworking.target: TINetworking.target
MODULE_NAME="TIMoyaNetworking" ./project-scripts/push_to_podspecs.sh
touch TIMoyaNetworking.target
TINetworkingCache.target: TINetworking.target
MODULE_NAME="TINetworkingCache" ./project-scripts/push_to_podspecs.sh
touch TINetworkingCache.target
TIMapUtils.target: TILogging TICoreGraphicsUtils.target
MODULE_NAME="TIMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIMapUtils.target
TIAppleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIAppleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIAppleMapUtils.target
TIGoogleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIGoogleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIGoogleMapUtils.target
TIYandexMapUtils.target: TIMapUtils.target
MODULE_NAME="TIYandexMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIYandexMapUtils.target
TIPagination.target: TISwiftUtils.target
MODULE_NAME="TIPagination" ./project-scripts/push_to_podspecs.sh
touch TIPagination.target
TIAuth.target: TIUIKitCore.target TIKeychainUtils.target
MODULE_NAME="TIAuth" ./project-scripts/push_to_podspecs.sh
touch TIAuth.target
TIEcommerce.target: TINetworking.target TIUIElements.target
MODULE_NAME="TIEcommerce" ./project-scripts/push_to_podspecs.sh
touch TIEcommerce.target
TITextProcessing.target:
MODULE_NAME="TITextProcessing" ./project-scripts/push_to_podspecs.sh
touch TITextProcessing.target
TIApplication.target: TIFoundationUtils.target TILogging.target
MODULE_NAME="TIApplication" ./project-scripts/push_to_podspecs.sh
touch TIApplication.target
clean:
rm *.target

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -1,151 +0,0 @@
# OTPSwiftView
![Platform](https://img.shields.io/badge/platform-iOS-green)
A fully customizable OTP view.
<p align="left">
<img src="Assets/preview.gif" width=300 height=533>
</p>
# Usage
```swift
class ViewController: UIViewController {
let otpView = CustomOTPSwiftView() // Custom OTP view
let config = OTPCodeConfig(codeSymbolsCount: 6, // Base configuration of OTP view
spacing: 6,
customSpacing: [2: 20])
override func viewDidLoad() {
super.viewDidLoad()
/*
Add your codeView and set layout
*/
/* Configure OTP view */
otpView.configure(with: config)
/* Bind events */
otpView.onTextEnter = { code in
// Get code from codeView
}
/* Update text */
otpView.code = "234435"
/* Update focus */
otpView.beginFirstResponder() // show keyboard
otpView.resignFirstResponder() // hide keyboard
}
}
```
# Customization
## Single OTP View
*OTPView* is a base class that describes a single OTP textfield.
To customize the appearance and layout, you must inherit from the OTPView.
*Don't forget to add UIGestureRecognizer to call closure `onTap?()`. Use UITapGestureRecognizer to avoid bugs.*
```swift
import OTPSwiftView
class CustomOTPView: OTPView {
override func addViews() {
super.addViews()
// Adding additional views to current view. The OTP textfield has already been added.
}
override func configureLayout() {
super.configureLayout()
// Confgiure layout of subviews
}
override func bindViews() {
super.bindViews()
// Binding to data or user actions
let gesture = UITapGestureRecognizer(target: self, action: #selector(onTapAction))
addGestureRecognizer(gesture)
}
private func onTapAction() {
onTap?()
}
override func configureAppearance() {
super.configureAppearance()
// Appearance configuration method
}
}
```
*If needed to set validation for input use `validationClosure: ValidationClosure<String>?`*. For example, only numbers validation:
```swift
import OTPSwiftView
class CustomOTPView: OTPView {
override func bindViews() {
super.bindViews()
codeTextField.validationClosure = { input in
input.allSatisfy { $0.isNumber }
}
}
}
```
## OTPSwiftView
*OTPSwiftView* is a base class that is responsible for the layout of single OTP views.
As with OTPView, you should create an heir class to configure your full OTP view.
```swift
import OTPSwiftView
final class CustomOTPSwiftView: OTPSwiftView<CustomOTPView> {
override func addViews() {
super.addViews()
// Adding additional views to current code view. The single OTP views has already been added.
}
override func configureLayout() {
super.configureLayout()
// Confgiure layout of subviews
}
override func bindViews() {
super.bindViews()
// Binding to data or user actions
}
override func configureAppearance() {
super.configureAppearance()
// Appearance configuration method
}
override func configure(with config: OTPCodeConfig) {
super.configure(with: config)
// Configure you code view with configuration
}
}
```
# Installation via SPM
You can install this framework as a target of LeadKit.

View File

@ -1,38 +0,0 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
/// Base configuration for OTPSwiftView
open class OTPCodeConfig {
public typealias Spacing = [Int: CGFloat]
public let codeSymbolsCount: Int
public let spacing: CGFloat
public let customSpacing: Spacing?
public init(codeSymbolsCount: Int, spacing: CGFloat, customSpacing: Spacing?) {
self.codeSymbolsCount = codeSymbolsCount
self.spacing = spacing
self.customSpacing = customSpacing
}
}

View File

@ -1,149 +0,0 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import TIUIElements
import TISwiftUtils
/// Base full OTP View for entering the verification code
open class OTPSwiftView<View: OTPView>: BaseInitializableControl {
private var emptyOTPView: View? {
textFieldsCollection.first { $0.codeTextField.text.orEmpty.isEmpty } ?? textFieldsCollection.last
}
public private(set) var codeStackView = UIStackView()
public private(set) var textFieldsCollection: [View] = []
public var onTextEnter: ParameterClosure<String>?
public var code: String {
get {
textFieldsCollection.compactMap { $0.codeTextField.text }.joined()
}
set {
textFieldsCollection.first?.codeTextField.set(inputText: newValue)
}
}
public override var isFirstResponder: Bool {
!textFieldsCollection.allSatisfy { !$0.codeTextField.isFirstResponder }
}
open override func addViews() {
super.addViews()
addSubview(codeStackView)
}
open override func configureAppearance() {
super.configureAppearance()
codeStackView.contentMode = .center
codeStackView.distribution = .fillEqually
}
open func configure(with config: OTPCodeConfig) {
textFieldsCollection = createTextFields(numberOfFields: config.codeSymbolsCount)
codeStackView.addArrangedSubviews(textFieldsCollection)
codeStackView.spacing = config.spacing
configure(customSpacing: config.customSpacing, for: codeStackView)
bindTextFields(with: config)
}
@discardableResult
open override func becomeFirstResponder() -> Bool {
guard let emptyOTPView = emptyOTPView, !emptyOTPView.isFirstResponder else {
return false
}
return emptyOTPView.codeTextField.becomeFirstResponder()
}
@discardableResult
open override func resignFirstResponder() -> Bool {
guard let emptyOTPView = emptyOTPView, emptyOTPView.isFirstResponder else {
return false
}
return emptyOTPView.codeTextField.resignFirstResponder()
}
}
// MARK: - Configure textfields
private extension OTPSwiftView {
func configure(customSpacing: OTPCodeConfig.Spacing?, for stackView: UIStackView) {
guard let customSpacing = customSpacing else {
return
}
customSpacing.forEach { viewIndex, spacing in
guard viewIndex < stackView.arrangedSubviews.count, viewIndex >= .zero else {
return
}
self.set(spacing: spacing,
after: stackView.arrangedSubviews[viewIndex],
for: stackView)
}
}
func set(spacing: CGFloat, after view: UIView, for stackView: UIStackView) {
stackView.setCustomSpacing(spacing, after: view)
}
func createTextFields(numberOfFields: Int) -> [View] {
var textFieldsCollection: [View] = []
(.zero..<numberOfFields).forEach { _ in
let textField = View()
textField.codeTextField.previousTextField = textFieldsCollection.last?.codeTextField
textFieldsCollection.last?.codeTextField.nextTextField = textField.codeTextField
textFieldsCollection.append(textField)
}
return textFieldsCollection
}
func bindTextFields(with config: OTPCodeConfig) {
let onTextChangedSignal: VoidClosure = { [weak self] in
guard let code = self?.code else {
return
}
let correctedCode = code.prefix(config.codeSymbolsCount).string
self?.onTextEnter?(correctedCode)
}
let onTap: VoidClosure = { [weak self] in
self?.becomeFirstResponder()
}
textFieldsCollection.forEach {
$0.codeTextField.onTextChangedSignal = onTextChangedSignal
$0.onTap = onTap
}
}
}

View File

@ -1,131 +0,0 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import TISwiftUtils
/// Base one symbol textfield
open class OTPTextField: UITextField {
private let maxSymbolsCount = 1
public weak var previousTextField: OTPTextField?
public weak var nextTextField: OTPTextField?
public var onTextChangedSignal: VoidClosure?
public var validationClosure: Closure<String, Bool>?
public var caretHeight: CGFloat?
public var lastNotEmpty: OTPTextField {
let isLastNotEmpty = !text.orEmpty.isEmpty && nextTextField?.text.orEmpty.isEmpty ?? true
return isLastNotEmpty ? self : nextTextField?.lastNotEmpty ?? self
}
open override var font: UIFont? {
didSet {
if caretHeight == nil, let font = font {
caretHeight = font.pointSize - font.descender
}
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
}
@available(*, unavailable)
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open override func deleteBackward() {
guard text.orEmpty.isEmpty else {
return
}
onTextChangedSignal?()
previousTextField?.text = ""
previousTextField?.becomeFirstResponder()
}
public func set(inputText: String) {
text = inputText.prefix(maxSymbolsCount).string
let nextInputText = inputText.count >= maxSymbolsCount
? inputText.suffix(inputText.count - maxSymbolsCount).string
: ""
nextTextField?.set(inputText: nextInputText)
}
open override func caretRect(for position: UITextPosition) -> CGRect {
guard let caretHeight = caretHeight else {
return super.caretRect(for: position)
}
var superRect = super.caretRect(for: position)
superRect.size.height = caretHeight
return superRect
}
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self && isFirstResponder ? view : nil
}
}
extension OTPTextField: UITextFieldDelegate {
public func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
guard let textField = textField as? OTPTextField else {
return true
}
let isInputEmpty = textField.text.orEmpty.isEmpty && string.isEmpty
guard isInputEmpty || validationClosure?(string) ?? true else {
return false
}
switch range.length {
case 0: // set text to textfield
textField.set(inputText: string)
let currentTextField = textField.lastNotEmpty.nextTextField ?? textField.lastNotEmpty
currentTextField.becomeFirstResponder()
textField.onTextChangedSignal?()
return false
case 1: // remove character from textfield
textField.text = ""
textField.onTextChangedSignal?()
return false
default:
return true
}
}
}

View File

@ -1,37 +0,0 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TISwiftUtils
/// Base OTP view with textfield for entering a one symbol
open class OTPView: BaseInitializableView {
public let codeTextField = OTPTextField()
public var onTap: VoidClosure?
open override func addViews() {
super.addViews()
addSubview(codeTextField)
}
}

View File

@ -1,95 +0,0 @@
{
"pins" : [
{
"identity" : "alamofire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Alamofire/Alamofire.git",
"state" : {
"revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
"version" : "5.7.1"
}
},
{
"identity" : "antlr4",
"kind" : "remoteSourceControl",
"location" : "https://github.com/antlr/antlr4",
"state" : {
"revision" : "44d87bc1d130c88aa452894aa5f7e2f710f68253",
"version" : "4.10.1"
}
},
{
"identity" : "cache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"revision" : "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version" : "6.0.0"
}
},
{
"identity" : "cursors",
"kind" : "remoteSourceControl",
"location" : "https://github.com/petropavel13/Cursors",
"state" : {
"revision" : "52f27b82cb1cbbc2b5fd09514c48b9c75e3b0300",
"version" : "0.6.0"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "moya",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Moya/Moya.git",
"state" : {
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
"version" : "15.0.3"
}
},
{
"identity" : "panmodal",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/PanModal",
"state" : {
"revision" : "ced7c1703f90746df0224b6e0d33c146d9ae4284",
"version" : "1.3.1"
}
},
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version" : "6.7.0"
}
},
{
"identity" : "rxswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
"version" : "6.6.0"
}
},
{
"identity" : "tablekit",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/TableKit.git",
"state" : {
"revision" : "fec9537745799fab55df7477cb3ec2b4ea5c254d",
"version" : "2.12.0"
}
}
],
"version" : 2
}

View File

@ -1,198 +0,0 @@
// swift-tools-version:5.7
#if canImport(PackageDescription)
import PackageDescription
let package = Package(
name: "LeadKit",
platforms: [
.iOS(.v12)
],
products: [
// MARK: - Application
.library(name: "TIApplication", targets: ["TIApplication"]),
// MARK: - UIKit
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
.library(name: "TIUIElements", targets: ["TIUIElements"]),
.library(name: "TIWebView", targets: ["TIWebView"]),
.library(name: "TIBottomSheet", targets: ["TIBottomSheet"]),
// MARK: - SwiftUI
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
// MARK: - Utils
.library(name: "TISwiftUtils", targets: ["TISwiftUtils"]),
.library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]),
.library(name: "TICoreGraphicsUtils", targets: ["TICoreGraphicsUtils"]),
.library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]),
.library(name: "TITableKitUtils", targets: ["TITableKitUtils"]),
.library(name: "TIDeeplink", targets: ["TIDeeplink"]),
.library(name: "TIDeveloperUtils", targets: ["TIDeveloperUtils"]),
// MARK: - Networking
.library(name: "TINetworking", targets: ["TINetworking"]),
.library(name: "TIMoyaNetworking", targets: ["TIMoyaNetworking"]),
.library(name: "TINetworkingCache", targets: ["TINetworkingCache"]),
// MARK: - Maps
.library(name: "TIMapUtils", targets: ["TIMapUtils"]),
.library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]),
// MARK: - Elements
.library(name: "OTPSwiftView", targets: ["OTPSwiftView"]),
.library(name: "TITransitions", targets: ["TITransitions"]),
.library(name: "TIPagination", targets: ["TIPagination"]),
.library(name: "TIAuth", targets: ["TIAuth"]),
.library(name: "TIEcommerce", targets: ["TIEcommerce"]),
.library(name: "TITextProcessing", targets: ["TITextProcessing"])
],
dependencies: [
.package(url: "https://git.svc.touchin.ru/TouchInstinct/TableKit.git", .upToNextMinor(from: "2.12.0")),
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")),
.package(url: "https://github.com/petropavel13/Cursors", .upToNextMajor(from: "0.5.1")),
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")),
.package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")),
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")),
.package(url: "https://github.com/antlr/antlr4", .upToNextMinor(from: "4.10.1")),
.package(url: "https://git.svc.touchin.ru/TouchInstinct/PanModal", .upToNextMinor(from: "1.3.0"))
],
targets: [
// MARK: - Application architecture
.target(name: "TIApplication",
dependencies: ["TILogging", "TIFoundationUtils", "KeychainAccess"],
path: "TIApplication/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - UIKit
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements",
dependencies: ["TIUIKitCore", "TILogging"],
path: "TIUIElements/Sources",
exclude: ["../TIUIElements.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
.target(name: "TIBottomSheet",
dependencies: ["PanModal", "TIUIElements", "TIUIKitCore", "TISwiftUtils"],
path: "TIBottomSheet/Sources",
exclude: ["../TIBottomSheet.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - SwiftUI
.target(name: "TISwiftUICore",
dependencies: ["TIUIKitCore", "TISwiftUtils"],
path: "TISwiftUICore/Sources"),
// MARK: - Utils
.target(name: "TISwiftUtils",
path: "TISwiftUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIFoundationUtils",
dependencies: ["TISwiftUtils", "TILogging"],
path: "TIFoundationUtils",
exclude: ["TIFoundationUtils.app"],
resources: [
.copy("PrivacyInfo.xcprivacy"),
],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TICoreGraphicsUtils",
dependencies: [],
path: "TICoreGraphicsUtils/Sources",
exclude: ["../TICoreGraphicsUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIKeychainUtils",
dependencies: ["TIFoundationUtils", "KeychainAccess"],
path: "TIKeychainUtils/Sources",
exclude: ["../TIKeychainUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"),
.target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink", exclude: ["TIDeeplink.app"]),
.target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"),
.target(name: "TILogging", path: "TILogging/Sources", plugins: ["TISwiftLintPlugin"]),
// MARK: - Networking
.target(name: "TINetworking",
dependencies: ["TIFoundationUtils", "Alamofire", "TILogging"],
path: "TINetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIMoyaNetworking",
dependencies: ["TINetworking", "Moya"],
path: "TIMoyaNetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TINetworkingCache",
dependencies: ["TINetworking", "Cache"],
path: "TINetworkingCache/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Maps
.target(name: "TIMapUtils",
dependencies: ["TILogging", "TICoreGraphicsUtils"],
path: "TIMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIAppleMapUtils",
dependencies: ["TIMapUtils"],
path: "TIAppleMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Elements
.target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"),
.target(name: "TITransitions", path: "TITransitions/Sources"),
.target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"),
.target(name: "TIAuth", dependencies: ["TIUIKitCore", "TIKeychainUtils"], path: "TIAuth/Sources"),
.target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"),
.target(name: "TITextProcessing",
dependencies: [.product(name: "Antlr4", package: "antlr4")],
path: "TITextProcessing/Sources",
exclude: ["../TITextProcessing.app"]),
.binaryTarget(name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"),
.plugin(name: "TISwiftLintPlugin",
capability: .buildTool(),
dependencies: ["SwiftLintBinary"]),
// MARK: - Tests
.testTarget(
name: "TITimerTests",
dependencies: ["TIFoundationUtils"],
path: "Tests/TITimerTests"),
.testTarget(
name: "TITextProcessingTests",
dependencies: ["TITextProcessing"],
path: "Tests/TITextProcessingTests"),
.testTarget(
name: "TIFoundationUtilsTests",
dependencies: ["TIFoundationUtils", "TISwiftUtils", "TILogging"],
path: "Tests/TIFoundationUtilsTests")
]
)
#endif

View File

@ -1,57 +0,0 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import PackagePlugin
import Foundation
@main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
let swiftlintScriptPath = context.package.directory.appending(["build-scripts", "xcode", "build_phases", "swiftlint.sh"])
let swiftlintExecutablePath = try context.tool(named: "swiftlint").path
let srcRoot = context.package.directory.string
let targetDir = target.directory.string
let relativeTargetDir = targetDir.replacingOccurrences(of: srcRoot, with: "")
let clearRelativeTargetDir = relativeTargetDir[relativeTargetDir.index(after: relativeTargetDir.startIndex)...] // trim leading /
return [
.prebuildCommand(displayName: "SwiftLint linting \(target.name)...",
executable: swiftlintScriptPath,
arguments: [
swiftlintExecutablePath,
context.package.directory.appending(subpath: "swiftlint_base.yml")
],
environment: [
"SCRIPT_DIR": swiftlintScriptPath.removingLastComponent().string,
"SRCROOT": srcRoot,
"SCRIPT_INPUT_FILE_COUNT": "1",
"SCRIPT_INPUT_FILE_0": clearRelativeTargetDir,
// "FORCE_LINT": "1", // Lint all files in target (not only modified)
// "AUTOCORRECT": "1"
],
outputFilesDirectory: context.package.directory)
]
}
}

139
README.md
View File

@ -1,139 +1,2 @@
# LeadKit
LeadKit is the iOS framework with a bunch of tools for rapid app development.
This repository contains the following frameworks:
- [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for Swift development.
- [TIFoundationUtils](TIFoundationUtils) - set of helpers for Foundation framework classes.
- [TIUIKitCore](TIUIKitCore) - core ui elements and protocols from LeadKit.
- [TISwiftUICore](TISwiftUICore) Core UI elements: protocols, views and helpers.
- [TIUIElements](TIUIElements) - bunch of of useful protocols and views.
- [OTPSwiftView](OTPSwiftView) - a fully customizable OTP view.
- [TITableKitUtils](TITableKitUtils) - set of helpers for TableKit classes.
- [TIKeychainUtils](TIKeychainUtils) - set of helpers for Keychain classes.
- [TIPagination](TIPagination) - realisation of paginating items from a data source.
- [TINetworking](TINetworking) - Swagger-frendly networking layer helpers.
- [TIMoyaNetworking](TIMoyaNetworking) - Moya + Swagger network service.
- [TIAppleMapUtils](TIAppleMapUtils) - set of helpers for map objects clustering and interacting using Apple MapKit.
- [TIGoogleMapUtils](TIGoogleMapUtils) - set of helpers for map objects clustering and interacting using Google Maps SDK.
- [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK.
- [TIAuth](TIAuth) - login, registration, confirmation and other related actions
## Playgrounds
### Create new Playground
```sh
$ cd TIModuleName
$ touch PlaygroundPodfile
$ echo "ENV[\"DEVELOPMENT_INSTALL\"] = \"true\"
target 'TIModuleName' do
platform :ios, IOS_VERSION_NUMBER
use_frameworks!
pod 'TIDependencyModuleName', :path => '../../../../TIDependencyModuleName/TIDependencyModuleName.podspec'
pod 'TIModuleName', :path => '../../../../TIModuleName/TIModuleName.podspec'
end" > PlaygroundPodfile
$ nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile
```
See example of `PlaygroundPodfile` in `TIFoundationUtils`
### Rename/add pages to Playground
For every new feature in module create new Playground page with documentation in comments. See [nef markdown documentation](https://github.com/bow-swift/nef#-generating-a-markdown-project).
### Create symlink to nef playground
```sh
$ cd TIModuleName
$ ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground
```
### Add nef files to TIModuleName.app/.gitignore
```
# gitignore nef files
**/build/
**/nef/
LICENSE
```
### Exclude .app bundles from package sources
#### SPM
```swift
.target(name: "TIModuleName", dependencies: ..., path: ..., exclude: ["TIModuleName.app"]),
```
#### Podspec
```ruby
sources = 'your_sources_expression'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
```
## Docs:
- [TIFoundationUtils](docs/tifoundationutils)
* [AsyncOperation](docs/tifoundationutils/asyncoperation.md)
- [TICoreGraphicsUtils](docs/ticoregraphicsutils)
* [DrawingOperations](docs/ticoregraphicsutils/drawingoperations.md)
- [TIKeychainUtils](docs/tikeychainutils)
* [SingleValueStorage](docs/tikeychainutils/singlevaluestorage.md)
- [TIUIElements](docs/tiuielements)
* [Skeletons](docs/tiuielements/skeletons.md)
* [Placeholders](docs/tiuielements/placeholder.md)
- [TITextProcessing](docs/titextprocessing)
* [TITextProcessing](docs/titextprocessing/titextprocessing.md)
- [TIDeeplink](docs/tideeplink/deeplinks.md)
- [TIBottomSheet](docs/tibottomsheet/tibottomsheet.md)
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
- [Snippets](docs/snippets.md) - useful commands and scripts for development.
## Contributing
- Run following script in framework's folder:
```
./setup
```
- If legacy [Source](https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://git.svc.touchin.ru/TouchInstinct/LeadKit/blob/master/docs/snippets.md#build-dependencies-for-LeadKit.xcodeproj).
- Make sure the commit message codestyle is followed. More about [Semantic Commit Messages](docs/semantic-commit-messages.md).
## Installation
### SPM
```swift
dependencies: [
.package(url: "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", from: "x.y.z"),
],
```
### Cocoapods
```ruby
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z'
# ...
```
## Legacy
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.
LeadKit it's a iOS framework with a bunch of tools for rapid app development

View File

@ -1,36 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
/// Base collection controller configurable with view model and CollectionViewWrapperView as custom view.
open class BaseCollectionContentController<ViewModel>: BaseScrollContentController<ViewModel, CollectionViewWrapperView> {
override open func createView() -> CollectionViewWrapperView {
CollectionViewWrapperView(layout: UICollectionViewFlowLayout())
}
/// Contained UICollectionView instance.
public var collectionView: UICollectionView {
customView.collectionView
}
}

View File

@ -1,69 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit.UIViewController
/// Base controller that should be configured with view model.
open class BaseConfigurableController<ViewModel>: BaseOrientationController, ConfigurableController {
/// A view model instance used by this controller.
public let viewModel: ViewModel
/// Initializer with view model parameter.
///
/// - Parameter viewModel: A view model to configure this controller.
public init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - ConfigurableController
open func addViews() {
// override in subclass
}
open func configureLayout() {
// override in subclass
}
open func bindViews() {
// override in subclass
}
open func configureAppearance() {
// override in subclass
}
open func localize() {
// override in subclass
}
open func configureBarButtons() {
// override in subclass
}
}

View File

@ -1,54 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit.UIView
/// Base controller configurable by view model and custom view.
open class BaseCustomViewController<ViewModel, View: UIView>: BaseConfigurableController<ViewModel> {
/// Contained custom view.
public private(set) lazy var customView = createView()
/// Initializer with view model and custom view parameters.
///
/// - Parameters:
/// - viewModel: A view model to configure this controller.
/// - customView: UIView instance to assign in view property.
public override init(viewModel: ViewModel) {
super.init(viewModel: viewModel)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func loadView() {
view = customView
}
/// Creates custom view.
///
/// - Returns: Initialized custom view.
open func createView() -> View {
View()
}
}

View File

@ -1,30 +0,0 @@
import Foundation
open class BaseOrientationController: UIViewController {
/// Ability to set forced screen orientation
open var forcedInterfaceOrientation: UIInterfaceOrientation?
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
switch forcedInterfaceOrientation {
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
case .portrait:
return .portrait
case .portraitUpsideDown:
return .portraitUpsideDown
default:
return super.supportedInterfaceOrientations
}
}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
forcedInterfaceOrientation ?? super.preferredInterfaceOrientationForPresentation
}
}

View File

@ -1,25 +0,0 @@
import UIKit
open class OrientationNavigationController: UINavigationController {
// MARK: - Public properties
open var presentedOrTopViewController: UIViewController? {
presentedViewController ?? topViewController
}
open override var shouldAutorotate: Bool {
presentedOrTopViewController?.shouldAutorotate
?? super.shouldAutorotate
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
presentedOrTopViewController?.supportedInterfaceOrientations
?? super.supportedInterfaceOrientations
}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
presentedOrTopViewController?.preferredInterfaceOrientationForPresentation
?? super.preferredInterfaceOrientationForPresentation
}
}

View File

@ -1,79 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
public typealias ScrollViewHolderView = UIView & ScrollViewHolder
/// Base controller configurable with view model and ScrollViewHolder custom view.
open class BaseScrollContentController<ViewModel, View: ScrollViewHolderView>: BaseCustomViewController<ViewModel, View> {
private var bottomInsetDisposable: Disposable?
private let defaultInsetsRelay = BehaviorRelay<UIEdgeInsets>(value: .zero)
/// Bind given driver to bottom inset of scroll view. Takes into account default bottom insets.
///
/// - Parameter bottomInsetDriver: Driver that emits CGFloat bottom inset changes.
public func bindBottomInsetBinding(from bottomInsetDriver: Driver<CGFloat>) {
bottomInsetDisposable = bottomInsetDriver
.withLatestFrom(defaultInsetsRelay.asDriver()) {
$0 + $1.bottom
}
.drive(customView.scrollView.rx.bottomInsetBinder)
}
/// Unbind scroll view from previous binding.
public func unbindBottomInsetBinding() {
bottomInsetDisposable?.dispose()
}
/// Contained UIScrollView instance.
public var scrollView: UIScrollView {
customView.scrollView
}
/// Default insets used for contained scroll view.
public var defaultInsets: UIEdgeInsets {
get {
defaultInsetsRelay.value
}
set {
defaultInsetsRelay.accept(newValue)
customView.scrollView.contentInset = newValue
customView.scrollView.scrollIndicatorInsets = newValue
}
}
}
public extension BaseScrollContentController {
/// On iOS, tvOS 11+ sets contentInsetAdjustmentBehavior to .never.
/// On earlier versions sets automaticallyAdjustsScrollViewInsets to false.
func disableAdjustsScrollViewInsets() {
if #available(iOS 11.0, tvOS 11.0, *) {
customView.scrollView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
}
}

View File

@ -1,52 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TableKit
/// Base table controller configurable with view model and TableViewWrapperView as custom view.
open class BaseTableContentController<ViewModel>: BaseScrollContentController<ViewModel, TableViewWrapperView> {
/// TableDirector binded to table view.
public private(set) lazy var tableDirector = createTableDirector()
/// Creates tableDirector for table view.
///
/// - Returns: Initialized TableDirector.
open func createTableDirector() -> TableDirector {
TableDirector(tableView: tableView)
}
override open func createView() -> TableViewWrapperView {
TableViewWrapperView(tableViewStyle: .plain)
}
override open func configureAppearance() {
super.configureAppearance()
tableView.separatorStyle = .none
}
/// Contained UITableView instance.
public var tableView: UITableView {
customView.tableView
}
}

View File

@ -1,93 +0,0 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
/// Single load cursor configuration for single load operation
public final class SingleLoadCursorConfiguration<Element>: TotalCountCursorConfiguration {
public typealias ResultType = [Element]
private let loadingSingle: Single<ResultType>
/// Initializer for Single with array result type.
///
/// - Parameter loadingSingle: Single that will emit array of result type.
public init(loadingSingle: Single<ResultType>) {
self.loadingSingle = loadingSingle
}
public func resultSingle() -> Single<ResultType> {
loadingSingle
}
public init(resetFrom other: SingleLoadCursorConfiguration) {
self.loadingSingle = other.loadingSingle
}
}
/// Cursor implementation for single load operation
@available(*, deprecated, message: "Use SingleLoadCursorConfiguration with TotalCountCursor.")
public class SingleLoadCursor<Element>: ResettableCursorType {
private let loadingObservable: Single<[Element]>
private var content: [Element] = []
/// Initializer for array content type
///
/// - Parameter loadingObservable: Single observable with element of [Element] type
public init(loadingObservable: Single<[Element]>) {
self.loadingObservable = loadingObservable
}
public required init(resetFrom other: SingleLoadCursor) {
self.loadingObservable = other.loadingObservable
}
public private(set) var exhausted = false
public var count: Int {
content.count
}
public subscript(index: Int) -> Element {
content[index]
}
public func loadNextBatch() -> Single<[Element]> {
Single.deferred {
if self.exhausted {
return .error(CursorError.exhausted)
}
return self.loadingObservable.do(onSuccess: { [weak self] newItems in
self?.onGot(result: newItems)
})
}
}
private func onGot(result: [Element]) {
content = result
exhausted = true
}
}

View File

@ -1,71 +0,0 @@
//
// Copyright (c) 2018 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
open class TotalCountCursor<CursorConfiguration: TotalCountCursorConfiguration>: ResettableRxDataSourceCursor {
public typealias Element = CursorConfiguration.ResultType.ElementType
public typealias ResultType = [Element]
private let configuration: CursorConfiguration
private var elements: [Element] = []
public private(set) var totalCount: Int = .max
public var exhausted: Bool {
count >= totalCount
}
public var count: Int {
elements.count
}
public subscript(index: Int) -> Element {
elements[index]
}
public init(configuration: CursorConfiguration) {
self.configuration = configuration
}
public required init(resetFrom other: TotalCountCursor) {
configuration = other.configuration.reset()
}
open func processResultFromConfigurationSingle() -> Single<CursorConfiguration.ResultType> {
configuration.resultSingle()
}
public func loadNextBatch() -> Single<[Element]> {
processResultFromConfigurationSingle()
.do(onSuccess: { [weak self] listingResult in
self?.totalCount = listingResult.totalCount
self?.elements = (self?.elements ?? []) + listingResult.results
})
.map {
$0.results
}
}
}

Some files were not shown because too many files have changed in this diff Show More