Compare commits

..

No commits in common. "master" and "feature/test" have entirely different histories.

405 changed files with 2097 additions and 25098 deletions

2
.gitmodules vendored
View File

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

View File

@ -1,146 +1,5 @@
# 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

View File

@ -89,10 +89,9 @@ GEM
PLATFORMS
x86_64-darwin-20
x86_64-darwin-21
DEPENDENCIES
cocoapods (~> 1.11)
BUNDLED WITH
2.3.26
2.3.10

View File

@ -2,10 +2,10 @@ Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "1.35.0"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://git.svc.touchin.ru/TouchInstinct/LeadKit"
s.homepage = "https://gitlab.ti/touchinstinct/LeadKit"
s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct"
s.source = { :git => "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", :tag => s.version }
s.source = { :git => "https://gitlab.ti/touchinstinct/LeadKit.git", :tag => s.version }
s.platform = :ios, '10.0'
s.swift_versions = ['5.1']

107
Makefile
View File

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

View File

@ -5,17 +5,8 @@
"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"
"revision" : "f96b619bcb2383b43d898402283924b80e2c4bae",
"version" : "5.4.3"
}
},
{
@ -32,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/petropavel13/Cursors",
"state" : {
"revision" : "52f27b82cb1cbbc2b5fd09514c48b9c75e3b0300",
"version" : "0.6.0"
"revision" : "a1561869135e72832eff3b1e729075c56c2eebf6",
"version" : "0.5.1"
}
},
{
@ -50,17 +41,8 @@
"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"
"revision" : "9b906860e3c3c09032879465c471e6375829593f",
"version" : "15.0.0"
}
},
{
@ -77,17 +59,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
"version" : "6.6.0"
"revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version" : "6.5.0"
}
},
{
"identity" : "tablekit",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/TableKit.git",
"location" : "https://github.com/maxsokolov/TableKit.git",
"state" : {
"revision" : "fec9537745799fab55df7477cb3ec2b4ea5c254d",
"version" : "2.12.0"
"revision" : "8bf4840d9d0475a92352f02f368f88b74eced447",
"version" : "2.11.0"
}
}
],

View File

@ -1,35 +1,24 @@
// swift-tools-version:5.7
#if canImport(PackageDescription)
import PackageDescription
let package = Package(
name: "LeadKit",
platforms: [
.iOS(.v12)
.iOS(.v11)
],
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"]),
@ -47,136 +36,55 @@ let package = Package(
.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"])
//MARK: - Skolkovo
.library(name: "TIEcommerce", targets: ["TIEcommerce"])
],
dependencies: [
.package(url: "https://git.svc.touchin.ru/TouchInstinct/TableKit.git", .upToNextMinor(from: "2.12.0")),
.package(url: "https://github.com/maxsokolov/TableKit.git", .upToNextMajor(from: "2.11.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"))
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.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: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.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"),
.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: "TISwiftUtils", path: "TISwiftUtils/Sources"),
.target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils", exclude: ["TIFoundationUtils.app"]),
.target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"),
.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")]),
.target(name: "TINetworking", dependencies: ["TIFoundationUtils", "Alamofire"], path: "TINetworking/Sources"),
.target(name: "TIMoyaNetworking", dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], path: "TIMoyaNetworking"),
.target(name: "TINetworkingCache", dependencies: ["TIFoundationUtils", "TINetworking", "Cache"], path: "TINetworkingCache/Sources"),
// 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")]),
.target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"),
.target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"),
// 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: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "KeychainAccess"], 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
@ -184,15 +92,5 @@ let package = Package(
name: "TITimerTests",
dependencies: ["TIFoundationUtils"],
path: "Tests/TITimerTests"),
.testTarget(
name: "TITextProcessingTests",
dependencies: ["TITextProcessing"],
path: "Tests/TITextProcessingTests"),
.testTarget(
name: "TIFoundationUtilsTests",
dependencies: ["TIFoundationUtils", "TISwiftUtils", "TILogging"],
path: "Tests/TIFoundationUtilsTests")
]
)
#endif

View File

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

View File

