feature/stateful_button_improvements #12
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")]),
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
ENV["DEVELOPMENT_INSTALL"] = "true"
|
||||
|
||||
target 'TICoreGraphicsUtils' do
|
||||
platform :ios, 11.0
|
||||
use_frameworks!
|
||||
|
||||
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
|
||||
end
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# gitignore nef files
|
||||
**/build/
|
||||
**/nef/
|
||||
LICENSE
|
||||
end
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
ENV["DEVELOPMENT_INSTALL"] = "true"
|
||||
|
||||
target 'TICoreGraphicsUtils' do
|
||||
platform :ios, 11.0
|
||||
use_frameworks!
|
||||
|
||||
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
|
||||
end
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'/>
|
||||
|
|
@ -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 */;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:TICoreGraphicsUtils.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
workspace="TICoreGraphicsUtils.xcworkspace"
|
||||
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
|
||||
|
||||
open "`pwd`/$workspacePath/$workspace"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
TICoreGraphicsUtils.app/Contents/MacOS/TICoreGraphicsUtils.playground
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TICoreGraphicsUtils
|
||||
import CoreGraphics
|
||||
|
||||
public struct CurrentLocationDrawingOperation: DrawingOperation {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
||||
public static var defaultAppearance: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultAppearance: BaseAppearance<DefaultWrappedLayout, DefaultContentLayout>, WrappedViewAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
nikita.semenov
commented
тут отступы не слетели? тут отступы не слетели?
ivan.smolin
commented
стандартнык (кривые) Xcode'а :) могу поправить если что-то еще потребует правок стандартнык (кривые) Xcode'а :) могу поправить если что-то еще потребует правок
|
||||
}
|
||||
|
||||
|
nikita.semenov
commented
Не лучше будет делегировать весь этот if statement, либо в отдельный метод, либо в Не лучше будет делегировать весь этот if statement, либо в отдельный метод, либо в `BaseContentLayout`
ivan.smolin
commented
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ TILogging
|
|||
TISwiftUtils
|
||||
TIPagination
|
||||
TIFoundationUtils
|
||||
TICoreGraphicsUtils
|
||||
TIKeychainUtils
|
||||
TIUIKitCore
|
||||
TISwiftUICore
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Как будто бы по неймингу не понять, чем
DefaultStateAppearanceотличается отDefaultAppearance. Важно ли пользователю класса понимать различия в данном контексте?State это настройки appearance для стейта, а второй для всего appearance кнопки.
Да, понимать разницу важно, если глубже dsl смотреть. А какие есть предложения по неймингу?