Merge pull request 'feat: Custom string attributes to `BaseTextAttributes`' (#15) from feature/uiviewbackground into master
Reviewed-on: #15
This commit is contained in:
commit
767c19d17b
|
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
### 1.53.0
|
||||
|
||||
- **Added**: Custom string attributes to `BaseTextAttributes`
|
||||
- **Added**: Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
|
||||
- **Added**: Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
|
||||
- **Update**: Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
|
||||
|
||||
### 1.52.0
|
||||
|
||||
- **Added**: `TIApplication` module with core dependencies of main application and its extension targets
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAppleMapUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct BundleIdentifier {
|
||||
public let appPrefix: String
|
||||
public let appIdentifier: String
|
||||
public let fullIdentifier: String
|
||||
public let defaultAppGroupIdenfier: String
|
||||
|
||||
static let bundleIdentifierSeparator = "."
|
||||
|
||||
public init(appPrefix: String, appIdentifier: String) {
|
||||
self.appPrefix = appPrefix
|
||||
self.appIdentifier = appIdentifier
|
||||
self.fullIdentifier = appPrefix + Self.bundleIdentifierSeparator + appIdentifier
|
||||
self.defaultAppGroupIdenfier = "group." + fullIdentifier
|
||||
}
|
||||
|
||||
public init?(bundle: Bundle = .main) {
|
||||
guard let fullIdenfifier = bundle.bundleIdentifier,
|
||||
var components = bundle.bundleIdentifier?
|
||||
.components(separatedBy: Self.bundleIdentifierSeparator),
|
||||
let lastComponent = components.popLast() else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
self.fullIdentifier = fullIdenfifier
|
||||
self.appIdentifier = lastComponent
|
||||
self.appPrefix = components.joined(separator: Self.bundleIdentifierSeparator)
|
||||
self.defaultAppGroupIdenfier = "group." + fullIdentifier
|
||||
}
|
||||
}
|
||||
|
|
@ -50,8 +50,7 @@ public struct CoreDependencies {
|
|||
|
||||
public var networkCallbackQueue: DispatchQueue
|
||||
|
||||
public init(bundleIdentifierPrefix: String,
|
||||
appIdentifier: String,
|
||||
public init(bundleIdentifier: BundleIdentifier,
|
||||
customAppGroupIdentifier: String? = nil) {
|
||||
|
||||
jsonCodingConfigurator = JsonCodingConfigurator(dateFormattersReusePool: dateFormattersResusePool,
|
||||
|
|
@ -60,14 +59,12 @@ public struct CoreDependencies {
|
|||
jsonKeyValueDecoder = JSONKeyValueDecoder(jsonDecoder: jsonCodingConfigurator.jsonDecoder)
|
||||
jsonKeyValueEncoder = JSONKeyValueEncoder(jsonEncoder: jsonCodingConfigurator.jsonEncoder)
|
||||
|
||||
let bundleIdentifier = bundleIdentifierPrefix + "." + appIdentifier
|
||||
logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier.fullIdentifier, category: "general")
|
||||
|
||||
logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier, category: "general")
|
||||
|
||||
keychain = Keychain(service: bundleIdentifier)
|
||||
keychain = Keychain(service: bundleIdentifier.fullIdentifier).accessibility(.whenUnlockedThisDeviceOnly)
|
||||
defaults = .standard
|
||||
|
||||
let appGroupIdentifier = customAppGroupIdentifier ?? "group." + bundleIdentifierPrefix
|
||||
let appGroupIdentifier = customAppGroupIdentifier ?? bundleIdentifier.defaultAppGroupIdenfier
|
||||
|
||||
if let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
|
||||
var appGroupCacheURL: URL
|
||||
|
|
@ -93,13 +90,27 @@ public struct CoreDependencies {
|
|||
}
|
||||
|
||||
appGroupDefaults = UserDefaults(suiteName: appGroupIdentifier)
|
||||
appGroupKeychain = Keychain(service: bundleIdentifierPrefix, accessGroup: appGroupIdentifier)
|
||||
appGroupKeychain = Keychain(service: bundleIdentifier.appPrefix,
|
||||
accessGroup: appGroupIdentifier)
|
||||
.accessibility(.whenUnlockedThisDeviceOnly)
|
||||
} else {
|
||||
appGroupCacheDirectory = nil
|
||||
appGroupDefaults = nil
|
||||
appGroupKeychain = nil
|
||||
}
|
||||
|
||||
networkCallbackQueue = DispatchQueue(label: bundleIdentifier + ".network-callback-queue", attributes: .concurrent)
|
||||
networkCallbackQueue = DispatchQueue(label: bundleIdentifier.fullIdentifier + ".network-callback-queue",
|
||||
attributes: .concurrent)
|
||||
}
|
||||
|
||||
public init?(bundle: Bundle = .main,
|
||||
customAppGroupIdentifier: String? = nil) {
|
||||
|
||||
guard let bundleIdentifier = BundleIdentifier(bundle: bundle) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(bundleIdentifier: bundleIdentifier,
|
||||
customAppGroupIdentifier: customAppGroupIdentifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIApplication'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Application architecture.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAuth'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Login, registration, confirmation and other related actions'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ extension BaseModalViewController {
|
|||
public var footerViewState: ModalFooterView<FooterContentView>.State
|
||||
|
||||
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
presentationDetents: [ModalViewPresentationDetent] = [.maxHeight],
|
||||
dragViewState: DragView.State = .hidden,
|
||||
|
|
@ -48,7 +48,7 @@ extension BaseModalViewController {
|
|||
self.footerViewState = footerViewState
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ open class BaseModalViewController<ContentView: UIView,
|
|||
|
||||
// MARK: - Modal View Controller Configuration
|
||||
|
||||
public var viewControllerAppearance: BaseAppearance = .init(backgroundColor: .white)
|
||||
public var viewControllerAppearance: BaseAppearance = .init(background: UIViewColorBackground(color: .white))
|
||||
|
||||
open var panScrollable: UIScrollView? {
|
||||
contentView as? UIScrollView
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ public final class DragView: BaseInitializableView, AppearanceConfigurable {
|
|||
}
|
||||
|
||||
static var dragViewBorder: UIViewBorder {
|
||||
UIViewBorder(cornerRadius: dragViewSize.height / 2, roundedCorners: .allCorners)
|
||||
UIViewRoundedBorder(cornerRadius: dragViewSize.height / 2)
|
||||
}
|
||||
|
||||
static var dragViewBackgroundColor: UIColor {
|
||||
.lightGray
|
||||
static var dragViewBackground: UIViewColorBackground {
|
||||
UIViewColorBackground(color: .lightGray)
|
||||
}
|
||||
|
||||
static var dragViewSize: CGSize {
|
||||
|
|
@ -57,7 +57,7 @@ public final class DragView: BaseInitializableView, AppearanceConfigurable {
|
|||
Self(layout: DefaultWrappedLayout(insets: .vertical(top: Constants.dragViewTopInset),
|
||||
size: Constants.dragViewSize,
|
||||
centerOffset: .centerHorizontal()),
|
||||
backgroundColor: Constants.dragViewBackgroundColor,
|
||||
background: Constants.dragViewBackground,
|
||||
border: Constants.dragViewBorder)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,14 +219,17 @@ public extension ModalHeaderView {
|
|||
public var contentViewState: ContentViewState
|
||||
|
||||
public init(layout: DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
contentViewState: ContentViewState = .leadingButton(.init())) {
|
||||
|
||||
self.contentViewState = contentViewState
|
||||
|
||||
super.init(layout: layout, backgroundColor: backgroundColor, border: border, shadow: shadow)
|
||||
super.init(layout: layout,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class EmptyViewController: BaseModalViewController<UIView, UIView> { }
|
|||
/*:
|
||||
## Обертка вокруг существующего контроллера
|
||||
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `BaseModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `DefaultModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
*/
|
||||
|
||||
import TIUIKitCore
|
||||
|
|
@ -24,13 +24,13 @@ final class OldMassiveViewController: BaseInitializableViewController {
|
|||
// some implementation
|
||||
}
|
||||
|
||||
typealias ModalOldMassiveViewController = BaseModalWrapperViewController<OldMassiveViewController>
|
||||
typealias ModalOldMassiveViewController = DefaultModalWrapperViewController<OldMassiveViewController>
|
||||
|
||||
class PresentingViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
|
||||
@objc private func onButtonTapped() {
|
||||
presentPanModal(ModalOldMassiveViewController())
|
||||
presentPanModal(ModalOldMassiveViewController(contentViewController: OldMassiveViewController()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,16 +48,18 @@ class PresentingViewController: BaseInitializableViewController {
|
|||
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
|
||||
*/
|
||||
|
||||
import TIUIElements
|
||||
|
||||
let customViewController = BaseModalViewController<UIView, UIView>()
|
||||
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
|
||||
$0.dragViewState = .presented(.defaultAppearance)
|
||||
$0.headerViewState = .presented(.make {
|
||||
$0.layout.size = .fixedHeight(52)
|
||||
$0.backgroundColor = .white
|
||||
$0.contentViewState = .buttonLeft(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(backgroundColor: .blue)
|
||||
])))
|
||||
$0.contentViewState = .leadingButton(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(background: UIViewColorBackground(color: .blue))
|
||||
])))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -84,10 +86,10 @@ detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnl
|
|||
|
||||
let shadowViewController = BaseModalViewController<UIView, UIView>()
|
||||
let dimmedView = PassthroughDimmedView()
|
||||
dimmedView.hitTestHandlerView = view
|
||||
dimmedView.configureUIView(appearance: .init(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
dimmedView.hitTestHandlerView = shadowViewController.view
|
||||
dimmedView.configureUIView(appearance: DefaultAppearance(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
|
||||
shadowViewController.dimmedView = dimmedView
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIBottomSheet'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Base models for creating bottom sheet view controllers'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TICoreGraphicsUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'CoreGraphics drawing helpers'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIDeeplink'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Deeplink service API'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Universal web view API'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIEcommerce'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Cart, products, promocodes, bonuses and other related actions'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIFoundationUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Set of helpers for Foundation framework classes.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIGoogleMapUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.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/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
import KeychainAccess
|
||||
|
||||
open class CodableSingleValueKeychainStorage<ValueType: Codable>: BaseSingleValueKeychainStorage<ValueType> {
|
||||
public init(keychain: Keychain,
|
||||
storageKey: StorageKey<ValueType>,
|
||||
encoder: CodableKeyValueEncoder = JSONKeyValueEncoder(),
|
||||
decoder: CodableKeyValueDecoder = JSONKeyValueDecoder()) {
|
||||
|
||||
let getValueClosure: GetValueClosure = {
|
||||
$0.codableObject(forKey: $1, decoder: decoder)
|
||||
}
|
||||
|
||||
let storeValueClosure: StoreValueClosure = {
|
||||
$0.set(encodableObject: $1, forKey: $2, encoder: encoder)
|
||||
}
|
||||
|
||||
super.init(keychain: keychain,
|
||||
storageKey: storageKey,
|
||||
getValueClosure: getValueClosure,
|
||||
storeValueClosure: storeValueClosure)
|
||||
}
|
||||
}
|
||||
|
|
@ -60,10 +60,10 @@ import Foundation
|
|||
|
||||
let defaults = UserDefaults.standard // or AppGroup defaults
|
||||
|
||||
let appReinstallChecker = AppReinstallChecker(defaultsStorage: defaults,
|
||||
storageKey: .deleteApiToken)
|
||||
let appReinstallChecker = DefaultAppFirstRunCheckStorage(defaults: defaults,
|
||||
storageKey: .deleteApiToken)
|
||||
|
||||
let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(reinstallChecker: appReinstallChecker)
|
||||
let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(appFirstRunCheckStorage: appReinstallChecker)
|
||||
|
||||
if appInstallAwareTokenStorage.hasStoredValue() {
|
||||
// app wasn't reinstalled, token is exist
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIKeychainUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Set of helpers for Keychain classes.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Logging for TI libraries.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMapUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMoyaNetworking'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Moya + Swagger network service.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Swagger-frendly networking layer helpers.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Caching results of EndpointRequests.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Generic pagination component.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
@ -10,7 +10,14 @@ Pod::Spec.new do |s|
|
|||
s.ios.deployment_target = '11.0'
|
||||
s.swift_versions = ['5.7']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
sources = 'Sources/**/*.swift'
|
||||
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
|
||||
s.source_files = sources
|
||||
s.exclude_files = s.name + '.app'
|
||||
else
|
||||
s.source_files = s.name + '/' + sources
|
||||
s.exclude_files = s.name + '/*.app'
|
||||
end
|
||||
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
s.dependency 'Cursors', "~> 0.6.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUICore'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Bunch of useful helpers for Swift development.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Set of helpers for TableKit classes.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.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/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -45,14 +45,15 @@ extension UIButton {
|
|||
}
|
||||
}
|
||||
|
||||
open class BaseAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>: UIView.BaseAppearance<Layout> {
|
||||
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(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
textAttributes: BaseTextAttributes? = nil,
|
||||
contentLayout: ContentLayout = .defaultLayout) {
|
||||
|
|
@ -61,7 +62,7 @@ extension UIButton {
|
|||
self.contentLayout = contentLayout
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ extension UILabel {
|
|||
public var textAttributes: BaseTextAttributes?
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
textAttributes: BaseTextAttributes? = nil) {
|
||||
|
||||
self.textAttributes = textAttributes
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,9 @@ import UIKit
|
|||
|
||||
public extension UIView {
|
||||
func configureUIView(appearance: BaseAppearance<some ViewLayout>) {
|
||||
backgroundColor = appearance.backgroundColor
|
||||
appearance.background.apply(to: self)
|
||||
layer.masksToBounds = true
|
||||
layer.maskedCorners = appearance.border.roundedCorners
|
||||
layer.cornerRadius = appearance.border.cornerRadius
|
||||
layer.borderWidth = appearance.border.width
|
||||
layer.borderColor = appearance.border.color.cgColor
|
||||
appearance.border.apply(to: self)
|
||||
|
||||
guard let shadow = appearance.shadow else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -29,17 +29,17 @@ extension UIView {
|
|||
open class BaseAppearance<Layout: ViewLayout> {
|
||||
public var layout: Layout
|
||||
|
||||
public var backgroundColor: UIColor
|
||||
public var background: UIViewBackground
|
||||
public var border: UIViewBorder
|
||||
public var shadow: UIViewShadow?
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil) {
|
||||
|
||||
self.layout = layout
|
||||
self.backgroundColor = backgroundColor
|
||||
self.background = background
|
||||
self.border = border
|
||||
self.shadow = shadow
|
||||
}
|
||||
|
|
@ -59,15 +59,15 @@ extension UIView {
|
|||
public var subviewAppearance: SubviewAppearance
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
subviewAppearance: SubviewAppearance = .defaultAppearance) {
|
||||
|
||||
self.subviewAppearance = subviewAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
@ -75,9 +75,9 @@ extension UIView {
|
|||
|
||||
open class BaseWrappedAppearance<Layout: WrappedViewLayout>: BaseAppearance<Layout> {}
|
||||
|
||||
public final class DefaultWrappedViewHolderAppearance<SubviewAppearance: WrappedViewAppearance,
|
||||
Layout: ViewLayout>: BaseWrappedViewHolderAppearance<SubviewAppearance, Layout>,
|
||||
WrappedViewHolderAppearance {
|
||||
public final class DefaultWrappedViewHolderAppearance<SubviewAppearance: WrappedViewAppearance, Layout: ViewLayout>:
|
||||
BaseWrappedViewHolderAppearance<SubviewAppearance, Layout>,
|
||||
WrappedViewHolderAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self()
|
||||
}
|
||||
|
|
@ -97,3 +97,14 @@ extension UIView {
|
|||
}
|
||||
|
||||
extension UIView.DefaultWrappedViewHolderAppearance: WrappedViewAppearance where Layout: WrappedViewLayout {}
|
||||
|
||||
public extension UIView.BaseAppearance {
|
||||
var backgroundColor: UIColor? {
|
||||
get {
|
||||
(background as? UIViewColorBackground)?.color
|
||||
}
|
||||
set {
|
||||
background = UIViewColorBackground(color: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import QuartzCore
|
||||
|
||||
public struct GradientValues {
|
||||
public enum Defaults {
|
||||
public static var colors: [CGColor]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public static var locations: [NSNumber]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public static var startPoint: CGPoint {
|
||||
CGPoint(x: 0.5, y: 0)
|
||||
}
|
||||
|
||||
public static var endPoint: CGPoint {
|
||||
CGPoint(x: 0.5, y: 1)
|
||||
}
|
||||
|
||||
public static var type: CAGradientLayerType {
|
||||
.axial
|
||||
}
|
||||
}
|
||||
|
||||
public var colors: [CGColor]?
|
||||
public var locations: [NSNumber]?
|
||||
|
||||
public var startPoint: CGPoint
|
||||
public var endPoint: CGPoint
|
||||
|
||||
public var type: CAGradientLayerType
|
||||
|
||||
public init(colors: [CGColor]? = Defaults.colors,
|
||||
locations: [NSNumber]? = Defaults.locations,
|
||||
startPoint: CGPoint = Defaults.startPoint,
|
||||
endPoint: CGPoint = Defaults.endPoint,
|
||||
type: CAGradientLayerType = Defaults.type) {
|
||||
|
||||
self.colors = colors
|
||||
self.locations = locations
|
||||
self.startPoint = startPoint
|
||||
self.endPoint = endPoint
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
|
||||
public extension GradientValues {
|
||||
typealias Stop = (color: CGColor, location: CGFloat)
|
||||
|
||||
static func elliptical(stops: [Stop],
|
||||
center: CGPoint,
|
||||
outerEdge: CGPoint) -> Self {
|
||||
|
||||
GradientValues(colors: stops.map { $0.color },
|
||||
locations: stops.map { $0.location as NSNumber },
|
||||
startPoint: center,
|
||||
endPoint: outerEdge,
|
||||
type: .radial)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class UIViewColorBackground: UIViewBackground {
|
||||
public var color: UIColor?
|
||||
|
||||
public init(color: UIColor?) {
|
||||
self.color = color
|
||||
}
|
||||
|
||||
// MARK: - UIViewBackground
|
||||
|
||||
open func apply(to view: UIView) {
|
||||
view.backgroundColor = color
|
||||
}
|
||||
|
||||
open func remove(from view: UIView) {
|
||||
view.backgroundColor = nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class UIViewGradientBackground: UIViewBackground {
|
||||
public var gradientLayer = CAGradientLayer()
|
||||
|
||||
public var gradientValues: GradientValues {
|
||||
didSet {
|
||||
update(gradientLayer: gradientLayer, with: gradientValues)
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) weak var hostingView: UIView?
|
||||
|
||||
public var observeViewBoundsChange: Bool {
|
||||
didSet {
|
||||
if let hostingView, observeViewBoundsChange {
|
||||
subscribeToBoundsChange(of: hostingView)
|
||||
} else if !observeViewBoundsChange {
|
||||
stopViewBoundsChangeObservation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var viewBoundsObservation: NSKeyValueObservation?
|
||||
|
||||
public init(values: GradientValues = GradientValues(),
|
||||
observeViewBoundsChange: Bool = true) {
|
||||
|
||||
self.gradientValues = values
|
||||
self.observeViewBoundsChange = observeViewBoundsChange
|
||||
|
||||
gradientLayer.name = "UIViewGradientBackground_" + UUID().uuidString
|
||||
|
||||
update(gradientLayer: gradientLayer, with: values)
|
||||
}
|
||||
|
||||
// MARK: - UIViewBackground
|
||||
|
||||
open func apply(to view: UIView) {
|
||||
guard indexOf(gradientSublayer: gradientLayer, in: view) == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
hostingView = view
|
||||
|
||||
view.layer.addSublayer(gradientLayer)
|
||||
|
||||
updateGradientLayer(bounds: view.bounds, in: view)
|
||||
|
||||
if observeViewBoundsChange {
|
||||
subscribeToBoundsChange(of: view)
|
||||
}
|
||||
}
|
||||
|
||||
open func remove(from view: UIView) {
|
||||
stopViewBoundsChangeObservation()
|
||||
|
||||
gradientLayer.removeFromSuperlayer()
|
||||
|
||||
hostingView = nil
|
||||
}
|
||||
|
||||
open func update(gradientLayer: CAGradientLayer, with values: GradientValues) {
|
||||
gradientLayer.colors = values.colors
|
||||
gradientLayer.locations = values.locations
|
||||
gradientLayer.startPoint = values.startPoint
|
||||
gradientLayer.endPoint = values.endPoint
|
||||
gradientLayer.type = values.type
|
||||
}
|
||||
|
||||
open func subscribeToBoundsChange(of view: UIView) {
|
||||
viewBoundsObservation = view.observe(\.bounds,
|
||||
options: [.new]) { [weak self] view, change in
|
||||
|
||||
guard let newvalue = change.newValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self?.updateGradientLayer(bounds: newvalue, in: view)
|
||||
}
|
||||
}
|
||||
|
||||
open func stopViewBoundsChangeObservation() {
|
||||
viewBoundsObservation = nil
|
||||
}
|
||||
|
||||
open func indexOf(gradientSublayer: CAGradientLayer, in view: UIView) -> Int? {
|
||||
view.layer.sublayers?.firstIndex(of: gradientSublayer)
|
||||
}
|
||||
|
||||
open func updateGradientLayer(bounds: CGRect, in view: UIView) {
|
||||
gradientLayer.frame = bounds
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class BaseUIViewBorder: UIViewBorder {
|
||||
open class Defaults { // swiftlint:disable:this convenience_type
|
||||
open class var color: UIColor {
|
||||
.black
|
||||
}
|
||||
|
||||
open class var width: CGFloat {
|
||||
.zero
|
||||
}
|
||||
}
|
||||
|
||||
public var color: UIColor
|
||||
public var width: CGFloat
|
||||
|
||||
public init(color: UIColor = Defaults.color,
|
||||
width: CGFloat = Defaults.width) {
|
||||
|
||||
self.color = color
|
||||
self.width = width
|
||||
}
|
||||
|
||||
// MARK: - UIViewBorder
|
||||
|
||||
open func apply(to view: UIView) {
|
||||
view.layer.borderWidth = width
|
||||
view.layer.borderColor = color.cgColor
|
||||
}
|
||||
|
||||
open func remove(from view: UIView) {
|
||||
view.layer.borderWidth = Defaults.width
|
||||
view.layer.borderColor = Defaults.color.cgColor
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class UIViewRoundedBorder: BaseUIViewBorder {
|
||||
open class Defaults: BaseUIViewBorder.Defaults {
|
||||
open class var cornerRadius: CGFloat {
|
||||
8
|
||||
}
|
||||
|
||||
open class var roundedCorners: CACornerMask {
|
||||
.allCorners
|
||||
}
|
||||
}
|
||||
|
||||
public var cornerRadius: CGFloat
|
||||
public var roundedCorners: CACornerMask
|
||||
|
||||
public init(color: UIColor = Defaults.color,
|
||||
width: CGFloat = Defaults.width,
|
||||
cornerRadius: CGFloat = Defaults.cornerRadius,
|
||||
roundedCorners: CACornerMask = Defaults.roundedCorners) {
|
||||
|
||||
self.cornerRadius = cornerRadius
|
||||
self.roundedCorners = roundedCorners
|
||||
|
||||
super.init(color: color, width: width)
|
||||
}
|
||||
|
||||
// MARK: - UIViewBorder
|
||||
|
||||
override open func apply(to view: UIView) {
|
||||
super.apply(to: view)
|
||||
|
||||
view.layer.maskedCorners = roundedCorners
|
||||
view.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
override open func remove(from view: UIView) {
|
||||
super.remove(from: view)
|
||||
|
||||
view.layer.maskedCorners = Defaults.roundedCorners
|
||||
view.layer.cornerRadius = Defaults.cornerRadius
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,6 @@ public final class SeparatorLayout: UIView.BaseWrappedLayout, WrappedViewLayout
|
|||
|
||||
public final class SeparatorAppearance: UIView.BaseAppearance<SeparatorLayout>, ViewAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self(backgroundColor: .lightGray)
|
||||
Self(background: UIViewColorBackground(color: .lightGray))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,15 +104,15 @@ extension BaseStackView: AppearanceConfigurable where View: AppearanceConfigurab
|
|||
public var arrangedSubviewsAppearance: View.Appearance
|
||||
|
||||
public init(layout: DefaultStackLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
arrangedSubviewsAppearance: View.Appearance = .defaultAppearance) {
|
||||
|
||||
self.arrangedSubviewsAppearance = arrangedSubviewsAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,28 +23,29 @@
|
|||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class BaseListItemAppearance<LeadingViewAppearance: WrappedViewAppearance,
|
||||
MiddleViewAppearance: WrappedViewAppearance,
|
||||
TrailingViewAppearance: WrappedViewAppearance>: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
|
||||
open class BaseListItemAppearance<LeadingAppearance: WrappedViewAppearance,
|
||||
MiddleAppearance: WrappedViewAppearance,
|
||||
TrailingAppearance: WrappedViewAppearance>:
|
||||
UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
|
||||
|
||||
public var leadingViewAppearance: LeadingViewAppearance
|
||||
public var middleViewAppearance: MiddleViewAppearance
|
||||
public var trailingAppearance: TrailingViewAppearance
|
||||
public var leadingViewAppearance: LeadingAppearance
|
||||
public var middleViewAppearance: MiddleAppearance
|
||||
public var trailingAppearance: TrailingAppearance
|
||||
|
||||
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
leadingViewAppearance: LeadingViewAppearance = .defaultAppearance,
|
||||
middleViewAppearance: MiddleViewAppearance = .defaultAppearance,
|
||||
trailingViewAppearance: TrailingViewAppearance = .defaultAppearance) {
|
||||
leadingViewAppearance: LeadingAppearance = .defaultAppearance,
|
||||
middleViewAppearance: MiddleAppearance = .defaultAppearance,
|
||||
trailingViewAppearance: TrailingAppearance = .defaultAppearance) {
|
||||
|
||||
self.leadingViewAppearance = leadingViewAppearance
|
||||
self.middleViewAppearance = middleViewAppearance
|
||||
self.trailingAppearance = trailingViewAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ open class PlaceholderFactory {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Placeholder creation
|
||||
|
||||
open func createEmptyStatePlaceholder() -> DefaultPlaceholderView {
|
||||
|
|
@ -123,9 +122,8 @@ private extension PlaceholderFactory {
|
|||
static var defaultButtonAppearance: StatefulButton.DefaultPositionAppearance {
|
||||
.make {
|
||||
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.border = UIViewRoundedBorder(cornerRadius: 25)
|
||||
normalAppearance.backgroundColor = #colorLiteral(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,
|
||||
|
|
|
|||
|
|
@ -269,8 +269,8 @@ extension BasePlaceholderView {
|
|||
public var controlsViewAppearance: UIView.DefaultSpacedWrappedAppearance
|
||||
|
||||
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
imageViewAppearance: ImageViewAppearance = .defaultAppearance,
|
||||
textViewAppearance: DefaultTitleSubtitleView.Appearance = .defaultAppearance,
|
||||
|
|
@ -281,7 +281,7 @@ extension BasePlaceholderView {
|
|||
self.controlsViewAppearance = controlsViewAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ public protocol SkeletonsPresenter {
|
|||
|
||||
func showSkeletons()
|
||||
func hideSkeletons()
|
||||
func startAnimation()
|
||||
func stopAnimation()
|
||||
func startSkeletonAnimation()
|
||||
func stopSkeletonAnimation()
|
||||
}
|
||||
|
||||
// MARK: - SkeletonsPresenter + Default implemetation
|
||||
|
|
@ -62,16 +62,16 @@ extension SkeletonsPresenter {
|
|||
|
||||
// MARK: - UIView + SkeletonsPresenter
|
||||
|
||||
extension SkeletonsPresenter where Self: UIView {
|
||||
public var skeletonsHolder: UIView {
|
||||
public extension SkeletonsPresenter where Self: UIView {
|
||||
var skeletonsHolder: UIView {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIViewController + SkeletonsPresenter
|
||||
|
||||
extension SkeletonsPresenter where Self: UIViewController {
|
||||
public var skeletonsHolder: UIView {
|
||||
public extension SkeletonsPresenter where Self: UIViewController {
|
||||
var skeletonsHolder: UIView {
|
||||
view
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,17 +23,18 @@
|
|||
import TISwiftUtils
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
public extension UIView {
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
/// Shows skeletons on the view
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - viewsToSkeletons: views that will be converted to skeletons. If nil was passed subviews will be converted to skeletons
|
||||
/// - viewsToSkeletons: views that will be converted to skeletons.
|
||||
/// If nil was passed subviews will be converted to skeletons
|
||||
/// - config: configuration of the skeletons' layers
|
||||
public func showSkeletons(viewsToSkeletons: [UIView]?,
|
||||
_ config: SkeletonsConfiguration) {
|
||||
func showSkeletons(viewsToSkeletons: [UIView]?,
|
||||
_ config: SkeletonsConfiguration) {
|
||||
|
||||
let viewsToSkeletons = viewsToSkeletons ?? skeletonableViews
|
||||
isUserInteractionEnabled = false
|
||||
|
|
@ -53,19 +54,19 @@ extension UIView {
|
|||
.skeletonsShown()
|
||||
}
|
||||
|
||||
public func hideSkeletons() {
|
||||
func hideSkeletons() {
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
layer.skeletonLayers
|
||||
.forEach { $0.remove(from: self) }
|
||||
}
|
||||
|
||||
public func startAnimation() {
|
||||
func startSkeletonAnimation() {
|
||||
layer.skeletonLayers
|
||||
.forEach { $0.startAnimation() }
|
||||
}
|
||||
|
||||
public func stopAnimation() {
|
||||
func stopSkeletonAnimation() {
|
||||
layer.skeletonLayers
|
||||
.forEach { $0.stopAnimation() }
|
||||
}
|
||||
|
|
@ -87,7 +88,6 @@ extension UIView {
|
|||
subviewSkeletonLayers = view.skeletonableViews
|
||||
.map { getSkeletonLayer(forView: $0, withConfiguration: conf, forceNoContainers: true) }
|
||||
.flatMap { $0 }
|
||||
|
||||
} else {
|
||||
skeletonLayer.bind(to: view.viewType)
|
||||
}
|
||||
|
|
@ -105,9 +105,9 @@ extension UIView {
|
|||
|
||||
// MARK: - Helper extension
|
||||
|
||||
extension Array where Element: SkeletonLayer {
|
||||
public extension Array where Element: SkeletonLayer {
|
||||
@discardableResult
|
||||
public func skeletonsShown() -> Self {
|
||||
func skeletonsShown() -> Self {
|
||||
self.forEach { subLayer in
|
||||
subLayer.skeletonsChangedState(.shown)
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ extension Array where Element: SkeletonLayer {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
public func insert(onto view: UIView, at index: UInt32 = .max) -> Self {
|
||||
func insert(onto view: UIView, at index: UInt32 = .max) -> Self {
|
||||
self.forEach { subLayer in
|
||||
view.layer.insertSublayer(subLayer, at: index)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,28 +22,29 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
public extension UIViewController {
|
||||
|
||||
/// Shows skeletons
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - viewsToSkeletons: views that will be converted to skeletons. If nil was passed subviews of the view will be converted to skeletons
|
||||
/// - viewsToSkeletons: views that will be converted to skeletons.
|
||||
/// If nil was passed subviews of the view will be converted to skeletons
|
||||
/// - config: configuration of the skeletons' layers
|
||||
public func showSkeletons(viewsToSkeletons: [UIView]?,
|
||||
_ config: SkeletonsConfiguration) {
|
||||
func showSkeletons(viewsToSkeletons: [UIView]?,
|
||||
_ config: SkeletonsConfiguration) {
|
||||
|
||||
view.showSkeletons(viewsToSkeletons: viewsToSkeletons, config)
|
||||
}
|
||||
|
||||
public func hideSkeletons() {
|
||||
func hideSkeletons() {
|
||||
view.hideSkeletons()
|
||||
}
|
||||
|
||||
public func startAnimation() {
|
||||
view.startAnimation()
|
||||
func startSkeletonAnimation() {
|
||||
view.startSkeletonAnimation()
|
||||
}
|
||||
|
||||
public func stopAnimation() {
|
||||
view.stopAnimation()
|
||||
func stopSkeletonAnimation() {
|
||||
view.stopSkeletonAnimation()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab
|
|||
stateViewModelMap = viewModel.stateViewModelMap
|
||||
|
||||
for (state, viewModel) in viewModel.stateViewModelMap {
|
||||
setTitle(viewModel.title, for: state)
|
||||
setImage(viewModel.image, for: state)
|
||||
setBackgroundImage(viewModel.backgroundImage, for: state)
|
||||
setTitle(viewModel.title, for: state.controlState)
|
||||
setImage(viewModel.image, for: state.controlState)
|
||||
setBackgroundImage(viewModel.backgroundImage, for: state.controlState)
|
||||
}
|
||||
|
||||
apply(state: viewModel.currentState)
|
||||
|
|
@ -68,7 +68,7 @@ public final class DefaultConfigurableStatefulButton: StatefulButton, Configurab
|
|||
|
||||
public extension DefaultConfigurableStatefulButton {
|
||||
final class ViewModel: DefaultUIViewPresenter<DefaultConfigurableStatefulButton> {
|
||||
public var stateViewModelMap: [State: BaseButtonViewModel]
|
||||
public var stateViewModelMap: [StateKey: BaseButtonViewModel]
|
||||
public var currentState: State {
|
||||
didSet {
|
||||
view?.apply(state: currentState)
|
||||
|
|
@ -83,7 +83,7 @@ public extension DefaultConfigurableStatefulButton {
|
|||
}
|
||||
}
|
||||
|
||||
public init(stateViewModelMap: [State: BaseButtonViewModel],
|
||||
public init(stateViewModelMap: [StateKey: BaseButtonViewModel],
|
||||
currentState: State,
|
||||
tapHander: UIVoidClosure?) {
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import UIKit
|
|||
|
||||
extension StatefulButton {
|
||||
public typealias StateAppearance = UIButton.BaseAppearance<UIView.NoLayout, DefaultContentLayout>
|
||||
public typealias StateAppearances = [State: StateAppearance]
|
||||
public typealias StateAppearances = [StateKey: StateAppearance]
|
||||
|
||||
open class BaseAppearance<Layout: ViewLayout, ContentLayout: BaseContentLayout & ViewLayout>:
|
||||
UIView.BaseAppearance<Layout> {
|
||||
|
|
@ -43,22 +43,22 @@ extension StatefulButton {
|
|||
public var stateAppearances: StateAppearances
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
stateAppearances: StateAppearances = defaultStateAppearances) {
|
||||
|
||||
self.stateAppearances = stateAppearances
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
||||
public func set(appearanceBuilder: (StateAppearance) -> Void, for states: [State]) {
|
||||
for state in states {
|
||||
stateAppearances[state] = DefaultStateAppearance.make(builder: appearanceBuilder)
|
||||
stateAppearances[.init(controlState: state)] = DefaultStateAppearance.make(builder: appearanceBuilder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,8 +69,8 @@ extension StatefulButton {
|
|||
public var activityIndicatorPosition: ActivityIndicatorPosition
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
stateAppearances: StateAppearances = defaultStateAppearances,
|
||||
activityIndicatorPosition: ActivityIndicatorPosition = .center) {
|
||||
|
|
@ -78,7 +78,7 @@ extension StatefulButton {
|
|||
self.activityIndicatorPosition = activityIndicatorPosition
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow,
|
||||
stateAppearances: stateAppearances)
|
||||
|
|
@ -99,8 +99,8 @@ extension StatefulButton {
|
|||
public var activityIndicatorPlacement: ActivityIndicatorPlacement
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
stateAppearances: StateAppearances = defaultStateAppearances,
|
||||
activityIndicatorPlacement: ActivityIndicatorPlacement = .center) {
|
||||
|
|
@ -108,7 +108,7 @@ extension StatefulButton {
|
|||
self.activityIndicatorPlacement = activityIndicatorPlacement
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow,
|
||||
stateAppearances: stateAppearances)
|
||||
|
|
@ -127,7 +127,7 @@ extension StatefulButton {
|
|||
extension StatefulButton {
|
||||
public func configureBaseStatefulButton(appearance: BaseAppearance<some ViewLayout, some BaseContentLayout>) {
|
||||
onStateChanged = { [weak self] in
|
||||
if let stateAppearance = appearance.stateAppearances[$0] {
|
||||
if let stateAppearance = appearance.stateAppearances[.init(controlState: $0)] {
|
||||
self?.configureUIButton(appearance: stateAppearance, for: $0)
|
||||
} else if $0 != .normal, let stateAppearance = appearance.stateAppearances[.normal] {
|
||||
self?.configureUIButton(appearance: stateAppearance, for: .normal)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,13 @@ public extension UIControl.State {
|
|||
static var loading = Self(rawValue: 1 << 16 | Self.disabled.rawValue) // includes disabled state
|
||||
}
|
||||
|
||||
open class StatefulButton: BaseInitializableButton {
|
||||
public extension UIControl.StateKey {
|
||||
static var loading: Self {
|
||||
.init(controlState: .loading)
|
||||
}
|
||||
}
|
||||
|
||||
open class StatefulButton: BaseInitializableButton {
|
||||
public enum ActivityIndicatorPosition {
|
||||
case beforeTitle(padding: CGFloat)
|
||||
case center
|
||||
|
|
@ -46,7 +51,7 @@ open class StatefulButton: BaseInitializableButton {
|
|||
case center
|
||||
}
|
||||
|
||||
public typealias StateEventPropagations = [State: Bool]
|
||||
public typealias StateEventPropagations = [StateKey: Bool]
|
||||
|
||||
private var backedIsLoading = false
|
||||
|
||||
|
|
@ -94,7 +99,7 @@ open class StatefulButton: BaseInitializableButton {
|
|||
|
||||
var activityIndicatorShouldCenterInView = false
|
||||
|
||||
var stateViewModelMap: [State: BaseButtonViewModel] = [:]
|
||||
var stateViewModelMap: [StateKey: BaseButtonViewModel] = [:]
|
||||
|
||||
var onStateChanged: ParameterClosure<State>?
|
||||
|
||||
|
|
@ -103,12 +108,12 @@ open class StatefulButton: BaseInitializableButton {
|
|||
private var eventPropagations: StateEventPropagations = [:]
|
||||
|
||||
public func setEventPropagation(_ eventPropagation: Bool, for state: State) {
|
||||
eventPropagations[state] = eventPropagation
|
||||
eventPropagations[.init(controlState: state)] = eventPropagation
|
||||
}
|
||||
|
||||
// MARK: - UIButton override
|
||||
|
||||
open override func setImage(_ image: UIImage?, for state: UIControl.State) {
|
||||
open override func setImage(_ image: UIImage?, for state: State) {
|
||||
guard state != .loading else {
|
||||
return
|
||||
}
|
||||
|
|
@ -116,21 +121,21 @@ open class StatefulButton: BaseInitializableButton {
|
|||
super.setImage(image, for: state)
|
||||
}
|
||||
|
||||
open override func title(for state: UIControl.State) -> String? {
|
||||
stateViewModelMap[state]?.title ?? super.title(for: state)
|
||||
open override func title(for state: State) -> String? {
|
||||
stateViewModelMap[.init(controlState: 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 image(for state: State) -> UIImage? {
|
||||
stateViewModelMap[.init(controlState: state)]?.image ?? super.image(for: state)
|
||||
}
|
||||
|
||||
open override func backgroundImage(for state: UIControl.State) -> UIImage? {
|
||||
stateViewModelMap[state]?.backgroundImage ?? super.backgroundImage(for: state)
|
||||
open override func backgroundImage(for state: State) -> UIImage? {
|
||||
stateViewModelMap[.init(controlState: state)]?.backgroundImage ?? super.backgroundImage(for: state)
|
||||
}
|
||||
|
||||
// MARK: - UIControl override
|
||||
|
||||
open override var state: UIControl.State {
|
||||
open override var state: State {
|
||||
if isLoading {
|
||||
return super.state.union(.loading)
|
||||
} else {
|
||||
|
|
@ -222,7 +227,7 @@ open class StatefulButton: BaseInitializableButton {
|
|||
|
||||
let touchEventReceiver = super.hitTest(point, with: event)
|
||||
|
||||
let shouldPropagateEvent = (eventPropagations[state] ?? true) || isHidden
|
||||
let shouldPropagateEvent = (eventPropagations[.init(controlState: state)] ?? true) || isHidden
|
||||
|
||||
if pointInsideView && touchEventReceiver == nil && !shouldPropagateEvent {
|
||||
return self // disable propagation
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@
|
|||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
extension DefaultTitleSubtitleView {
|
||||
public extension DefaultTitleSubtitleView {
|
||||
|
||||
public final class Appearance: UIView.BaseAppearance<UIView.DefaultSpacedWrappedLayout>, WrappedViewAppearance {
|
||||
final class Appearance: UIView.BaseAppearance<UIView.DefaultSpacedWrappedLayout>, WrappedViewAppearance {
|
||||
|
||||
public static var defaultAppearance: Appearance {
|
||||
Self()
|
||||
|
|
@ -35,8 +35,8 @@ extension DefaultTitleSubtitleView {
|
|||
public var subtitleAppearance: UILabel.DefaultAppearance
|
||||
|
||||
public init(layout: Layout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
background: UIViewBackground = UIViewColorBackground(color: .clear),
|
||||
border: UIViewBorder = BaseUIViewBorder(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
titleAppearance: UILabel.DefaultAppearance = .defaultAppearance,
|
||||
subtitleAppearance: UILabel.DefaultAppearance = .defaultAppearance) {
|
||||
|
|
@ -45,7 +45,7 @@ extension DefaultTitleSubtitleView {
|
|||
self.subtitleAppearance = subtitleAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
background: background,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
Базовая настройка для показа скелетонов не требуется. `UIView` и `UIViewController` уже имеют все необходимые методы для работы:
|
||||
- `showSkeletons(viewsToSkeletons:_:)` : используется для показа скелетонов, если была передана конфигурация анимации, то она запустится автоматически. `viewsToSkeletons` - опциональный массив `UIView`, определяющий, какие вью будут конвертироваться в скелетоны. Если nil, то проход будет осуществляться по списку subview
|
||||
- `hideSkeletons()` : используется для скрытия скелетонов
|
||||
- `startAnimation()` : используется для старта анимации на скелетонах (если анимания не была сконфигурирована в методе `showSkeletons(viewsToSkeletons:_:)` то ничего не произойдет)
|
||||
- `stopAnimation()` : используется для остановки анимации на скелетонах
|
||||
- `startSkeletonAnimation()` : используется для старта анимации на скелетонах (если анимания не была сконфигурирована в методе `showSkeletons(viewsToSkeletons:_:)` то ничего не произойдет)
|
||||
- `stopSkeletonAnimation()` : используется для остановки анимации на скелетонах
|
||||
*/
|
||||
import TIUIKitCore
|
||||
import TIUIElements
|
||||
|
|
@ -74,7 +74,7 @@ class CanShowAndHideSkeletons: BaseInitializableViewController {
|
|||
|
||||
let textAttributes = BaseTextAttributes(font: .systemFont(ofSize: 25), color: .black, alignment: .natural, isMultiline: false)
|
||||
|
||||
view.configureUIView(appearance: UIView.DefaultAppearance(backgroundColor: .white))
|
||||
view.configureUIView(appearance: UIView.DefaultAppearance(background: UIViewColorBackground(color: .white)))
|
||||
|
||||
label.configureUILabel(appearance: UILabel.DefaultAppearance.make {
|
||||
$0.textAttributes = textAttributes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
/*:
|
||||
# UIViewBackground
|
||||
|
||||
Для задания фона UIView можно использовать реализации протокола UIViewBackground:
|
||||
|
||||
## UIViewColorBackground - сплошной фон одного цвета
|
||||
*/
|
||||
|
||||
import TIUIElements
|
||||
import UIKit
|
||||
|
||||
let viewFrame = CGRect(origin: .zero,
|
||||
size: CGSize(width: 164, height: 192))
|
||||
|
||||
let solidFillBackground = UIViewColorBackground(color: .green)
|
||||
|
||||
let genericView = UIView(frame: viewFrame)
|
||||
|
||||
solidFillBackground.apply(to: genericView)
|
||||
|
||||
Nef.Playground.liveView(genericView)
|
||||
|
||||
/*:
|
||||
## UIViewGradientBackground - градиентный фон
|
||||
|
||||
Для задания градиентного фона необходимо определить GradientValues и применить фон в UIView:
|
||||
*/
|
||||
|
||||
let gradientView = UIView(frame: viewFrame)
|
||||
|
||||
let gradientColorStart = UIColor(red: 0.8, green: 0.74, blue: 1, alpha: 1).cgColor
|
||||
let gradientColorEnd = UIColor(red: 1, green: 0.82, blue: 0.84, alpha: 1).cgColor
|
||||
|
||||
let centerPoint = CGPoint(x: 0.98, y: 0.95)
|
||||
let outerPoint = CGPoint(x: 0.02, y: .zero)
|
||||
|
||||
let gradientValues: GradientValues = .elliptical(stops: [
|
||||
(gradientColorStart, 0),
|
||||
(gradientColorEnd, 1)
|
||||
],
|
||||
center: centerPoint,
|
||||
outerEdge: outerPoint)
|
||||
|
||||
let gradientBackground = UIViewGradientBackground(values: gradientValues)
|
||||
|
||||
gradientBackground.apply(to: gradientView)
|
||||
gradientView.layer.round(corners: .allCorners, radius: 20)
|
||||
|
||||
Nef.Playground.liveView(gradientView)
|
||||
|
||||
/*:
|
||||
### Использование внутри кастомной view
|
||||
|
||||
Также возможно использование градиентного фона внутри кастомной view с более точным контролем над обновлением состояния
|
||||
*/
|
||||
|
||||
final class GradientView: BaseInitializableView {
|
||||
var gradientBackground = UIViewGradientBackground(observeViewBoundsChange: false) {
|
||||
willSet {
|
||||
gradientBackground.remove(from: self)
|
||||
}
|
||||
|
||||
didSet {
|
||||
gradientBackground.gradientValues = gradientValues
|
||||
}
|
||||
}
|
||||
|
||||
var gradientValues: GradientValues = GradientValues() {
|
||||
didSet {
|
||||
gradientBackground.gradientValues = gradientValues
|
||||
}
|
||||
}
|
||||
|
||||
override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
gradientBackground.apply(to: self)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
gradientBackground.updateGradientLayer(bounds: bounds, in: self)
|
||||
}
|
||||
}
|
||||
|
||||
let customGradientView = GradientView(frame: viewFrame)
|
||||
customGradientView.gradientValues = gradientValues
|
||||
customGradientView.layer.round(corners: .allCorners, radius: 20)
|
||||
|
||||
Nef.Playground.liveView(customGradientView)
|
||||
|
|
@ -3,5 +3,6 @@
|
|||
<pages>
|
||||
<page name='Skeletons'/>
|
||||
<page name='Placeholder'/>
|
||||
<page name='ViewBackground'/>
|
||||
</pages>
|
||||
</playground>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIElements'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Bunch of useful protocols and views.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit.UIView
|
||||
|
||||
public protocol UIViewBackground {
|
||||
func apply(to view: UIView)
|
||||
func remove(from view: UIView)
|
||||
}
|
||||
|
|
@ -22,20 +22,7 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public struct UIViewBorder {
|
||||
public var color: UIColor
|
||||
public var width: CGFloat
|
||||
public var cornerRadius: CGFloat
|
||||
public var roundedCorners: CACornerMask
|
||||
|
||||
public init(color: UIColor = .clear,
|
||||
width: CGFloat = .zero,
|
||||
cornerRadius: CGFloat = .zero,
|
||||
roundedCorners: CACornerMask = []) {
|
||||
|
||||
self.color = color
|
||||
self.width = width
|
||||
self.cornerRadius = cornerRadius
|
||||
self.roundedCorners = roundedCorners
|
||||
}
|
||||
public protocol UIViewBorder {
|
||||
func apply(to view: UIView)
|
||||
func remove(from view: UIView)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public protocol ViewAppearance {
|
|||
static var defaultAppearance: Self { get }
|
||||
|
||||
var layout: Layout { get }
|
||||
var backgroundColor: UIColor { get }
|
||||
var background: UIViewBackground { get }
|
||||
var border: UIViewBorder { get }
|
||||
var shadow: UIViewShadow? { get }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,24 +24,24 @@ import UIKit.UIButton
|
|||
|
||||
public extension UIButton {
|
||||
func set(titleColors: StateColors) {
|
||||
titleColors.forEach { setTitleColor($1, for: $0) }
|
||||
titleColors.forEach { setTitleColor($1, for: $0.controlState) }
|
||||
}
|
||||
|
||||
func set(titles: StateTitles) {
|
||||
titles.forEach { setTitle($1, for: $0) }
|
||||
titles.forEach { setTitle($1, for: $0.controlState) }
|
||||
}
|
||||
|
||||
func set(attributtedTitles: StateAttributedTitles) {
|
||||
attributtedTitles.forEach { setAttributedTitle($1, for: $0) }
|
||||
attributtedTitles.forEach { setAttributedTitle($1, for: $0.controlState) }
|
||||
}
|
||||
|
||||
// MARK: - Images
|
||||
|
||||
func set(images: StateImages) {
|
||||
images.forEach { setImage($1, for: $0) }
|
||||
images.forEach { setImage($1, for: $0.controlState) }
|
||||
}
|
||||
|
||||
func set(backgroundImages: StateImages) {
|
||||
backgroundImages.forEach { setBackgroundImage($1, for: $0) }
|
||||
backgroundImages.forEach { setBackgroundImage($1, for: $0.controlState) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,15 +22,43 @@
|
|||
|
||||
import UIKit.UIControl
|
||||
|
||||
extension UIControl.State: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(Int(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIControl {
|
||||
typealias StateColors = [UIControl.State: UIColor?]
|
||||
typealias StateImages = [UIControl.State: UIImage?]
|
||||
typealias StateTitles = [UIControl.State: String?]
|
||||
typealias StateAttributedTitles = [UIControl.State: NSAttributedString?]
|
||||
struct StateKey: Hashable {
|
||||
public static var normal: StateKey {
|
||||
.init(controlState: .normal)
|
||||
}
|
||||
|
||||
public static var highlighted: StateKey {
|
||||
.init(controlState: .highlighted)
|
||||
}
|
||||
|
||||
public static var disabled: StateKey {
|
||||
.init(controlState: .disabled)
|
||||
}
|
||||
|
||||
public static var selected: StateKey {
|
||||
.init(controlState: .selected)
|
||||
}
|
||||
|
||||
public let controlState: State
|
||||
|
||||
public init(controlState: State) {
|
||||
self.controlState = controlState
|
||||
}
|
||||
|
||||
public init(rawValue: State.RawValue) {
|
||||
self.controlState = State(rawValue: rawValue)
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(controlState.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
typealias StateColors = [StateKey: UIColor?]
|
||||
typealias StateImages = [StateKey: UIImage?]
|
||||
typealias StateTitles = [StateKey: String?]
|
||||
typealias StateAttributedTitles = [StateKey: NSAttributedString?]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ open class BaseTextAttributes {
|
|||
public let color: UIColor
|
||||
public let paragraphStyle: NSParagraphStyle
|
||||
public let numberOfLines: Int
|
||||
public let customAttributes: [NSAttributedString.Key: Any]
|
||||
|
||||
private let forceAttributedStringUsage: Bool
|
||||
|
||||
|
|
@ -39,18 +40,23 @@ open class BaseTextAttributes {
|
|||
.foregroundColor: color,
|
||||
.paragraphStyle: paragraphStyle
|
||||
]
|
||||
.merging(customAttributes) { _, new in
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
public init(font: UIFont,
|
||||
color: UIColor,
|
||||
numberOfLines: Int,
|
||||
paragraphStyleConfiguration: ParameterClosure<NSMutableParagraphStyle>) {
|
||||
customAttributes: [NSAttributedString.Key: Any] = [:],
|
||||
paragraphStyleConfiguration: ParameterClosure<NSMutableParagraphStyle>? = nil) {
|
||||
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.customAttributes = customAttributes
|
||||
|
||||
let mutableParagraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyleConfiguration(mutableParagraphStyle)
|
||||
paragraphStyleConfiguration?(mutableParagraphStyle)
|
||||
|
||||
let equator = KeyPathEquatable(rhs: mutableParagraphStyle, lhs: NSParagraphStyle.default)
|
||||
|
||||
|
|
@ -71,7 +77,7 @@ open class BaseTextAttributes {
|
|||
equator(\.lineBreakStrategy)
|
||||
]
|
||||
|
||||
forceAttributedStringUsage = !equalityResults.allSatisfy { $0 }
|
||||
forceAttributedStringUsage = !equalityResults.allSatisfy { $0 } || !customAttributes.isEmpty
|
||||
|
||||
self.paragraphStyle = mutableParagraphStyle
|
||||
self.numberOfLines = numberOfLines
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIKitCore'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + 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.52.0'
|
||||
s.version = '1.53.0'
|
||||
s.summary = 'Universal web view API'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
@ -11,7 +11,14 @@ Pod::Spec.new do |s|
|
|||
s.ios.deployment_target = '11.0'
|
||||
s.swift_versions = ['5.7']
|
||||
|
||||
s.source_files = s.name + '/Sources/**/*'
|
||||
sources = 'Sources/**/*.swift'
|
||||
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
|
||||
s.source_files = sources
|
||||
s.exclude_files = s.name + '.app'
|
||||
else
|
||||
s.source_files = s.name + '/' + sources
|
||||
s.exclude_files = s.name + '/*.app'
|
||||
end
|
||||
|
||||
s.dependency 'TIUIKitCore', s.version.to_s
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIYandexMapUtils'
|
||||
s.version = '1.52.0'
|
||||
s.version = '1.53.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/src/tag/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class EmptyViewController: BaseModalViewController<UIView, UIView> { }
|
|||
|
||||
## Обертка вокруг существующего контроллера
|
||||
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `BaseModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `DefaultModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
|
||||
```swift
|
||||
import TIUIKitCore
|
||||
|
|
@ -25,13 +25,13 @@ final class OldMassiveViewController: BaseInitializableViewController {
|
|||
// some implementation
|
||||
}
|
||||
|
||||
typealias ModalOldMassiveViewController = BaseModalWrapperViewController<OldMassiveViewController>
|
||||
typealias ModalOldMassiveViewController = DefaultModalWrapperViewController<OldMassiveViewController>
|
||||
|
||||
class PresentingViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
|
||||
@objc private func onButtonTapped() {
|
||||
presentPanModal(ModalOldMassiveViewController())
|
||||
presentPanModal(ModalOldMassiveViewController(contentViewController: OldMassiveViewController()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -49,16 +49,18 @@ class PresentingViewController: BaseInitializableViewController {
|
|||
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
|
||||
|
||||
```swift
|
||||
import TIUIElements
|
||||
|
||||
let customViewController = BaseModalViewController<UIView, UIView>()
|
||||
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
|
||||
$0.dragViewState = .presented(.defaultAppearance)
|
||||
$0.headerViewState = .presented(.make {
|
||||
$0.layout.size = .fixedHeight(52)
|
||||
$0.backgroundColor = .white
|
||||
$0.contentViewState = .buttonLeft(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(backgroundColor: .blue)
|
||||
])))
|
||||
$0.contentViewState = .leadingButton(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(background: UIViewColorBackground(color: .blue))
|
||||
])))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
|
@ -85,10 +87,10 @@ detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnl
|
|||
```swift
|
||||
let shadowViewController = BaseModalViewController<UIView, UIView>()
|
||||
let dimmedView = PassthroughDimmedView()
|
||||
dimmedView.hitTestHandlerView = view
|
||||
dimmedView.configureUIView(appearance: .init(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
dimmedView.hitTestHandlerView = shadowViewController.view
|
||||
dimmedView.configureUIView(appearance: DefaultAppearance(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
|
||||
shadowViewController.dimmedView = dimmedView
|
||||
```
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ import Foundation
|
|||
|
||||
let defaults = UserDefaults.standard // or AppGroup defaults
|
||||
|
||||
let appReinstallChecker = AppReinstallChecker(defaultsStorage: defaults,
|
||||
storageKey: .deleteApiToken)
|
||||
let appReinstallChecker = DefaultAppFirstRunCheckStorage(defaults: defaults,
|
||||
storageKey: .deleteApiToken)
|
||||
|
||||
let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(reinstallChecker: appReinstallChecker)
|
||||
let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(appFirstRunCheckStorage: appReinstallChecker)
|
||||
|
||||
if appInstallAwareTokenStorage.hasStoredValue() {
|
||||
// app wasn't reinstalled, token is exist
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
Базовая настройка для показа скелетонов не требуется. `UIView` и `UIViewController` уже имеют все необходимые методы для работы:
|
||||
- `showSkeletons(viewsToSkeletons:_:)` : используется для показа скелетонов, если была передана конфигурация анимации, то она запустится автоматически. `viewsToSkeletons` - опциональный массив `UIView`, определяющий, какие вью будут конвертироваться в скелетоны. Если nil, то проход будет осуществляться по списку subview
|
||||
- `hideSkeletons()` : используется для скрытия скелетонов
|
||||
- `startAnimation()` : используется для старта анимации на скелетонах (если анимания не была сконфигурирована в методе `showSkeletons(viewsToSkeletons:_:)` то ничего не произойдет)
|
||||
- `stopAnimation()` : используется для остановки анимации на скелетонах
|
||||
- `startSkeletonAnimation()` : используется для старта анимации на скелетонах (если анимания не была сконфигурирована в методе `showSkeletons(viewsToSkeletons:_:)` то ничего не произойдет)
|
||||
- `stopSkeletonAnimation()` : используется для остановки анимации на скелетонах
|
||||
|
||||
```swift
|
||||
import TIUIKitCore
|
||||
|
|
@ -75,7 +75,7 @@ class CanShowAndHideSkeletons: BaseInitializableViewController {
|
|||
|
||||
let textAttributes = BaseTextAttributes(font: .systemFont(ofSize: 25), color: .black, alignment: .natural, isMultiline: false)
|
||||
|
||||
view.configureUIView(appearance: UIView.DefaultAppearance(backgroundColor: .white))
|
||||
view.configureUIView(appearance: UIView.DefaultAppearance(background: UIViewColorBackground(color: .white)))
|
||||
|
||||
label.configureUILabel(appearance: UILabel.DefaultAppearance.make {
|
||||
$0.textAttributes = textAttributes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
# UIViewBackground
|
||||
|
||||
Для задания фона UIView можно использовать реализации протокола UIViewBackground:
|
||||
|
||||
## UIViewColorBackground - сплошной фон одного цвета
|
||||
|
||||
```swift
|
||||
import TIUIElements
|
||||
import UIKit
|
||||
|
||||
let viewFrame = CGRect(origin: .zero,
|
||||
size: CGSize(width: 164, height: 192))
|
||||
|
||||
let solidFillBackground = UIViewColorBackground(color: .green)
|
||||
|
||||
let genericView = UIView(frame: viewFrame)
|
||||
|
||||
solidFillBackground.apply(to: genericView)
|
||||
|
||||
Nef.Playground.liveView(genericView)
|
||||
```
|
||||
|
||||
## UIViewGradientBackground - градиентный фон
|
||||
|
||||
Для задания градиентного фона необходимо определить GradientValues и применить фон в UIView:
|
||||
|
||||
```swift
|
||||
let gradientView = UIView(frame: viewFrame)
|
||||
|
||||
let gradientColorStart = UIColor(red: 0.8, green: 0.74, blue: 1, alpha: 1).cgColor
|
||||
let gradientColorEnd = UIColor(red: 1, green: 0.82, blue: 0.84, alpha: 1).cgColor
|
||||
|
||||
let centerPoint = CGPoint(x: 0.98, y: 0.95)
|
||||
let outerPoint = CGPoint(x: 0.02, y: .zero)
|
||||
|
||||
let gradientValues: GradientValues = .elliptical(stops: [
|
||||
(gradientColorStart, 0),
|
||||
(gradientColorEnd, 1)
|
||||
],
|
||||
center: centerPoint,
|
||||
outerEdge: outerPoint)
|
||||
|
||||
let gradientBackground = UIViewGradientBackground(values: gradientValues)
|
||||
|
||||
gradientBackground.apply(to: gradientView)
|
||||
gradientView.layer.round(corners: .allCorners, radius: 20)
|
||||
|
||||
Nef.Playground.liveView(gradientView)
|
||||
```
|
||||
|
||||
### Использование внутри кастомной view
|
||||
|
||||
Также возможно использование градиентного фона внутри кастомной view с более точным контролем над обновлением состояния
|
||||
|
||||
```swift
|
||||
final class GradientView: BaseInitializableView {
|
||||
var gradientBackground = UIViewGradientBackground(observeViewBoundsChange: false) {
|
||||
willSet {
|
||||
gradientBackground.remove(from: self)
|
||||
}
|
||||
|
||||
didSet {
|
||||
gradientBackground.gradientValues = gradientValues
|
||||
}
|
||||
}
|
||||
|
||||
var gradientValues: GradientValues = GradientValues() {
|
||||
didSet {
|
||||
gradientBackground.gradientValues = gradientValues
|
||||
}
|
||||
}
|
||||
|
||||
override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
gradientBackground.apply(to: self)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
gradientBackground.updateGradientLayer(bounds: bounds, in: self)
|
||||
}
|
||||
}
|
||||
|
||||
let customGradientView = GradientView(frame: viewFrame)
|
||||
customGradientView.gradientValues = gradientValues
|
||||
customGradientView.layer.round(corners: .allCorners, radius: 20)
|
||||
|
||||
Nef.Playground.liveView(customGradientView)
|
||||
```
|
||||
Loading…
Reference in New Issue