@ -36,7 +36,6 @@ target 'TIModuleName' do
use_frameworks!
pod 'TIDependencyModuleName', :path => '../../../../TIDependencyModuleName/TIDependencyModuleName.podspec'
pod 'TIModuleName', :path => '../../../../TIModuleName/TIModuleName.podspec'
end" > PlaygroundPodfile
$ nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile
@ -89,17 +88,10 @@ LICENSE
- [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.
@ -110,7 +102,7 @@ LICENSE
./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).
- If legacy [Source](https://gitlab.ti/touchinstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://gitlab.ti/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).
@ -120,14 +112,14 @@ LICENSE
```swift
dependencies: [
.package(url: "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", from: "x.y.z"),
.package(url: "https://gitlab.ti/touchinstinct/LeadKit.git", from: "x.y.z"),
],
```
### Cocoapods
```ruby
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
source 'https://gitlab.ti/touchinstinct/Podspecs.git'
pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z'

View File

@ -24,10 +24,9 @@ import TIMapUtils
import MapKit
open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
ApplePlacemarkManager<DataModel>,
AppleClusterPlacemarkManager<DataModel>,
MKCameraUpdate,
AppleMapUISettings> {
ApplePlacemarkManager<DataModel>,
AppleClusterPlacemarkManager<DataModel>,
MKCameraUpdate> {
public typealias ClusteringIdentifier = String
@ -67,18 +66,27 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
}
public convenience init(map: MKMapView,
iconFactory: DefaultMarkerIconFactory<DataModel>? = nil,
clusterIconFactory: DefaultClusterMarkerIconFactory<DataModel>? = nil,
positionGetter: @escaping PositionGetter,
clusteringIdentifierGetter: @escaping (DataModel) -> ClusteringIdentifier,
mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler)
where DataModel: MapLocatable, DataModel.Position == CLLocationCoordinate2D,
DataModel: Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier {
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) {
self.init(map: map,
positionGetter: positionGetter,
clusteringIdentifierGetter: clusteringIdentifierGetter,
iconFactory: nil as DefaultMarkerIconFactory<DataModel>?,
clusterIconFactory: nil as DefaultClusterMarkerIconFactory<DataModel>?,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}
public convenience init(map: MKMapView,
mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) where DataModel: MapLocatable & Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier, DataModel.Position == CLLocationCoordinate2D {
self.init(map: map,
positionGetter: { $0.position },
clusteringIdentifierGetter: { $0.clusterIdentifier },
iconFactory: iconFactory,
clusterIconFactory: clusterIconFactory,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}

View File

@ -1,58 +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 TIMapUtils
import MapKit
open class AppleMapUISettings: BaseMapUISettings<MKMapView> {
open class Defaults: BaseMapUISettings<MKMapView>.Defaults {
public static var showCompassButton: Bool {
false
}
}
public var showCompassButton = false
public init(showUserLocation: Bool = Defaults.showUserLocation,
isZoomEnabled: Bool = Defaults.isZoomEnabled,
isTiltEnabled: Bool = Defaults.isTiltEnabled,
isRotationEnabled: Bool = Defaults.isRotationEnabled,
showCompassButton: Bool = Defaults.showCompassButton) {
self.showCompassButton = showCompassButton
super.init(showUserLocation: showUserLocation,
isZoomEnabled: isZoomEnabled,
isTiltEnabled: isTiltEnabled,
isRotationEnabled: isRotationEnabled)
}
override open func apply(to mapView: MKMapView) {
super.apply(to: mapView)
mapView.showsUserLocation = showUserLocation
mapView.isZoomEnabled = isZoomEnabled
mapView.isPitchEnabled = isTiltEnabled
mapView.isRotateEnabled = isRotationEnabled
mapView.showsCompass = showCompassButton
}
}

View File

@ -50,13 +50,8 @@ open class ApplePlacemarkManager<Model>: BaseItemPlacemarkManager<MKAnnotationVi
Although the icon is being updated, it is necessary to manually deselect
the annotation of the current placemark if it is currently selected.
*/
if let annotation = placemark.annotation {
switch state {
case .default:
map.deselectAnnotation(annotation, animated: true)
case .selected:
map.selectAnnotation(annotation, animated: true)
}
if state == .default, let annotation = placemark.annotation {
map.deselectAnnotation(annotation, animated: true)
}
placemark.image = iconFactory?.markerIcon(for: dataModel, state: state)

View File

@ -1,23 +1,16 @@
Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils'
s.version = '1.56.0'
s.version = '1.42.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.3']
sources = 'Sources/**/*'
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
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIMapUtils', s.version.to_s
end

View File

@ -1,54 +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 Foundation
public struct BundleIdentifier {
public let appPrefix: String
public let appIdentifier: String
public let fullIdentifier: String
public let defaultAppGroupIdenfier: String
static let bundleIdentifierSeparator = "."
public init(appPrefix: String, appIdentifier: String) {
self.appPrefix = appPrefix
self.appIdentifier = appIdentifier
self.fullIdentifier = appPrefix + Self.bundleIdentifierSeparator + appIdentifier
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
public init?(bundle: Bundle = .main) {
guard let fullIdenfifier = bundle.bundleIdentifier,
var components = bundle.bundleIdentifier?
.components(separatedBy: Self.bundleIdentifierSeparator),
let lastComponent = components.popLast() else {
return nil
}
self.fullIdentifier = fullIdenfifier
self.appIdentifier = lastComponent
self.appPrefix = components.joined(separator: Self.bundleIdentifierSeparator)
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
}

View File

@ -1,116 +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 Foundation
import TIFoundationUtils
import KeychainAccess
import TILogging
import UIKit
public struct CoreDependencies {
public var dateFormattersResusePool = DateFormattersReusePool()
public var iso8601DateFormattersReusePool = ISO8601DateFormattersReusePool()
public var jsonCodingConfigurator: JsonCodingConfigurator
public var jsonKeyValueDecoder: JSONKeyValueDecoder
public var jsonKeyValueEncoder: JSONKeyValueEncoder
public var device: UIDevice = .current
public var bundle: Bundle = .main
public var fileManager: FileManager = .default
public var logger: DefaultOSLogErrorLogger
public var keychain: Keychain
public var defaults: UserDefaults
public var appGroupDefaults: UserDefaults?
public var appGroupKeychain: Keychain?
public var appGroupCacheDirectory: URL?
public var networkCallbackQueue: DispatchQueue
public init(bundleIdentifier: BundleIdentifier,
customAppGroupIdentifier: String? = nil) {
jsonCodingConfigurator = JsonCodingConfigurator(dateFormattersReusePool: dateFormattersResusePool,
iso8601DateFormattersReusePool: iso8601DateFormattersReusePool)
jsonKeyValueDecoder = JSONKeyValueDecoder(jsonDecoder: jsonCodingConfigurator.jsonDecoder)
jsonKeyValueEncoder = JSONKeyValueEncoder(jsonEncoder: jsonCodingConfigurator.jsonEncoder)
logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier.fullIdentifier, category: "general")
keychain = Keychain(service: bundleIdentifier.fullIdentifier).accessibility(.whenUnlockedThisDeviceOnly)
defaults = .standard
let appGroupIdentifier = customAppGroupIdentifier ?? bundleIdentifier.defaultAppGroupIdenfier
if let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
var appGroupCacheURL: URL
if #available(iOS 16.0, *) {
appGroupCacheURL = containerURL.appending(path: "Caches", directoryHint: .isDirectory)
} else {
appGroupCacheURL = containerURL.appendingPathComponent("Caches", isDirectory: true)
}
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
do {
try fileManager.createDirectory(at: appGroupCacheURL,
withIntermediateDirectories: true)
try appGroupCacheURL.setResourceValues(resourceValues)
appGroupCacheDirectory = appGroupCacheURL
} catch {
logger.log(error: error, file: #file, line: #line)
}
appGroupDefaults = UserDefaults(suiteName: appGroupIdentifier)
appGroupKeychain = Keychain(service: bundleIdentifier.appPrefix,
accessGroup: appGroupIdentifier)
.accessibility(.whenUnlockedThisDeviceOnly)
} else {
appGroupCacheDirectory = nil
appGroupDefaults = nil
appGroupKeychain = nil
}
networkCallbackQueue = DispatchQueue(label: bundleIdentifier.fullIdentifier + ".network-callback-queue",
attributes: .concurrent)
}
public init?(bundle: Bundle = .main,
customAppGroupIdentifier: String? = nil) {
guard let bundleIdentifier = BundleIdentifier(bundle: bundle) else {
return nil
}
self.init(bundleIdentifier: bundleIdentifier,
customAppGroupIdentifier: customAppGroupIdentifier)
}
}

View File

@ -1,25 +0,0 @@
Pod::Spec.new do |s|
s.name = 'TIApplication'
s.version = '1.56.0'
s.summary = 'Application architecture.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
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
s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TILogging', s.version.to_s
s.dependency 'KeychainAccess', "~> 4.2"
end

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2023 Touch Instinct
// Copyright (c) 2022 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
@ -22,6 +22,7 @@
import Foundation
public protocol RegexPlaceholderGenerator {
func generatePlaceholder(matrixOfSymbols: [[Character]]) -> String
public protocol AuthSettingsStorage: AnyObject {
/// Should be true by default (on app first run)
var shouldResetStoredAuthData: Bool { get set }
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2023 Touch Instinct
// Copyright (c) 2022 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
@ -21,25 +21,32 @@
//
import TIFoundationUtils
import Foundation
final class StringSingleValueMockStorage: BaseSingleValueMockStorage<String> {
init(storage: MockStorage, storageKey: StorageKey<String>) {
let getValueClosure: GetValueClosure = { storage, storageKey in
guard let value = storage.getValue(forKey: storageKey) else {
return .failure(.valueNotFound)
}
return .success(value)
open class DefaultAuthSettingsStorage: AuthSettingsStorage {
public enum Defaults {
public static var shouldResetAuthDataKey: String {
"shouldResetAuthData"
}
}
let storeValueClosure: StoreValueClosure = { storage, value, storageKey in
.success(storage.store(value: value, forKey: storageKey))
private let reinstallChecker: AppReinstallChecker
// MARK: - PinCodeSettingsStorage
open var shouldResetStoredAuthData: Bool {
get {
reinstallChecker.isAppFirstRun
}
set {
reinstallChecker.isAppFirstRun = newValue
}
}
super.init(storage: storage,
storageKey: storageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure)
public init(defaultsStorage: UserDefaults = .standard,
storageKey: String = Defaults.shouldResetAuthDataKey) {
self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage,
storageKey: storageKey)
}
}

View File

@ -22,13 +22,12 @@
import KeychainAccess
import Foundation
import TIFoundationUtils
import LocalAuthentication
open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenKeyStorageKey: StorageKey<Data> {
.init(rawValue: keychainServiceIdentifier + ".encryptedTokenKey")
public static var encryptedTokenKeyStorageKey: String {
keychainServiceIdentifier + ".encryptedTokenKey"
}
public static var reusableLAContext: LAContext {
@ -40,12 +39,12 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
localAuthContext: LAContext = Defaults.reusableLAContext,
encryptedTokenKeyStorageKey: StorageKey<Data> = Defaults.encryptedTokenKeyStorageKey,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(),
encryptedTokenKeyStorageKey: String = Defaults.encryptedTokenKeyStorageKey) {
let getValueClosure: GetValueClosure = { keychain, storageKey in
do {
guard let value = try keychain.getData(storageKey.rawValue) else {
guard let value = try keychain.getData(storageKey) else {
return .failure(.valueNotFound)
}
@ -55,18 +54,18 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
}
}
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
let setValueClosure: SetValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value, key: storageKey.rawValue))
return .success(try keychain.set(value, key: storageKey))
} catch {
return .failure(.unableToWriteData(underlyingError: error))
}
}
super.init(keychain: keychain.authenticationContext(localAuthContext),
settingsStorage: settingsStorage,
storageKey: encryptedTokenKeyStorageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
setValueClosure: setValueClosure)
}
}

View File

@ -21,23 +21,22 @@
//
import Foundation
import TIFoundationUtils
import KeychainAccess
open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEncryptionResult> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenStorageKey: StorageKey<StringEncryptionResult> {
.init(rawValue: keychainServiceIdentifier + ".encryptedToken")
public static var encryptedTokenStorageKey: String {
keychainServiceIdentifier + ".encryptedToken"
}
}
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
encryptedTokenStorageKey: StorageKey<StringEncryptionResult> = Defaults.encryptedTokenStorageKey,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(),
encryptedTokenStorageKey: String = Defaults.encryptedTokenStorageKey) {
let getValueClosure: GetValueClosure = { keychain, storageKey in
do {
guard let value = try keychain.getData(storageKey.rawValue) else {
guard let value = try keychain.getData(storageKey) else {
return .failure(.valueNotFound)
}
@ -51,18 +50,18 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEn
}
}
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
let setValueClosure: SetValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value.asStorableData(), key: storageKey.rawValue))
return .success(try keychain.set(value.asStorableData(), key: storageKey))
} catch {
return .failure(.unableToWriteData(underlyingError: error))
}
}
super.init(keychain: keychain,
settingsStorage: settingsStorage,
storageKey: encryptedTokenStorageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
setValueClosure: setValueClosure)
}
}

View File

@ -23,31 +23,62 @@
import TIFoundationUtils
import KeychainAccess
import Foundation
import TIKeychainUtils
open class SingleValueAuthKeychainStorage<ValueType>:
AppInstallLifetimeSingleValueStorage<BaseSingleValueKeychainStorage<ValueType>, BoolValueDefaultsStorage> {
public typealias GetValueClosure = BaseSingleValueKeychainStorage<ValueType>.GetValueClosure
public typealias StoreValueClosure = BaseSingleValueKeychainStorage<ValueType>.StoreValueClosure
open class SingleValueAuthKeychainStorage<ValueType>: SingleValueStorage {
open class Defaults {
public static var keychainServiceIdentifier: String {
Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth"
}
}
public typealias GetValueClosure = (Keychain, String) -> Result<ValueType, StorageError>
public typealias SetValueClosure = (Keychain, ValueType, String) -> Result<Void, StorageError>
public let keychain: Keychain
public let settingsStorage: AuthSettingsStorage
public let storageKey: String
public let getValueClosure: GetValueClosure
public let setValueClosure: SetValueClosure
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
storageKey: StorageKey<ValueType>,
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(),
storageKey: String,
getValueClosure: @escaping GetValueClosure,
storeValueClosure: @escaping StoreValueClosure,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
setValueClosure: @escaping SetValueClosure) {
let keychainStorage = BaseSingleValueKeychainStorage(keychain: keychain,
storageKey: storageKey,
getValueClosure: getValueClosure,
storeValueClosure: storeValueClosure)
self.keychain = keychain
self.settingsStorage = settingsStorage
self.storageKey = storageKey
self.getValueClosure = getValueClosure
self.setValueClosure = setValueClosure
}
super.init(storage: keychainStorage, appFirstRunCheckStorage: appFirstRunCheckStorage)
// MARK: - SingleValueStorage
open func hasStoredValue() -> Bool {
!settingsStorage.shouldResetStoredAuthData && ((try? keychain.contains(storageKey)) ?? false)
}
open func store(value: ValueType) -> Result<Void, StorageError> {
return setValueClosure(keychain, value, storageKey)
}
open func getValue() -> Result<ValueType, StorageError> {
guard !settingsStorage.shouldResetStoredAuthData else {
let result: Result<ValueType, StorageError>
do {
try keychain.remove(storageKey)
settingsStorage.shouldResetStoredAuthData = false
result = .failure(.valueNotFound)
} catch {
result = .failure(.unableToWriteData(underlyingError: error))
}
return result
}
return getValueClosure(keychain, storageKey)
}
}

View File

@ -1,25 +1,18 @@
Pod::Spec.new do |s|
s.name = 'TIAuth'
s.version = '1.56.0'
s.version = '1.42.0'
s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_versions = ['5.7']
s.swift_versions = ['5.3']
sources = 'Sources/**/*'
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
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TIKeychainUtils', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'KeychainAccess', "~> 4.2"
end

View File

@ -1,14 +0,0 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 11
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -1,62 +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 TIUIElements
import TIUIKitCore
import UIKit
extension BaseModalViewController {
open class BaseAppearance: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
public var presentationDetents: [ModalViewPresentationDetent]
public var dragViewState: DragView.State
public var headerViewState: ModalHeaderView.State
public var footerViewState: ModalFooterView<FooterContentView>.State
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
presentationDetents: [ModalViewPresentationDetent] = [.maxHeight],
dragViewState: DragView.State = .hidden,
headerViewState: ModalHeaderView.State = .hidden,
footerViewState: ModalFooterView<FooterContentView>.State = .hidden) {
self.presentationDetents = presentationDetents
self.dragViewState = dragViewState
self.headerViewState = headerViewState
self.footerViewState = footerViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
public final class DefaultAppearance: BaseAppearance, ViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}

View File

@ -1,473 +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 TISwiftUtils
import TIUIElements
import TIUIKitCore
import UIKit
import PanModal
open class BaseModalViewController<ContentView: UIView,
FooterContentView: UIView>: BaseInitializableViewController, PanModalPresentable {
// MARK: - Public Properties
public let dragView = DragView()
public let headerView = ModalHeaderView()
private(set) public lazy var contentView = createContentView()
private(set) public lazy var footerView = createFooterView()
public private(set) lazy var dragViewBottomToHeaderViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: headerView.topAnchor)
}()
public private(set) lazy var dragViewBottomToContentViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var dragViewConstraints: SubviewConstraints = {
let trailingConstraint = dragView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: dragView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingConstraint: trailingConstraint,
topConstraint: dragView.topAnchor.constraint(equalTo: view.topAnchor),
bottomConstraint: dragViewBottomToHeaderViewTopConstraint)
let centerXConstraint = dragView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = dragView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: dragView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: dragView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var headerViewToSuperviewTopConstraint: NSLayoutConstraint = {
headerView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var headerBottomToContentTopConstraint: NSLayoutConstraint = {
headerView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var headerViewConstraints: SubviewConstraints = {
let leadingConstraint = headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: dragViewBottomToHeaderViewTopConstraint,
bottomConstraint: headerBottomToContentTopConstraint)
let centerXConstraint = headerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = headerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: headerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: headerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var contentViewTopToSuperviewConstraint: NSLayoutConstraint = {
contentView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var contentViewBottomToSuperviewConstraint: NSLayoutConstraint = {
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
}()
public private(set) lazy var contentViewConstraints: SubviewConstraints = {
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: headerBottomToContentTopConstraint,
bottomConstraint: contentViewBottomToSuperviewConstraint)
let centerXConstraint = contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: contentView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: contentView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var footerViewConstraints: SubviewConstraints = {
let leadingConstraint = footerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = footerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: footerView.topAnchor.constraint(equalTo: contentView.bottomAnchor),
bottomConstraint: footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor))
let centerXConstraint = footerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = footerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: footerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: footerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
// MARK: - Modal View Controller Configuration
public var viewControllerAppearance: BaseAppearance = .init(background: UIViewColorBackground(color: .white))
open var panScrollable: UIScrollView? {
contentView as? UIScrollView
}
public var panScrollableInsets: UIEdgeInsets = .zero {
didSet {
panScrollable?.contentInset = panScrollableInsets
}
}
public var dimmedView = DimmedView()
public var showDragIndicator = true
open var headerViewHeight: CGFloat {
let dragViewHeight = getHeight(of: dragView)
let headerViewHeight = getHeight(of: headerView)
let dragViewVerticalInsets = getDragViewVerticalInsets()
let headerViewVerticalInsets = getHeaderViewVerticalInsets()
return dragViewHeight + headerViewHeight + dragViewVerticalInsets + headerViewVerticalInsets
}
open var longFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.max()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var mediumFormHeight: PanModalHeight {
let detents = getSortedDetents()
if detents.count > 1 {
return detents[1].panModalHeight(headerHeight: headerViewHeight)
}
return detents.first?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var shortFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.min()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
private var keyboardDidShownObserver: NSObjectProtocol?
private var keyboardDidHiddenObserver: NSObjectProtocol?
// MARK: - Life Cycle
deinit {
let notificationCenter = NotificationCenter.default
if let keyboardDidShownObserver {
notificationCenter.removeObserver(keyboardDidShownObserver)
}
if let keyboardDidHiddenObserver {
notificationCenter.removeObserver(keyboardDidHiddenObserver)
}
}
// MARK: - BaseInitializableViewController
open override func addViews() {
super.addViews()
view.addSubviews(dragView, headerView, contentView, footerView)
}
open override func configureLayout() {
super.configureLayout()
for view in [dragView, headerView, contentView, footerView] {
view.translatesAutoresizingMaskIntoConstraints = false
}
configureDragViewLayout()
configureHeaderViewLayout()
configureContentViewLayout()
configureFooterViewLayout()
}
open override func bindViews() {
super.bindViews()
keyboardDidShownObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidShowNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: false)
}
keyboardDidHiddenObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidHideNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: true)
}
}
open override func configureAppearance() {
super.configureAppearance()
view.configureUIView(appearance: viewControllerAppearance)
configureDragViewAppearance()
configureHeaderViewAppearance()
configureFooterViewAppearance()
}
// MARK: - Open Methods
open func createContentView() -> ContentView {
ContentView()
}
open func createFooterView() -> ModalFooterView<FooterContentView> {
ModalFooterView<FooterContentView>()
}
open func configureLayoutForKeyboard(_ notification: Notification, isKeyboardHidden: Bool) {
guard let keyboardHeight = getKeyboardHeight(notification) else {
return
}
if case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState {
let bottomInset = footerViewAppearance.layout.insets.add(\.bottom,
to: \.bottom,
of: .vertical(bottom: keyboardHeight))
footerViewConstraints.edgeConstraints.bottomConstraint.constant = bottomInset
} else if let panScrollable {
var insets = panScrollableInsets
if isKeyboardHidden {
panScrollable.contentInset = insets
} else {
insets.bottom += keyboardHeight
panScrollable.contentInset = insets
}
}
}
// MARK: - Private Methods
private func configureDragViewLayout() {
guard case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState else {
return
}
let bottomConstraint: NSLayoutConstraint
let bottomConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToHeaderViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToContentViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.bottom
}
dragViewConstraints.edgeConstraints.bottomConstraint = bottomConstraint
dragViewConstraints.update(from: dragViewAppearance.layout)
bottomConstraint.setActiveConstantOrDeactivate(constant: bottomConstant)
}
private func configureHeaderViewLayout() {
guard case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState else {
return
}
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = dragViewBottomToHeaderViewTopConstraint
topConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
topConstraint = headerViewToSuperviewTopConstraint
let topInset = headerViewAppearance.layout.insets.top
topConstant = topInset.isFinite ? topInset : .zero
}
headerViewConstraints.edgeConstraints.topConstraint = topConstraint
headerViewConstraints.update(from: headerViewAppearance.layout)
topConstraint.setActiveConstantOrDeactivate(constant: topConstant)
}
private func configureContentViewLayout() {
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
contentViewTopToSuperviewConstraint.isActive = false
topConstraint = headerBottomToContentTopConstraint
topConstant = headerViewAppearance.layout.insets.bottom
} else if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
contentViewTopToSuperviewConstraint.isActive = false
headerBottomToContentTopConstraint.isActive = false
topConstraint = dragViewBottomToContentViewTopConstraint
topConstant = dragViewAppearance.layout.insets.bottom
} else {
headerBottomToContentTopConstraint.isActive = false
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = contentViewTopToSuperviewConstraint
topConstant = .zero
}
contentViewConstraints.edgeConstraints.topConstraint = topConstraint
let layout = UIView.DefaultWrappedLayout(insets: .horizontal(.zero)
.vertical(top: topConstant)
.replacingNan(with: .zero))
contentViewConstraints.update(from: layout)
}
private func configureFooterViewLayout() {
guard case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState else {
return
}
contentViewConstraints.edgeConstraints.bottomConstraint.isActive = false
contentViewConstraints.edgeConstraints.bottomConstraint = footerViewConstraints.edgeConstraints.topConstraint
footerViewConstraints.update(from: footerViewAppearance.layout)
}
private func configureDragViewAppearance() {
switch viewControllerAppearance.dragViewState {
case .hidden:
dragView.isHidden = true
case let .presented(appearance):
dragView.configure(appearance: appearance)
}
}
private func configureHeaderViewAppearance() {
switch viewControllerAppearance.headerViewState {
case .hidden:
headerView.isHidden = true
case let .presented(appearance):
headerView.configure(appearance: appearance)
}
}
private func configureFooterViewAppearance() {
switch viewControllerAppearance.footerViewState {
case .hidden:
footerView.isHidden = true
case let .presented(appearance):
footerView.configureBaseWrappedViewHolder(appearance: appearance)
}
}
private func getSortedDetents() -> [ModalViewPresentationDetent] {
viewControllerAppearance.presentationDetents.uniqued().sorted()
}
private func getHeight(of view: UIView) -> CGFloat {
guard !view.isHidden else {
return .zero
}
return getFittingSize(forView: view).height
}
private func getDragViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.dragViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getHeaderViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.headerViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getFittingSize(forView view: UIView) -> CGSize {
let targetSize = CGSize(width: UIScreen.main.bounds.width,
height: UIView.layoutFittingCompressedSize.height)
return view.systemLayoutSizeFitting(targetSize)
}
private func getKeyboardHeight(_ notification: Notification) -> CGFloat? {
guard let userInfo = notification.userInfo else {
return nil
}
return (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height
}
}

View File

@ -1,54 +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 UIKit
open class DefaultModalWrapperViewController<ContentController: UIViewController>: BaseModalViewController<UIView, UIView> {
private(set) public var contentViewController: ContentController
public init(contentViewController: ContentController) {
self.contentViewController = contentViewController
super.init(nibName: nil, bundle: nil)
addChild(contentViewController)
contentViewController.didMove(toParent: self)
}
@available(*, unavailable)
required public init?(coder: NSCoder) {
contentViewController = ContentController()
super.init(coder: coder)
}
open override func createContentView() -> UIView {
contentViewController.view
}
}
public extension UIViewController {
func wrappedInBottomSheetController() -> DefaultModalWrapperViewController<UIViewController> {
DefaultModalWrapperViewController(contentViewController: self)
}
}

View File

@ -1,34 +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 TIUIKitCore
extension UIViewShadow {
public static var defaultModalViewShadow: Self {
.init {
Color(.black)
Opacity(0.5)
Offset(0, -5)
Radius(10)
}
}
}

View File

@ -1,67 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import PanModal
public struct ModalViewPresentationDetent: Hashable {
// MARK: - Default Values
public static var headerOnly: Self {
Self(height: -.greatestFiniteMagnitude)
}
public static func height(_ height: CGFloat) -> Self {
Self(height: height)
}
public static var maxHeight: Self {
Self(height: .greatestFiniteMagnitude)
}
// MARK: - Public Properties
public var height: CGFloat
// MARK: - Internal Methods
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
if self == .headerOnly {
return .contentHeight(headerHeight)
}
if self == .maxHeight {
return .maxHeight
}
return .contentHeight(height)
}
}
// MARK: - Comparable
extension ModalViewPresentationDetent: Comparable {
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
lhs.height < rhs.height
}
}

View File

@ -1,70 +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 TIUIElements
import TIUIKitCore
import UIKit
public final class DragView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
private enum Constants {
static var dragViewTopInset: CGFloat {
8
}
static var dragViewBorder: UIViewBorder {
UIViewRoundedBorder(cornerRadius: dragViewSize.height / 2)
}
static var dragViewBackground: UIViewColorBackground {
UIViewColorBackground(color: .lightGray)
}
static var dragViewSize: CGSize {
CGSize(width: 52, height: 7)
}
}
public enum State {
case hidden
case presented(Appearance)
}
public final class Appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .vertical(top: Constants.dragViewTopInset),
size: Constants.dragViewSize,
centerOffset: .centerHorizontal()),
background: Constants.dragViewBackground,
border: Constants.dragViewBorder)
}
}
// MARK: - AppearanceConfigurable
public func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
}
}

View File

@ -1,45 +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 TIUIElements
import TIUIKitCore
import UIKit
open class ModalFooterView<ContentView: UIView>: ContainerView<ContentView> {
// MARK: - Nested Types
public enum State {
case hidden
case presented(Appearance)
}
}
public extension ModalFooterView {
final class Appearance: BaseWrappedViewHolderAppearance<DefaultWrappedAppearance, DefaultWrappedLayout>,
WrappedViewHolderAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(bottom: .zero),
size: .fixedHeight(44)))
}
}
}

View File

@ -1,235 +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 TIUIElements
import TIUIKitCore
import UIKit
open class ModalHeaderView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
public enum State {
case hidden
case presented(Appearance)
}
public enum ContentViewState {
case leadingButton(BaseButtonStyle)
case trailingButton(BaseButtonStyle)
case buttons(leading: BaseButtonStyle, trailing: BaseButtonStyle)
case custom(view: UIView, appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>)
}
// MARK: - Public properties
public let leadingButton = StatefulButton()
public let trailingButton = StatefulButton()
public private(set) lazy var leftTrailingToRightLeadingConstraint: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor)
}()
public private(set) lazy var leftTrailingToSuperviewTrailing: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
}()
public private(set) lazy var leadingButtonConstraints: SubviewConstraints = {
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingButton.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: leftTrailingToRightLeadingConstraint,
topConstraint: leadingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: leadingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = leadingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = leadingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: leadingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: leadingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var rightLeadingToSuperviewLeadingConstraint: NSLayoutConstraint = {
trailingButton.leadingAnchor.constraint(equalTo: leadingAnchor)
}()
public private(set) lazy var trailingButtonConstraints: SubviewConstraints = {
let trailingConstraint = trailingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leftTrailingToRightLeadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: trailingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: trailingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = trailingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = trailingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: trailingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: trailingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public var customViewConstraints: SubviewConstraints?
// MARK: - BaseInitializableView
open override func addViews() {
super.addViews()
addSubviews(leadingButton, trailingButton)
}
open override func configureLayout() {
super.configureLayout()
for view in [leadingButton, trailingButton] {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
// MARK: - AppearanceConfigurable
open func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
configureContentView(state: appearance.contentViewState)
}
open func configureContentView(state: ContentViewState) {
var leadingButtonStyle: BaseButtonStyle?
var trailingButtonStyle: BaseButtonStyle?
switch state {
case let .leadingButton(style):
leadingButtonStyle = style
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToSuperviewTrailing
case let .trailingButton(style):
trailingButtonStyle = style
trailingButtonConstraints.edgeConstraints.leadingConstraint = rightLeadingToSuperviewLeadingConstraint
case let .buttons(leading, trailing):
leadingButtonStyle = leading
trailingButtonStyle = trailing
case let .custom(view, appearance):
configureCustomView(view, withLayout: appearance.layout)
view.configureUIView(appearance: appearance)
}
configure(buttonStyle: leadingButtonStyle, forButton: leadingButton, constraints: leadingButtonConstraints)
configure(buttonStyle: trailingButtonStyle, forButton: trailingButton, constraints: trailingButtonConstraints)
if let leadingButtonStyle, let trailingButtonStyle {
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToRightLeadingConstraint
trailingButtonConstraints.edgeConstraints.leadingConstraint = leftTrailingToRightLeadingConstraint
let spacing = leadingButtonStyle.appearance.layout.insets.add(\.right,
to: \.left,
of: trailingButtonStyle.appearance.layout.insets)
leftTrailingToRightLeadingConstraint.setActiveConstantOrDeactivate(constant: spacing)
}
}
// MARK: - Private methods
private func configureCustomView(_ view: UIView, withLayout layout: WrappedViewLayout) {
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
let edgeConstraints = EdgeConstraints(leadingConstraint: view.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: view.trailingAnchor.constraint(equalTo: trailingAnchor),
topConstraint: view.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: view.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerConstraints = CenterConstraints(centerXConstraint: view.centerXAnchor.constraint(equalTo: centerXAnchor),
centerYConstraint: view.centerYAnchor.constraint(equalTo: centerYAnchor))
let sizeConstraints = SizeConstraints(widthConstraint: view.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: view.heightAnchor.constraint(equalToConstant: .zero))
let customViewConstraints = SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
self.customViewConstraints = customViewConstraints
customViewConstraints.update(from: layout)
}
private func configure(buttonStyle: BaseButtonStyle?,
forButton button: StatefulButton,
constraints: SubviewConstraints?) {
guard let buttonStyle else {
button.isHidden = true
return
}
button.isHidden = false
constraints?.update(from: buttonStyle.appearance.layout)
button.apply(style: buttonStyle)
}
}
// MARK: - Appearance
public extension ModalHeaderView {
final class Appearance: BaseWrappedAppearance<DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(top: .zero),
size: .fixedHeight(44)))
}
public var contentViewState: ContentViewState
public init(layout: DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
contentViewState: ContentViewState = .leadingButton(.init())) {
self.contentViewState = contentViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
}

View File

@ -1,50 +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 UIKit
import PanModal
open class PassthroughDimmedView: DimmedView {
public weak var hitTestHandlerView: UIView?
public var isTransparent = false
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hitTestHandlerView else {
return super.hitTest(point, with: event)
}
return hitTestHandlerView.hitTest(point, with: event)
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
hitTestHandlerView == nil || super.point(inside: point, with: event)
}
open override func onChange(dimState: DimmedView.DimState) {
super.onChange(dimState: dimState)
if isTransparent {
alpha = .zero
}
}
}

View File

@ -1,3 +0,0 @@
**/build/
**/nef/
LICENSE

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -1,26 +0,0 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -1,14 +0,0 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 12
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -1,110 +0,0 @@
/*:
# TIBottomSheet
TIBottomSheet содержить базовую реализацию модального котроллера и немного видоизмененную библиотеку PanModal.
## Базовый контроллер
Для создания модального котроллера можно унаследоваться от `BaseModalViewController`. Данный клас принимает два generic типа: тип основного контента, тип контента футера.
*/
import TIBottomSheet
import UIKit
class EmptyViewController: BaseModalViewController<UIView, UIView> { }
/*:
## Обертка вокруг существующего контроллера
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `DefaultModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
*/
import TIUIKitCore
final class OldMassiveViewController: BaseInitializableViewController {
// some implementation
}
typealias ModalOldMassiveViewController = DefaultModalWrapperViewController<OldMassiveViewController>
class PresentingViewController: BaseInitializableViewController {
// some implementation
@objc private func onButtonTapped() {
presentPanModal(ModalOldMassiveViewController(contentViewController: OldMassiveViewController()))
}
}
/*:
## Контент модального контроллера
Модальный котроллер может содержать следующие элементы: `DragView`, `HeaderView`, `FooterView`. Каждый из них является опциональным и без дополнительных настроек не будет показываться.
DragView - небольшая view, за которую пользователь "держит" модальный контроллер
HeaderView - контейнер, содержащий в себе кнопки назад/закрыть или какие-то другие элементы управления
FooterView - view, располагающаяся внизу контроллера, поверх всего контента (модальный контроллер уже настроен так, чтобы при скролле в самый низ, футер не перекрывал последнюю ячейку)
Для настройки каждого у котроллера есть свойство `viewControllerAppearance`. Через него будет настраиваться весь контроллер. Однако стоит заметить, что котроллер не будет настраивать передаваимую вью, содержащую основной контент. Стандартно котроллер будет пытаться расположить контент так, чтобы он заполнил все пространство.
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
*/
import TIUIElements
let customViewController = BaseModalViewController<UIView, UIView>()
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
$0.dragViewState = .presented(.defaultAppearance)
$0.headerViewState = .presented(.make {
$0.layout.size = .fixedHeight(52)
$0.backgroundColor = .white
$0.contentViewState = .leadingButton(.init(titles: [.normal: "Close"],
appearance: .init(stateAppearances: [
.normal: .init(background: UIViewColorBackground(color: .blue))
])))
})
}
/*:
## "Якори" контроллера
Раньше для настройки высоты контроллера необходимо было пользоваться свойствами `longFormHeight`, `shortFormHeight`. В базовом контроллере можно лишь передать список точек на которых контроллер должен будет задержаться:
*/
let detentsViewController = BaseModalViewController<UIView, UIView>()
detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnly, .height(300), .maxHeight]
/*:
- headerOnly будет сам пытаться вычеслить высоту хедера и dragView, показывая только их
- height(_) будет показывать контроллер на переданной высоте
- maxHeight - вся высота экрана (до safeArea)
В данный массив не рекомендуется передавать больше 3 значений, т.к. модальное окно все равно сможет занять только 3 положения на экране.
## DimmedView и PassthroughDimmedView
Для контроля `DimmedView` (затемняющей view) есть отдельное свойство `dimmedView`. Эти классы позволяют настраивать поведение при тапе в затемнённую область и кастомизировать затемнение под ваши нужды.
*/
let shadowViewController = BaseModalViewController<UIView, UIView>()
let dimmedView = PassthroughDimmedView()
dimmedView.hitTestHandlerView = shadowViewController.view
dimmedView.configureUIView(appearance: UIView.DefaultAppearance(shadow: UIViewShadow(radius: 8,
color: .black,
opacity: 0.3)))
shadowViewController.dimmedView = dimmedView
/*:
## Контроль закрытия
`PanModalPresentable` не умеет в настройку закрытия контроллера, делая это самостоятельно через `dismiss(animated:completion:)`. Теперь можно настроить закрытие самостоятельно через свойства: `onTapToDismiss` и `onDragToDismiss`.
# Взаимодействие с PanModal
Если нет необходимости или возможности использовать `BaseModalViewController`, вы все так же можете пользоваться протоколом `PanModalRepresentable`. Вот список изменений протокола:
- Открытие/закрытие модального окна теперь можно настроить с помощью свойств `onTapToDismiss` и `onDragToDismiss`
- Можно настроить промежуточное состояние модального окна с `mediumFormHeight`
- `DimmedView` открыт для наследования и может создаваться в `dimmedView` у
> Для `BaseModalViewController` все свойства из `PanModalPresentable` все также работают, т.е. вы можете их переопределять, добавлять и изменять по необходимости.
*/

