Compare commits
2 Commits
master
...
feature/im
| Author | SHA1 | Date |
|---|---|---|
|
|
39e789c3ae | |
|
|
a885479a2c |
|
|
@ -1,2 +0,0 @@
|
||||||
---
|
|
||||||
BUNDLE_PATH: ".gem"
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
|
# ================
|
||||||
|
# Swift.gitignore
|
||||||
|
# ================
|
||||||
|
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||||
|
|
||||||
## User settings
|
## Build generated
|
||||||
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/
|
build/
|
||||||
DerivedData/
|
DerivedData
|
||||||
*.moved-aside
|
|
||||||
|
## Various settings
|
||||||
*.pbxuser
|
*.pbxuser
|
||||||
!default.pbxuser
|
!default.pbxuser
|
||||||
*.mode1v3
|
*.mode1v3
|
||||||
|
|
@ -21,14 +19,17 @@ DerivedData/
|
||||||
!default.mode2v3
|
!default.mode2v3
|
||||||
*.perspectivev3
|
*.perspectivev3
|
||||||
!default.perspectivev3
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
|
||||||
|
## Other
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
*.xcuserstate
|
||||||
|
*.xcscmblueprint
|
||||||
|
|
||||||
## Obj-C/Swift specific
|
## Obj-C/Swift specific
|
||||||
*.hmap
|
*.hmap
|
||||||
|
|
||||||
## App packaging
|
|
||||||
*.ipa
|
*.ipa
|
||||||
*.dSYM.zip
|
|
||||||
*.dSYM
|
|
||||||
|
|
||||||
## Playgrounds
|
## Playgrounds
|
||||||
timeline.xctimeline
|
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.
|
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||||
# Packages/
|
# 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/
|
.build/
|
||||||
|
|
||||||
# CocoaPods
|
# CocoaPods
|
||||||
|
|
@ -55,56 +48,28 @@ playground.xcworkspace
|
||||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||||
#
|
#
|
||||||
Pods/
|
Pods/
|
||||||
#
|
|
||||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
|
||||||
# *.xcworkspace
|
|
||||||
|
|
||||||
# Carthage
|
# Carthage
|
||||||
#
|
#
|
||||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||||
Carthage/Checkouts
|
Carthage/Checkouts
|
||||||
|
|
||||||
Carthage/Build/
|
Carthage/Build
|
||||||
|
|
||||||
# Accio dependency management
|
|
||||||
Dependencies/
|
|
||||||
.accio/
|
|
||||||
|
|
||||||
# fastlane
|
# fastlane
|
||||||
#
|
#
|
||||||
# It is recommended to not store the screenshots in the git repo.
|
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
# screenshots whenever they are needed.
|
||||||
# For more information about the recommended setup visit:
|
# 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/report.xml
|
||||||
fastlane/Preview.html
|
fastlane/screenshots
|
||||||
fastlane/screenshots/**/*.png
|
|
||||||
fastlane/test_output
|
|
||||||
|
|
||||||
# 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
|
.idea/workspace.xml
|
||||||
Brewfile.lock.json
|
.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
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "build-scripts"]
|
|
||||||
path = build-scripts
|
|
||||||
url = https://git.svc.touchin.ru/TouchInstinct/BuildScripts.git
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3.0
|
||||||
941
CHANGELOG.md
941
CHANGELOG.md
|
|
@ -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
|
|
||||||
12
Cartfile
12
Cartfile
|
|
@ -1,7 +1,5 @@
|
||||||
github "malcommac/SwiftDate"
|
github "CocoaLumberjack/CocoaLumberjack" ~> 3.0.0
|
||||||
github "Alamofire/Alamofire"
|
github "ReactiveX/RxSwift" "3.0.1"
|
||||||
github "RxSwiftCommunity/RxAlamofire" ~> 6.1
|
github "RxSwiftCommunity/RxAlamofire" "3.0.1"
|
||||||
github "TouchInstinct/TableKit"
|
github "Hearst-DD/ObjectMapper" ~> 2.1
|
||||||
github "ReactiveX/RxSwift" ~> 6.2
|
github "Alamofire/AlamofireImage" ~> 3.2
|
||||||
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"
|
|
||||||
github "SnapKit/SnapKit" ~> 5.0
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
github "Alamofire/Alamofire" "5.4.3"
|
github "Alamofire/Alamofire" "4.2.0"
|
||||||
github "ReactiveX/RxSwift" "6.2.0"
|
github "CocoaLumberjack/CocoaLumberjack" "3.0.0"
|
||||||
github "RxSwiftCommunity/RxAlamofire" "v6.1.2"
|
github "Hearst-DD/ObjectMapper" "2.2.1"
|
||||||
github "SnapKit/SnapKit" "5.0.1"
|
github "ReactiveX/RxSwift" "3.0.1"
|
||||||
github "TouchInstinct/TableKit" "2.10008.1"
|
github "Alamofire/AlamofireImage" "3.2.0"
|
||||||
github "malcommac/SwiftDate" "6.3.1"
|
github "RxSwiftCommunity/RxAlamofire" "3.0.1"
|
||||||
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"
|
|
||||||
|
|
|
||||||
5
Gemfile
5
Gemfile
|
|
@ -1,5 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
source "https://rubygems.org"
|
|
||||||
|
|
||||||
gem "cocoapods", "~> 1.11"
|
|
||||||
98
Gemfile.lock
98
Gemfile.lock
|
|
@ -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
|
|
||||||
125
LeadKit.podspec
125
LeadKit.podspec
|
|
@ -1,122 +1,17 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = "LeadKit"
|
s.name = "LeadKit"
|
||||||
s.version = "1.35.0"
|
s.version = "0.2.6"
|
||||||
s.summary = "iOS framework with a bunch of tools for rapid development"
|
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.license = "Apache License, Version 2.0"
|
||||||
s.author = "Touch Instinct"
|
s.author = "Touch Instinct"
|
||||||
s.source = { :git => "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", :tag => s.version }
|
s.platform = :ios, "9.0"
|
||||||
s.platform = :ios, '10.0'
|
s.source = { :git => "https://github.com/TouchInstinct/LeadKit.git", :tag => s.version }
|
||||||
s.swift_versions = ['5.1']
|
s.source_files = "LeadKit/LeadKit/**/*.swift"
|
||||||
|
|
||||||
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.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
|
end
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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>
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
||||||
|
disabled_rules:
|
||||||
|
- force_cast
|
||||||
|
- trailing_whitespace
|
||||||
|
excluded:
|
||||||
|
- Carthage
|
||||||
|
- Pods
|
||||||
|
- RxAlamofire
|
||||||
|
line_length: 128
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
exclude:
|
||||||
|
- 'Pods'
|
||||||
|
- 'Carthage'
|
||||||
|
- 'RxAlamofire'
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1230"
|
LastUpgradeVersion = "0800"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
|
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
|
||||||
BuildableName = "LeadKit.framework"
|
BuildableName = "LeadKit.framework"
|
||||||
BlueprintName = "LeadKit iOS"
|
BlueprintName = "LeadKit"
|
||||||
ReferencedContainer = "container:LeadKit.xcodeproj">
|
ReferencedContainer = "container:LeadKit.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
|
|
@ -27,27 +27,29 @@
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
|
|
||||||
BuildableName = "LeadKit.framework"
|
|
||||||
BlueprintName = "LeadKit iOS"
|
|
||||||
ReferencedContainer = "container:LeadKit.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "67186B2F1EB248F100CFAFFB"
|
BlueprintIdentifier = "78CFEE331C5C456B00F50370"
|
||||||
BuildableName = "LeadKit iOSTests.xctest"
|
BuildableName = "LeadKitTests.xctest"
|
||||||
BlueprintName = "LeadKit iOSTests"
|
BlueprintName = "LeadKitTests"
|
||||||
ReferencedContainer = "container:LeadKit.xcodeproj">
|
ReferencedContainer = "container:LeadKit.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
|
||||||
|
BuildableName = "LeadKit.framework"
|
||||||
|
BlueprintName = "LeadKit"
|
||||||
|
ReferencedContainer = "container:LeadKit.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
|
|
@ -62,12 +64,14 @@
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
|
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
|
||||||
BuildableName = "LeadKit.framework"
|
BuildableName = "LeadKit.framework"
|
||||||
BlueprintName = "LeadKit iOS"
|
BlueprintName = "LeadKit"
|
||||||
ReferencedContainer = "container:LeadKit.xcodeproj">
|
ReferencedContainer = "container:LeadKit.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|
@ -78,9 +82,9 @@
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "67186B271EB248F100CFAFFB"
|
BlueprintIdentifier = "78CFEE291C5C456B00F50370"
|
||||||
BuildableName = "LeadKit.framework"
|
BuildableName = "LeadKit.framework"
|
||||||
BlueprintName = "LeadKit iOS"
|
BlueprintName = "LeadKit"
|
||||||
ReferencedContainer = "container:LeadKit.xcodeproj">
|
ReferencedContainer = "container:LeadKit.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// FixedPageCursor.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
/// Paging cursor implementation with enclosed cursor for fetching results
|
||||||
|
public class FixedPageCursor<Cursor: CursorType>: CursorType where Cursor.LoadResultType == CountableRange<Int> {
|
||||||
|
|
||||||
|
public typealias LoadResultType = CountableRange<Int>
|
||||||
|
|
||||||
|
private let cursor: Cursor
|
||||||
|
|
||||||
|
private let pageSize: Int
|
||||||
|
|
||||||
|
/// Initializer with enclosed cursor
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - cursor: enclosed cursor
|
||||||
|
/// - pageSize: number of items loaded at once
|
||||||
|
public init(cursor: Cursor, pageSize: Int) {
|
||||||
|
self.cursor = cursor
|
||||||
|
self.pageSize = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
public var exhausted: Bool {
|
||||||
|
return cursor.exhausted && cursor.count == count
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var count: Int = 0
|
||||||
|
|
||||||
|
public subscript(index: Int) -> Cursor.Element {
|
||||||
|
return cursor[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadNextBatch() -> Observable<LoadResultType> {
|
||||||
|
return Observable.deferred {
|
||||||
|
if self.exhausted {
|
||||||
|
throw CursorError.exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
let restOfLoaded = self.cursor.count - self.count
|
||||||
|
|
||||||
|
if restOfLoaded >= self.pageSize || self.cursor.exhausted {
|
||||||
|
let startIndex = self.count
|
||||||
|
self.count += min(restOfLoaded, self.pageSize)
|
||||||
|
|
||||||
|
return Observable.just(startIndex..<self.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.cursor.loadNextBatch()
|
||||||
|
.flatMap { _ in self.loadNextBatch() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// MapCursor.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
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 MapCursorInstance
|
||||||
|
func flatMap<T>(transform: @escaping MapCursor<Self, T>.Transform) -> MapCursor<Self, T> {
|
||||||
|
return MapCursor(cursor: self, transform: transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map cursor implementation with enclosed cursor for fetching results
|
||||||
|
public class MapCursor<Cursor: CursorType, T>: CursorType where Cursor.LoadResultType == MapCursorLoadResultType {
|
||||||
|
|
||||||
|
public typealias LoadResultType = Cursor.LoadResultType
|
||||||
|
|
||||||
|
public typealias Transform = (Cursor.Element) -> T?
|
||||||
|
|
||||||
|
private let cursor: Cursor
|
||||||
|
|
||||||
|
private let transform: Transform
|
||||||
|
|
||||||
|
private var elements: [T] = []
|
||||||
|
|
||||||
|
/// Initializer with enclosed cursor
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - cursor: enclosed cursor
|
||||||
|
/// - transform: closure to transform elements
|
||||||
|
public init(cursor: Cursor, transform: @escaping Transform) {
|
||||||
|
self.cursor = cursor
|
||||||
|
self.transform = transform
|
||||||
|
}
|
||||||
|
|
||||||
|
public var exhausted: Bool {
|
||||||
|
return cursor.exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
public var count: Int {
|
||||||
|
return elements.count
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscript(index: Int) -> T {
|
||||||
|
return elements[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 startIndex..<self.elements.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// StaticCursor.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
/// Stub cursor implementation for array content type
|
||||||
|
public class StaticCursor<Element>: CursorType {
|
||||||
|
|
||||||
|
public typealias LoadResultType = CountableRange<Int>
|
||||||
|
|
||||||
|
private let content: [Element]
|
||||||
|
|
||||||
|
/// Initializer for array content type
|
||||||
|
///
|
||||||
|
/// - Parameter content: array with elements of Elemet type
|
||||||
|
public init(content: [Element]) {
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var exhausted = false
|
||||||
|
|
||||||
|
public private(set) var count = 0
|
||||||
|
|
||||||
|
public subscript(index: Int) -> Element {
|
||||||
|
return content[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadNextBatch() -> Observable<LoadResultType> {
|
||||||
|
return Observable.deferred {
|
||||||
|
if self.exhausted {
|
||||||
|
throw CursorError.exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
self.count = self.content.count
|
||||||
|
|
||||||
|
self.exhausted = true
|
||||||
|
|
||||||
|
return Observable.just(0..<self.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
//
|
||||||
|
// ImageLoader.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageLoader<TKey: CustomStringConvertible> {
|
||||||
|
private let imageSource: ImageSource<TKey>
|
||||||
|
private let imageCache: MemoryStore<String, UIImage>
|
||||||
|
|
||||||
|
private var loadingObservablesCache = [String : Observable<UIImage>]()
|
||||||
|
|
||||||
|
private let loadingSyncLock = Sync.Lock()
|
||||||
|
|
||||||
|
public func loadImage(key: TKey) -> ImageOperation {
|
||||||
|
let loadingObservable = Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
let imageCacheKey = key.description
|
||||||
|
return self.beginLoadingImage(key: key, imageCacheKey: imageCacheKey)
|
||||||
|
})
|
||||||
|
.subcribeOnBackgroundScheduler()
|
||||||
|
|
||||||
|
return ImageLoadingOperation(imageLoader: self, key: key, loadingObservable: loadingObservable)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
|
||||||
|
return Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
let operationCachingKey = operation._cachingKey
|
||||||
|
|
||||||
|
if let cachedImage = self.imageCache.loadData(key: operationCachingKey) {
|
||||||
|
return Observable.just(cachedImage)
|
||||||
|
} else {
|
||||||
|
return operation._imageSource
|
||||||
|
.observeOnBackgroundScheduler()
|
||||||
|
.do(onNext: { (image) in
|
||||||
|
self.imageCache.storeData(key: operationCachingKey, data: image)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subcribeOnBackgroundScheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginLoadingImage(key: TKey, imageCacheKey: String) -> Observable<UIImage> {
|
||||||
|
return loadingSyncLock.sync {
|
||||||
|
if let loadingObservable = loadingObservablesCache[imageCacheKey] {
|
||||||
|
return loadingObservable
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadingObservable = imageSource.loadImage(key)
|
||||||
|
.observeOnBackgroundScheduler()
|
||||||
|
.do(onDispose: {
|
||||||
|
self.removeLoadingObservable(cacheKey: imageCacheKey)
|
||||||
|
})
|
||||||
|
.shareReplay(1)
|
||||||
|
|
||||||
|
loadingObservablesCache[imageCacheKey] = loadingObservable
|
||||||
|
|
||||||
|
return loadingObservable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeLoadingObservable(cacheKey: String) {
|
||||||
|
loadingSyncLock.sync {
|
||||||
|
loadingObservablesCache.removeValue(forKey: cacheKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<TSource: ImageSourceType>(imageSource: TSource,
|
||||||
|
imageCache: MemoryStore<String, UIImage> = MemoryStore()) where TSource.KeyType == TKey {
|
||||||
|
self.imageSource = imageSource.asImageSource()
|
||||||
|
self.imageCache = imageCache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// ImageFakeOperation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageFakeOperation: ImageOperationWrapper {
|
||||||
|
public typealias PerformerObservableHandler = (Observable<UIImage>) -> Observable<UIImage>
|
||||||
|
|
||||||
|
private let performerObservableHandler: PerformerObservableHandler
|
||||||
|
|
||||||
|
public override func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
|
||||||
|
return super._preparePerformerObservable(performObservable: performerObservableHandler(performObservable))
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(originalOperation: ImageOperation, performerObservableHandler: @escaping PerformerObservableHandler) {
|
||||||
|
self.performerObservableHandler = performerObservableHandler
|
||||||
|
super.init(originalOperation: originalOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func performWith(_ perfrormHandler: @escaping ImageFakeOperation.PerformerObservableHandler) -> ImageOperation {
|
||||||
|
return ImageFakeOperation(originalOperation: self, performerObservableHandler: perfrormHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// ImageLoadingOperation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageLoadingOperation<TKey: CustomStringConvertible>: ImageOperation, ImageOperationPerformer {
|
||||||
|
public override var _imageSource: Observable<UIImage> {
|
||||||
|
return loadingObservable
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var _performer: ImageOperationPerformer {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var _cachingKey: String {
|
||||||
|
return key.description
|
||||||
|
}
|
||||||
|
|
||||||
|
public func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
|
||||||
|
return imageLoader.performImageOperation(operation: operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private let loadingObservable: Observable<UIImage>
|
||||||
|
private let imageLoader: ImageLoader<TKey>
|
||||||
|
private let key: TKey
|
||||||
|
|
||||||
|
public init(imageLoader: ImageLoader<TKey>, key: TKey, loadingObservable: Observable<UIImage>) {
|
||||||
|
self.imageLoader = imageLoader
|
||||||
|
self.key = key
|
||||||
|
self.loadingObservable = loadingObservable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// ImageOperation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageOperation: ObservableType {
|
||||||
|
public typealias E = UIImage
|
||||||
|
|
||||||
|
open var _imageSource: Observable<UIImage> {
|
||||||
|
abstractMethod("_imageSource")
|
||||||
|
}
|
||||||
|
|
||||||
|
open var _performer: ImageOperationPerformer {
|
||||||
|
abstractMethod("_performer")
|
||||||
|
}
|
||||||
|
|
||||||
|
open var _cachingKey: String {
|
||||||
|
abstractMethod("_cachingKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
open func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
|
||||||
|
return performObservable
|
||||||
|
}
|
||||||
|
|
||||||
|
public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == UIImage {
|
||||||
|
return _preparePerformerObservable(performObservable: _performer.performImageOperation(operation: self))
|
||||||
|
.subscribe(observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// ImageOperationPerformer.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public protocol ImageOperationPerformer {
|
||||||
|
func performImageOperation(operation: ImageOperation) -> Observable<UIImage>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// ImageOperationWrapper.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageOperationWrapper: ImageOperation {
|
||||||
|
open override var _imageSource: Observable<UIImage> {
|
||||||
|
return _prepareWorkingObservable(workingObservable: originalOperation._imageSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var _performer: ImageOperationPerformer {
|
||||||
|
return originalOperation._performer
|
||||||
|
}
|
||||||
|
|
||||||
|
open override var _cachingKey: String {
|
||||||
|
return _prepareCachingKey(cachingKey: originalOperation._cachingKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
open func _prepareWorkingObservable(workingObservable: Observable<UIImage>) -> Observable<UIImage> {
|
||||||
|
return workingObservable
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
|
||||||
|
return super._preparePerformerObservable(performObservable:
|
||||||
|
originalOperation._preparePerformerObservable(performObservable: performObservable))
|
||||||
|
}
|
||||||
|
|
||||||
|
open func _prepareCachingKey(cachingKey: String) -> String {
|
||||||
|
return cachingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
private let originalOperation: ImageOperation
|
||||||
|
|
||||||
|
public init(originalOperation: ImageOperation) {
|
||||||
|
self.originalOperation = originalOperation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ImageStartOperation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageStartOperation: ImageOperation, ImageOperationPerformer {
|
||||||
|
public override var _imageSource: Observable<UIImage> {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var _performer: ImageOperationPerformer {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public override var _cachingKey: String {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
public func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
|
||||||
|
return operation._imageSource
|
||||||
|
}
|
||||||
|
|
||||||
|
private let source: Observable<UIImage>
|
||||||
|
private let key: String
|
||||||
|
|
||||||
|
public init(source: Observable<UIImage>, key: String? = nil) {
|
||||||
|
self.key = key ?? String()
|
||||||
|
self.source = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension UIImage {
|
||||||
|
public func asImageOperation(key: String? = nil) -> ImageOperation {
|
||||||
|
return Observable.just(self).asImageOperation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func asImageOperation(key: String? = nil) -> ImageOperation {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ObservableConvertibleType where E == UIImage {
|
||||||
|
public func asImageOperation(key: String? = nil) -> ImageOperation {
|
||||||
|
return ImageStartOperation(source: self.asObservable(), key: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// ImageBluringPlugin.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class ImageBluringPlugin: ImageFilteringPlugin {
|
||||||
|
public enum BlurType {
|
||||||
|
case box(radius: CGFloat)
|
||||||
|
case disc(radius: CGFloat)
|
||||||
|
case gaussian(radius: CGFloat)
|
||||||
|
case masked(radius: CGFloat, maskImage: UIImage)
|
||||||
|
case median()
|
||||||
|
case motion(radius: CGFloat, angle: CGFloat)
|
||||||
|
|
||||||
|
public func filterParams() -> [String : Any] {
|
||||||
|
var resultRadius: CGFloat?
|
||||||
|
var resultParams = [String : Any]()
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .box(let radius):
|
||||||
|
resultRadius = radius
|
||||||
|
case .disc(let radius):
|
||||||
|
resultRadius = radius
|
||||||
|
case .gaussian(let radius):
|
||||||
|
resultRadius = radius
|
||||||
|
case .masked(let radius, let maskImage):
|
||||||
|
resultRadius = radius
|
||||||
|
resultParams["inputMask"] = maskImage.cgImage
|
||||||
|
case.median:
|
||||||
|
break
|
||||||
|
case .motion(let radius, let angle):
|
||||||
|
resultRadius = radius
|
||||||
|
resultParams["inputAngle"] = angle
|
||||||
|
}
|
||||||
|
|
||||||
|
if let resultRadius = resultRadius {
|
||||||
|
resultParams["inputRadius"] = resultRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultParams
|
||||||
|
}
|
||||||
|
|
||||||
|
public func filterName() -> String {
|
||||||
|
switch self {
|
||||||
|
case .box:
|
||||||
|
return "CIBoxBlur"
|
||||||
|
case .disc:
|
||||||
|
return "CIDiscBlur"
|
||||||
|
case .gaussian:
|
||||||
|
return "CIGaussianBlur"
|
||||||
|
case .masked:
|
||||||
|
return "CIMaskedVariableBlur"
|
||||||
|
case.median:
|
||||||
|
return "CIMedianFilter"
|
||||||
|
case .motion:
|
||||||
|
return "CIMotionBlur"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(blurType: BlurType) {
|
||||||
|
super.init(name: blurType.filterName(), params: blurType.filterParams())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func blur(blurType: ImageBluringPlugin.BlurType) -> ImageOperation {
|
||||||
|
return ImagePluginOperation(originalOperation: self, plugin: ImageBluringPlugin(blurType: blurType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ImageFilteringPlugin.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AlamofireImage
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageFilteringPlugin: ImagePluginType {
|
||||||
|
public enum FilterError: Error {
|
||||||
|
case unknownError(description: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias FitlerParamsType = [String : Any]
|
||||||
|
|
||||||
|
private let filterName: String
|
||||||
|
private let filterParams: FitlerParamsType?
|
||||||
|
|
||||||
|
public var pluginKey: String {
|
||||||
|
var resultKey = filterName
|
||||||
|
if let filterParams = filterParams {
|
||||||
|
resultKey += filterParams.description
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public func perform(image: UIImage) -> Observable<UIImage> {
|
||||||
|
return Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
guard let reusltImage = image.af_imageFiltered(withCoreImageFilter: self.filterName, parameters: self.filterParams) else {
|
||||||
|
throw FilterError.unknownError(description: "ImageFilterPlugin failed with filter named \(self.filterName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.just(reusltImage)
|
||||||
|
})
|
||||||
|
.subcribeOnBackgroundScheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(name: String, params: FitlerParamsType? = nil) {
|
||||||
|
filterName = name
|
||||||
|
filterParams = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func filter(name: String, params: ImageFilteringPlugin.FitlerParamsType? = nil) -> ImageOperation {
|
||||||
|
return ImagePluginOperation(originalOperation: self, plugin: ImageFilteringPlugin(name: name, params: params))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// ImagePlugin.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public protocol ImagePluginType {
|
||||||
|
func perform(image: UIImage) -> Observable<UIImage>
|
||||||
|
var pluginKey: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIImage {
|
||||||
|
public func performPlugins(plugins: [ImagePluginType]) -> Observable<UIImage> {
|
||||||
|
var observable = Observable.just(self)
|
||||||
|
|
||||||
|
for plugin in plugins {
|
||||||
|
observable = observable.flatMap(plugin.perform)
|
||||||
|
}
|
||||||
|
|
||||||
|
return observable
|
||||||
|
}
|
||||||
|
|
||||||
|
public func performPlugins(plugins: ImagePluginType ...) -> Observable<UIImage> {
|
||||||
|
return performPlugins(plugins: plugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// ImagePluginOperation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImagePluginOperation: ImageOperationWrapper {
|
||||||
|
public override func _prepareCachingKey(cachingKey: String) -> String {
|
||||||
|
return cachingKey + plugin.pluginKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func _prepareWorkingObservable(workingObservable: Observable<UIImage>) -> Observable<UIImage> {
|
||||||
|
return workingObservable
|
||||||
|
.flatMap({ (image) -> Observable<UIImage> in
|
||||||
|
return self.plugin.perform(image: image)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private let plugin: ImagePluginType
|
||||||
|
|
||||||
|
public init(originalOperation: ImageOperation, plugin: ImagePluginType) {
|
||||||
|
self.plugin = plugin
|
||||||
|
super.init(originalOperation: originalOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// ImageResizingPlugin.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AlamofireImage
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageResizingPlugin: ImagePluginType {
|
||||||
|
public enum Mode: String {
|
||||||
|
case scale
|
||||||
|
case aspectFit
|
||||||
|
case aspectFill
|
||||||
|
|
||||||
|
static var `default`: Mode {
|
||||||
|
return .scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let mode: Mode
|
||||||
|
private let size: CGSize
|
||||||
|
|
||||||
|
public var pluginKey: String {
|
||||||
|
return mode.rawValue + String(describing: size.width) + String(describing: size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func perform(image: UIImage) -> Observable<UIImage> {
|
||||||
|
return Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
return Observable.just(self.beginPerform(image: image))
|
||||||
|
})
|
||||||
|
.subcribeOnBackgroundScheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginPerform(image: UIImage) -> UIImage {
|
||||||
|
switch mode {
|
||||||
|
case .scale:
|
||||||
|
return image.af_imageScaled(to: size)
|
||||||
|
case .aspectFit:
|
||||||
|
return image.af_imageAspectScaled(toFit: size)
|
||||||
|
case .aspectFill:
|
||||||
|
return image.af_imageAspectScaled(toFill: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(size: CGSize, mode: Mode = .default) {
|
||||||
|
self.size = size
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func resize(size: CGSize, mode: ImageResizingPlugin.Mode = .default) -> ImageOperation {
|
||||||
|
return ImagePluginOperation(originalOperation: self, plugin: ImageResizingPlugin(size: size, mode: mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// ImageRoundingPlugin.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AlamofireImage
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public class ImageRoundingPlugin: ImagePluginType {
|
||||||
|
public enum Mode {
|
||||||
|
case cicrular
|
||||||
|
case corners(radius: CGFloat)
|
||||||
|
|
||||||
|
static var `default`: Mode {
|
||||||
|
return .cicrular
|
||||||
|
}
|
||||||
|
|
||||||
|
public var key: String {
|
||||||
|
switch self {
|
||||||
|
case .cicrular:
|
||||||
|
return "Circular"
|
||||||
|
case .corners(let radius):
|
||||||
|
return "Corners" + String(describing: radius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let mode: Mode
|
||||||
|
|
||||||
|
public var pluginKey: String {
|
||||||
|
return mode.key
|
||||||
|
}
|
||||||
|
|
||||||
|
public func perform(image: UIImage) -> Observable<UIImage> {
|
||||||
|
return Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
return Observable.just(self.beginPerform(image: image))
|
||||||
|
})
|
||||||
|
.subcribeOnBackgroundScheduler()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginPerform(image: UIImage) -> UIImage {
|
||||||
|
switch mode {
|
||||||
|
case .cicrular:
|
||||||
|
return image.af_imageRoundedIntoCircle()
|
||||||
|
case .corners(let radius):
|
||||||
|
return image.af_imageRounded(withCornerRadius: radius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(mode: Mode = .default) {
|
||||||
|
self.mode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ImageOperation {
|
||||||
|
public func round(mode: ImageRoundingPlugin.Mode = .default) -> ImageOperation {
|
||||||
|
return ImagePluginOperation(originalOperation: self, plugin: ImageRoundingPlugin(mode: mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// ImageSource.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public protocol ImageSourceType {
|
||||||
|
associatedtype KeyType
|
||||||
|
|
||||||
|
func loadImage(_ key: KeyType) -> Observable<UIImage>
|
||||||
|
|
||||||
|
func asImageSource() -> ImageSource<KeyType>
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageSourceType {
|
||||||
|
public func asImageSource() -> ImageSource<KeyType> {
|
||||||
|
return ImageSource(source: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageSource<TKey>: ImageSourceType {
|
||||||
|
public typealias KeyType = TKey
|
||||||
|
public typealias ImageLoadingHandler = (KeyType) -> Observable<UIImage>
|
||||||
|
|
||||||
|
private let sourceLoadImageHandler: ImageLoadingHandler
|
||||||
|
|
||||||
|
public func loadImage(_ key: KeyType) -> Observable<UIImage> {
|
||||||
|
return sourceLoadImageHandler(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(imageLoadingHandler: @escaping ImageLoadingHandler) {
|
||||||
|
sourceLoadImageHandler = imageLoadingHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
public convenience init<TSource: ImageSourceType>(source: TSource) where TSource.KeyType == KeyType {
|
||||||
|
self.init(imageLoadingHandler: source.loadImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageSource {
|
||||||
|
public func asImageSource() -> ImageSource<KeyType> {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// NetworkImageSource.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
import Alamofire
|
||||||
|
import AlamofireImage
|
||||||
|
|
||||||
|
public class NetworkImageSource<TKey: URLConvertible>: ImageSourceType where TKey: CustomStringConvertible {
|
||||||
|
public typealias KeyType = TKey
|
||||||
|
|
||||||
|
private let sessionManager: SessionManager
|
||||||
|
|
||||||
|
public func loadImage(_ key: KeyType) -> Observable<UIImage> {
|
||||||
|
return Observable.deferred({ () -> Observable<UIImage> in
|
||||||
|
return self.beginLoadImage(try key.asURL())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginLoadImage(_ url: URL) -> Observable<UIImage> {
|
||||||
|
return Observable.create({ (observer) -> Disposable in
|
||||||
|
let request = self.sessionManager.request(url)
|
||||||
|
.responseImage(completionHandler: { (response) in
|
||||||
|
switch response.result {
|
||||||
|
case .success(let image):
|
||||||
|
observer.onNext(image)
|
||||||
|
observer.onCompleted()
|
||||||
|
case .failure(let error):
|
||||||
|
observer.onError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Disposables.create {
|
||||||
|
request.cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(sessionManager: SessionManager = SessionManager.default) {
|
||||||
|
self.sessionManager = sessionManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// UIImageView+ImageLoader.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public extension ImageResizingPlugin.Mode {
|
||||||
|
public static func fromUIContentMode(contentMode: UIViewContentMode) -> ImageResizingPlugin.Mode {
|
||||||
|
switch contentMode {
|
||||||
|
case .scaleAspectFill:
|
||||||
|
return .aspectFill
|
||||||
|
case .scaleAspectFit:
|
||||||
|
return .aspectFit
|
||||||
|
default:
|
||||||
|
return .scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension UIImageView {
|
||||||
|
public func loadImage<TKey: CustomStringConvertible>(key: TKey, loader: ImageLoader<TKey>, resizeImage: Bool = true,
|
||||||
|
placeholder: UIImage? = nil) -> ImageOperation {
|
||||||
|
|
||||||
|
var resultOperation = loader.loadImage(key: key)
|
||||||
|
|
||||||
|
if resizeImage {
|
||||||
|
resultOperation = resultOperation.resize(size: frame.size, mode: ImageResizingPlugin.Mode.fromUIContentMode(contentMode: contentMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
resultOperation = resultOperation.performWith({ (observable) -> Observable<UIImage> in
|
||||||
|
var resultObservable = observable.observeOnMainScheduler()
|
||||||
|
.do(onNext: { [weak self] (image) in
|
||||||
|
self?.image = image
|
||||||
|
})
|
||||||
|
.takeUntil(self.rx.deallocated)
|
||||||
|
|
||||||
|
if let placeholder = placeholder {
|
||||||
|
resultObservable = resultObservable.startWith(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultObservable
|
||||||
|
})
|
||||||
|
|
||||||
|
return resultOperation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// App.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Николай Ашанин on 24.01.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Log.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Николай Ашанин on 24.01.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// LogFormatter.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Николай Ашанин on 24.01.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CocoaLumberjack
|
||||||
|
import CocoaLumberjack.DDDispatchQueueLogFormatter
|
||||||
|
|
||||||
|
class LogFormatter: DDDispatchQueueLogFormatter {
|
||||||
|
fileprivate let dateFormatter: DateFormatter
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.formatterBehavior = .behavior10_4
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"
|
||||||
|
|
||||||
|
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)"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// CursorError.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// AlamofireManager+Extensions.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 04/08/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Alamofire
|
||||||
|
import RxSwift
|
||||||
|
import RxAlamofire
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
|
public extension 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 serialize response into target object
|
||||||
|
|
||||||
|
- parameter requestParameters: api parameters to pass Alamofire
|
||||||
|
|
||||||
|
- returns: Observable with HTTP URL Response and target object
|
||||||
|
*/
|
||||||
|
func responseModel<T: ImmutableMappable>(requestParameters: ApiRequestParameters) -> Observable<(HTTPURLResponse, T)> {
|
||||||
|
return apiRequest(requestParameters: requestParameters).flatMap { $0.rx.apiResponse() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// AlamofireRequest+Extensions.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 04/08/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Alamofire
|
||||||
|
import RxSwift
|
||||||
|
import ObjectMapper
|
||||||
|
import RxAlamofire
|
||||||
|
|
||||||
|
public enum ApiResponseMappingError: Error {
|
||||||
|
|
||||||
|
case incorrectValueType(message: String)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Reactive where Base: DataRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
method which serialize response into target object
|
||||||
|
|
||||||
|
- returns: Observable with HTTP URL Response and target object
|
||||||
|
*/
|
||||||
|
func apiResponse<T: ImmutableMappable>() -> Observable<(HTTPURLResponse, T)> {
|
||||||
|
return responseJSON().map { resp, value in
|
||||||
|
if let json = value as? [String: Any] {
|
||||||
|
return (resp, try T(JSON: json))
|
||||||
|
} else {
|
||||||
|
let failureReason = "Value has incorrect type: \(type(of: value)), expected: [String: Any]"
|
||||||
|
|
||||||
|
throw ApiResponseMappingError.incorrectValueType(message: failureReason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// CGContext+Initializers.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 04/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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 channel’s 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// CGImage+Alpha.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 04/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
|
public extension CGImage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
- 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// CGImage+Creation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 05/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// CGImage+Crop.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 06/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
|
public extension 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
|
||||||
|
let heightCropSize = height > shortest ? (height - shortest) : 0
|
||||||
|
|
||||||
|
let left = widthCropSize / 2
|
||||||
|
let top = heightCropSize / 2
|
||||||
|
|
||||||
|
let cropRect = CGRect(x: left,
|
||||||
|
y: top,
|
||||||
|
width: width - widthCropSize,
|
||||||
|
height: height - heightCropSize)
|
||||||
|
|
||||||
|
return cropping(to: cropRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// CGImage+Template.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 05/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
|
public extension 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: 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
//
|
||||||
|
// CGImage+Transform.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 05/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// CGImage+Utils.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 05/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// CursorType+Slice.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// Double+Rounding.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 07/09/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// IndexPath+ImmutableIndexPath.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 21/03/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// ObservableType+Extensions.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
public extension ObservableType {
|
||||||
|
public func subcribeOnBackgroundScheduler() -> Observable<E> {
|
||||||
|
return self.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func observeOnBackgroundScheduler() -> Observable<E> {
|
||||||
|
return self.observeOn(ConcurrentDispatchQueueScheduler(qos: .default))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func subcribeOnMainScheduler() -> Observable<E> {
|
||||||
|
return self.subscribeOn(MainScheduler.instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func observeOnMainScheduler() -> Observable<E> {
|
||||||
|
return self.observeOn(MainScheduler.instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// StoryboardProtocol+DefaultBundle.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 21/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension StoryboardProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
- returns: default bundle for storyboard initialization
|
||||||
|
*/
|
||||||
|
public static var bundle: Bundle? {
|
||||||
|
return Bundle.main
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// StoryboardProtocol+Extensions.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 20/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// String+Localization.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Николай Ашанин on 29.09.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension String {
|
||||||
|
|
||||||
|
/**
|
||||||
|
method returns localized string with default comment and self name
|
||||||
|
|
||||||
|
- returns: localized string
|
||||||
|
*/
|
||||||
|
public func localized() -> String {
|
||||||
|
return NSLocalizedString(self, comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// String+SizeCalculation.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 21/03/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// UICollectionView+CellRegistration.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Fedor on 18.10.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
//
|
||||||
|
// UIColor+Hex.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 07/09/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension UIColor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
the shorthand three-digit hexadecimal representation of color.
|
||||||
|
#RGB defines to the color #RRGGBB.
|
||||||
|
|
||||||
|
- parameter hex3: Three-digit hexadecimal value.
|
||||||
|
- parameter alpha: 0.0 - 1.0. The default is 1.0.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
the shorthand four-digit hexadecimal representation of color with alpha.
|
||||||
|
#RGBA defines to the color #RRGGBBAA.
|
||||||
|
|
||||||
|
- parameter hex4: Four-digit hexadecimal value.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
the six-digit hexadecimal representation of color of the form #RRGGBB.
|
||||||
|
|
||||||
|
- parameter hex6: Six-digit hexadecimal value.
|
||||||
|
- parameter alpha: alpha: 0.0 - 1.0. The default is 1.0.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
the six-digit hexadecimal representation of color with alpha of the form #RRGGBBAA.
|
||||||
|
|
||||||
|
- parameter hex8: Eight-digit hexadecimal value.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
convenience failable initializer which creates an instance with given hex color values if string has a correct format
|
||||||
|
|
||||||
|
- parameter hexString: hex string with red green and blue values (can have `#` sign)
|
||||||
|
- parameter alpha: alpha component used if not given in hexString
|
||||||
|
*/
|
||||||
|
public convenience init?(hexString: String, alpha: CGFloat = 1) {
|
||||||
|
let hexStr: String
|
||||||
|
|
||||||
|
if hexString.hasPrefix("#") {
|
||||||
|
hexStr = hexString.substring(from: hexString.characters.index(hexString.startIndex, offsetBy: 1))
|
||||||
|
} else {
|
||||||
|
hexStr = hexString
|
||||||
|
}
|
||||||
|
|
||||||
|
let charactersCount = hexStr.characters.count
|
||||||
|
|
||||||
|
switch charactersCount {
|
||||||
|
case 3, 4:
|
||||||
|
if let hex = UInt16(hexStr, radix: 16) {
|
||||||
|
if charactersCount == 3 {
|
||||||
|
self.init(hex3: hex, alpha: alpha)
|
||||||
|
} else {
|
||||||
|
self.init(hex4: hex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case 6, 8:
|
||||||
|
if let hex = UInt32(hexStr, radix: 16) {
|
||||||
|
if charactersCount == 6 {
|
||||||
|
self.init(hex6: hex, alpha: alpha)
|
||||||
|
} else {
|
||||||
|
self.init(hex8: hex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
//
|
||||||
|
// UIImage+Extensions.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 05/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// UIStoryboard+InstantiateViewController.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 24/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// UITableView+CellRegistration.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public extension UITableView {
|
||||||
|
/**
|
||||||
|
method which register UITableViewCell subclass for reusing in UITableView with reuse identifier
|
||||||
|
provided by ReuseIdentifierProtocol protocol implementation and nib name
|
||||||
|
provided by StaticNibNameProtocol protocol implementation
|
||||||
|
|
||||||
|
- parameter cellClass: UITableViewCell subclass which implements ReuseIdentifierProtocol and StaticNibNameProtocol
|
||||||
|
|
||||||
|
- see: ReuseIdentifierProtocol, StaticNibNameProtocol
|
||||||
|
*/
|
||||||
|
|
||||||
|
public func registerNib<T>(forCellClass cellClass: T.Type)
|
||||||
|
where T: ReuseIdentifierProtocol, T: UITableViewCell, T: StaticNibNameProtocol {
|
||||||
|
|
||||||
|
register(UINib(nibName: T.nibName), forCellReuseIdentifier: T.reuseIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
method which register UITableViewCell subclass for reusing in UITableView with reuse identifier
|
||||||
|
provided by ReuseIdentifierProtocol protocol implementation and nib name
|
||||||
|
provided by NibNameProtocol protocol implementation
|
||||||
|
|
||||||
|
- parameter cellClass: UITableViewCell 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: UITableViewCell, T: NibNameProtocol {
|
||||||
|
|
||||||
|
let nib = UINib(nibName: T.nibName(forConfiguration: interfaceIdiom))
|
||||||
|
register(nib, forCellReuseIdentifier: T.reuseIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// UIView+DefaultNibName.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 10/02/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIView: StaticNibNameProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
default implementation of StaticNibNameProtocol
|
||||||
|
|
||||||
|
- returns: class name string
|
||||||
|
*/
|
||||||
|
open class var nibName: String {
|
||||||
|
return className(of: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// UIView+DefaultReuseIdentifier.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 26/07/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIView: ReuseIdentifierProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
default implementation of ReuseIdentifierProtocol
|
||||||
|
|
||||||
|
- returns: type name string
|
||||||
|
*/
|
||||||
|
open class var reuseIdentifier: String {
|
||||||
|
return className(of: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// UIView+LoadFromNib.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// UIViewController+DefaultStoryboardIdentifier.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 06/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIViewController: StoryboardIdentifierProtocol {
|
||||||
|
|
||||||
|
/**
|
||||||
|
default implementation of StoryboardIdentifierProtocol
|
||||||
|
|
||||||
|
- returns: type name string
|
||||||
|
*/
|
||||||
|
open class var storyboardIdentifier: String {
|
||||||
|
return className(of: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
//
|
||||||
|
// UserDefaults+MappableDataTypes.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 26/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
/// - wrongStoredValueType: the stored value type is unsuitable for performing mapping with it
|
||||||
|
/// - unableToMap: the value cannot be mapped to given type for some reason
|
||||||
|
public enum UserDefaultsError: Error {
|
||||||
|
|
||||||
|
case noSuchValue(key: String)
|
||||||
|
case wrongStoredValueType(expected: Any.Type, received: Any.Type)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let storedValue = objectForKey as? ST else {
|
||||||
|
throw UserDefaultsError.wrongStoredValueType(expected: ST.self, received: type(of: objectForKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// AbstractMethod.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public func abstractMethod(_ methodName: String = "Method") -> Never {
|
||||||
|
fatalError("\(methodName) has not been implemented")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Any+TypeName.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 06/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// MemoryStore.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class MemoryStore<TKey: Hashable, TData> {
|
||||||
|
public typealias KeyType = TKey
|
||||||
|
public typealias DataType = TData
|
||||||
|
|
||||||
|
private var syncLock = Sync.Lock()
|
||||||
|
private var dictionaryStore: [KeyType : DataType] = [:]
|
||||||
|
private let clearOnMemoryWarning: Bool
|
||||||
|
|
||||||
|
public init(clearOnMemoryWarning: Bool = false) {
|
||||||
|
self.clearOnMemoryWarning = clearOnMemoryWarning
|
||||||
|
|
||||||
|
if clearOnMemoryWarning {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(onMemoryWarning),
|
||||||
|
name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeAllData() {
|
||||||
|
syncLock.sync {
|
||||||
|
return dictionaryStore.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadData(key: KeyType) -> DataType? {
|
||||||
|
return syncLock.sync {
|
||||||
|
return dictionaryStore[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func storeData(key: TKey, data: DataType?) {
|
||||||
|
syncLock.sync {
|
||||||
|
guard let data = data else {
|
||||||
|
dictionaryStore.removeValue(forKey: key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionaryStore[key] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func onMemoryWarning() {
|
||||||
|
removeAllData()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if clearOnMemoryWarning {
|
||||||
|
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// Sync.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Anton on 26.11.16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
public final class Sync {
|
||||||
|
public final class Lock {
|
||||||
|
public func lock() {
|
||||||
|
Sync.beginSyncLock(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unlock() {
|
||||||
|
Sync.endSyncLock(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sync(block: () throws -> Void) rethrows {
|
||||||
|
try Sync.synchronized(lockObj: self, block: block)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sync<T>(block: () throws -> T) rethrows -> T {
|
||||||
|
return try Sync.synchronized(lockObj: self, block: block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func beginSyncLock(_ lockObj: Any) -> Void {
|
||||||
|
objc_sync_enter(lockObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func endSyncLock(_ lockObj: Any) -> Void {
|
||||||
|
objc_sync_exit(lockObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func synchronized(lockObj: Any, block: () throws -> Void) rethrows -> Void {
|
||||||
|
beginSyncLock(lockObj)
|
||||||
|
try block()
|
||||||
|
endSyncLock(lockObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func synchronized<T>(lockObj: Any, block: () throws -> T) rethrows -> T {
|
||||||
|
beginSyncLock(lockObj)
|
||||||
|
let result: T = try block()
|
||||||
|
endSyncLock(lockObj)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.0</string>
|
<string>0.2.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// CursorType.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 23/11/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
|
||||||
|
/// Protocol which describes Cursor data type
|
||||||
|
public protocol CursorType {
|
||||||
|
|
||||||
|
associatedtype Element
|
||||||
|
|
||||||
|
associatedtype LoadResultType
|
||||||
|
|
||||||
|
/// Indicates that cursor load all available results
|
||||||
|
var exhausted: Bool { get }
|
||||||
|
|
||||||
|
/// Current number of items in cursor
|
||||||
|
var count: Int { get }
|
||||||
|
|
||||||
|
subscript(index: Int) -> Self.Element { get }
|
||||||
|
|
||||||
|
/// Loads next batch of results
|
||||||
|
///
|
||||||
|
/// - Returns: Observable of LoadResultType
|
||||||
|
func loadNextBatch() -> Observable<LoadResultType>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// EstimatedViewheightProtocol.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 12/04/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// NIbNameProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// ReuseIdentifierProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 24/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* protocol which ensures that specific type can return reuse identifier for view
|
||||||
|
*/
|
||||||
|
public protocol AbstractReuseIdentifierProtocol {
|
||||||
|
associatedtype IdentifierType
|
||||||
|
|
||||||
|
/**
|
||||||
|
- returns: reuse identifier with protocol associated type
|
||||||
|
*/
|
||||||
|
static var reuseIdentifier: IdentifierType { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* protocol which ensures that specific type can return string reuse identifier for view
|
||||||
|
*/
|
||||||
|
public protocol ReuseIdentifierProtocol: AbstractReuseIdentifierProtocol {
|
||||||
|
/**
|
||||||
|
- returns: reuse identifier with string type
|
||||||
|
*/
|
||||||
|
static var reuseIdentifier: String { get }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// StaticEstimatedViewheightProtocol.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 31/03/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// NibNameProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* protocol which ensures that specific type can return nib name of view
|
||||||
|
*/
|
||||||
|
public protocol StaticNibNameProtocol {
|
||||||
|
/**
|
||||||
|
- returns: nib name string
|
||||||
|
*/
|
||||||
|
static var nibName: String { get }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// StaticViewHeightProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* protocol which ensures that specific type can return height of view
|
||||||
|
*/
|
||||||
|
public protocol StaticViewHeightProtocol {
|
||||||
|
/**
|
||||||
|
method which returns view height
|
||||||
|
|
||||||
|
- returns: view height
|
||||||
|
*/
|
||||||
|
static func viewHeight() -> CGFloat
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// StoryboardIdentifierProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 24/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// StoryboardProtocol.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 20/10/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// ViewHeightProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- parameter viewModel: object which represents view model of view
|
||||||
|
|
||||||
|
- returns: view height
|
||||||
|
*/
|
||||||
|
static func viewHeight(forViewModel viewModel: ViewModelType) -> CGFloat
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// ViewModelProtocol.swift
|
||||||
|
// Knapsack
|
||||||
|
//
|
||||||
|
// Created by Иван Смолин on 30/01/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* protocol which ensures that specific type can create view model and can apply new view state with view model
|
||||||
|
*/
|
||||||
|
public protocol AbstractViewModelProtocol {
|
||||||
|
associatedtype ViewModelType
|
||||||
|
|
||||||
|
/**
|
||||||
|
method which applies new view state with view model object
|
||||||
|
|
||||||
|
- parameter viewModel: view model to apply new view state
|
||||||
|
|
||||||
|
- returns: nothing
|
||||||
|
*/
|
||||||
|
func setViewModel(_ viewModel: ViewModelType)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// ApiRequestParameters.swift
|
||||||
|
// LeadKit
|
||||||
|
//
|
||||||
|
// Created by Ivan Smolin on 27/07/16.
|
||||||
|
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Struct which keeps base parameters required for api request
|
||||||
|
*/
|
||||||
|
public struct ApiRequestParameters {
|
||||||
|
|
||||||
|
let method: HTTPMethod
|
||||||
|
let url: URLConvertible
|
||||||
|
let parameters: Parameters?
|
||||||
|
let encoding: ParameterEncoding
|
||||||
|
let headers: HTTPHeaders?
|
||||||
|
|
||||||
|
public init(url: URLConvertible,
|
||||||
|
method: HTTPMethod = .get,
|
||||||
|
parameters: Parameters? = nil,
|
||||||
|
encoding: ParameterEncoding = URLEncoding.default,
|
||||||
|
headers: HTTPHeaders? = nil) {
|
||||||
|
|
||||||
|
self.method = method
|
||||||
|
self.url = url
|
||||||
|
self.parameters = parameters
|
||||||
|
self.encoding = encoding
|
||||||
|
self.headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
@ -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
107
Makefile
|
|
@ -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 |
|
|
@ -1,151 +0,0 @@
|
||||||
# OTPSwiftView
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
198
Package.swift
198
Package.swift
|
|
@ -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
|
|
||||||
|
|
@ -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
139
README.md
|
|
@ -1,139 +1,2 @@
|
||||||
# LeadKit
|
# LeadKit
|
||||||
|
LeadKit it's a iOS framework with a bunch of tools for rapid app development
|
||||||
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.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue