feature/stateful_button_improvements #12

Merged
ivan.smolin merged 8 commits from feature/stateful_button_improvements into master 2023-07-24 10:00:38 +03:00
85 changed files with 1693 additions and 392 deletions

View File

@ -1,5 +1,12 @@
# Changelog
### 1.50.0
- **Updated**: Fix activity indicator positioning for `StatefulButton` on iOS 15+ and disabled state touch handling
- **Added**: iOS 15+ activity indicator placement support in `StatefulButton`
- **Added**: `TICoreGraphicsUtils` module for drawing operations and other CoreGraphics related functionality
- **Update**: `MarkerIconFactory` can now return optional `UIImage`. In this case MapManagers will show the default marker icon.
### 1.49.0
- **Added**: `BaseMigratingSingleValueKeychainStorage` and `BaseMigratingSingleValueDefaultsStorage` implementations for migrating keys from one storage to another.

View File

@ -22,6 +22,7 @@ let package = Package(
// MARK: - Utils
.library(name: "TISwiftUtils", targets: ["TISwiftUtils"]),
.library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]),
.library(name: "TICoreGraphicsUtils", targets: ["TICoreGraphicsUtils"]),
.library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]),
.library(name: "TITableKitUtils", targets: ["TITableKitUtils"]),
.library(name: "TIDeeplink", targets: ["TIDeeplink"]),
@ -59,7 +60,13 @@ let package = Package(
// MARK: - UIKit
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.target(name: "TIUIElements",
dependencies: ["TIUIKitCore", "TISwiftUtils"],
path: "TIUIElements/Sources",
exclude: ["../TIUIElements.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
// MARK: - SwiftUI
@ -77,6 +84,12 @@ let package = Package(
exclude: ["TIFoundationUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TICoreGraphicsUtils",
dependencies: [],
path: "TICoreGraphicsUtils/Sources",
exclude: ["../TICoreGraphicsUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIKeychainUtils",
dependencies: ["TIFoundationUtils", "KeychainAccess"],
path: "TIKeychainUtils/Sources",
@ -108,7 +121,7 @@ let package = Package(
// MARK: - Maps
.target(name: "TIMapUtils",
dependencies: ["TILogging"],
dependencies: ["TILogging", "TICoreGraphicsUtils"],
path: "TIMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),

View File

@ -89,6 +89,8 @@ LICENSE
- [TIFoundationUtils](docs/tifoundationutils)
* [AsyncOperation](docs/tifoundationutils/asyncoperation.md)
- [TICoreGraphicsUtils](docs/ticoregraphicsutils)
* [DrawingOperations](docs/ticoregraphicsutils/drawingoperations.md)
- [TIKeychainUtils](docs/tikeychainutils)
* [SingleValueStorage](docs/tikeychainutils/singlevaluestorage.md)
- [TIUIElements](docs/tiuielements)

View File

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

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIAuth'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

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

View File

@ -22,11 +22,9 @@
import CoreGraphics.CGGeometry
public typealias CGContextSize = (width: Int, height: Int)
public extension CGSize {
var ceiledContextSize: CGContextSize {
(width: Int(ceil(width)), height: Int(ceil(height)))
var ceiledContextSize: CGSize {
CGSize(width: ceil(width), height: ceil(height))
}
}

View File

@ -55,8 +55,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape {
case let .rect(rect):
return rect
case let .ellipse(rect):
return rect
case let .path(path):
return path.boundingBox
}
@ -68,8 +70,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape {
case let .rect(rect):
context.fill(rect)
case let .ellipse(rect):
context.fillEllipse(in: rect)
case let .path(path):
context.addPath(path)
context.fillPath()

View File

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

View File

@ -26,48 +26,45 @@ import CoreText
public struct TextDrawingOperation: OrientationAwareDrawingOperation {
public var text: String
public var font: CTFont
public var textColor: CGColor
public var textAttributes: [NSAttributedString.Key: Any]
public var flipHorizontallyDuringDrawing: Bool
public var desiredOffset: CGPoint
private var line: CTLine {
let textAttributes: [NSAttributedString.Key : Any] = [
.font: font,
.foregroundColor: textColor
]
public var attributedString: NSAttributedString {
NSAttributedString(string: text, attributes: textAttributes)
}
let attributedString = NSAttributedString(string: text, attributes: textAttributes)
public var line: CTLine {
CTLineCreateWithAttributedString(attributedString)
}
return CTLineCreateWithAttributedString(attributedString)
public var desiredContextSize: CGSize {
let imageBounds = CTLineGetImageBounds(line, nil)
return CGSize(width: max(imageBounds.width, imageBounds.maxX) + desiredOffset.x,
height: max(imageBounds.height, imageBounds.maxY) + desiredOffset.y)
}
public init(text: String,
font: CTFont,
textColor: CGColor,
textAttributes: [NSAttributedString.Key: Any],
flipHorizontallyDuringDrawing: Bool = true,
desiredOffset: CGPoint = .zero) {
self.text = text
self.font = font
self.textColor = textColor
self.textAttributes = textAttributes
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
self.desiredOffset = desiredOffset
}
// MARK: - DrawingOperation
public func affectedArea(in context: CGContext? = nil) -> CGRect {
CGRect(origin: desiredOffset, size: CTLineGetImageBounds(line, context).size)
CTLineGetImageBounds(line, context).offset(by: desiredOffset)
}
public func apply(in context: CGContext) {
apply(in: context) {
let originForDrawing = offsetForDrawing(CTLineGetImageBounds(line, context).origin)
let desiredOffsetForDrawing = offsetForDrawing(desiredOffset)
let textPosition = CGPoint(x: desiredOffsetForDrawing.x - originForDrawing.x,
y: desiredOffsetForDrawing.y - originForDrawing.y)
$0.textPosition = textPosition
$0.textPosition = offsetForDrawing(affectedArea(in: context).origin)
CTLineDraw(line, $0)
}
}

View File

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

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,8 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 11.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TICoreGraphicsUtils.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 = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

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

View File

@ -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="TICoreGraphicsUtils.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

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

View File

@ -0,0 +1,23 @@
Pod::Spec.new do |s|
s.name = 'TICoreGraphicsUtils'
s.version = '1.50.0'
s.summary = 'CoreGraphics drawing helpers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.framework = 'CoreGraphics'
end

View File

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

View File

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

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIDeeplink'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Deeplink service API'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIDeveloperUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Universal web view API'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIEcommerce'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Cart, products, promocodes, bonuses and other related actions'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

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

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIFoundationUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for Foundation framework classes.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -62,24 +62,16 @@ open class GoogleMapManager<DataModel>: BaseMapManager<GMSMapView,
}
public convenience init(map: GMSMapView,
positionGetter: @escaping PositionGetter,
iconFactory: DefaultMarkerIconFactory<DataModel>? = nil,
clusterIconFactory: DefaultClusterMarkerIconFactory<DataModel>? = nil,
mapViewDelegate: GMSMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) {
self.init(map: map,
positionGetter: positionGetter,
iconFactory: nil as DefaultMarkerIconFactory<DataModel>?,
clusterIconFactory: nil as DefaultClusterMarkerIconFactory<DataModel>?,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}
public convenience init(map: GMSMapView,
mapViewDelegate: GMSMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) where DataModel: MapLocatable, DataModel.Position == CLLocationCoordinate2D {
selectPlacemarkHandler: @escaping SelectPlacemarkHandler)
where DataModel: MapLocatable, DataModel.Position == CLLocationCoordinate2D {
self.init(map: map,
positionGetter: { $0.position },
iconFactory: iconFactory,
clusterIconFactory: clusterIconFactory,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIGoogleMapUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

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

View File

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

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIKeychainUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for Keychain classes.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TILogging'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Logging for TI libraries.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

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

View File

@ -20,6 +20,7 @@
// THE SOFTWARE.
//
import TICoreGraphicsUtils
import CoreGraphics
public struct CurrentLocationDrawingOperation: DrawingOperation {

View File

@ -37,12 +37,15 @@ open class DefaultCachableMarkerIconFactory<M, K: AnyObject>: DefaultMarkerIconF
super.init(createIconClosure: createIconClosure)
}
open override func markerIcon(for model: M, state: MarkerState) -> UIImage {
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, state: state)
cache.setObject(icon, forKey: cacheKey)
if let icon {
cache.setObject(icon, forKey: cacheKey)
}
return icon
}

View File

@ -21,6 +21,7 @@
//
import UIKit
import TICoreGraphicsUtils
open class DefaultClusterIconRenderer {
public struct TextAttributes {
@ -74,14 +75,11 @@ open class DefaultClusterIconRenderer {
}
open func textDrawingOperation(for text: String) -> TextDrawingOperation {
let ctFont = CTFontCreateWithFontDescriptorAndOptions(textAttributes.font.fontDescriptor,
textAttributes.font.pointSize,
nil,
[])
return TextDrawingOperation(text: text,
font: ctFont,
textColor: textAttributes.color.cgColor)
TextDrawingOperation(text: text,
textAttributes: [
.font: textAttributes.font,
.foregroundColor: textAttributes.color
])
}
open func backgroundDrawingOperation(iconSize: CGSize,
@ -98,11 +96,12 @@ open class DefaultClusterIconRenderer {
return SolidFillDrawingOperation(color: color.cgColor,
path: path)
case let .image(image):
guard let cgImage = image.cgImage else {
return nil
}
return TransformDrawingOperation(image: cgImage,
imageSize: image.size,
maxNewSize: iconSize,
@ -138,7 +137,7 @@ open class DefaultClusterIconRenderer {
var textDrawingOperation = textDrawingOperation(for: text)
let textSize = textDrawingOperation.affectedArea().size
let textSize = textDrawingOperation.desiredContextSize
let textRadius = sqrt(textSize.height * textSize.height + textSize.width * textSize.width) / 2
let internalRadius = textRadius + marginToText
@ -149,7 +148,7 @@ open class DefaultClusterIconRenderer {
let radius = CGFloat(min(iconSizeWithBorder.width, iconSizeWithBorder.height) / 2)
textDrawingOperation.desiredOffset = CGPoint(x: (iconSizeWithBorder.width - textSize.width) / 2,
y: (iconSizeWithBorder.height - textSize.height) / 2)
y: (iconSizeWithBorder.height - textSize.height) / 2)
let backgroundDrawingOperation = backgroundDrawingOperation(iconSize: iconSize,
iconSizeWithBorder: iconSizeWithBorder,
@ -158,9 +157,11 @@ open class DefaultClusterIconRenderer {
let borderDrawindOperation = borderDrawingOperation(iconSize: iconSize,
cornerRadius: radius)
let operations = [backgroundDrawingOperation,
textDrawingOperation,
borderDrawindOperation]
let operations = [
backgroundDrawingOperation,
textDrawingOperation,
borderDrawindOperation
]
.compactMap { $0 }
return execute(drawingOperations: operations,

View File

@ -25,19 +25,26 @@ import Foundation.NSString
public final class DefaultClusterMarkerIconFactory<Model>: DefaultCachableMarkerIconFactory<[Model], NSString> {
public typealias RendererPreparationClosure = ([Model], DefaultClusterIconRenderer) -> Void
public var clusterIconRenderer: DefaultClusterIconRenderer
public var beforeRenderCallback: RendererPreparationClosure?
public init(beforeRenderCallback: RendererPreparationClosure? = nil) {
self.beforeRenderCallback = beforeRenderCallback
self.clusterIconRenderer = DefaultClusterIconRenderer()
super.init { [clusterIconRenderer] models, _ in
beforeRenderCallback?(models, clusterIconRenderer)
return clusterIconRenderer.renderCluster(of: models.count)
} cacheKeyProvider: { models, _ in
public static var defaultCacheKeyProvider: CacheKeyProvider {
{ models, _ in
String(models.count) as NSString
}
}
public var clusterIconRenderer: DefaultClusterIconRenderer
public var beforeRenderCallback: RendererPreparationClosure?
public init(clusterIconRenderer: DefaultClusterIconRenderer = DefaultClusterIconRenderer(),
beforeRenderCallback: RendererPreparationClosure? = nil,
cacheKeyProvider: @escaping CacheKeyProvider = defaultCacheKeyProvider) {
self.beforeRenderCallback = beforeRenderCallback
self.clusterIconRenderer = clusterIconRenderer
super.init(createIconClosure: { [clusterIconRenderer] models, _ in
beforeRenderCallback?(models, clusterIconRenderer)
return clusterIconRenderer.renderCluster(of: models.count)
}, cacheKeyProvider: cacheKeyProvider)
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIMapUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for map objects clustering and interacting.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
@ -19,6 +19,7 @@ Pod::Spec.new do |s|
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TICoreGraphicsUtils', s.version.to_s
s.dependency 'TILogging', s.version.to_s
end

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIMoyaNetworking'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Moya + Swagger network service.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TINetworking'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Swagger-frendly networking layer helpers.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TINetworkingCache'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Caching results of EndpointRequests.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIPagination'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Generic pagination component.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TISwiftUICore'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Core UI elements: protocols, views and helpers.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TISwiftUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Bunch of useful helpers for Swift development.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TITableKitUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for TableKit classes.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TITextProcessing'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'A text processing service helping to get a text mask and a placeholder from incoming regex.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -24,26 +24,41 @@ import TIUIKitCore
import UIKit
extension UIButton {
open class BaseAppearance<Layout: ViewLayout>: UIView.BaseAppearance<Layout> {
public var textAttributes: BaseTextAttributes?
open class BaseContentLayout {
public var contentInsets: UIEdgeInsets
public var titleInsets: UIEdgeInsets
public var imageInsets: UIEdgeInsets
public init(contentInsets: UIEdgeInsets = .zero,
titleInsets: UIEdgeInsets = .zero,
imageInsets: UIEdgeInsets = .zero) {
self.contentInsets = contentInsets
self.titleInsets = titleInsets
self.imageInsets = imageInsets
}
}
public final class DefaultContentLayout: BaseContentLayout, ViewLayout {
public static var defaultLayout: Self {
Self()
}
}
open class BaseAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>: UIView.BaseAppearance<Layout> {
public var textAttributes: BaseTextAttributes?
public var contentLayout: ContentLayout
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) {
contentLayout: ContentLayout = .defaultLayout) {
self.textAttributes = textAttributes
self.contentInsets = contentInsets
self.titleInsets = titleInsets
self.imageInsets = imageInsets
self.contentLayout = contentLayout
super.init(layout: layout,
backgroundColor: backgroundColor,
@ -52,7 +67,13 @@ extension UIButton {
}
}
public final class DefaultAppearance: BaseAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public final class DefaultStateAppearance: BaseAppearance<UIView.NoLayout, DefaultContentLayout>, ViewAppearance {
Review

Как будто бы по неймингу не понять, чем DefaultStateAppearance отличается от DefaultAppearance. Важно ли пользователю класса понимать различия в данном контексте?

Как будто бы по неймингу не понять, чем `DefaultStateAppearance` отличается от `DefaultAppearance`. Важно ли пользователю класса понимать различия в данном контексте?
Review

State это настройки appearance для стейта, а второй для всего appearance кнопки.
Да, понимать разницу важно, если глубже dsl смотреть. А какие есть предложения по неймингу?

State это настройки appearance для стейта, а второй для всего appearance кнопки. Да, понимать разницу важно, если глубже dsl смотреть. А какие есть предложения по неймингу?
public static var defaultAppearance: Self {
Self()
}
}
public final class DefaultAppearance: BaseAppearance<DefaultWrappedLayout, DefaultContentLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}

View File

@ -24,30 +24,84 @@ import TIUIKitCore
import UIKit
extension UIButton {
public func configureUIButton(appearance: UIButton.BaseAppearance<some ViewLayout>) {
public func configureUIButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>,
for state: UIControl.State = .normal) {
appearance.textAttributes?
.configure(button: self,
with: titleLabel?.attributedText?.string ?? titleLabel?.text)
with: title(for: state),
for: state)
if #available(iOS 15, *) {
configuration?.contentInsets = .init(insets: appearance.contentInsets)
var config = configuration ?? .plain()
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
}
configureInsets(in: &config,
contentInsets: appearance.contentLayout.contentInsets,
titleInsets: appearance.contentLayout.titleInsets,
imageInsets: appearance.contentLayout.imageInsets)
configuration = config
} else {
contentEdgeInsets = appearance.contentInsets
titleEdgeInsets = appearance.titleInsets
imageEdgeInsets = appearance.imageInsets
contentEdgeInsets = appearance.contentLayout.contentInsets
titleEdgeInsets = appearance.contentLayout.titleInsets
imageEdgeInsets = appearance.contentLayout.imageInsets
}
super.configureUIView(appearance: appearance)

тут отступы не слетели?

тут отступы не слетели?

стандартнык (кривые) Xcode'а :) могу поправить если что-то еще потребует правок

стандартнык (кривые) Xcode'а :) могу поправить если что-то еще потребует правок
}

Не лучше будет делегировать весь этот if statement, либо в отдельный метод, либо в BaseContentLayout

Не лучше будет делегировать весь этот if statement, либо в отдельный метод, либо в `BaseContentLayout`

why not, ок сделаю

why not, ок сделаю
@available(iOS 15.0, *)
private func configureInsets(in config: inout UIButton.Configuration,
contentInsets: UIEdgeInsets,
titleInsets: UIEdgeInsets,
imageInsets: UIEdgeInsets) {
let topContentInset = contentInsets.top + titleInsets.top
let bottomContentInset = contentInsets.bottom + titleInsets.bottom
let fullLeadingContentInset = contentInsets.left + titleInsets.left
let fullTrailingContentInset = contentInsets.right + titleInsets.right
let hasNonEmptyImage = [
config.image,
currentImage
]
.compactMap { $0 }
.first { !($0.size.width.isZero || $0.size.height.isZero) } != nil
if hasNonEmptyImage {
let leadingContentInset: CGFloat
let trailingContentInset: CGFloat
let imagePadding: CGFloat
switch config.imagePlacement {
case .leading:
leadingContentInset = contentInsets.left
trailingContentInset = fullTrailingContentInset
imagePadding = titleInsets.left + imageInsets.right
case .trailing:
leadingContentInset = fullLeadingContentInset
trailingContentInset = contentInsets.right
imagePadding = titleInsets.right + imageInsets.left
default:
leadingContentInset = fullLeadingContentInset
trailingContentInset = fullTrailingContentInset
imagePadding = .zero
}
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: leadingContentInset,
bottom: bottomContentInset,
trailing: trailingContentInset)
config.imagePadding = imagePadding
} else {
config.contentInsets = NSDirectionalEdgeInsets(top: topContentInset,
leading: fullLeadingContentInset,
bottom: bottomContentInset,
trailing: fullTrailingContentInset)
}
}
}

View File

@ -23,8 +23,8 @@
import TIUIKitCore
import UIKit
extension UIView {
public func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
public extension UIView {
func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
backgroundColor = appearance.backgroundColor
layer.masksToBounds = true
layer.maskedCorners = appearance.border.roundedCorners

View File

@ -11,7 +11,16 @@ public extension UIViewController {
if #available(iOS 13.0, *) {
statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
statusBarHeight = UIApplication.shared.statusBarFrame.height
// prevent compile-time failure: 'shared' is unavailable in application extensions for iOS
let sharedSelector = Selector(("shared"))
if UIApplication.responds(to: sharedSelector),
let sharedApplication = UIApplication.perform(sharedSelector).takeRetainedValue() as? UIApplication {
statusBarHeight = sharedApplication.statusBarFrame.height
} else { // application extension, statusBarFrame is not applicable
statusBarHeight = .zero
}
}
let navigationBarHeight = navigationBar?.bounds.height ?? 0

View File

@ -73,7 +73,7 @@ open class PlaceholderFactory {
}
.withButton { buttonStyle in
buttonStyle.titles = [.normal: localizationProvider.repeatButtonTitle]
buttonStyle.appearance = [.normal: Self.defaultButtonAppearance]
buttonStyle.appearance = Self.defaultButtonAppearance
}
}
@ -120,15 +120,17 @@ open class PlaceholderFactory {
// MARK: - Private configurations
private extension PlaceholderFactory {
static var defaultButtonAppearance: UIButton.DefaultAppearance {
static var defaultButtonAppearance: StatefulButton.DefaultPositionAppearance {
.make {
$0.border.cornerRadius = 25
$0.border.roundedCorners = .allCorners
$0.backgroundColor = UIColor(red: 0.892, green: 0.906, blue: 0.92, alpha: 0.5)
$0.textAttributes = .init(font: .systemFont(ofSize: 20, weight: .bold),
color: .black,
alignment: .natural,
isMultiline: false)
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.border.cornerRadius = 25
normalAppearance.border.roundedCorners = .allCorners
normalAppearance.backgroundColor = UIColor(red: 0.892, green: 0.906, blue: 0.92, alpha: 0.5)
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20, weight: .bold),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}

View File

@ -28,12 +28,12 @@ open class PlaceholderButtonStyle {
public var titles: UIControl.StateTitles
public var images: UIControl.StateImages
public var appearance: StatefulButton.StateAppearance
public var appearance: StatefulButton.DefaultPositionAppearance
public var action: UIButton.Action?
public init(titles: UIControl.StateTitles = [:],
images: UIControl.StateImages = [:],
appearance: StatefulButton.StateAppearance = [:],
appearance: StatefulButton.DefaultPositionAppearance = .defaultAppearance,
action: UIButton.Action? = nil) {
self.titles = titles

View File

@ -155,7 +155,7 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
button.set(titles: $0.titles)
button.set(images: $0.images)
button.set(appearance: $0.appearance)
button.configureStatefulButton(appearance: $0.appearance)
if let action = $0.action {
button.addTarget(action.target, action: action.action, for: action.event)

View File

@ -1,83 +0,0 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
import UIKit
public final class DefaultConfigurableStatefulButton: StatefulButton, ConfigurableView, AppearanceConfigurable {
// MARK: - ConfigurableView
public func configure(with stateViewModelMap: [State: BaseButtonViewModel]) {
for (state, viewModel) in stateViewModelMap {
setTitle(viewModel.title, for: state)
setImage(viewModel.image, for: state)
setBackgroundImage(viewModel.backgroundImage, for: state)
}
}
// MARK: - AppearanceConfigurable
public func configure(appearance: Appearance) {
configureUIButton(appearance: appearance)
stateAppearance = appearance.stateAppearance
}
}
extension DefaultConfigurableStatefulButton {
public final class Appearance: UIButton.BaseAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public enum Defaults {
public static var stateAppearance: StateAppearance {
[
.normal: DefaultAppearance.defaultAppearance,
.highlighted: DefaultAppearance.defaultAppearance,
.selected: DefaultAppearance.defaultAppearance,
.disabled: DefaultAppearance.defaultAppearance
]
}
}
public static var defaultAppearance: Self {
Self()
}
public var stateAppearance: StateAppearance
public init(stateAppearance: StateAppearance = Defaults.stateAppearance) {
self.stateAppearance = stateAppearance
let defaultAppearance = stateAppearance[.normal]
super.init(layout: defaultAppearance?.layout ?? .defaultLayout,
backgroundColor: defaultAppearance?.backgroundColor ?? .clear,
border: defaultAppearance?.border ?? .init(),
shadow: defaultAppearance?.shadow,
textAttributes: defaultAppearance?.textAttributes,
contentInsets: defaultAppearance?.contentInsets ?? .zero,
titleInsets: defaultAppearance?.titleInsets ?? .zero,
imageInsets: defaultAppearance?.imageInsets ?? .zero)
}
}
}

View File

@ -0,0 +1,74 @@
//
// 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 DefaultConfigurableStatefulButton: StatefulButton, ConfigurableView, AppearanceConfigurable {
private var appearance: Appearance = .defaultAppearance
// MARK: - ConfigurableView
public func configure(with viewModel: ViewModel) {
viewModel.willReuse(view: self)
stateViewModelMap = viewModel.stateViewModelMap
for (state, viewModel) in viewModel.stateViewModelMap {
setTitle(viewModel.title, for: state)
setImage(viewModel.image, for: state)
setBackgroundImage(viewModel.backgroundImage, for: state)
}
apply(state: viewModel.currentState)
configureStatefulButton(appearance: appearance)
viewModel.didCompleteConfiguration(of: self)
}
// MARK: - AppearanceConfigurable
public func configure(appearance: DefaultPositionAppearance) {
self.appearance = appearance
configureStatefulButton(appearance: appearance)
}
}
extension DefaultConfigurableStatefulButton {
public final class ViewModel: DefaultUIViewPresenter<DefaultConfigurableStatefulButton> {
public var stateViewModelMap: [State: BaseButtonViewModel]
public var currentState: State {
didSet {
view?.apply(state: currentState)
}
}
public init(stateViewModelMap: [State: BaseButtonViewModel],
currentState: State) {
self.stateViewModelMap = stateViewModelMap
self.currentState = currentState
}
}
}

View File

@ -0,0 +1,198 @@
//
// 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 StatefulButton {
public typealias StateAppearance = UIButton.BaseAppearance<UIView.NoLayout, DefaultContentLayout>
public typealias StateAppearances = [State: StateAppearance]
open class BaseAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>:
UIView.BaseAppearance<Layout> {
public enum Defaults {
public static var stateAppearances: StateAppearances {
[
.normal: UIButton.DefaultStateAppearance.defaultAppearance,
.highlighted: UIButton.DefaultStateAppearance.defaultAppearance,
.selected: UIButton.DefaultStateAppearance.defaultAppearance,
.disabled: UIButton.DefaultStateAppearance.defaultAppearance,
.loading: UIButton.DefaultStateAppearance.defaultAppearance
]
}
}
public var stateAppearances: StateAppearances
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
stateAppearances: StateAppearances = Defaults.stateAppearances) {
self.stateAppearances = stateAppearances
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow)
}
public func set(appearanceBuilder: (StateAppearance) -> Void, for states: [State]) {
for state in states {
stateAppearances[state] = DefaultStateAppearance.make(builder: appearanceBuilder)
}
}
}
open class BasePositionAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>: BaseAppearance<Layout, ContentLayout> {
public var activityIndicatorPosition: ActivityIndicatorPosition
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
stateAppearances: StateAppearances = Defaults.stateAppearances,
activityIndicatorPosition: ActivityIndicatorPosition = .center) {
self.activityIndicatorPosition = activityIndicatorPosition
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow,
stateAppearances: stateAppearances)
}
}
public final class DefaultPositionAppearance: BasePositionAppearance<DefaultWrappedLayout, DefaultContentLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
@available(iOS 15.0, *)
open class BasePlacementAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>:
BaseAppearance<Layout, ContentLayout> {
public var activityIndicatorPlacement: ActivityIndicatorPlacement
public init(layout: Layout = .defaultLayout,
backgroundColor: UIColor = .clear,
border: UIViewBorder = .init(),
shadow: UIViewShadow? = nil,
stateAppearances: StateAppearances = Defaults.stateAppearances,
activityIndicatorPlacement: ActivityIndicatorPlacement = .center) {
self.activityIndicatorPlacement = activityIndicatorPlacement
super.init(layout: layout,
backgroundColor: backgroundColor,
border: border,
shadow: shadow,
stateAppearances: stateAppearances)
}
}
@available(iOS 15.0, *)
public final class DefaultPlacementAppearance: BasePlacementAppearance<DefaultWrappedLayout, DefaultContentLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}
extension StatefulButton {
public func configureBaseStatefulButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>) {
onStateChanged = { [weak self] in
if let stateAppearance = appearance.stateAppearances[$0] {
self?.configureUIButton(appearance: stateAppearance, for: $0)
} else if $0 != .normal, let stateAppearance = appearance.stateAppearances[.normal] {
self?.configureUIButton(appearance: stateAppearance, for: .normal)
}
}
onStateChanged?(state)
}
public func configureStatefulButton(appearance: BasePositionAppearance<some ViewLayout, some BaseContentLayout>) {
configureBaseStatefulButton(appearance: appearance)
switch appearance.activityIndicatorPosition {
case .beforeTitle:
activityIndicatorShouldCenterInView = false
let transparentImage = UIGraphicsImageRenderer(size: activityIndicatorSize)
.image { _ in }
super.setImage(transparentImage, for: .loading)
case .center:
activityIndicatorShouldCenterInView = true
super.setImage(.init(), for: .loading)
}
if #available(iOS 15.0, *) {
var config = configuration ?? .plain()
if case let .beforeTitle(padding) = appearance.activityIndicatorPosition {
config.imagePlacement = .leading
config.imagePadding = padding
}
configuration = config
} else {
if case let .beforeTitle(padding) = appearance.activityIndicatorPosition {
imageEdgeInsets.right += padding
}
}
}
@available(iOS 15.0, *)
public func configureStatefulButton(appearance: BasePlacementAppearance<some ViewLayout, some BaseContentLayout>) {
configureBaseStatefulButton(appearance: appearance)
var config = configuration ?? .plain()
switch appearance.activityIndicatorPlacement {
case let .placement(imagePlacement, padding):
activityIndicatorShouldCenterInView = false
let transparentImage = UIGraphicsImageRenderer(size: activityIndicatorSize)
.image { _ in }
super.setImage(transparentImage, for: .loading)
config.imagePlacement = imagePlacement
config.imagePadding = padding
case .center:
activityIndicatorShouldCenterInView = true
super.setImage(.init(), for: .loading)
}
configuration = config
}
}

View File

@ -24,29 +24,53 @@ import TISwiftUtils
import TIUIKitCore
import UIKit
public extension UIControl.State {
// All the bits from 17 - 24 (from 1 << 16 until 1 << 23) are there for your application to use
// while 25 - 32 (from 1 << 24 until 1 << 31) are there for internal frameworks to use.
// https://developer.apple.com/documentation/uikit/uicontrolstate/uicontrolstateapplication?language=objc
// https://developer.apple.com/documentation/uikit/uicontrolstate/uicontrolstatereserved?language=objc
// https://stackoverflow.com/a/43760213
static var loading = Self(rawValue: 1 << 16 | Self.disabled.rawValue) // includes disabled state
}
open class StatefulButton: BaseInitializableButton {
public enum ActivityIndicatorPosition {
case beforeTitle(padding: CGFloat)
case center
case before(view: UIView, offset: CGFloat)
case after(view: UIView, offset: CGFloat)
}
var offset: CGFloat {
switch self {
case .center:
return .zero
@available(iOS 15.0, *)
public enum ActivityIndicatorPlacement {
case placement(NSDirectionalRectEdge, padding: CGFloat)
case center
}
case let .before(_, offset), let .after(_, offset):
return offset
public typealias StateEventPropagations = [State: Bool]
public var isLoading = false {
didSet {
if isLoading {
if activityIndicator == nil {
configureDefaultActivityIndicator()
}
activityIndicator?.startAnimating()
} else {
activityIndicator?.stopAnimating()
}
updateAppearance()
}
}
public typealias Appearance = UIButton.BaseAppearance<UIView.DefaultWrappedLayout>
public typealias StateAppearance = [State: Appearance]
public typealias StateEventPropagations = [State: Bool]
public var additionalHitTestMargins: UIEdgeInsets = .zero
private var activityIndicator: ActivityIndicator? {
public var onDisabledStateTapHandler: VoidClosure?
// MARK: - Internal properties
var activityIndicator: ActivityIndicator? {
willSet {
activityIndicator?.removeFromSuperview()
}
@ -57,48 +81,52 @@ open class StatefulButton: BaseInitializableButton {
}
}
public var isLoading = false {
didSet {
isLoading
? activityIndicator?.startAnimating()
: activityIndicator?.stopAnimating()
var activityIndicatorShouldCenterInView = false
isEnabled = !isLoading
}
}
var stateViewModelMap: [State: BaseButtonViewModel] = [:]
public var additionalHitTestMargins: UIEdgeInsets = .zero
var onStateChanged: ParameterClosure<State>?
public var onDisabledStateTapHandler: VoidClosure?
// MARK: - Private properties
private var eventPropagations: StateEventPropagations = [:]
// MARK: - Background
var stateAppearance: StateAppearance = [:] {
didSet {
updateAppearance()
}
}
public func set(appearance: StateAppearance) {
appearance.forEach { setAppearance($1, for: $0) }
}
public func setAppearance(_ appearance: Appearance, for state: State) {
stateAppearance[state] = appearance
}
public func appearance(for state: State) -> Appearance? {
stateAppearance[state]
}
public func setEventPropagation(_ eventPropagation: Bool, for state: State) {
eventPropagations[state] = eventPropagation
}
// MARK: - UIButton override
open override func setImage(_ image: UIImage?, for state: UIControl.State) {
guard state != .loading else {
return
}
super.setImage(image, for: state)
}
open override func title(for state: UIControl.State) -> String? {
stateViewModelMap[state]?.title ?? super.title(for: state)
}
open override func image(for state: UIControl.State) -> UIImage? {
stateViewModelMap[state]?.image ?? super.image(for: state)
}
open override func backgroundImage(for state: UIControl.State) -> UIImage? {
stateViewModelMap[state]?.backgroundImage ?? super.backgroundImage(for: state)
}
// MARK: - UIControl override
open override var state: UIControl.State {
if isLoading {
return super.state.union(.loading)
} else {
return super.state
}
}
override open var isEnabled: Bool {
didSet {
updateAppearance()
@ -119,6 +147,24 @@ open class StatefulButton: BaseInitializableButton {
// MARK: - UIView override
open override func layoutSubviews() {
super.layoutSubviews()
guard let activityIndicator, isLoading else {
return
}
if activityIndicatorShouldCenterInView {
let indicatorSize = activityIndicatorSize
activityIndicator.frame = CGRect(origin: CGPoint(x: bounds.midX - indicatorSize.width / 2,
y: bounds.midY - indicatorSize.height / 2),
size: indicatorSize)
} else {
activityIndicator.frame = imageView?.frame ?? .zero
}
}
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let insetBounds = CGRect(x: bounds.minX - additionalHitTestMargins.left,
y: bounds.minY - additionalHitTestMargins.top,
@ -126,16 +172,13 @@ open class StatefulButton: BaseInitializableButton {
height: bounds.height + additionalHitTestMargins.bottom)
return super.point(inside: point, with: event)
|| insetBounds.contains(point)
|| insetBounds.contains(point)
}
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pointInsideView = self.point(inside: point, with: event)
// hitTest called multiple times, but we need only one
// so the current solution is to pick a final call with nil event
if !isEnabled && pointInsideView && event == nil {
if !isEnabled && pointInsideView, event?.allTouches?.isEmpty == false {
onDisabledStateTapHandler?()
}
@ -152,63 +195,44 @@ open class StatefulButton: BaseInitializableButton {
// MARK: - Public
public func configure(activityIndicator: ActivityIndicator,
at position: ActivityIndicatorPosition) {
open func configure(activityIndicator: ActivityIndicator) {
self.activityIndicator = activityIndicator
let titleInset = activityIndicator.intrinsicContentSize.width + position.offset
switch position {
case .center:
titleEdgeInsets = .zero
case .before:
titleEdgeInsets = UIEdgeInsets(top: .zero,
left: titleInset,
bottom: .zero,
right: .zero)
case .after:
titleEdgeInsets = UIEdgeInsets(top: .zero,
left: .zero,
bottom: .zero,
right: titleInset)
}
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints(for: activityIndicator,
at: position))
setNeedsLayout()
}
open func configureDefaultActivityIndicator() {
let systemIndicator: UIActivityIndicatorView
if #available(iOS 13.0, *) {
systemIndicator = UIActivityIndicatorView(style: .medium)
} else {
systemIndicator = UIActivityIndicatorView(style: .gray)
}
configure(activityIndicator: systemIndicator)
}
open func apply(state: State) {
isSelected = state.isSelected
isEnabled = !state.isDisabled
isHighlighted = state.isHightlightted
isLoading = state.isLoading
}
// MARK: - Internal
var activityIndicatorSize: CGSize {
activityIndicator?.intrinsicContentSize ?? .zero
}
// MARK: - Private
private func constraints(for activityIndicator: ActivityIndicator,
at position: ActivityIndicatorPosition) -> [NSLayoutConstraint] {
switch position {
case .center:
return [
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor)
]
case let .before(view, offset):
return [
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
activityIndicator.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: -offset)
]
case let .after(view, offset):
return [
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor),
activityIndicator.leadingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset)
]
}
}
private func updateAppearance() {
if isEnabled {
if isLoading {
updateAppearance(to: .loading)
} else if isEnabled {
if isHighlighted {
updateAppearance(to: .highlighted)
} else {
@ -220,10 +244,24 @@ open class StatefulButton: BaseInitializableButton {
}
private func updateAppearance(to state: State) {
if let appearance = stateAppearance[state] {
configureUIButton(appearance: appearance)
} else if state != .normal, let appearance = stateAppearance[.normal] {
configureUIButton(appearance: appearance)
}
onStateChanged?(state)
}
}
private extension UIControl.State {
var isHightlightted: Bool {
contains([.highlighted])
}
var isSelected: Bool {
contains([.selected])
}
var isDisabled: Bool {
contains([.disabled])
}
var isLoading: Bool {
contains([.loading])
}
}

View File

@ -56,12 +56,12 @@ let customStyle = DefaultPlaceholderStyle(
}, buttonsStyles: [
.init(titles: [.normal: "Reload"],
appearance: [
appearance: .init(stateAppearances: [
.normal: UIButton.DefaultAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false))
])
]))
])
let placeholderView = factory.createImageStylePlaceholder(customStyle)
@ -103,34 +103,40 @@ let styleWithAppearance = styleFromMake.updateAppearance { placeholder in
let styleWithButton = styleWithAppearance.withButton { buttonStyle in
buttonStyle.titles = [.normal: "Reload"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
//: Стоит обратить внимание на то, что если бы метод использовался на `styleWithButton`, то у уже добавленной кнопки (она была бы с индексом 0) сохранился стиль, так что его можно было либо дополнить, либо переопределить
let styleWithTwoButtons = styleWithAppearance.withButtons(2, axis: .vertical) { index, buttonStyle in
if index == .zero {
buttonStyle.titles = [.normal: "Reload"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
if index == 1 {
buttonStyle.titles = [.normal: "Wait"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIUIElements'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Bunch of useful protocols and views.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIUIKitCore'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Core UI elements: protocols, views and helpers.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIWebView'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Universal web view API'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -60,25 +60,15 @@ open class YandexMapManager<DataModel>: BaseMapManager<YMKMapView,
}
public convenience init(map: YMKMapView,
positionGetter: @escaping PositionGetter,
defaultMarkerIcon: UIImage,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) {
self.init(map: map,
positionGetter: positionGetter,
iconFactory: StaticImageIconFactory(image: defaultMarkerIcon),
clusterIconFactory: DefaultClusterMarkerIconFactory(),
selectPlacemarkHandler: selectPlacemarkHandler)
}
public convenience init(map: YMKMapView,
defaultMarkerIcon: UIImage,
iconFactory: DefaultMarkerIconFactory<DataModel>,
clusterIconFactory: DefaultClusterMarkerIconFactory<DataModel>? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler)
where DataModel: MapLocatable, DataModel.Position == YMKPoint {
self.init(map: map,
positionGetter: { $0.position },
defaultMarkerIcon: defaultMarkerIcon,
iconFactory: iconFactory,
clusterIconFactory: clusterIconFactory,
selectPlacemarkHandler: selectPlacemarkHandler)
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIYandexMapUtils'
s.version = '1.49.0'
s.version = '1.50.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

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

View File

@ -59,12 +59,12 @@ let customStyle = DefaultPlaceholderStyle(
}, buttonsStyles: [
.init(titles: [.normal: "Reload"],
appearance: [
appearance: .init(stateAppearances: [
.normal: UIButton.DefaultAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false))
])
]))
])
let placeholderView = factory.createImageStylePlaceholder(customStyle)
@ -107,12 +107,14 @@ let styleWithAppearance = styleFromMake.updateAppearance { placeholder in
let styleWithButton = styleWithAppearance.withButton { buttonStyle in
buttonStyle.titles = [.normal: "Reload"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
```
@ -122,22 +124,26 @@ let styleWithButton = styleWithAppearance.withButton { buttonStyle in
let styleWithTwoButtons = styleWithAppearance.withButtons(2, axis: .vertical) { index, buttonStyle in
if index == .zero {
buttonStyle.titles = [.normal: "Reload"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
if index == 1 {
buttonStyle.titles = [.normal: "Wait"]
buttonStyle.appearance = [.normal: UIButton.DefaultAppearance.make { button in
button.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}]
buttonStyle.appearance = .make {
if let normalAppearance = $0.stateAppearances[.normal] {
normalAppearance.textAttributes = .init(font: .systemFont(ofSize: 20),
color: .black,
alignment: .natural,
isMultiline: false)
}
}
}
}
```

View File

@ -2,6 +2,7 @@ TILogging
TISwiftUtils
TIPagination
TIFoundationUtils
TICoreGraphicsUtils
TIKeychainUtils
TIUIKitCore
TISwiftUICore