Merge branch 'master' into feature/deeplink_api

# Conflicts:
#	LeadKit.podspec
#	Package.swift
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
#	project-scripts/push_to_podspecs.sh
This commit is contained in:
Nikita Semenov 2023-03-27 10:34:04 +03:00
commit c8985cde1e
189 changed files with 7174 additions and 520 deletions

2
.gitmodules vendored
View File

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

View File

@ -1,5 +1,59 @@
# Changelog
### 1.39.0
- **Added**: UIButton Appearance model
- **Added**: `SpacedWrappedViewLayout` for spacing configurations
- **Update**: UIView appearance model with border configurations
### 1.38.0
- **Added**: Placemarks states for icon updating
- **Added**: Selecting / deselecting markers through cluster manager
### 1.37.0
- **Added**: API for converting view hierarchy to skeletons
### 1.36.1
- **Update**: `YandexMapsMobile` version updated
- **Fix**: Map manager memory leak removed
### 1.36.0
- **Removed**: `TILogger`module
- **Updated**: moved `LoggingPresenter` to `TIDeveloperUtils` module.
### 1.35.1
- **Added**: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
- **Updated**: `AsyncOperation` fixed ordering of chain operations execution
### 1.35.0
- **Added**: `TIDeveloperUtils` framework, that contains different utils for development
- **Added**: `UIView` and `UIViewController` extensions for showing SwiftUI previews
- **Added**: `DashedBoundsLayer` for debugging views' frames visually
### 1.34.0
- **Added**: `BaseListItemView` for displaying three views horizontally
- **Added**: `DefaultTitleSubtitleView` for displaying one or two labels vertically
- **Update**: `StatefulButton` now can be configured with `ViewAppearance` model for each state
### 1.33.0
- **Added**: `ViewAppearance` and `ViewLayout` models for setting up Views' appearance and layout
- **Added**: `TableKit.Row` extension for configuration inner View's appearance and layout
- **Added**: `WrappableView` with typealiases for creating wrapped in the container views
- **Added**: `CollectionTableViewCell` and `ContainerView`
- **Update**: Separator appearance configureation for table views
### 1.32.0
- **Added**: `BaseInitializableWebView` with navigation and error handling api.
### 1.31.0
- **Added**: `URLInteractiveTextView` for terms and conditions hints in login flow

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "1.33.0"
s.version = "1.35.0"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKit"
s.homepage = "https://gitlab.ti/touchinstinct/LeadKit"
s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct"
s.source = { :git => "https://github.com/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']

View File

@ -1,79 +1,77 @@
{
"object": {
"pins": [
{
"package": "Alamofire",
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
"state": {
"branch": null,
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
"version": "5.4.3"
}
},
{
"package": "Cache",
"repositoryURL": "https://github.com/hyperoslo/Cache.git",
"state": {
"branch": null,
"revision": "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version": "6.0.0"
}
},
{
"package": "Cursors",
"repositoryURL": "https://github.com/petropavel13/Cursors",
"state": {
"branch": null,
"revision": "a1561869135e72832eff3b1e729075c56c2eebf6",
"version": "0.5.1"
}
},
{
"package": "KeychainAccess",
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state": {
"branch": null,
"revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
"version": "4.2.2"
}
},
{
"package": "Moya",
"repositoryURL": "https://github.com/Moya/Moya.git",
"state": {
"branch": null,
"revision": "9b906860e3c3c09032879465c471e6375829593f",
"version": "15.0.0"
}
},
{
"package": "ReactiveSwift",
"repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state": {
"branch": null,
"revision": "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version": "6.7.0"
}
},
{
"package": "RxSwift",
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
"state": {
"branch": null,
"revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version": "6.5.0"
}
},
{
"package": "TableKit",
"repositoryURL": "https://github.com/maxsokolov/TableKit.git",
"state": {
"branch": null,
"revision": "8bf4840d9d0475a92352f02f368f88b74eced447",
"version": "2.11.0"
}
"pins" : [
{
"identity" : "alamofire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Alamofire/Alamofire.git",
"state" : {
"revision" : "f96b619bcb2383b43d898402283924b80e2c4bae",
"version" : "5.4.3"
}
]
},
"version": 1
},
{
"identity" : "cache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"revision" : "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version" : "6.0.0"
}
},
{
"identity" : "cursors",
"kind" : "remoteSourceControl",
"location" : "https://github.com/petropavel13/Cursors",
"state" : {
"revision" : "a1561869135e72832eff3b1e729075c56c2eebf6",
"version" : "0.5.1"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "moya",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Moya/Moya.git",
"state" : {
"revision" : "9b906860e3c3c09032879465c471e6375829593f",
"version" : "15.0.0"
}
},
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version" : "6.7.0"
}
},
{
"identity" : "rxswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version" : "6.5.0"
}
},
{
"identity" : "tablekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/maxsokolov/TableKit.git",
"state" : {
"revision" : "8bf4840d9d0475a92352f02f368f88b74eced447",
"version" : "2.11.0"
}
}
],
"version" : 2
}

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.7
import PackageDescription
let package = Package(
@ -11,17 +11,18 @@ let package = Package(
// MARK: - UIKit
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
.library(name: "TIUIElements", targets: ["TIUIElements"]),
.library(name: "TIWebView", targets: ["TIWebView"]),
// MARK: - SwiftUI
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
// MARK: - Utils
.library(name: "TISwiftUtils", targets: ["TISwiftUtils"]),
.library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]),
.library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]),
.library(name: "TITableKitUtils", targets: ["TITableKitUtils"]),
.library(name: "TILogging", targets: ["TILogging"]),
.library(name: "TIDeeplink", targets: ["TIDeeplink"]),
.library(name: "TIDeveloperUtils", targets: ["TIDeveloperUtils"]),
// MARK: - Networking
@ -39,7 +40,7 @@ let package = Package(
.library(name: "TITransitions", targets: ["TITransitions"]),
.library(name: "TIPagination", targets: ["TIPagination"]),
.library(name: "TIAuth", targets: ["TIAuth"]),
//MARK: - Skolkovo
.library(name: "TIEcommerce", targets: ["TIEcommerce"])
],
@ -52,21 +53,22 @@ let package = Package(
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0"))
],
targets: [
// MARK: - UIKit
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
// MARK: - SwiftUI
.target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"),
// MARK: - Utils
.target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"),
.target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils"),
.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: "TILogging", dependencies: ["TIUIElements", "TISwiftUtils", "TIUIKitCore"], path: "TILogging/Sources"),
.target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink/Sources"),
.target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"),
// MARK: - Networking
.target(name: "TINetworking", dependencies: ["TIFoundationUtils", "Alamofire"], path: "TINetworking/Sources"),
@ -83,9 +85,9 @@ let package = Package(
.target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"),
.target(name: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "KeychainAccess"], path: "TIAuth/Sources"),
.target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"),
// MARK: - Tests
.testTarget(
name: "TITimerTests",
dependencies: ["TIFoundationUtils"],

View File

@ -20,8 +20,71 @@ This repository contains the following frameworks:
- [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK.
- [TIAuth](TIAuth) - login, registration, confirmation and other related actions
Useful docs:
## Playgrounds
### Create new Playground
```sh
cd TIModuleName
nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile
```
See example of `PlaygroundPodfile` in `TIFoundationUtils`
### Rename/add pages to Playground
For every new feature in module create new Playground page with documentation in comments. See [nef markdown documentation](https://github.com/bow-swift/nef#-generating-a-markdown-project).
### Create symlink to nef playground
```sh
cd TIModuleName
ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground
```
### Add nef files to TIModuleName.app/.gitignore
```
# gitignore nef files
**/build/
**/nef/
LICENSE
```
### Add new playground to pre release script
`project-scripts/gen_docs_from_playgrounds.sh`:
```sh
PLAYGROUNDS="${SRCROOT}/TIFoundationUtils/TIFoundationUtils.app
${SRCROOT}/TIModuleName/TIModuleName.app"
```
### Exclude .app bundles from package sources
#### SPM
```swift
.target(name: "TIModuleName", dependencies: ..., path: ..., exclude: ["TIModuleName.app"]),
```
#### Podspec
```ruby
sources = 'your_sources_expression'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
```
## Docs:
- [TIFoundationUtils](docs/tifoundationutils)
* [AsyncOperation](docs/tifoundationutils/asyncoperation.md)
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
- [Snippets](docs/snippets.md) - useful commands and scripts for development.
@ -32,7 +95,7 @@ Useful docs:
./setup
```
- If legacy [Source](https://github.com/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://github.com/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).
@ -42,14 +105,14 @@ Useful docs:
```swift
dependencies: [
.package(url: "https://github.com/TouchInstinct/LeadKit.git", from: "x.y.z"),
.package(url: "https://gitlab.ti/touchinstinct/LeadKit.git", from: "x.y.z"),
],
```
### Cocoapods
```ruby
source 'https://github.com/TouchInstinct/Podspecs.git'
source 'https://gitlab.ti/touchinstinct/Podspecs.git'
pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z'
@ -58,4 +121,4 @@ pod 'TIFoundationUtils', 'x.y.z'
## Legacy
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.

View File

@ -24,7 +24,11 @@ import TIMapUtils
import MapKit
import UIKit
open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, [ApplePlacemarkManager<Model>], MKMapRect>, MKMapViewDelegate {
open class AppleClusterPlacemarkManager<Model>: BaseClusterPlacemarkManager<MKAnnotationView,
ApplePlacemarkManager<Model>,
MKMapRect>,
MKMapViewDelegate {
public weak var mapViewDelegate: MKMapViewDelegate?
private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self)
@ -36,7 +40,8 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
self.mapViewDelegate = mapViewDelegate
super.init(dataModel: placemarkManagers,
super.init(placemarkPosition: .from(coordinates: placemarkManagers.map(\.placemarkPosition)),
dataModel: placemarkManagers,
iconFactory: iconFactory?.asAnyMarkerIconFactory { $0.map { $0.dataModel } },
tapHandler: tapHandler)
}
@ -55,10 +60,10 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
override open func configure(placemark: MKAnnotationView) {
guard let clusterAnnotation = placemark.annotation as? MKClusterAnnotation,
let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else {
return
}
return
}
placemark.image = iconFactory?.markerIcon(for: placemarkManagers)
placemark.image = iconFactory?.markerIcon(for: placemarkManagers, state: .default)
}
// MARK: - MKMapViewDelegate
@ -79,6 +84,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
configure(placemark: defaultAnnotationView)
return defaultAnnotationView
case let placemarkManager as ApplePlacemarkManager<Model>:
let defaultAnnotationView = placemarkManager.iconFactory != nil
? MKAnnotationView(annotation: annotation,
@ -89,6 +95,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
placemarkManager.configure(placemark: defaultAnnotationView)
return defaultAnnotationView
default:
return nil
}
@ -107,8 +114,29 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
}
_ = tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.coordinate }))
case let placemarkManager as ApplePlacemarkManager<Model>:
_ = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate)
let isTapHandled = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) ?? false
if isTapHandled {
placemarkManager.state = .selected
}
default:
return
}
}
open func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard !(mapViewDelegate?.responds(to: #selector(mapView(_:didDeselect:))) ?? false) else {
mapViewDelegate?.mapView?(mapView, didDeselect: view)
return
}
switch view.annotation {
case let placemarkManager as ApplePlacemarkManager<Model>:
placemarkManager.state = .default
default:
return
}

View File

@ -44,7 +44,8 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
return nil
}
return ApplePlacemarkManager(dataModel: $0,
return ApplePlacemarkManager(map: map,
dataModel: $0,
position: position,
clusteringIdentifier: clusteringIdentifierGetter($0),
iconFactory: iconFactory?.asAnyMarkerIconFactory(),

View File

@ -23,23 +23,53 @@
import TIMapUtils
import MapKit
open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>, MKAnnotation {
open class ApplePlacemarkManager<Model>: BaseItemPlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>,
MKAnnotation {
// MARK: - MKAnnotation
/// A map where all placemarks are placed
public let map: MKMapView
public let coordinate: CLLocationCoordinate2D
/// Identifier required for correct cluster placement
public var clusteringIdentifier: String?
/// Point (coordinates) itself of the current placemark manager
public var coordinate: CLLocationCoordinate2D {
placemarkPosition
}
/// The current state of a manager's placemark
override public var state: MarkerState {
didSet {
guard let placemark = placemark else {
return
}
/*
Although the icon is being updated, it is necessary to manually deselect
the annotation of the current placemark if it is currently selected.
*/
if state == .default, let annotation = placemark.annotation {
map.deselectAnnotation(annotation, animated: true)
}
placemark.image = iconFactory?.markerIcon(for: dataModel, state: state)
}
}
public init(dataModel: Model,
public init(map: MKMapView,
dataModel: Model,
position: CLLocationCoordinate2D,
clusteringIdentifier: String?,
iconFactory: AnyMarkerIconFactory<DataModel>?,
tapHandler: TapHandlerClosure?) {
self.coordinate = position
self.map = map
self.clusteringIdentifier = clusteringIdentifier
super.init(dataModel: dataModel,
super.init(placemarkPosition: position,
dataModel: dataModel,
iconFactory: iconFactory,
tapHandler: tapHandler)
}
@ -47,7 +77,10 @@ open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView,
// MARK: - PlacemarkManager
override open func configure(placemark: MKAnnotationView) {
placemark.image = iconFactory?.markerIcon(for: dataModel)
placemark.clusteringIdentifier = clusteringIdentifier
super.configure(placemark: placemark)
// Setting required values of the current manager and placemark respectively
self.placemark?.clusteringIdentifier = clusteringIdentifier
self.state = .default
}
}

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIAuth'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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.3']

View File

@ -2,11 +2,11 @@ Pod::Spec.new do |s|
s.name = 'TIDeeplink'
s.version = '1.33.0'
s.summary = 'Deeplink service API'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']
@ -14,5 +14,5 @@ Pod::Spec.new do |s|
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIFoundationUtils', s.version.to_s
end

View File

@ -0,0 +1,100 @@
//
// 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 DashedBoundsLayer: CAShapeLayer {
public static var predefinedColors: [UIColor] {
[.red, .green, .blue, .brown, .gray, .yellow, .magenta, .black, .orange, .purple, .cyan]
}
private var viewBoundsObservation: NSKeyValueObservation?
public var dashColor: UIColor = predefinedColors.randomElement() ?? .black
// MARK: - Open methods
open func configure(on view: UIView) {
fillColor = UIColor.clear.cgColor
strokeColor = dashColor.cgColor
lineWidth = 1
lineDashPattern = [4.0, 2.0]
updateGeometry(from: view)
view.layer.addSublayer(self)
viewBoundsObservation = view.observe(\.bounds, options: [.new]) { [weak self] view, _ in
self?.updateGeometry(from: view)
}
}
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

@ -0,0 +1,43 @@
//
// 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 SwiftUI
import UIKit
@available(iOS 13, *)
public extension UIView {
private struct Preview: UIViewRepresentable {
let view: UIView
func makeUIView(context: Context) -> UIView {
view
}
func updateUIView(_ uiView: UIView, context: Context) {
//
}
}
func showPreview() -> some View {
Preview(view: self)
}
}

View File

@ -0,0 +1,44 @@
//
// 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 SwiftUI
import UIKit
@available(iOS 13, *)
public extension UIViewController {
private struct Preview: UIViewControllerRepresentable {
let viewController: UIViewController
func makeUIViewController(context: Context) -> UIViewController {
viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
//
}
}
func showPreview() -> some View {
Preview(viewController: self)
}
}

View File

@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = 'TILogging'
s.version = '1.33.0'
s.summary = 'Logging API'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.name = 'TIDeveloperUtils'
s.version = '1.39.0'
s.summary = 'Universal web view API'
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://github.com/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 = '11.0'
s.swift_versions = ['5.3']
@ -14,7 +14,7 @@ Pod::Spec.new do |s|
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
s.dependency 'TIUIElements', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
end

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIEcommerce'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Cart, products, promocodes, bonuses and other related actions'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,45 +0,0 @@
import Foundation
import TIFoundationUtils
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
struct NonCancellableTask: CancellableTask {
func cancel() {}
}
ClosureAsyncOperation<Int, Error> { completion in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
completion(.success(1))
}
return NonCancellableTask()
}
.map { $0 * 2 }
.observe(onSuccess: { result in
debugPrint("Async operation one has finished with \(result)")
})
.add(to: operationQueue)
ClosureAsyncOperation<Int, Error> { completion in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
completion(.success(2))
}
return NonCancellableTask()
}
.map { $0 * 2 }
.observe(onSuccess: { result in
debugPrint("Async operation two has finished with \(result)")
})
.add(to: operationQueue)
// "Async operation one has finished with 2"
// "Async operation two has finished with 4"
struct Some {
var arr: [Int] = []
mutating func add(_ i: Int) {
arr.append(i)
}
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios' buildActiveScheme='true' importAppTypes='true'>
<timeline fileName='timeline.xctimeline'/>
</playground>

View File

@ -25,15 +25,25 @@ import Foundation
private final class ClosureObserverOperation<Output, Failure: Error>: DependendAsyncOperation<Output, Failure> {
public typealias OnResultClosure = (Result<Output, Failure>) -> Void
private let onResult: OnResultClosure?
private let callbackQueue: DispatchQueue
public init(dependency: AsyncOperation<Output, Failure>,
onResult: OnResultClosure? = nil,
callbackQueue: DispatchQueue = .main) {
super.init(dependency: dependency) { result in
callbackQueue.async {
onResult?(result)
}
return result
self.onResult = onResult
self.callbackQueue = callbackQueue
super.init(dependency: dependency) { $0 }
}
override func handle(result: Result<Output, Failure>) {
self.result = result
callbackQueue.async {
self.onResult?(result)
self.state = .isFinished
}
}
}

View File

@ -24,22 +24,26 @@ import Foundation
open class DependendAsyncOperation<Output, Failure: Error>: AsyncOperation<Output, Failure> {
public var dependencyObservation: NSKeyValueObservation?
private let dependency: Operation
public init<DependencyOutput, DependencyFailure: Error>(dependency: AsyncOperation<DependencyOutput, DependencyFailure>,
resultObservation: @escaping (Result<DependencyOutput, DependencyFailure>) -> Result<Output, Failure>) {
self.dependency = dependency
super.init()
cancelOnCancellation(of: dependency)
dependency.cancelOnCancellation(of: self)
dependencyObservation = dependency.subscribe { [weak self] in
self?.result = resultObservation($0)
self?.state = .isReady
self?.handle(result: resultObservation($0))
}
addDependency(dependency) // keeps strong reference to dependency as well
state = nil // prevent start of current operation if result is not yet provided by dependency
state = .isReady
}
open override func start() {
state = .isExecuting
dependency.start()
}
}

View File

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

View File

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

View File

@ -0,0 +1,28 @@
<?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

@ -0,0 +1,26 @@
## 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

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

View File

@ -0,0 +1,77 @@
/*:
# `AsyncOperation<Result, Error>` - generic сабкласс Operation
Позволяет запускать:
- асинхронный код внутри операции
- собирать цепочки из операций
- подписываться на результат выполнения
## Базовые операции
"Из коробки", на данный момент, доступен всего один сабкласс асинхронной операции, потому что больше обычно и не нужно.
Но можно наследоваться и создавать собственные сабклассы при необходимости.
*/
/*:
### `ClosureAsyncOperation<Result, Error>`
Операция принимающая некий closure, который по окончании своей работы вызовет completion, переданный ему параметром
*/
import Foundation
import TIFoundationUtils
let intResultOperation = ClosureAsyncOperation<Int, Never> { completion in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(3)) {
completion(.success(1))
}
return Cancellables.nonCancellable()
}
/*:
## Базовые операторы
На данный момент реализовано всего два оператора:
- `map(mapOutput:mapFailure:)` - конвертирует ResultType в новый NewResultType и ErrorType в новый NewErrorType
- `observe(onSuccess:onFailure)` - просто вызывает переданные callback'и при получении результата или ошибки
*/
//: ### Пример запуска асинхронных операци с применением операторов в последовательной очереди и вывод результатов
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
ClosureAsyncOperation<Int, Never> { completion in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(3)) {
completion(.success(1))
}
return Cancellables.nonCancellable()
}
.map { $0 * 2 }
.observe(onSuccess: { result in
debugPrint("Async operation one has finished with \(result)")
})
.add(to: operationQueue)
ClosureAsyncOperation<String, Never> { completion in
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
completion(.success("Success"))
}
return Cancellables.nonCancellable()
}
.observe(onSuccess: { result in
debugPrint("Async operation two has finished with \(result)")
})
.add(to: operationQueue)
/*:
В консоли будет выведено:
```
"Async operation one has finished with 2"
"Async operation two has finished with Success"
```
*/

View File

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

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
A02229F337222E1B9E665195 /* Pods_TIFoundationUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CC8351C190B190691F8FC76 /* Pods_TIFoundationUtils.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0CC8351C190B190691F8FC76 /* Pods_TIFoundationUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIFoundationUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
59A923BF05D59C9BEC19C6FD /* Pods-TIFoundationUtils.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIFoundationUtils.release.xcconfig"; path = "Target Support Files/Pods-TIFoundationUtils/Pods-TIFoundationUtils.release.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TIFoundationUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIFoundationUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EC0CA80D0000D9F6C5FACF34 /* Pods-TIFoundationUtils.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIFoundationUtils.debug.xcconfig"; path = "Target Support Files/Pods-TIFoundationUtils/Pods-TIFoundationUtils.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A02229F337222E1B9E665195 /* Pods_TIFoundationUtils.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
37058452818BA6D3D6C19AA7 /* Frameworks */ = {
isa = PBXGroup;
children = (
0CC8351C190B190691F8FC76 /* Pods_TIFoundationUtils.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TIFoundationUtils */,
8B39A26C21D40F8700DE2643 /* Products */,
AAAF022BA9D15672298175E6 /* Pods */,
37058452818BA6D3D6C19AA7 /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TIFoundationUtils.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TIFoundationUtils */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TIFoundationUtils;
sourceTree = "<group>";
};
AAAF022BA9D15672298175E6 /* Pods */ = {
isa = PBXGroup;
children = (
EC0CA80D0000D9F6C5FACF34 /* Pods-TIFoundationUtils.debug.xcconfig */,
59A923BF05D59C9BEC19C6FD /* Pods-TIFoundationUtils.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TIFoundationUtils */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIFoundationUtils" */;
buildPhases = (
F4F8AAE8BFCE7066005B0EF5 /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TIFoundationUtils;
productName = TIFoundationUtils2;
productReference = 8BACBE8322576CAD00266845 /* TIFoundationUtils.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 "TIFoundationUtils" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TIFoundationUtils */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
F4F8AAE8BFCE7066005B0EF5 /* [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-TIFoundationUtils-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 = EC0CA80D0000D9F6C5FACF34 /* Pods-TIFoundationUtils.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIFoundationUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIFoundationUtils/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.TIFoundationUtils;
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 = 59A923BF05D59C9BEC19C6FD /* Pods-TIFoundationUtils.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIFoundationUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIFoundationUtils/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.TIFoundationUtils;
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 "TIFoundationUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIFoundationUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

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

View File

@ -0,0 +1,76 @@
<?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 = "TIFoundationUtils.framework"
BlueprintName = "TIFoundationUtils"
ReferencedContainer = "container:TIFoundationUtils.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 = "TIFoundationUtils.framework"
BlueprintName = "TIFoundationUtils"
ReferencedContainer = "container:TIFoundationUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIFoundationUtils.framework"
BlueprintName = "TIFoundationUtils"
ReferencedContainer = "container:TIFoundationUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

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

View File

@ -0,0 +1,24 @@
<?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

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

View File

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

View File

@ -1,16 +1,23 @@
Pod::Spec.new do |s|
s.name = 'TIFoundationUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for Foundation framework classes.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '10.0'
s.swift_versions = ['5.3']
s.source_files = s.name + '/**/Sources/**/*'
sources = '**/Sources/**/*.swift'
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 'TISwiftUtils', s.version.to_s
s.framework = 'Foundation'

View File

@ -24,7 +24,9 @@ import TIMapUtils
import GoogleMapsUtils
import GoogleMaps
open class GoogleClusterPlacemarkManager<Model>: BasePlacemarkManager<GMSMarker, [GooglePlacemarkManager<Model>], GMSCoordinateBounds>,
open class GoogleClusterPlacemarkManager<Model>: BaseClusterPlacemarkManager<GMSMarker,
GooglePlacemarkManager<Model>,
GMSCoordinateBounds>,
GMUClusterRendererDelegate,
GMUClusterManagerDelegate,
GMUClusterIconGenerator {
@ -44,7 +46,8 @@ open class GoogleClusterPlacemarkManager<Model>: BasePlacemarkManager<GMSMarker,
mapDelegate: GMSMapViewDelegate? = nil,
tapHandler: TapHandlerClosure?) where IF.Model == [Model] {
super.init(dataModel: placemarkManagers,
super.init(placemarkPosition: .from(coordinates: placemarkManagers.map(\.placemarkPosition)) ?? .init(),
dataModel: placemarkManagers,
iconFactory: iconFactory?.asAnyMarkerIconFactory { $0.map { $0.dataModel } },
tapHandler: tapHandler)
@ -91,7 +94,7 @@ open class GoogleClusterPlacemarkManager<Model>: BasePlacemarkManager<GMSMarker,
return
}
marker.icon = iconFactory?.markerIcon(for: placemarkManagers)
marker.icon = iconFactory?.markerIcon(for: placemarkManagers, state: .default)
?? defaultClusterIconGenerator.icon(forSize: UInt(placemarkManagers.count))
case let clusterItem as GooglePlacemarkManager<Model>:
clusterItem.configure(placemark: marker)
@ -111,7 +114,7 @@ open class GoogleClusterPlacemarkManager<Model>: BasePlacemarkManager<GMSMarker,
return false
}
return tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.position }) ?? .init()) ?? false
return tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map(\.placemarkPosition)) ?? .init()) ?? false
}
open func clusterManager(_ clusterManager: GMUClusterManager, didTap clusterItem: GMUClusterItem) -> Bool {

View File

@ -24,19 +24,34 @@ import TIMapUtils
import GoogleMaps
import GoogleMapsUtils
open class GooglePlacemarkManager<Model>: BasePlacemarkManager<GMSMarker, Model, CLLocationCoordinate2D>, GMUClusterItem {
open class GooglePlacemarkManager<Model>: BaseItemPlacemarkManager<GMSMarker, Model, CLLocationCoordinate2D>,
GMUClusterItem {
// MARK: - GMUClusterItem
public let position: CLLocationCoordinate2D
/// Point (coordinates) itself of the current placemark manager
public var position: CLLocationCoordinate2D {
placemarkPosition
}
/// The current state of a manager's placemark
override public var state: MarkerState {
didSet {
guard let placemark = placemark else {
return
}
placemark.icon = iconFactory?.markerIcon(for: dataModel, state: state)
}
}
public init(dataModel: Model,
position: CLLocationCoordinate2D,
iconFactory: AnyMarkerIconFactory<DataModel>?,
tapHandler: TapHandlerClosure?) {
self.position = position
super.init(dataModel: dataModel,
super.init(placemarkPosition: position,
dataModel: dataModel,
iconFactory: iconFactory,
tapHandler: tapHandler)
}
@ -44,6 +59,13 @@ open class GooglePlacemarkManager<Model>: BasePlacemarkManager<GMSMarker, Model,
// MARK: - PlacemarkManager
override open func configure(placemark: GMSMarker) {
placemark.icon = iconFactory?.markerIcon(for: dataModel)
super.configure(placemark: placemark)
/*
Note: it is required not to just resetting a state but setting a value depending
on a previous state due to Google Maps' map re-rendering on zoom to save
an appearance of the pin as it was previously
*/
self.state = state == .default ? .default : .selected
}
}

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIGoogleMapUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIKeychainUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for Keychain classes.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,77 +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 os
public struct TILogger: LoggerRepresentable {
// MARK: - Properties
@available(iOS 12, *)
public static let defaultLogger = TILogger(subsystem: .defaultSubsystem ?? "", category: .pointsOfInterest)
public let logInfo: OSLog
// MARK: - Init
public init(subsystem: String, category: String) {
self.logInfo = .init(subsystem: subsystem, category: category)
}
@available(iOS 12, *)
public init(subsystem: String, category: OSLog.Category) {
self.logInfo = .init(subsystem: subsystem, category: category)
}
// MARK: - LoggerRepresentable
public func log(_ message: StaticString, log: OSLog?, type: OSLogType, _ arguments: CVarArg...) {
os_log(message, log: log ?? logInfo, type: type, arguments)
}
// MARK: - Public methods
public func verbose(_ message: StaticString, _ arguments: CVarArg...) {
self.log(message, log: logInfo, type: .default, arguments)
}
public func info(_ message: StaticString, _ arguments: CVarArg...) {
self.log(message, log: logInfo, type: .info, arguments)
}
public func debug(_ message: StaticString, _ arguments: CVarArg...) {
self.log(message, log: logInfo, type: .debug, arguments)
}
public func error(_ message: StaticString, _ arguments: CVarArg...) {
self.log(message, log: logInfo, type: .error, arguments)
}
public func fault(_ message: StaticString, _ arguments: CVarArg...) {
self.log(message, log: logInfo, type: .fault, arguments)
}
}
private extension String {
static let defaultSubsystem = Bundle.main.bundleIdentifier
}

View File

@ -0,0 +1,29 @@
//
// 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 CoreLocation
extension CLLocationCoordinate2D: Equatable {
public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}

View File

@ -23,20 +23,20 @@
import UIKit.UIImage
public final class AnyMarkerIconFactory<Model>: MarkerIconFactory {
public typealias IconProviderClosure = (Model) -> UIImage
public typealias IconProviderClosure = (Model, MarkerState) -> UIImage
public var iconProviderClosure: IconProviderClosure
public init<IF: MarkerIconFactory>(iconFactory: IF) where IF.Model == Model {
self.iconProviderClosure = { iconFactory.markerIcon(for: $0) }
self.iconProviderClosure = { iconFactory.markerIcon(for: $0, state: $1) }
}
public init<IF: MarkerIconFactory, T>(iconFactory: IF, transform: @escaping (Model) -> T) where IF.Model == T {
self.iconProviderClosure = { iconFactory.markerIcon(for: transform($0)) }
self.iconProviderClosure = { iconFactory.markerIcon(for: transform($0), state: $1) }
}
public func markerIcon(for model: Model) -> UIImage {
iconProviderClosure(model)
public func markerIcon(for model: Model, state: MarkerState) -> UIImage {
iconProviderClosure(model, state)
}
}

View File

@ -23,7 +23,7 @@
import UIKit.UIImage
open class DefaultCachableMarkerIconFactory<M, K: AnyObject>: DefaultMarkerIconFactory<M> {
public typealias CacheKeyProvider = (M) -> K
public typealias CacheKeyProvider = (M, MarkerState) -> K
public let cache = NSCache<K, UIImage>()
@ -37,11 +37,11 @@ open class DefaultCachableMarkerIconFactory<M, K: AnyObject>: DefaultMarkerIconF
super.init(createIconClosure: createIconClosure)
}
open override func markerIcon(for model: M) -> UIImage {
let cacheKey = cacheKeyProvider(model)
open override func markerIcon(for model: M, state: MarkerState) -> UIImage {
let cacheKey = cacheKeyProvider(model, state)
guard let cachedIcon = cache.object(forKey: cacheKey) else {
let icon = super.markerIcon(for: model)
let icon = super.markerIcon(for: model, state: state)
cache.setObject(icon, forKey: cacheKey)
return icon

View File

@ -32,12 +32,12 @@ public final class DefaultClusterMarkerIconFactory<Model>: DefaultCachableMarker
self.beforeRenderCallback = beforeRenderCallback
self.clusterIconRenderer = DefaultClusterIconRenderer()
super.init { [clusterIconRenderer] in
beforeRenderCallback?($0, clusterIconRenderer)
super.init { [clusterIconRenderer] models, _ in
beforeRenderCallback?(models, clusterIconRenderer)
return clusterIconRenderer.renderCluster(of: $0.count)
} cacheKeyProvider: {
String($0.count) as NSString
return clusterIconRenderer.renderCluster(of: models.count)
} cacheKeyProvider: { models, _ in
String(models.count) as NSString
}
}
}

View File

@ -23,7 +23,7 @@
import UIKit.UIImage
open class DefaultMarkerIconFactory<M>: MarkerIconFactory {
public typealias CreateIconClosure = (M) -> UIImage
public typealias CreateIconClosure = (M, MarkerState) -> UIImage
private let createIconClosure: CreateIconClosure
@ -31,8 +31,8 @@ open class DefaultMarkerIconFactory<M>: MarkerIconFactory {
self.createIconClosure = createIconClosure
}
open func markerIcon(for model: M) -> UIImage {
postprocess(icon: createIconClosure(model))
open func markerIcon(for model: M, state: MarkerState) -> UIImage {
postprocess(icon: createIconClosure(model, state))
}
open func postprocess(icon: UIImage) -> UIImage {

View File

@ -25,5 +25,5 @@ import UIKit.UIImage
public protocol MarkerIconFactory {
associatedtype Model
func markerIcon(for model: Model) -> UIImage
func markerIcon(for model: Model, state: MarkerState) -> UIImage
}

View File

@ -0,0 +1,31 @@
//
// 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.
//
/// Available marker states on any map
public enum MarkerState: String {
/// A state where a map point is selected and a marker is highlighted
case selected
/// A default state where a map point is shown on a map
case `default`
}

View File

@ -29,7 +29,7 @@ public final class StaticImageIconFactory<Model>: MarkerIconFactory {
self.image = image
}
public func markerIcon(for model: Model) -> UIImage {
public func markerIcon(for model: Model, state: MarkerState) -> UIImage {
image
}
}

View File

@ -0,0 +1,43 @@
//
// 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.
//
/**
Base cluster placemark manager
- Parameters:
- ClusterPlacemark: a generic parameter describing a cluster placemark itself
- ItemPlacemarkManager: a single placemarks manager element of array for a cluster
- ClusterBounds: a rectangle area of the current cluster or a collection of cluster placemark objects
*/
open class BaseClusterPlacemarkManager<ClusterPlacemark, ItemPlacemarkManager: PlacemarkManager, ClusterBounds>:
BasePlacemarkManager<ClusterPlacemark, [ItemPlacemarkManager], ClusterBounds> where ItemPlacemarkManager.Position : Equatable {
/// Manual selecting of a placemark with an incoming point coordinates
open func selectMarker(with point: ItemPlacemarkManager.Position) {
dataModel.filter { $0.placemarkPosition == point }.forEach { $0.state = .selected }
}
/// Manual state resetting of all placemarks with currently selected state
open func resetMarkersState() {
dataModel.filter { $0.state == .selected }.forEach { $0.state = .default }
}
}

View File

@ -0,0 +1,43 @@
//
// 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.
//
/**
Base single item placemark manager
Contains all properties of `BasePlacemarkManager` and adds a `placemark` property to use
- Parameters:
- Placemark: a placemark itself managed by an item placemark manager
- DataModel: a data model of a placemark which is used for configuration etc.
- Location: latitude and longitude of a placemark
*/
open class BaseItemPlacemarkManager<Placemark, DataModel, Location>: BasePlacemarkManager<Placemark, DataModel, Location> {
/// Placemark itself of the current placemark manager
public private(set) var placemark: Placemark?
override open func configure(placemark: Placemark) {
super.configure(placemark: placemark)
self.placemark = placemark
}
}

View File

@ -23,7 +23,7 @@
import Foundation
import UIKit.UIGeometry
open class BaseMapManager<Map,
open class BaseMapManager<Map: AnyObject,
PM: PlacemarkManager,
CPM: PlacemarkManager,
CUF: CameraUpdateFactory> where PM.Position: LocationCoordinate,
@ -79,16 +79,20 @@ open class BaseMapManager<Map,
}
open func set(items: [PM.DataModel]) {
let placemarkTapHandler: PM.TapHandlerClosure = { [map, selectPlacemarkHandler, animationDuration, cameraUpdateOnMarkerTap] model, location in
cameraUpdateOnMarkerTap?(location).update(map: map, animationDuration: animationDuration)
let placemarkTapHandler: PM.TapHandlerClosure = { [weak map, selectPlacemarkHandler, animationDuration, cameraUpdateOnMarkerTap] model, location in
if let map = map {
cameraUpdateOnMarkerTap?(location).update(map: map, animationDuration: animationDuration)
}
return selectPlacemarkHandler(model)
}
let placemarkManagers = items.compactMap { placemarkManagerCreator($0, placemarkTapHandler) }
let clusterTapHandler: CPM.TapHandlerClosure = { [map, animationDuration, cameraUpdateOnClusterTap] managers, boundingBox in
cameraUpdateOnClusterTap?(boundingBox).update(map: map, animationDuration: animationDuration)
let clusterTapHandler: CPM.TapHandlerClosure = { [weak map, animationDuration, cameraUpdateOnClusterTap] managers, boundingBox in
if let map = map {
cameraUpdateOnClusterTap?(boundingBox).update(map: map, animationDuration: animationDuration)
}
return true
}

View File

@ -23,17 +23,27 @@
import ObjectiveC
open class BasePlacemarkManager<Placemark, DataModel, Location>: NSObject, PlacemarkManager, PlacemarkConfigurator {
public typealias TapHandlerClosure = (DataModel, Location) -> Bool
/// The current state of a manager's placemark
open var state: MarkerState = .default
/// Point (coordinates) itself of the current placemark manager
public let placemarkPosition: Location
/// Model for the current placemark manager
public let dataModel: DataModel
public var tapHandler: TapHandlerClosure?
public var iconFactory: AnyMarkerIconFactory<DataModel>?
public let dataModel: DataModel
public init(dataModel: DataModel,
public init(placemarkPosition: Location,
dataModel: DataModel,
iconFactory: AnyMarkerIconFactory<DataModel>?,
tapHandler: TapHandlerClosure?) {
self.placemarkPosition = placemarkPosition
self.dataModel = dataModel
self.iconFactory = iconFactory
self.tapHandler = tapHandler

View File

@ -20,12 +20,24 @@
// THE SOFTWARE.
//
public protocol PlacemarkManager {
public protocol PlacemarkManager: AnyObject {
associatedtype DataModel
associatedtype Position
typealias TapHandlerClosure = (DataModel, Position) -> Bool
var placemarkPosition: Position { get }
var dataModel: DataModel { get }
var state: MarkerState { get set }
///
/// Validates whether the current tap could be handled or not
///
/// - Parameters:
/// - DataModel: A data model of the current placemark manager
/// - Position: A position of the current placemark
///
/// - Returns: A `Bool` value of the handling desicion
///
var tapHandler: TapHandlerClosure? { get set }
}

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIMapUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for map objects clustering and interacting.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '10.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIMoyaNetworking'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Moya + Swagger network service.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TINetworking'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Swagger-frendly networking layer helpers.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '10.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TINetworkingCache'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Caching results of EndpointRequests.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TIPagination'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Generic pagination component.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '10.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TISwiftUICore'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Core UI elements: protocols, views and helpers.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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.3']

View File

@ -1,14 +1,21 @@
Pod::Spec.new do |s|
s.name = 'TISwiftUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Bunch of useful helpers for Swift development.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '9.0'
s.swift_versions = ['5.3']
s.source_files = s.name + '/Sources/**/*'
sources = 'Sources/**/*.swift'
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
end

View File

@ -0,0 +1,46 @@
//
// 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 TableKit
import TIUIKitCore
extension TableRow: AppearanceConfigurable where CellType: AppearanceConfigurable {
private static var configureAppearanceActionId: String {
"TableRowConfigureAppearanceActionId"
}
public func with(appearance: CellType.Appearance) -> Self {
configure(appearance: appearance)
return self
}
public func configure(appearance: CellType.Appearance) {
removeAction(forActionId: Self.configureAppearanceActionId)
let action = TableRowAction<CellType>(.configure) { options in
options.cell?.configure(appearance: appearance)
}
action.id = Self.configureAppearanceActionId
on(action)
}
}

View File

@ -0,0 +1,35 @@
//
// 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 TableKit
import TIUIElements
import TIUIKitCore
import UIKit
public extension WrappableView where Self: ConfigurableView {
typealias InTableRow = TableKit.TableRow<Self.InTableCell>
typealias InSeparatableRow = TableKit.TableRow<Self.InSeparatableTableCell>
}
public extension WrappableView where Self: UITableViewCell & ConfigurableCell {
typealias TableRow = TableKit.TableRow<Self>
}

View File

@ -31,23 +31,23 @@ public extension Array where Element == SeparatorRowBox {
}
/// Configure separators from SeparatorRowBox array
/// - parameter extreme: Configuration that will be used for extreme values, for first or last row
/// - parameter middle: Configuration for intermediate rows
func configureSeparators(extreme extremeSeparatorConfiguration: SeparatorConfiguration,
middle middleSeparatorConfiguration: SeparatorConfiguration) {
/// - parameter extreme: Appearance that will be used for extreme values, for first or last row
/// - parameter middle: Apearance for intermediate rows
func configureSeparators(extreme extremeSeparatorsAppearance: SeparatorAppearance,
middle middleSeparatorsAppearance: SeparatorAppearance) {
configureSeparators(first: extremeSeparatorConfiguration,
middle: middleSeparatorConfiguration,
last: extremeSeparatorConfiguration)
configureSeparators(first: extremeSeparatorsAppearance,
middle: middleSeparatorsAppearance,
last: extremeSeparatorsAppearance)
}
/// Configure separators from SeparatorRowBox array
/// - parameter first: Configuration of the top separator of the first row
/// - parameter middle: Configuration of the separators between the rows
/// - parameter last: Configuration of the bottom separator of the last row
func configureSeparators(first firstSeparatorConfiguration: SeparatorConfiguration,
middle middleSeparatorConfiguration: SeparatorConfiguration,
last lastSeparatorConfiguration: SeparatorConfiguration) {
/// - parameter first: Appearance of the top separator of the first row
/// - parameter middle: Appearance of the separators between the rows
/// - parameter last: Appearance of the bottom separator of the last row
func configureSeparators(first firstSeparatorsAppearance: SeparatorAppearance,
middle middleSeparatorsAppearance: SeparatorAppearance,
last lastSeparatorsAppearance: SeparatorAppearance) {
if isEmpty {
return
@ -55,12 +55,12 @@ public extension Array where Element == SeparatorRowBox {
switch count {
case 1:
first?.set(separatorType: .full(firstSeparatorConfiguration, lastSeparatorConfiguration))
first?.set(separatorType: .full(top: firstSeparatorsAppearance, bottom: lastSeparatorsAppearance))
default:
dropFirst().dropLast().forEach { $0.set(separatorType: .bottom(middleSeparatorConfiguration)) }
first?.set(separatorType: .full(firstSeparatorConfiguration, middleSeparatorConfiguration))
last?.set(separatorType: .bottom(lastSeparatorConfiguration))
dropFirst().dropLast().forEach { $0.set(separatorType: .bottom(middleSeparatorsAppearance)) }
first?.set(separatorType: .full(top: firstSeparatorsAppearance, bottom: middleSeparatorsAppearance))
last?.set(separatorType: .bottom(lastSeparatorsAppearance))
}
}
}

View File

@ -0,0 +1,31 @@
//
// 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 TableKit
import TIUIElements
import TIUIKitCore
extension ContainerTableViewCell: ConfigurableCell where View: ConfigurableView {
public func configure(with viewModel: View.ViewModelType) {
wrappedView.configure(with: viewModel)
}
}

View File

@ -23,28 +23,29 @@
import TableKit
import TIUIElements
private let configureSeparatorActionId = "TableRowConfigureSeparatorActionId"
extension TableRow: SeparatorsConfigurable where CellType: SeparatorsConfigurable {
private static var configureSeparatorsActionId: String {
"TableRowConfigureSeparatorsActionId"
}
public extension TableRow where CellType: SeparatorConfigurable {
func with(separatorType: ViewSeparatorType) -> Self {
set(separatorType: separatorType)
public func with(separators: SeparatorsConfiguration) -> Self {
configureSeparators(with: separators)
return self
}
func set(separatorType: ViewSeparatorType) {
removeAction(forActionId: configureSeparatorActionId)
public func configureSeparators(with separatorsConfiguration: SeparatorsConfiguration) {
removeAction(forActionId: Self.configureSeparatorsActionId)
let action = TableRowAction<CellType>(.configure) {
$0.cell?.configureSeparators(with: separatorType)
let action = TableRowAction<CellType>(.configure) { options in
options.cell?.configureSeparators(with: separatorsConfiguration)
}
action.id = configureSeparatorActionId
action.id = Self.configureSeparatorsActionId
on(action)
}
}
public extension TableRow where CellType: SeparatorConfigurable {
public extension TableRow where CellType: SeparatorsConfigurable {
/// TableRow typed as SeparatorRowBox
var separatorRowBox: SeparatorRowBox {

View File

@ -21,21 +21,21 @@
//
import TableKit
import TIUIElements
import TISwiftUtils
import TIUIElements
/// Class that used to configure separators when multiply cells presented in one section
public final class SeparatorRowBox {
private let setSeparatorHandler: ParameterClosure<ViewSeparatorType>
private let setSeparatorHandler: ParameterClosure<SeparatorsConfiguration>
public func set(separatorType: ViewSeparatorType) {
public func set(separatorType: SeparatorsConfiguration) {
setSeparatorHandler(separatorType)
}
public let row: Row
public init<T>(row: TableRow<T>) where T: SeparatorConfigurable {
public init<T>(row: TableRow<T>) where T: SeparatorsConfigurable {
self.row = row
setSeparatorHandler = row.set
setSeparatorHandler = row.configureSeparators(with:)
}
}

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TITableKitUtils'
s.version = '1.33.0'
s.version = '1.39.0'
s.summary = 'Set of helpers for TableKit classes.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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://github.com/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 = '11.0'
s.swift_versions = ['5.3']

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = 'TITransitions'
s.version = '1.33.0'
s.version = '1.34.1'
s.summary = 'Set of custom transitions to present controller. '
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + 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 = { 'Loupehope' => 'vladislav.suhomlinov@touchin.ru' }
s.source = { :git => 'https://github.com/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 = '11.0'
s.swift_versions = ['5.0']

View File

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

View File

@ -0,0 +1,60 @@
//
// 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
import UIKit
extension UIButton {
open class BaseAppearance<Layout: ViewLayout>: UIView.BaseAppearance<Layout> {
public var textAttributes: BaseTextAttributes?
public var contentInsets: UIEdgeInsets
public var titleInsets: UIEdgeInsets
public var imageInsets: UIEdgeInsets
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
textAttributes: BaseTextAttributes? = nil,
contentInsets: UIEdgeInsets = .zero,
titleInsets: UIEdgeInsets = .zero,
imageInsets: UIEdgeInsets = .zero) {
self.textAttributes = textAttributes
self.contentInsets = contentInsets
self.titleInsets = titleInsets
self.imageInsets = imageInsets
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow)
}
}
public final class DefaultAppearance: BaseAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}

View File

@ -0,0 +1,53 @@
//
// 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
import UIKit
extension UIButton {
public func configureUIButton(appearance: UIButton.BaseAppearance<some ViewLayout>) {
appearance.textAttributes?
.configure(button: self,
with: titleLabel?.attributedText?.string ?? titleLabel?.text)
if #available(iOS 15, *) {
configuration?.contentInsets = .init(insets: appearance.contentInsets)
if configuration?.imagePlacement == .leading {
let padding = appearance.titleInsets.left + appearance.imageInsets.right
configuration?.imagePadding = padding
}
if configuration?.imagePlacement == .trailing {
let padding = appearance.titleInsets.right + appearance.imageInsets.left
configuration?.imagePadding = padding
}
} else {
contentEdgeInsets = appearance.contentInsets
titleEdgeInsets = appearance.titleInsets
imageEdgeInsets = appearance.imageInsets
}
super.configureUIView(appearance: appearance)
}
}

View File

@ -0,0 +1,51 @@
//
// 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
import UIKit
extension UILabel {
open class BaseAppearance<Layout: ViewLayout>: UIView.BaseAppearance<Layout> {
public var textAttributes: BaseTextAttributes?
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
textAttributes: BaseTextAttributes? = nil) {
self.textAttributes = textAttributes
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow)
}
}
public final class DefaultAppearance: BaseAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: DefaultAppearance {
DefaultAppearance()
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Touch Instinct
// 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
@ -20,17 +20,15 @@
// THE SOFTWARE.
//
import TIUIKitCore
import UIKit
public struct SeparatorConfiguration {
extension UILabel {
public func configureUILabel(appearance: BaseAppearance<some ViewLayout>) {
appearance.textAttributes?
.configure(label: self,
with: attributedText?.string ?? text)
public let color: UIColor
public let insets: UIEdgeInsets
public let height: CGFloat
public init(color: UIColor, insets: UIEdgeInsets = .zero, height: CGFloat = 1) {
self.color = color
self.insets = insets
self.height = height
super.configureUIView(appearance: appearance)
}
}

View File

@ -0,0 +1,45 @@
//
// 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
import UIKit
extension UIView {
public func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
backgroundColor = appearance.backgroundColor
layer.masksToBounds = true
layer.maskedCorners = appearance.border.roundedCorners
layer.cornerRadius = appearance.border.cornerRadius
layer.borderWidth = appearance.border.width
layer.borderColor = appearance.border.color.cgColor
guard let shadow = appearance.shadow else {
return
}
layer.shadowOpacity = shadow.opacity
layer.shadowOffset = shadow.offset
layer.shadowColor = shadow.color.cgColor
layer.shadowRadius = shadow.radius
clipsToBounds = false
}
}

View File

@ -0,0 +1,164 @@
//
// 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
import UIKit
extension UIView {
// MARK: - Layout Variations
public struct NoLayout: ViewLayout {
public static var defaultLayout: Self {
Self()
}
}
open class BaseSizeLayout {
public var size: CGSize
public init(size: CGSize = .infinity) {
self.size = size
}
}
public final class DefaultLayout: BaseSizeLayout, SizeViewLayout {
public static var defaultLayout: Self {
Self()
}
}
// MARK: - WrappedView Layout
open class BaseWrappedLayout: BaseSizeLayout {
public var centerOffset: UIOffset
public var insets: UIEdgeInsets
public init(insets: UIEdgeInsets = .zero,
size: CGSize = .infinity,
centerOffset: UIOffset = .nan) {
self.centerOffset = centerOffset
self.insets = insets
super.init(size: size)
}
}
open class BaseSpecedWrappedLayout: BaseWrappedLayout {
public var spacing: CGFloat
public init(insets: UIEdgeInsets = .zero,
size: CGSize = .infinity,
centerOffset: UIOffset = .nan,
spacing: CGFloat = .zero) {
self.spacing = spacing
super.init(insets: insets, size: size, centerOffset: centerOffset)
}
}
public final class DefaultWrappedLayout: BaseWrappedLayout, WrappedViewLayout {
public static var defaultLayout: Self {
Self()
}
}
public final class DefaultSpacedWrappedLayout: BaseSpecedWrappedLayout, SpacedWrappedViewLayout {
public static var defaultLayout: Self {
Self()
}
}
// MARK: - Appearance Variations
open class BaseAppearance<Layout: ViewLayout> {
public var layout: Layout
public var backgroundColor: UIColor
public var border: UIViewBorder
public var shadow: UIViewShadow?
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil) {
self.layout = layout
self.backgroundColor = backgroundColor
self.border = border
self.shadow = shadow
}
}
public final class DefaultAppearance: BaseAppearance<DefaultLayout>, ViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
// MARK: - WrappedView Appearance
open class BaseWrappedViewHolderAppearance<SubviewAppearance: WrappedViewAppearance,
Layout: ViewLayout>: BaseAppearance<Layout> {
public var subviewAppearance: SubviewAppearance
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
subviewAppearance: SubviewAppearance = .defaultAppearance) {
self.subviewAppearance = subviewAppearance
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow)
}
}
public final class DefaultWrappedViewHolderAppearance<SubviewAppearance: WrappedViewAppearance,
Layout: ViewLayout>: BaseWrappedViewHolderAppearance<SubviewAppearance, Layout>,
WrappedViewHolderAppearance {
public static var defaultAppearance: Self {
Self()
}
}
public final class DefaultWrappedAppearance: BaseAppearance<DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
public final class DefaultSpacedWrappedAppearance: BaseAppearance<DefaultSpacedWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}
extension UIView.DefaultWrappedViewHolderAppearance: WrappedViewAppearance where Layout: WrappedViewLayout {
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Touch Instinct
// 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
@ -22,7 +22,8 @@
import UIKit
open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable {
open class ContainerSeparatorTableViewCell<View: UIView>: ContainerTableViewCell<View>, SeparatorsConfigurable {
private lazy var topSeparatorView = createTopSeparator()
private lazy var bottomSeparatorView = createBottomSeparator()
@ -52,23 +53,25 @@ open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable {
contentView.addSubview(bottomSeparatorView)
}
public func configureSeparators(with separatorType: ViewSeparatorType) {
topSeparatorView.isHidden = separatorType.topIsHidden
bottomSeparatorView.isHidden = separatorType.bottomIsHidden
// MARK: - SeparatorsConfigurable
switch separatorType {
public func configureSeparators(with separatorsConfiguration: SeparatorsConfiguration) {
topSeparatorView.isHidden = separatorsConfiguration.topIsHidden
bottomSeparatorView.isHidden = separatorsConfiguration.bottomIsHidden
switch separatorsConfiguration {
case .none:
break
case let .bottom(configuration):
updateBottomSeparator(with: configuration)
case let .bottom(appearance):
updateBottomSeparator(with: appearance)
case let .top(configuration):
updateTopSeparator(with: configuration)
case let .top(appearance):
updateTopSeparator(with: appearance)
case let .full(topConfiguration, bottomConfiguration):
updateTopSeparator(with: topConfiguration)
updateBottomSeparator(with: bottomConfiguration)
case let .full(topAppearance, bottomAppearance):
updateTopSeparator(with: topAppearance)
updateBottomSeparator(with: bottomAppearance)
}
}
@ -127,26 +130,26 @@ open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
}
private extension BaseSeparatorCell {
func updateTopSeparator(with configuration: SeparatorConfiguration) {
topSeparatorView.backgroundColor = configuration.color
// MARK: - Private
topViewHeightConstraint?.constant = configuration.height
private func updateTopSeparator(with appearance: SeparatorAppearance) {
topSeparatorView.configureUIView(appearance: appearance)
topViewTopConstraint?.constant = configuration.insets.top
topViewLeftConstraint?.constant = configuration.insets.left
topViewRightConstraint?.constant = configuration.insets.right
topViewHeightConstraint?.constant = appearance.layout.size.height
topViewTopConstraint?.constant = appearance.layout.insets.top
topViewLeftConstraint?.constant = appearance.layout.insets.left
topViewRightConstraint?.constant = appearance.layout.insets.right
}
func updateBottomSeparator(with configuration: SeparatorConfiguration) {
bottomSeparatorView.backgroundColor = configuration.color
private func updateBottomSeparator(with appearance: SeparatorAppearance) {
bottomSeparatorView.configureUIView(appearance: appearance)
bottomViewHeightConstraint?.constant = configuration.height
bottomViewHeightConstraint?.constant = appearance.layout.size.height
bottomViewBottomConstraint?.constant = configuration.insets.bottom
bottomViewLeftConstraint?.constant = configuration.insets.left
bottomViewRightConstraint?.constant = configuration.insets.right
bottomViewBottomConstraint?.constant = appearance.layout.insets.bottom
bottomViewLeftConstraint?.constant = appearance.layout.insets.left
bottomViewRightConstraint?.constant = appearance.layout.insets.right
}
}

View File

@ -0,0 +1,44 @@
//
// 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
import UIKit
public final class SeparatorLayout: UIView.BaseWrappedLayout, WrappedViewLayout {
public static var defaultLayout: Self {
Self()
}
public init(insets: UIEdgeInsets = .zero,
size: CGSize = .fixedHeight(0.5)) {
super.init(insets: insets,
size: size,
centerOffset: .nan)
}
}
public final class SeparatorAppearance: UIView.BaseAppearance<SeparatorLayout>, ViewAppearance {
public static var defaultAppearance: Self {
Self(backgroundColor: .lightGray)
}
}

View File

@ -20,6 +20,6 @@
// THE SOFTWARE.
//
public protocol SeparatorConfigurable {
func configureSeparators(with separatorType: ViewSeparatorType)
public protocol SeparatorsConfigurable {
func configureSeparators(with separatorsConfiguration: SeparatorsConfiguration)
}

View File

@ -20,22 +20,14 @@
// THE SOFTWARE.
//
public enum ViewSeparatorType {
/// All separators for view is hidden
public enum SeparatorsConfiguration {
case none
/// Show only top separator
case top(SeparatorConfiguration)
/// Show only bottom separator
case bottom(SeparatorConfiguration)
/// First configuration for top, second for bottom
case full(SeparatorConfiguration, SeparatorConfiguration)
case top(SeparatorAppearance)
case bottom(SeparatorAppearance)
case full(top: SeparatorAppearance, bottom: SeparatorAppearance)
}
public extension ViewSeparatorType {
public extension SeparatorsConfiguration {
/// Determine if bottom separator is hidden.
var bottomIsHidden: Bool {
@ -48,7 +40,7 @@ public extension ViewSeparatorType {
}
/// Returns top configuration if type is top or full.
var topConfiguration: SeparatorConfiguration? {
var topConfiguration: SeparatorAppearance? {
switch self {
case let .top(configuration), let .full(configuration, _):
return configuration
@ -59,7 +51,7 @@ public extension ViewSeparatorType {
}
/// Returns bottom configuration if type is bottom or full.
var bottomConfiguration: SeparatorConfiguration? {
var bottomConfiguration: SeparatorAppearance? {
switch self {
case let .bottom(configuration), let .full(_, configuration):
return configuration

View File

@ -0,0 +1,86 @@
//
// 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
import UIKit
extension WrappedViewLayout {
func setupSize(widthConstraint: NSLayoutConstraint?,
heightConstraint: NSLayoutConstraint?) {
if size.width.isFinite {
widthConstraint?.constant = size.width
widthConstraint?.isActive = true
} else {
widthConstraint?.isActive = false
}
if size.height.isFinite {
heightConstraint?.constant = size.height
heightConstraint?.isActive = true
} else {
heightConstraint?.isActive = false
}
}
func setupCenterYOffset(centerYConstraint: NSLayoutConstraint?,
topConstraint: NSLayoutConstraint?,
bottomConstraint: NSLayoutConstraint?) {
let centerYOffset = centerOffset.vertical
if centerYOffset.isFinite {
centerYConstraint?.constant = centerYOffset
centerYConstraint?.isActive = true
topConstraint?.isActive = false
bottomConstraint?.isActive = false
} else {
topConstraint?.constant = insets.top
bottomConstraint?.constant = -insets.bottom
centerYConstraint?.isActive = false
topConstraint?.isActive = true
bottomConstraint?.isActive = true
}
}
func setupCenterXOffset(centerXConstraint: NSLayoutConstraint?,
leadingConstraint: NSLayoutConstraint?,
trailingConstraint: NSLayoutConstraint?) {
let centerXOffset = centerOffset.horizontal
if centerXOffset.isFinite {
centerXConstraint?.constant = centerXOffset
centerXConstraint?.isActive = true
leadingConstraint?.isActive = false
trailingConstraint?.isActive = false
} else {
leadingConstraint?.constant = insets.left
trailingConstraint?.constant = -insets.right
centerXConstraint?.isActive = false
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
}
}
}

View File

@ -0,0 +1,51 @@
//
// 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
import UIKit
open class BaseListItemAppearance<LeadingViewAppearance: WrappedViewAppearance,
MiddleViewAppearance: WrappedViewAppearance,
TrailingViewAppearance: WrappedViewAppearance>: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
public var leadingViewAppearance: LeadingViewAppearance
public var middleViewAppearance: MiddleViewAppearance
public var trailingAppearance: TrailingViewAppearance
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
leadingViewAppearance: LeadingViewAppearance = .defaultAppearance,
middleViewAppearance: MiddleViewAppearance = .defaultAppearance,
trailingViewAppearance: TrailingViewAppearance = .defaultAppearance) {
self.leadingViewAppearance = leadingViewAppearance
self.middleViewAppearance = middleViewAppearance
self.trailingAppearance = trailingViewAppearance
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow)
}
}

View File

@ -0,0 +1,258 @@
//
// 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
import UIKit
open class BaseListItemView<LeadingView: UIView,
MiddleView: UIView,
TrailingView: UIView>: BaseInitializableView {
public let leadingView = LeadingView()
public let middleView = MiddleView()
public let trailingView = TrailingView()
// MARK: - Constraints
public var leadingViewLeadingToSuperviewConstraint: NSLayoutConstraint?
public var leadingViewTopConstraint: NSLayoutConstraint?
public var leadingViewBottomConstraint: NSLayoutConstraint?
public var leadingViewCenterYConstraint: NSLayoutConstraint?
public var leadingViewWidthConstraint: NSLayoutConstraint?
public var leadingViewHeightConstraint: NSLayoutConstraint?
public var middleViewLeadingToSuperViewConstraint: NSLayoutConstraint?
public var middleViewLeadingConstraint: NSLayoutConstraint?
public var middleViewTopConstraint: NSLayoutConstraint?
public var middleViewTrailingToSuperViewConstraint: NSLayoutConstraint?
public var middleViewBottomConstraint: NSLayoutConstraint?
public var middleViewCenterYConstraint: NSLayoutConstraint?
public var middleViewWidthConstraint: NSLayoutConstraint?
public var middleViewHeightConstraint: NSLayoutConstraint?
public var trailingViewLeadingToMiddleViewConstraint: NSLayoutConstraint?
public var trailingViewTopConstraint: NSLayoutConstraint?
public var trailingViewTrailingToSuperviewConstraint: NSLayoutConstraint?
public var trailingViewBottomConstraint: NSLayoutConstraint?
public var trailingViewCenterYConstraint: NSLayoutConstraint?
public var trailingViewWidthConstraint: NSLayoutConstraint?
public var trailingViewHeightConstraint: NSLayoutConstraint?
// MARK: - Public Properties
public var leadingViewConstraints: [NSLayoutConstraint?] {
[
leadingViewLeadingToSuperviewConstraint,
leadingViewTopConstraint,
leadingViewBottomConstraint,
leadingViewCenterYConstraint,
leadingViewHeightConstraint,
leadingViewWidthConstraint
]
}
public var trailingViewConstraints: [NSLayoutConstraint?] {
[
trailingViewLeadingToMiddleViewConstraint,
trailingViewTopConstraint,
trailingViewBottomConstraint,
trailingViewTrailingToSuperviewConstraint,
trailingViewCenterYConstraint,
trailingViewHeightConstraint,
trailingViewWidthConstraint,
]
}
open var isLeadingViewHidden: Bool {
leadingView.isHidden
}
open var isTrailingViewHidden: Bool {
trailingView.isHidden
}
// MARK: - BaseInitializableView
open override func addViews() {
super.addViews()
addSubviews(leadingView, middleView, trailingView)
}
open override func configureLayout() {
super.configureLayout()
[leadingView, middleView, trailingView]
.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
leadingViewLeadingToSuperviewConstraint = leadingView.leadingAnchor.constraint(equalTo: leadingAnchor)
leadingViewTopConstraint = leadingView.topAnchor.constraint(equalTo: topAnchor)
leadingViewBottomConstraint = leadingView.bottomAnchor.constraint(equalTo: bottomAnchor)
leadingViewCenterYConstraint = leadingView.centerYAnchor.constraint(equalTo: centerYAnchor)
leadingViewWidthConstraint = leadingView.widthAnchor.constraint(equalToConstant: .zero)
leadingViewHeightConstraint = leadingView.heightAnchor.constraint(equalToConstant: .zero)
middleViewLeadingToSuperViewConstraint = middleView.leadingAnchor.constraint(equalTo: leadingAnchor)
middleViewLeadingConstraint = middleView.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor)
middleViewCenterYConstraint = middleView.centerYAnchor.constraint(equalTo: centerYAnchor)
middleViewTopConstraint = middleView.topAnchor.constraint(equalTo: topAnchor)
middleViewTrailingToSuperViewConstraint = middleView.trailingAnchor.constraint(equalTo: trailingAnchor)
middleViewBottomConstraint = middleView.bottomAnchor.constraint(equalTo: bottomAnchor)
middleViewWidthConstraint = middleView.widthAnchor.constraint(equalToConstant: .zero)
middleViewHeightConstraint = middleView.heightAnchor.constraint(equalToConstant: .zero)
trailingViewLeadingToMiddleViewConstraint = trailingView.leadingAnchor.constraint(equalTo: middleView.trailingAnchor)
trailingViewTopConstraint = trailingView.topAnchor.constraint(equalTo: topAnchor)
trailingViewTrailingToSuperviewConstraint = trailingView.trailingAnchor.constraint(equalTo: trailingAnchor)
trailingViewBottomConstraint = trailingView.bottomAnchor.constraint(equalTo: bottomAnchor)
trailingViewCenterYConstraint = trailingView.centerYAnchor.constraint(equalTo: centerYAnchor)
trailingViewWidthConstraint = trailingView.widthAnchor.constraint(equalToConstant: .zero)
trailingViewHeightConstraint = trailingView.heightAnchor.constraint(equalToConstant: .zero)
NSLayoutConstraint.activate([
leadingViewLeadingToSuperviewConstraint,
leadingViewTopConstraint,
leadingViewBottomConstraint,
middleViewLeadingConstraint,
trailingViewTopConstraint,
trailingViewBottomConstraint,
trailingViewLeadingToMiddleViewConstraint,
trailingViewTrailingToSuperviewConstraint,
].compactMap { $0 })
NSLayoutConstraint.deactivate([
leadingViewCenterYConstraint,
leadingViewWidthConstraint,
leadingViewHeightConstraint,
middleViewLeadingToSuperViewConstraint,
middleViewTopConstraint,
middleViewTrailingToSuperViewConstraint,
middleViewBottomConstraint,
middleViewCenterYConstraint,
middleViewWidthConstraint,
middleViewHeightConstraint,
trailingViewCenterYConstraint,
trailingViewHeightConstraint,
trailingViewWidthConstraint,
].compactMap { $0 })
}
// MARK: - Public methods
public func configureBaseListItem(appearance: BaseListItemAppearance<some WrappedViewAppearance,
some WrappedViewAppearance,
some WrappedViewAppearance>) {
configureUIView(appearance: appearance)
updateLeadingViewLayout(leadingViewLayout: appearance.leadingViewAppearance.layout,
middleViewLayout: appearance.middleViewAppearance.layout)
updateMiddleViewLayout(containerLayout: appearance.layout,
middleViewLayout: appearance.middleViewAppearance.layout)
updateTrailingViewLayout(trailingViewLayout: appearance.trailingAppearance.layout,
middleViewLayout: appearance.middleViewAppearance.layout)
}
// MARK: - Private methdos
private func updateLeadingViewLayout(leadingViewLayout: WrappedViewLayout,
middleViewLayout: WrappedViewLayout) {
let leadingToSuperviewContraint: NSLayoutConstraint?
let leadingViewLeftInset: CGFloat
if isLeadingViewHidden {
NSLayoutConstraint.deactivate(leadingViewConstraints.compactMap { $0 })
middleViewLeadingToSuperViewConstraint?.isActive = true
leadingToSuperviewContraint = middleViewLeadingToSuperViewConstraint
leadingViewLeftInset = middleViewLayout.insets.left
} else {
middleViewLeadingConstraint?.constant = leadingViewLayout.insets.right + middleViewLayout.insets.left
leadingViewLeadingToSuperviewConstraint?.isActive = true
middleViewLeadingConstraint?.isActive = true
middleViewLeadingToSuperViewConstraint?.isActive = false
leadingViewLayout.setupCenterYOffset(centerYConstraint: leadingViewCenterYConstraint,
topConstraint: leadingViewTopConstraint,
bottomConstraint: leadingViewBottomConstraint)
leadingViewLayout.setupSize(widthConstraint: leadingViewWidthConstraint,
heightConstraint: leadingViewHeightConstraint)
leadingToSuperviewContraint = leadingViewLeadingToSuperviewConstraint
leadingViewLeftInset = leadingViewLayout.insets.left
}
leadingToSuperviewContraint?.constant = leadingViewLeftInset
}
private func updateMiddleViewLayout(containerLayout: ViewLayout,
middleViewLayout: WrappedViewLayout) {
middleViewLayout.setupCenterYOffset(centerYConstraint: middleViewCenterYConstraint,
topConstraint: middleViewTopConstraint,
bottomConstraint: middleViewBottomConstraint)
middleViewLayout.setupSize(widthConstraint: middleViewWidthConstraint,
heightConstraint: middleViewHeightConstraint)
}
private func updateTrailingViewLayout(trailingViewLayout: WrappedViewLayout,
middleViewLayout: WrappedViewLayout) {
let trailingToSuperviewConstraint: NSLayoutConstraint?
let trailingViewRightInset: CGFloat
if isTrailingViewHidden {
NSLayoutConstraint.deactivate(trailingViewConstraints.compactMap { $0 })
middleViewTrailingToSuperViewConstraint?.isActive = true
trailingToSuperviewConstraint = middleViewTrailingToSuperViewConstraint
trailingViewRightInset = middleViewLayout.insets.right
} else {
trailingViewLeadingToMiddleViewConstraint?.constant = middleViewLayout.insets.right + trailingViewLayout.insets.left
trailingViewLeadingToMiddleViewConstraint?.isActive = true
middleViewTrailingToSuperViewConstraint?.isActive = false
trailingViewLayout.setupCenterYOffset(centerYConstraint: trailingViewCenterYConstraint,
topConstraint: trailingViewTopConstraint,
bottomConstraint: trailingViewBottomConstraint)
trailingViewLayout.setupSize(widthConstraint: trailingViewWidthConstraint,
heightConstraint: trailingViewWidthConstraint)
trailingToSuperviewConstraint = trailingViewTrailingToSuperviewConstraint
trailingViewRightInset = trailingViewLayout.insets.right
}
trailingToSuperviewConstraint?.constant = -trailingViewRightInset
}
}

View File

@ -0,0 +1,35 @@
//
// 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 QuartzCore
open class BaseSkeletonsAnimationConfiguration {
public var duration: CFTimeInterval
public var timingFunction: CAMediaTimingFunction?
public init(duration: CFTimeInterval = 1, timingFunction: CAMediaTimingFunction? = nil) {
self.duration = duration
self.timingFunction = timingFunction
}
}

View File

@ -0,0 +1,37 @@
//
// 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 QuartzCore
open class DirectionalSkeletonsAnimationConfiguration: BaseSkeletonsAnimationConfiguration {
public var direction: SkeletonsAnimationDirection
public init(direction: SkeletonsAnimationDirection = .leftToRight,
duration: CFTimeInterval = 1.5,
timingFunction: CAMediaTimingFunction? = nil) {
self.direction = direction
super.init(duration: duration, timingFunction: timingFunction)
}
}

View File

@ -0,0 +1,45 @@
//
// 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 QuartzCore
open class SkeletonsAnimationBuilder {
public static func createDirectionalGradientAnimation(_ conf: DirectionalSkeletonsAnimationConfiguration) -> CAAnimationGroup {
let startPointAnimation = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
startPointAnimation.fromValue = conf.direction.startPoint.from
startPointAnimation.toValue = conf.direction.startPoint.to
let endPointAnimation = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
endPointAnimation.fromValue = conf.direction.endPoint.from
endPointAnimation.toValue = conf.direction.endPoint.to
let animationGroup = CAAnimationGroup()
animationGroup.timingFunction = conf.timingFunction
animationGroup.duration = conf.duration
animationGroup.animations = [startPointAnimation, endPointAnimation]
animationGroup.repeatCount = .infinity
return animationGroup
}
}

View File

@ -0,0 +1,92 @@
//
// 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 struct CoreGraphics.CGPoint
typealias GradientAnimationAnchorPoints = (from: CGPoint, to: CGPoint)
public enum SkeletonsAnimationDirection {
case leftToRight
case rightToLeft
case topToBottom
case bottomToTop
case topLeftToBottomRight
case topRightToBottomLeft
case bottomLeftToTopRight
case bottomRightToTopLeft
var startPoint: GradientAnimationAnchorPoints {
switch self {
case .leftToRight:
return (from: CGPoint(x: -1, y: 0.5), to: CGPoint(x: 1, y: 0.5))
case .rightToLeft:
return (from: Self.leftToRight.startPoint.to, to: Self.leftToRight.startPoint.from)
case .topToBottom:
return (from: CGPoint(x: 0.5, y: -1), to: CGPoint(x: 0.5, y: 1))
case .bottomToTop:
return (from: Self.topToBottom.startPoint.to, to: Self.topToBottom.startPoint.from)
case .topLeftToBottomRight:
return (from: CGPoint(x: -1, y: -1), to: CGPoint(x: 1, y: 1))
case .topRightToBottomLeft:
return (from: Self.bottomLeftToTopRight.startPoint.to, to: Self.bottomLeftToTopRight.startPoint.from)
case .bottomLeftToTopRight:
return (from: CGPoint(x: -1, y: 2), to: CGPoint(x: 1, y: 0))
case .bottomRightToTopLeft:
return (from: Self.topLeftToBottomRight.startPoint.to, to: Self.topLeftToBottomRight.startPoint.from)
}
}
var endPoint: GradientAnimationAnchorPoints {
switch self {
case .leftToRight:
return (from: CGPoint(x: 0, y: 0.5), to: CGPoint(x: 2, y: 0.5))
case .rightToLeft:
return (from: Self.leftToRight.endPoint.to, to: Self.leftToRight.endPoint.from)
case .topToBottom:
return (from: CGPoint(x: 0.5, y: 0), to: CGPoint(x: 0.5, y: 2))
case .bottomToTop:
return (from: Self.topToBottom.endPoint.to, to: Self.topToBottom.endPoint.from)
case .topLeftToBottomRight:
return (from: CGPoint(x: 0, y: 0), to: CGPoint(x: 2, y: 2))
case .topRightToBottomLeft:
return (from: Self.bottomLeftToTopRight.endPoint.to, to: Self.bottomLeftToTopRight.endPoint.from)
case .bottomLeftToTopRight:
return (from: CGPoint(x: 0, y: 1), to: CGPoint(x: 2, y: -1))
case .bottomRightToTopLeft:
return (from: Self.topLeftToBottomRight.endPoint.to, to: Self.topLeftToBottomRight.endPoint.from)
}
}
}

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