View File

@ -1,30 +0,0 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -1,47 +0,0 @@
import Foundation
import XCTest
public extension Nef {
static func run<T: XCTestCase>(testCase class: T.Type) {
startTestObserver()
T.defaultTestSuite.run()
}
static private func startTestObserver() {
_ = testObserverInstalled
}
static private var testObserverInstalled = { () -> NefTestFailObserver in
let testObserver = NefTestFailObserver()
XCTestObservationCenter.shared.addTestObserver(testObserver)
return testObserver
}()
}
// MARK: enrich the output for XCTest
fileprivate class NefTestFailObserver: NSObject, XCTestObservation {
private var numberOfFailedTests = 0
func testSuiteWillStart(_ testSuite: XCTestSuite) {
numberOfFailedTests = 0
}
func testSuiteDidFinish(_ testSuite: XCTestSuite) {
if numberOfFailedTests > 0 {
print("💢 Test Suite '\(testSuite.name)' finished with \(numberOfFailedTests) failed \(numberOfFailedTests > 1 ? "tests" : "test").")
} else {
print("🔅 Test Suite '\(testSuite.name)' finished successfully.")
}
}
func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: Int) {
numberOfFailedTests += 1
print("Test Fail '\(testCase.name)':\(UInt(lineNumber)): \(description.description)")
}
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -1,396 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.debug.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.release.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
11F06D2789C6CF40767861CF /* Frameworks */ = {
isa = PBXGroup;
children = (
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1F7782E3A7AD7291B7C09F56 /* Pods */ = {
isa = PBXGroup;
children = (
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */,
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TIBottomSheet */,
8B39A26C21D40F8700DE2643 /* Products */,
1F7782E3A7AD7291B7C09F56 /* Pods */,
11F06D2789C6CF40767861CF /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TIBottomSheet */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TIBottomSheet;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TIBottomSheet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */;
buildPhases = (
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TIBottomSheet;
productName = TIBottomSheet2;
productReference = 8BACBE8322576CAD00266845 /* TIBottomSheet.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TIBottomSheet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TIBottomSheet-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TIBottomSheet.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</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 = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef location = "group:TIBottomSheet.playground"></FileRef>
<FileRef
location = "group:TIBottomSheet.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -1,6 +0,0 @@
#!/bin/bash
workspace="TIBottomSheet.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -1 +0,0 @@
TIBottomSheet.app/Contents/MacOS/TIBottomSheet.playground

View File

@ -1,29 +0,0 @@
Pod::Spec.new do |s|
s.name = 'TIBottomSheet'
s.version = '1.56.0'
s.summary = 'Base models for creating bottom sheet view controllers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'castlele' => 'nikita.semenov@touchin.ru',
'petropavel13' => 'ivan.smolin@touchin.ru'}
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
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
s.dependency 'TIUIElements', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
s.dependency 'PanModal', '~> 1.3.0'
end

View File

@ -1,8 +0,0 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 11.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -1,57 +0,0 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public struct TemplateDrawingOperation: OrientationAwareDrawingOperation {
public var image: CGImage
public var imageSize: CGSize
public var color: CGColor
public var flipHorizontallyDuringDrawing: Bool
public init(image: CGImage,
imageSize: CGSize,
color: CGColor,
flipHorizontallyDuringDrawing: Bool = true) {
self.image = image
self.imageSize = imageSize
self.color = color
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
}
public func affectedArea(in context: CGContext?) -> CGRect {
CGRect(origin: .zero, size: imageSize)
}
public func apply(in context: CGContext) {
let imageRect = CGRect(origin: .zero, size: imageSize)
apply(in: context) {
$0.setFillColor(color)
$0.clip(to: imageRect, mask: image)
$0.fill(imageRect)
$0.setBlendMode(.multiply)
}
}
}

View File

@ -1,5 +0,0 @@
# gitignore nef files
**/build/
**/nef/
LICENSE
end

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -1,26 +0,0 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -1,8 +0,0 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 12.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -1,128 +0,0 @@
/*:
# `DrawingOperation` - протокол для инкапсулирования низкоуровневых вызовов отрисовки CoreGraphicss.
Позволяет:
- сгруппировать низкоуровневые операции CoreGraphics в более высокоуровневые
- использовать композицию из высокоуровневых операций
- получить размер изображения необходимый для создания контекста
## Базовые операции
"Из коробки" доступны самые часто испольуемые операции.
*/
/*:
### `SolidFillDrawingOperation`
Операция для заполнения области рисования выбранным цветом и выбранной формой.
Например, можно нарисовать прямоугольник или круг на существующей области рисования
или просто создать изображение с однотонный фоном.
*/
import TICoreGraphicsUtils
import UIKit
let solidFillSize = CGSize(width: 200, height: 200)
let renderer = UIGraphicsImageRenderer(size: solidFillSize)
let solidFillDrawingOperation = SolidFillDrawingOperation(color: UIColor.purple.cgColor,
rect: CGRect(origin: .zero,
size: solidFillSize))
let solidFillImage = renderer.image {
solidFillDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `BorderDrawingOperation`
Операция создаёт рамку определённой формы и размера.
*/
let borderDrawingOperation = BorderDrawingOperation(frameableContentRect: CGRect(origin: .zero,
size: solidFillSize),
border: 2,
color: UIColor.red.cgColor,
radius: 4,
exteriorBorder: false)
let borderImage = renderer.image {
borderDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `CALayerDrawingOperation`
Операция отрисовывает содержимое CALayer в изображение. Обычно используется для снапшота UIView.
*/
let button = UIButton(type: .custom)
button.setTitle("This is button", for: .normal)
button.setBackgroundImage(borderImage, for: .normal)
button.sizeToFit()
let layerDrawingOperation = CALayerDrawingOperation(layer: button.layer, offset: .zero)
let layerRenderer = UIGraphicsImageRenderer(size: button.bounds.size)
let buttonSnapshotImage = layerRenderer.image {
layerDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `TextDrawingOperation`
Операция отрисовывает текст с заданными атрибутами
*/
let textDrawingOperaton = TextDrawingOperation(text: "This is string",
textAttributes: [
.font: UIFont.boldSystemFont(ofSize: 15),
.foregroundColor: UIColor.white
])
let textRenderer = UIGraphicsImageRenderer(size: textDrawingOperaton.desiredContextSize)
let textImage = textRenderer.image {
textDrawingOperaton.apply(in: $0.cgContext)
}
/*:
### `TemplateDrawingOperation`
Операция заменяет все цвета кроме прозрачного на переданный цвет.
Используется для приданиям иконкам определённого цвета (аналог tintColor).
*/
if let cgImage = textImage.cgImage {
let templateDrawingOperation = TemplateDrawingOperation(image: cgImage,
imageSize: textImage.size,
color: UIColor.red.cgColor)
let tintedImage = textRenderer.image {
templateDrawingOperation.apply(in: $0.cgContext)
}
}
/*:
### `TransformDrawingOperation`
Операция производит изменение размера изображения учитывая пропорции
и другие настройки (по аналогии с contentMode у UIImage).
*/
if let cgImage = textImage.cgImage {
let newSize = CGSize(width: textImage.size.width / 1.5, height: textImage.size.height)
let resizeOperation = TransformDrawingOperation(image: cgImage,
imageSize: textImage.size,
maxNewSize: newSize,
resizeMode: .scaleAspectFill)
let resizeRenderer = UIGraphicsImageRenderer(size: newSize)
let resizedImage = resizeRenderer.image {
resizeOperation.apply(in: $0.cgContext)
}
}

View File

@ -1,30 +0,0 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -1,396 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.debug.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.release.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.release.xcconfig"; sourceTree = "<group>"; };
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2238D5BA030C14EBEF939AC4 /* Pods */ = {
isa = PBXGroup;
children = (
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */,
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */,
8B39A26C21D40F8700DE2643 /* Products */,
2238D5BA030C14EBEF939AC4 /* Pods */,
B51B4BA44249652EDC4FBF61 /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TICoreGraphicsUtils;
sourceTree = "<group>";
};
B51B4BA44249652EDC4FBF61 /* Frameworks */ = {
isa = PBXGroup;
children = (
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */;
buildPhases = (
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TICoreGraphicsUtils;
productName = TICoreGraphicsUtils2;
productReference = 8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TICoreGraphicsUtils-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TICoreGraphicsUtils.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</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 = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef location = "group:TICoreGraphicsUtils.playground"></FileRef>
<FileRef
location = "group:TICoreGraphicsUtils.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -1,6 +0,0 @@
#!/bin/bash
workspace="TICoreGraphicsUtils.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -1 +0,0 @@
TICoreGraphicsUtils.app/Contents/MacOS/TICoreGraphicsUtils.playground

View File

@ -1,23 +0,0 @@
Pod::Spec.new do |s|
s.name = 'TICoreGraphicsUtils'
s.version = '1.56.0'
s.summary = 'CoreGraphics drawing helpers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
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
s.framework = 'CoreGraphics'
end

View File

@ -4,8 +4,6 @@ target 'TIDeeplink' do
platform :ios, 11
use_frameworks!
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIDeeplink', :path => '../../../../TIDeeplink/TIDeeplink.podspec'
end

View File

@ -1,11 +1,9 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TIDeeplink' do
platform :ios, 12
platform :ios, 11
use_frameworks!
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIDeeplink', :path => '../../../../TIDeeplink/TIDeeplink.podspec'
end

View File

@ -1,16 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:TIDeeplink.playground">
</FileRef>
<FileRef
<FileRef location = "group:TIDeeplink.playground"></FileRef>
<FileRef
location = "group:TIDeeplink.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
<FileRef
location = "group:../../../../TIDeepLink/TIDeeplink.app/Contents/MacOS/Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,22 +1,22 @@
Pod::Spec.new do |s|
s.name = 'TIDeeplink'
s.version = '1.56.0'
s.version = '1.42.0'
s.summary = 'Deeplink service API'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru',
'castlele' => 'nikita.semenov@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.3']
sources = 'Sources/**/*'
sources = '/Sources/**/*'
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.source_files = s.name + sources
s.exclude_files = s.name + '/*.app'
end

View File

@ -35,26 +35,66 @@ open class DashedBoundsLayer: CAShapeLayer {
// MARK: - Open methods
open func configure(on view: UIView) {
configure(on: view.layer)
}
open func configure(on layer: CALayer) {
fillColor = UIColor.clear.cgColor
strokeColor = dashColor.cgColor
lineWidth = 1
lineDashPattern = [4.0, 2.0]
updateGeometry(newBounds: layer.bounds)
updateGeometry(from: view)
layer.addSublayer(self)
view.layer.addSublayer(self)
viewBoundsObservation = layer.observe(\.bounds, options: [.new]) { [weak self] layer, _ in
self?.updateGeometry(newBounds: layer.bounds)
viewBoundsObservation = view.observe(\.bounds, options: [.new]) { [weak self] view, _ in
self?.updateGeometry(from: view)
}
}
open func updateGeometry(newBounds: CGRect) {
frame = newBounds
path = UIBezierPath(rect: newBounds).cgPath
open func updateGeometry(from view: UIView) {
frame = view.bounds
path = UIBezierPath(rect: view.bounds).cgPath
}
}
// MARK: - UIView + DashedBoundsLayer
public extension UIView {
@discardableResult
func debugBoundsVisually(debugSubviews: Bool = true) -> UIView {
disableBoundsVisuallyDebug()
if debugSubviews {
for subview in subviews {
subview.debugBoundsVisually(debugSubviews: debugSubviews)
}
}
let dashedLayer = DashedBoundsLayer()
dashedLayer.configure(on: self)
return self
}
func disableBoundsVisuallyDebug() {
for sublayer in layer.sublayers ?? [] {
if sublayer is DashedBoundsLayer {
sublayer.removeFromSuperlayer()
}
}
for subview in subviews {
subview.disableBoundsVisuallyDebug()
}
}
}
// MARK: - UIViewController + DashedBoundsLayer
public extension UIViewController {
@discardableResult
func debugBoundsVisually(debugSubviews: Bool = true) -> UIViewController {
view.debugBoundsVisually(debugSubviews: debugSubviews)
return self
}
}

View File

@ -1,69 +0,0 @@
//
// Copyright (c) 2024 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension CALayer {
@discardableResult
func debugBoundsVisually(debugSublayers: Bool = true,
after delay: DispatchTimeInterval? = nil,
filterSublayers isIncluded: @escaping (CALayer) -> Bool = { _ in true }) -> Self {
guard let delay else {
debugBoundsVisually(debugSublayers: debugSublayers, filterSublayers: isIncluded)
return self
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.debugBoundsVisually(debugSublayers: debugSublayers, filterSublayers: isIncluded)
}
return self
}
@discardableResult
func debugBoundsVisually(debugSublayers: Bool = true,
filterSublayers isIncluded: (CALayer) -> Bool = { _ in true }) -> Self {
disableBoundsVisuallyDebug()
if debugSublayers {
for sublayer in sublayers?.filter(isIncluded) ?? [] {
sublayer.debugBoundsVisually(debugSublayers: debugSublayers)
}
}
let dashedLayer = DashedBoundsLayer()
dashedLayer.configure(on: self)
return self
}
func disableBoundsVisuallyDebug() {
for sublayer in sublayers ?? [] {
if sublayer is DashedBoundsLayer {
sublayer.removeFromSuperlayer()
} else {
sublayer.disableBoundsVisuallyDebug()
}
}
}
}

View File

@ -1,71 +0,0 @@
//
// Copyright (c) 2024 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension UIView {
@discardableResult
func debugBoundsVisually(debugSubviews: Bool = true,
after delay: DispatchTimeInterval? = nil,
filterSubviews isIncluded: @escaping (UIView) -> Bool = { _ in true }) -> Self {
guard let delay else {
debugBoundsVisually(debugSubviews: debugSubviews)
return self
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.debugBoundsVisually(debugSubviews: debugSubviews)
}
return self
}
@discardableResult
func debugBoundsVisually(debugSubviews: Bool = true,
filterSubviews isIncluded: @escaping (UIView) -> Bool = { _ in true }) -> Self {
disableBoundsVisuallyDebug()
if debugSubviews {
for subview in subviews.filter(isIncluded) {
subview.debugBoundsVisually(debugSubviews: debugSubviews)
}
}
let dashedLayer = DashedBoundsLayer()
dashedLayer.configure(on: self)
return self
}
func disableBoundsVisuallyDebug() {
for sublayer in layer.sublayers ?? [] {
if sublayer is DashedBoundsLayer {
sublayer.removeFromSuperlayer()
}
}
for subview in subviews {
subview.disableBoundsVisuallyDebug()
}
}
}

View File

@ -1,37 +0,0 @@
//
// Copyright (c) 2024 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension UIViewController {
@discardableResult
func debugBoundsVisually(debugSubviews: Bool = true,
filterSubviews isIncluded: @escaping (UIView) -> Bool = { _ in true },
after delay: DispatchTimeInterval? = nil) -> Self {
view.debugBoundsVisually(debugSubviews: debugSubviews,
after: delay,
filterSubviews: isIncluded)
return self
}
}

View File

@ -1,24 +1,17 @@
Pod::Spec.new do |s|
s.name = 'TIDeveloperUtils'
s.version = '1.56.0'
s.version = '1.42.0'
s.summary = 'Universal web view API'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru',
'castlele' => 'nikita.semenov@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.3']
sources = 'Sources/**/*'
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
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TIUIElements', s.version.to_s

View File

@ -20,6 +20,7 @@
// THE SOFTWARE.
//
import TISwiftUtils
import UIKit
open class BaseFilterViewModel<CellViewModelType: FilterCellViewModelProtocol & Hashable,

View File

@ -20,6 +20,7 @@
// THE SOFTWARE.
//
import TISwiftUtils
import TIUIElements
import TIUIKitCore
import UIKit
@ -27,7 +28,8 @@ import UIKit
@available(iOS 13.0, *)
open class BaseFiltersTableView<CellType: UITableViewCell & ConfigurableView,
PropertyValue: FilterPropertyValueRepresenter & Hashable>:
BaseInitializeableTableView,
UITableView,
InitializableViewProtocol,
Updatable,
UITableViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable {
@ -65,18 +67,26 @@ open class BaseFiltersTableView<CellType: UITableViewCell & ConfigurableView,
// MARK: - Life cycle
open override func bindViews() {
super.bindViews()
open func addViews() {
// override in subclass
}
open func configureLayout() {
// override in subclass
}
open func bindViews() {
delegate = self
}
open override func configureAppearance() {
super.configureAppearance()
open func configureAppearance() {
alwaysBounceVertical = false
}
open func localize() {
// override in subclass
}
open func viewDidLoad() {
registerCell()

View File

@ -60,7 +60,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell<DefaultPickerView>
// MARK: - Open methods
open func updateAppearance(with appearance: FilterCellStateAppearance) {
wrappedContentInsets = appearance.contentInsets
contentInsets = appearance.contentInsets
wrappedView.textColor = appearance.fontColor
wrappedView.images = appearance.stateImages ?? [:]

View File

@ -21,10 +21,9 @@
//
import TIUIKitCore
import TIUIElements
import UIKit
open class StepRangeSlider: BaseInitializableControl {
open class StepRangeSlider: UIControl, InitializableViewProtocol {
// MARK: - Public properties
@ -162,16 +161,16 @@ open class StepRangeSlider: BaseInitializableControl {
super.layoutSubviews()
}
open override func addViews() {
super.addViews()
open func addViews() {
addSubviews(trackView, leftThumbView, rightThumbView)
trackView.addSubviews(activeTrack)
}
open override func bindViews() {
super.bindViews()
open func configureLayout() {
// override
}
open func bindViews() {
thumbs.forEach {
let panGestureRecognizer = UIPanGestureRecognizer(target: self,
action: #selector(draggingThumb(_:)))
@ -179,9 +178,7 @@ open class StepRangeSlider: BaseInitializableControl {
}
}
open override func configureAppearance() {
super.configureAppearance()
open func configureAppearance() {
trackView.backgroundColor = trackColor
activeTrack.backgroundColor = activeTrackColor
@ -193,6 +190,10 @@ open class StepRangeSlider: BaseInitializableControl {
updateActiveTrack()
}
open func localize() {
// override
}
// MARK: - Internal Methods
func getPositionX(from value: CGFloat) -> CGFloat {

View File

@ -56,7 +56,7 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell<UILabel>, Co
// MARK: - Open methdos
open func updateAppearance(with appearance: FilterCellStateAppearance) {
wrappedContentInsets = appearance.contentInsets
contentInsets = appearance.contentInsets
wrappedView.textColor = appearance.fontColor
backgroundColor = appearance.backgroundColor

View File

@ -22,13 +22,13 @@
import TISwiftUtils
import TIUIKitCore
import TIUIElements
import UIKit
@available(iOS 13.0, *)
open class BaseFiltersCollectionView<CellType: UICollectionViewCell & ConfigurableView,
PropertyValue: FilterPropertyValueRepresenter & Hashable>:
BaseInitializeableCollectionView,
UICollectionView,
InitializableViewProtocol,
Updatable,
UICollectionViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable {
@ -53,6 +53,7 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
super.init(frame: .zero, collectionViewLayout: layout)
initializeView()
viewDidLoad()
}
@ -63,18 +64,26 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
// MARK: - Life cycle
open override func bindViews() {
super.bindViews()
open func addViews() {
// override in subclass
}
open func configureLayout() {
// override in subclass
}
open func bindViews() {
delegate = self
}
open override func configureAppearance() {
super.configureAppearance()
open func configureAppearance() {
backgroundColor = .white
}
open func localize() {
// override in subclass
}
open func viewDidLoad() {
registerCell()

View File

@ -1,14 +1,14 @@
Pod::Spec.new do |s|
s.name = 'TIEcommerce'
s.version = '1.56.0'
s.version = '1.42.0'
s.summary = 'Cart, products, promocodes, bonuses and other related actions'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.homepage = 'https://gitlab.ti/touchinstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.source = { :git => 'https://gitlab.ti/touchinstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.3']
s.source_files = s.name + '/Sources/**/*'

View File

@ -1,88 +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 Foundation
private final class FlatMapAsyncOperation<Output, Failure: Error>: AsyncOperation<Output, Failure> {
private var dependencyObservation: NSKeyValueObservation?
private var flatMapObservation: NSKeyValueObservation?
private let dependency: Operation
private var flatMapDependency: AsyncOperation<Output, Failure>?
init<DependencyOutput, DependencyFailure>(dependency: AsyncOperation<DependencyOutput, DependencyFailure>,
flatMapOutput: ((DependencyOutput) -> AsyncOperation<Output, Failure>)?,
flatMapFailure: ((DependencyFailure) -> AsyncOperation<Output, Failure>)?)
where DependencyFailure: Error {
self.dependency = dependency
super.init()
dependency.cancelOnCancellation(of: self)
dependencyObservation = dependency.subscribe { [weak self] in
switch $0 {
case let .success(success):
self?.flatMapDependency = flatMapOutput?(success)
case let .failure(error):
self?.flatMapDependency = flatMapFailure?(error)
}
if let self {
self.flatMapDependency?.cancelOnCancellation(of: self)
}
self?.flatMapObservation = self?.flatMapDependency?.subscribe {
self?.handle(result: $0)
}
self?.flatMapDependency?.start()
}
state = .isReady
}
override func start() {
state = .isExecuting
dependency.start()
}
}
public extension AsyncOperation {
func flatMap<NewOutput>(_ transform: @escaping (Output) -> AsyncOperation<NewOutput, Failure>)
-> AsyncOperation<NewOutput, Failure> {
FlatMapAsyncOperation(dependency: self,
flatMapOutput: transform,
flatMapFailure: { .just(failure: $0) })
}
func flatMapError<NewFailure>(_ transform: @escaping (Failure) -> AsyncOperation<Output, NewFailure>)
-> AsyncOperation<Output, NewFailure> {
FlatMapAsyncOperation(dependency: self,
flatMapOutput: { .just(success: $0) },
flatMapFailure: transform)
}
}

View File

@ -1,45 +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 Foundation
private final class JustAsyncOperation<Output, Failure: Error>: AsyncOperation<Output, Failure> {
init(result: Result<Output, Failure>) {
super.init()
self.result = result
}
}
public extension AsyncOperation {
static func just(result: Result<Output, Failure>) -> AsyncOperation<Output, Failure> {
JustAsyncOperation(result: result)
}
static func just(success: Output) -> AsyncOperation<Output, Failure> {
just(result: .success(success))
}
static func just(failure: Failure) -> AsyncOperation<Output, Failure> {
just(result: .failure(failure))
}
}

View File

@ -23,9 +23,9 @@
import Foundation
private final class MapAsyncOperation<Output, Failure: Error>: DependendAsyncOperation<Output, Failure> {
init<DependencyOutput, DependencyFailure: Error>(dependency: AsyncOperation<DependencyOutput, DependencyFailure>,
mapOutput: @escaping (DependencyOutput) -> Result<Output, Failure>,
mapFailure: @escaping (DependencyFailure) -> Failure) {
public init<DependencyOutput, DependencyFailure: Error>(dependency: AsyncOperation<DependencyOutput, DependencyFailure>,
mapOutput: @escaping (DependencyOutput) -> Result<Output, Failure>,
mapFailure: @escaping (DependencyFailure) -> Failure) {
super.init(dependency: dependency) {
$0.mapError(mapFailure).flatMap(mapOutput)

View File

@ -23,14 +23,14 @@
import Foundation
private final class ClosureObserverOperation<Output, Failure: Error>: DependendAsyncOperation<Output, Failure> {
typealias OnResultClosure = (Result<Output, Failure>) -> Void
public typealias OnResultClosure = (Result<Output, Failure>) -> Void
private let onResult: OnResultClosure?
private let callbackQueue: DispatchQueue
init(dependency: AsyncOperation<Output, Failure>,
onResult: OnResultClosure? = nil,
callbackQueue: DispatchQueue = .main) {
public init(dependency: AsyncOperation<Output, Failure>,
onResult: OnResultClosure? = nil,
callbackQueue: DispatchQueue = .main) {
self.onResult = onResult
self.callbackQueue = callbackQueue
@ -48,29 +48,6 @@ private final class ClosureObserverOperation<Output, Failure: Error>: DependendA
}
}
private final class UIClosureObserverOperation<Output, Failure: Error>: DependendAsyncOperation<Output, Failure> {
typealias OnResultClosure = @MainActor (Result<Output, Failure>) -> Void
private let onResult: OnResultClosure?
init(dependency: AsyncOperation<Output, Failure>,
onResult: OnResultClosure? = nil) {
self.onResult = onResult
super.init(dependency: dependency) { $0 }
}
override func handle(result: Result<Output, Failure>) {
self.result = result
DispatchQueue.main.async {
self.onResult?(result)
self.state = .isFinished
}
}
}
public extension AsyncOperation {
func observe(onResult: ((Result<Output, Failure>) -> Void)? = nil,
callbackQueue: DispatchQueue = .main) -> AsyncOperation<Output, Failure> {
@ -80,26 +57,6 @@ public extension AsyncOperation {
callbackQueue: callbackQueue)
}
func observe(onResult: (@MainActor (Result<Output, Failure>) -> Void)? = nil) -> AsyncOperation<Output, Failure> {
UIClosureObserverOperation(dependency: self, onResult: onResult)
}
func observe(onSuccess: (@MainActor (Output) -> Void)? = nil,
onFailure: (@MainActor (Failure) -> Void)? = nil) -> AsyncOperation<Output, Failure> {
let onResult: UIClosureObserverOperation<Output, Failure>.OnResultClosure = {
switch $0 {
case let .success(output):
onSuccess?(output)
case let .failure(error):
onFailure?(error)
}
}
return observe(onResult: onResult)
}
func observe(onSuccess: ((Output) -> Void)? = nil,
onFailure: ((Failure) -> Void)? = nil,
callbackQueue: DispatchQueue = .main) -> AsyncOperation<Output, Failure> {

View File

@ -40,10 +40,8 @@ public extension Operation {
leafDependency.addDependency(startOperation)
}
}
@discardableResult
func add(to operationQueue: OperationQueue, waitUntilFinished: Bool = false) -> Self {
func add(to operationQueue: OperationQueue, waitUntilFinished: Bool = false) {
operationQueue.addOperations(flattenDependencies + [self], waitUntilFinished: waitUntilFinished)
return self
}
}

View File

@ -23,12 +23,6 @@
open class BaseCancellable: Cancellable {
private(set) public var isCancelled = false
public init() {}
deinit {
cancel()
}
open func cancel() {
isCancelled = true
}

View File

@ -20,7 +20,7 @@
// THE SOFTWARE.
//
public enum Cancellables {
public struct Cancellables {
public static func nonCancellable() -> Cancellable {
NonCancellable()
}
@ -28,27 +28,21 @@ public enum Cancellables {
public static func scoped(scopeCancellableClosure: ScopeCancellable.ScopeCancellableClosure) -> Cancellable {
ScopeCancellable(scopeCancellableClosure: scopeCancellableClosure)
}
public static func weakTargetClosure<T: AnyObject>(target: T?,
cancelClosure: @escaping WeakTargetCancellable<T>.CancelClosure) -> Cancellable {
WeakTargetCancellable(target: target, cancelClosure: cancelClosure)
}
}
@available(iOS 13.0.0, *)
public func withTaskCancellableClosure<T>(closure: (@escaping (T) -> Void) -> Cancellable) async -> T {
let cancellableBag = BaseCancellableBag()
return await withTaskCancellationHandler(operation: {
return await withTaskCancellationHandler(handler: {
cancellableBag.cancel()
}, operation: {
await withCheckedContinuation { continuation in
closure {
continuation.resume(returning: $0)
}
.add(to: cancellableBag)
}
}, onCancel: {
cancellableBag.cancel()
})
}

View File

@ -32,8 +32,8 @@ public extension BackingStore where Store: CodableKeyValueStorage, StoreContent:
where StoreContent == Value? {
self.init(store: codableKeyValueStorage,
getClosure: { try? $0.codableObject(forKey: key, decoder: decoder).get() },
setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder).get() })
getClosure: { try? $0.codableObject(forKey: key, decoder: decoder) },
setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder) })
}
init(wrappedValue: StoreContent,
@ -44,6 +44,6 @@ public extension BackingStore where Store: CodableKeyValueStorage, StoreContent:
self.init(store: codableKeyValueStorage,
getClosure: { $0.codableObject(forKey: key, defaultValue: wrappedValue, decoder: decoder) },
setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder).get() })
setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder) })
}
}

View File

@ -31,7 +31,7 @@ public protocol CodableKeyValueStorage {
/// or throw exception if the key was not found.
/// - Throws: CodableStorageError
func codableObject<Value: Decodable>(forKey key: StorageKey<Value>,
decoder: CodableKeyValueDecoder) -> Result<Value, StorageError>
decoder: CodableKeyValueDecoder) throws -> Value
/// Set or remove the value of the specified key in the storage.
/// - Parameters:
@ -41,14 +41,12 @@ public protocol CodableKeyValueStorage {
/// - Throws: EncodingError if error is occured during passed object encoding.
func set<Value: Encodable>(encodableObject: Value,
forKey key: StorageKey<Value>,
encoder: CodableKeyValueEncoder) -> Result<Void, StorageError>
encoder: CodableKeyValueEncoder) throws
/// Removes value for specific key
/// - Parameter key: The key with which to associate with the value.
/// - Throws: EncodingError if error is occured during reading/writing.
func removeCodableValue<Value: Codable>(forKey key: StorageKey<Value>) -> Result<Void, StorageError>
func hasCodableValue<Value: Decodable>(forKey key: StorageKey<Value>) -> Result<Bool, StorageError>
func removeCodableValue<Value: Codable>(forKey key: StorageKey<Value>) throws
}
public extension CodableKeyValueStorage {
@ -65,17 +63,17 @@ public extension CodableKeyValueStorage {
defaultValue: Value,
decoder: CodableKeyValueDecoder = JSONKeyValueDecoder()) -> Value {
(try? codableObject(forKey: key, decoder: decoder).get()) ?? defaultValue
(try? codableObject(forKey: key, decoder: decoder)) ?? defaultValue
}
func setOrRemove<Value: Codable>(codableObject: Value?,
forKey key: StorageKey<Value>,
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) -> Result<Void, StorageError> {
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) throws {
if let codableObject {
return set(encodableObject: codableObject, forKey: key, encoder: encoder)
if let codableObject = codableObject {
try set(encodableObject: codableObject, forKey: key, encoder: encoder)
} else {
return removeCodableValue(forKey: key)
try? removeCodableValue(forKey: key)
}
}
@ -84,10 +82,10 @@ public extension CodableKeyValueStorage {
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) -> Value? {
get {
try? codableObject(forKey: key, decoder: decoder).get()
try? codableObject(forKey: key, decoder: decoder)
}
set {
try? setOrRemove(codableObject: newValue, forKey: key, encoder: encoder).get()
try? setOrRemove(codableObject: newValue, forKey: key, encoder: encoder)
}
}
}

View File

@ -1,88 +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 TISwiftUtils
public extension MigratingBackingStore where Store: CodableKeyValueStorage,
StoreContent: Codable {
init<Value: Codable>(key: StorageKey<Value>,
storageContainer: Store,
decoder: CodableKeyValueDecoder = JSONKeyValueDecoder(),
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder())
where StoreContent == Value? {
let getClosure: GetClosure = { container in
Self.getValue(from: container, forKey: key, decoder: decoder, encoder: encoder)
}
let setClosure: SetClosure = { container, value in
try? container.setOrRemove(codableObject: value, forKey: key, encoder: encoder).get()
}
self.init(store: storageContainer, getClosure: getClosure, setClosure: setClosure)
}
init<Value: Codable>(wrappedValue: Value,
key: StorageKey<Value>,
storageContainer: Store,
decoder: CodableKeyValueDecoder = JSONKeyValueDecoder(),
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder())
where StoreContent == Value? {
let getClosure: GetClosure = { container in
Self.getValue(from: container, forKey: key, wrappedValue: wrappedValue, decoder: decoder, encoder: encoder)
}
let setClosure: SetClosure = { container, value in
try? container.setOrRemove(codableObject: value, forKey: key, encoder: encoder).get()
}
self.init(store: storageContainer, getClosure: getClosure, setClosure: setClosure)
}
static func getValue<Value: Codable>(from container: Store,
forKey key: StorageKey<Value>,
wrappedValue: Value? = nil,
decoder: CodableKeyValueDecoder,
encoder: CodableKeyValueEncoder) -> StoreContent where StoreContent == Value? {
// Storage container can't handle migration when source storage has value and target doesn't.
// This happens because of Decodable restrictions for Value in method `codableObject(forKey:decoder)`
// and absence of encoder.
try? container.codableObject(forKey: key, decoder: decoder)
.flatMap {
let _ = container.set(encodableObject: $0, forKey: key, encoder: encoder)
return .success($0)
}
.flatMapError {
guard let wrappedValue else {
return .failure($0)
}
return container.set(encodableObject: wrappedValue, forKey: key, encoder: encoder)
.flatMap { _ in .success(wrappedValue) }
}
.get()
}
}

View File

@ -23,5 +23,5 @@
import Foundation
public protocol CodableKeyValueDecoder {
func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) -> Result<Value, StorageError>
func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) throws -> Value
}

View File

@ -29,12 +29,11 @@ open class JSONKeyValueDecoder: CodableKeyValueDecoder {
self.jsonDecoder = jsonDecoder
}
open func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) -> Result<Value, StorageError> {
Result {
try jsonDecoder.decode(Value.self, from: data)
}
.mapError {
.unableToDecode(underlyingError: $0)
open func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) throws -> Value {
do {
return try jsonDecoder.decode(Value.self, from: data)
} catch {
throw StorageError.unableToDecode(underlyingError: error)
}
}
}

View File

@ -26,13 +26,13 @@ import Foundation
open class UnarchiverKeyValueDecoder: CodableKeyValueDecoder {
public init() {}
open func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) -> Result<Value, StorageError> {
open func decodeDecodable<Value: Decodable>(from data: Data, for key: StorageKey<Value>) throws -> Value {
let unarchiver: NSKeyedUnarchiver
do {
unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
} catch {
return .failure(.unableToDecode(underlyingError: error))
throw StorageError.unableToDecode(underlyingError: error)
}
defer {
@ -40,9 +40,9 @@ open class UnarchiverKeyValueDecoder: CodableKeyValueDecoder {
}
guard let decodableObject = unarchiver.decodeDecodable(Value.self, forKey: key.rawValue) else {
return .failure(.valueNotFound)
throw StorageError.valueNotFound
}
return .success(decodableObject)
return decodableObject
}
}

View File

@ -26,17 +26,17 @@ import Foundation
open class ArchiverKeyValueEncoder: CodableKeyValueEncoder {
public init() {}
open func encodeEncodable<Value: Encodable>(value: Value, for key: StorageKey<Value>) -> Result<Data, StorageError> {
open func encodeEncodable<Value: Encodable>(value: Value, for key: StorageKey<Value>) throws -> Data {
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
do {
try archiver.encodeEncodable(value, forKey: key.rawValue)
} catch {
return .failure(.unableToEncode(underlyingError: error))
throw StorageError.unableToEncode(underlyingError: error)
}
archiver.finishEncoding()
return .success(archiver.encodedData)
return archiver.encodedData
}
}

View File

@ -23,5 +23,5 @@
import Foundation
public protocol CodableKeyValueEncoder {
func encodeEncodable<Value: Encodable>(value: Value, for key: StorageKey<Value>) -> Result<Data, StorageError>
func encodeEncodable<Value: Encodable>(value: Value, for key: StorageKey<Value>) throws -> Data
}

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