Merge pull request 'feature/stack_appearance_layout' (#9) from feature/stack_appearance_layout into master
Reviewed-on: #9
This commit is contained in:
commit
77559babdb
|
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
### 1.48.0
|
||||
|
||||
- **Added**: `BaseStackView` with configurable items appearance
|
||||
- **Fixed**: `CollectionTableViewCell` self-sizing
|
||||
- **Added**: `ViewAppearance.WrappedViewLayout` support for all `WrappedViewHolders`
|
||||
- **Added**: `ViewCallbacks` support for all `BaseInitializeableViews`
|
||||
|
||||
### 1.47.0
|
||||
|
||||
- **Added**: `flatMap` operator for `AsyncOperation`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAppleMapUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIDeeplink'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -59,6 +59,20 @@ open class DashedBoundsLayer: CAShapeLayer {
|
|||
|
||||
public extension UIView {
|
||||
|
||||
@discardableResult
|
||||
func debugBoundsVisually(debugSubviews: Bool = true, after delay: DispatchTimeInterval? = nil) -> UIView {
|
||||
guard let delay else {
|
||||
debugBoundsVisually(debugSubviews: debugSubviews)
|
||||
return self
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
self.debugBoundsVisually(debugSubviews: debugSubviews)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func debugBoundsVisually(debugSubviews: Bool = true) -> UIView {
|
||||
disableBoundsVisuallyDebug()
|
||||
|
|
@ -93,8 +107,10 @@ public extension UIView {
|
|||
public extension UIViewController {
|
||||
|
||||
@discardableResult
|
||||
func debugBoundsVisually(debugSubviews: Bool = true) -> UIViewController {
|
||||
view.debugBoundsVisually(debugSubviews: debugSubviews)
|
||||
func debugBoundsVisually(debugSubviews: Bool = true,
|
||||
after delay: DispatchTimeInterval? = nil) -> UIViewController {
|
||||
|
||||
view.debugBoundsVisually(debugSubviews: debugSubviews, after: delay)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIDeveloperUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ import UIKit
|
|||
@available(iOS 13.0, *)
|
||||
open class BaseFiltersTableView<CellType: UITableViewCell & ConfigurableView,
|
||||
PropertyValue: FilterPropertyValueRepresenter & Hashable>:
|
||||
UITableView,
|
||||
InitializableViewProtocol,
|
||||
BaseInitializeableTableView,
|
||||
Updatable,
|
||||
UITableViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable {
|
||||
|
||||
|
|
@ -67,24 +66,16 @@ open class BaseFiltersTableView<CellType: UITableViewCell & ConfigurableView,
|
|||
|
||||
// MARK: - Life cycle
|
||||
|
||||
open func addViews() {
|
||||
// override in subclass
|
||||
}
|
||||
open override func bindViews() {
|
||||
super.bindViews()
|
||||
|
||||
open func configureLayout() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
delegate = self
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
alwaysBounceVertical = false
|
||||
}
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
open func localize() {
|
||||
// override in subclass
|
||||
alwaysBounceVertical = false
|
||||
}
|
||||
|
||||
open func viewDidLoad() {
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@
|
|||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import TIUIElements
|
||||
import UIKit
|
||||
|
||||
open class StepRangeSlider: UIControl, InitializableViewProtocol {
|
||||
open class StepRangeSlider: BaseInitializableControl {
|
||||
|
||||
// MARK: - Public properties
|
||||
|
||||
|
|
@ -161,16 +162,16 @@ open class StepRangeSlider: UIControl, InitializableViewProtocol {
|
|||
super.layoutSubviews()
|
||||
}
|
||||
|
||||
open func addViews() {
|
||||
open override func addViews() {
|
||||
super.addViews()
|
||||
|
||||
addSubviews(trackView, leftThumbView, rightThumbView)
|
||||
trackView.addSubviews(activeTrack)
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// override
|
||||
}
|
||||
open override func bindViews() {
|
||||
super.bindViews()
|
||||
|
||||
open func bindViews() {
|
||||
thumbs.forEach {
|
||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self,
|
||||
action: #selector(draggingThumb(_:)))
|
||||
|
|
@ -178,7 +179,9 @@ open class StepRangeSlider: UIControl, InitializableViewProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
trackView.backgroundColor = trackColor
|
||||
activeTrack.backgroundColor = activeTrackColor
|
||||
|
||||
|
|
@ -190,10 +193,6 @@ open class StepRangeSlider: UIControl, InitializableViewProtocol {
|
|||
updateActiveTrack()
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// override
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
func getPositionX(from value: CGFloat) -> CGFloat {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@
|
|||
|
||||
import TISwiftUtils
|
||||
import TIUIKitCore
|
||||
import TIUIElements
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
open class BaseFiltersCollectionView<CellType: UICollectionViewCell & ConfigurableView,
|
||||
PropertyValue: FilterPropertyValueRepresenter & Hashable>:
|
||||
UICollectionView,
|
||||
InitializableViewProtocol,
|
||||
BaseInitializeableCollectionView,
|
||||
Updatable,
|
||||
UICollectionViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable {
|
||||
|
||||
|
|
@ -53,7 +53,6 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
|
|||
|
||||
super.init(frame: .zero, collectionViewLayout: layout)
|
||||
|
||||
initializeView()
|
||||
viewDidLoad()
|
||||
}
|
||||
|
||||
|
|
@ -64,24 +63,16 @@ open class BaseFiltersCollectionView<CellType: UICollectionViewCell & Configurab
|
|||
|
||||
// MARK: - Life cycle
|
||||
|
||||
open func addViews() {
|
||||
// override in subclass
|
||||
}
|
||||
open override func bindViews() {
|
||||
super.bindViews()
|
||||
|
||||
open func configureLayout() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
delegate = self
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
backgroundColor = .white
|
||||
}
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
open func localize() {
|
||||
// override in subclass
|
||||
backgroundColor = .white
|
||||
}
|
||||
|
||||
open func viewDidLoad() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIEcommerce'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIFoundationUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIGoogleMapUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ switch expirationCheckStorage.getValue() {
|
|||
case let .success(token):
|
||||
// use token
|
||||
break
|
||||
case let .failure(storageError)
|
||||
if .valueNotFound = storageError {
|
||||
case let .failure(storageError):
|
||||
if .valueNotFound == storageError {
|
||||
// token is missing or expired, request new token
|
||||
} else {
|
||||
// handle storage error
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIKeychainUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMapUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMoyaNetworking'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
@ -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/**/*'
|
||||
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
|
||||
s.source_files = sources
|
||||
s.exclude_files = s.name + '.app'
|
||||
else
|
||||
s.source_files = s.name + '/' + sources
|
||||
s.exclude_files = s.name + '/*.app'
|
||||
end
|
||||
|
||||
s.dependency 'TIUIElements', s.version.to_s
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITextProcessing'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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,71 +24,6 @@ import TIUIKitCore
|
|||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
|
||||
// MARK: - Layout Variations
|
||||
|
||||
public struct NoLayout: ViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseSizeLayout {
|
||||
public var size: CGSize
|
||||
|
||||
public init(size: CGSize = .infinity) {
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultLayout: BaseSizeLayout, SizeViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WrappedView Layout
|
||||
|
||||
open class BaseWrappedLayout: BaseSizeLayout {
|
||||
public var centerOffset: UIOffset
|
||||
public var insets: UIEdgeInsets
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan) {
|
||||
|
||||
self.centerOffset = centerOffset
|
||||
self.insets = insets
|
||||
|
||||
super.init(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseSpecedWrappedLayout: BaseWrappedLayout {
|
||||
public var spacing: CGFloat
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan,
|
||||
spacing: CGFloat = .zero) {
|
||||
self.spacing = spacing
|
||||
|
||||
super.init(insets: insets, size: size, centerOffset: centerOffset)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultWrappedLayout: BaseWrappedLayout, WrappedViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultSpacedWrappedLayout: BaseSpecedWrappedLayout, SpacedWrappedViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Appearance Variations
|
||||
|
||||
open class BaseAppearance<Layout: ViewLayout> {
|
||||
|
|
@ -138,9 +73,7 @@ extension UIView {
|
|||
}
|
||||
}
|
||||
|
||||
open class BaseWrappedAppearance<Layout: WrappedViewLayout>: BaseAppearance<Layout> {
|
||||
|
||||
}
|
||||
open class BaseWrappedAppearance<Layout: WrappedViewLayout>: BaseAppearance<Layout> {}
|
||||
|
||||
public final class DefaultWrappedViewHolderAppearance<SubviewAppearance: WrappedViewAppearance,
|
||||
Layout: ViewLayout>: BaseWrappedViewHolderAppearance<SubviewAppearance, Layout>,
|
||||
|
|
@ -163,6 +96,4 @@ extension UIView {
|
|||
}
|
||||
}
|
||||
|
||||
extension UIView.DefaultWrappedViewHolderAppearance: WrappedViewAppearance where Layout: WrappedViewLayout {
|
||||
|
||||
}
|
||||
extension UIView.DefaultWrappedViewHolderAppearance: WrappedViewAppearance where Layout: WrappedViewLayout {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// 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
|
||||
import TIUIKitCore
|
||||
|
||||
extension UIView {
|
||||
|
||||
// MARK: - Layout Variations
|
||||
|
||||
public struct NoLayout: ViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseSizeLayout {
|
||||
public var size: CGSize
|
||||
|
||||
public init(size: CGSize = .infinity) {
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultLayout: BaseSizeLayout, SizeViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WrappedView Layout
|
||||
|
||||
open class BaseWrappedLayout: BaseSizeLayout {
|
||||
public var centerOffset: UIOffset
|
||||
public var insets: UIEdgeInsets
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan) {
|
||||
|
||||
self.centerOffset = centerOffset
|
||||
self.insets = insets
|
||||
|
||||
super.init(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultWrappedLayout: BaseWrappedLayout, WrappedViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseSpecedWrappedLayout: BaseWrappedLayout {
|
||||
public var spacing: CGFloat
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan,
|
||||
spacing: CGFloat = .zero) {
|
||||
self.spacing = spacing
|
||||
|
||||
super.init(insets: insets, size: size, centerOffset: centerOffset)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SpacedWrappedLayout
|
||||
|
||||
public final class DefaultSpacedWrappedLayout: BaseSpecedWrappedLayout, SpacedWrappedViewLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - StackLayout
|
||||
|
||||
open class BaseStackLayout: UIView.BaseSpecedWrappedLayout {
|
||||
public var axis: NSLayoutConstraint.Axis
|
||||
public var distribution: UIStackView.Distribution
|
||||
public var alignment: UIStackView.Alignment
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan,
|
||||
spacing: CGFloat = .zero,
|
||||
axis: NSLayoutConstraint.Axis = .vertical,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
alignment: UIStackView.Alignment = .fill) {
|
||||
|
||||
self.axis = axis
|
||||
self.distribution = distribution
|
||||
self.alignment = alignment
|
||||
|
||||
super.init(insets: insets,
|
||||
size: size,
|
||||
centerOffset: centerOffset,
|
||||
spacing: spacing)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultStackLayout: BaseStackLayout, StackLayout {
|
||||
public static var defaultLayout: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
extension UIView {
|
||||
open class RoundCornersCallback<View: UIView>: BaseViewCallbacks<View> {
|
||||
public var corners: CACornerMask
|
||||
|
||||
public init(view: View, corners: CACornerMask = .allCorners) {
|
||||
self.corners = corners
|
||||
|
||||
super.init(view: view)
|
||||
}
|
||||
|
||||
override open func onDidLayoutSubviews() {
|
||||
withStrongView {
|
||||
$0.layer.round(corners: corners, radius: calculateCornerRadius(for: $0.bounds))
|
||||
}
|
||||
}
|
||||
|
||||
open func calculateCornerRadius(for bounds: CGRect) -> CGFloat {
|
||||
min(bounds.width, bounds.height) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializableButton: UIButton, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ import TIUIKitCore
|
|||
import UIKit
|
||||
|
||||
open class BaseInitializableCell: UITableViewCell, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
|
|
@ -33,6 +35,16 @@ open class BaseInitializableCell: UITableViewCell, InitializableViewProtocol {
|
|||
@available(*, unavailable)
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
|
@ -24,6 +24,8 @@ import UIKit
|
|||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializableControl: UIControl, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
|
|
@ -36,6 +38,14 @@ open class BaseInitializableControl: UIControl, InitializableViewProtocol {
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
|
|
@ -24,6 +24,8 @@ import UIKit.UITextView
|
|||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializableTextView: UITextView, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
public override init(frame: CGRect, textContainer: NSTextContainer?) {
|
||||
super.init(frame: frame, textContainer: textContainer)
|
||||
|
||||
|
|
@ -36,6 +38,14 @@ open class BaseInitializableTextView: UITextView, InitializableViewProtocol {
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
|
|
@ -24,6 +24,8 @@ import UIKit
|
|||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializableView: UIView, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
|
|
@ -36,6 +38,14 @@ open class BaseInitializableView: UIView, InitializableViewProtocol {
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializeableCollectionView: UICollectionView, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
public override init(frame: CGRect, collectionViewLayout: UICollectionViewLayout) {
|
||||
super.init(frame: frame, collectionViewLayout: collectionViewLayout)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
backgroundColor = .clear
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class BaseInitializeableTableView: UITableView, InitializableViewProtocol {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
public override init(frame: CGRect, style: UITableView.Style) {
|
||||
super.init(frame: frame, style: style)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
backgroundColor = .clear
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// empty for subclasses overriding
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class BaseStackView<View: UIView>: UIStackView {
|
||||
public var customArrangedSubviews: [View] = []
|
||||
|
||||
@available(*, unavailable, message: "Use strongly-typed version of this function")
|
||||
open override func addArrangedSubview(_ view: UIView) {
|
||||
super.addArrangedSubview(view)
|
||||
}
|
||||
|
||||
@available(*, unavailable, message: "Use strongly-typed version of this function")
|
||||
open override func removeArrangedSubview(_ view: UIView) {
|
||||
super.removeArrangedSubview(view)
|
||||
}
|
||||
|
||||
@available(*, unavailable, message: "Use strongly-typed version of this function")
|
||||
open override func insertArrangedSubview(_ view: UIView, at stackIndex: Int) {
|
||||
super.insertArrangedSubview(view, at: stackIndex)
|
||||
}
|
||||
|
||||
open func addArrangedSubview(_ view: View) {
|
||||
customArrangedSubviews.append(view)
|
||||
|
||||
super.addArrangedSubview(view)
|
||||
}
|
||||
|
||||
open func removeArrangedSubview(_ view: View) {
|
||||
guard let indexOfView = customArrangedSubviews.firstIndex(where: { $0 === view }) else {
|
||||
assertionFailure("Unable to find \(view) in \(customArrangedSubviews)")
|
||||
return
|
||||
}
|
||||
|
||||
customArrangedSubviews.remove(at: indexOfView)
|
||||
|
||||
super.removeArrangedSubview(view)
|
||||
}
|
||||
|
||||
open func replaceArrangedSubviews(_ newViews: [View]) {
|
||||
for existingSubview in customArrangedSubviews {
|
||||
removeArrangedSubview(existingSubview)
|
||||
}
|
||||
|
||||
for view in newViews {
|
||||
addArrangedSubview(view)
|
||||
}
|
||||
}
|
||||
|
||||
open func insertArrangedSubview(_ view: View, at stackIndex: Int) {
|
||||
customArrangedSubviews.insert(view, at: stackIndex)
|
||||
|
||||
super.insertArrangedSubview(view, at: stackIndex)
|
||||
}
|
||||
|
||||
open func configureUIStackView(appearance: BaseWrappedAppearance<some StackLayout>) {
|
||||
configureUIView(appearance: appearance)
|
||||
|
||||
axis = appearance.layout.axis
|
||||
distribution = appearance.layout.distribution
|
||||
alignment = appearance.layout.alignment
|
||||
spacing = appearance.layout.spacing
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseStackView: ConfigurableView where View: ConfigurableView {
|
||||
public func configure(with viewModels: [View.ViewModelType]) {
|
||||
replaceArrangedSubviews(viewModels.map { View(viewModel: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseStackView: AppearanceConfigurable where View: AppearanceConfigurable {
|
||||
public final class Appearance: UIView.BaseWrappedAppearance<UIView.DefaultStackLayout>, WrappedViewAppearance {
|
||||
|
||||
public static var defaultAppearance: Self {
|
||||
Self()
|
||||
}
|
||||
|
||||
public var arrangedSubviewsAppearance: View.Appearance
|
||||
|
||||
public init(layout: UIView.DefaultStackLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
arrangedSubviewsAppearance: View.Appearance = .defaultAppearance) {
|
||||
|
||||
self.arrangedSubviewsAppearance = arrangedSubviewsAppearance
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(appearance: Appearance) {
|
||||
configureUIStackView(appearance: appearance)
|
||||
|
||||
for customSubview in customArrangedSubviews {
|
||||
customSubview.configure(appearance: appearance.arrangedSubviewsAppearance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2020 Touch Instinct
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
|
|
@ -20,38 +20,19 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class RoundedStatefulButton: StatefulButton {
|
||||
public final class DefaultConfigurableLabel: UILabel, ConfigurableView, AppearanceConfigurable {
|
||||
// MARK: - ConfigurableView
|
||||
|
||||
// UIView override
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
configureAppearance()
|
||||
public func configure(with text: String) {
|
||||
self.text = text
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
// MARK: - AppearanceConfigurable
|
||||
|
||||
configureAppearance()
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
layer.cornerRadius = calculateCornerRadius(for: bounds)
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func configureAppearance() {
|
||||
layer.round(corners: .allCorners)
|
||||
}
|
||||
|
||||
open func calculateCornerRadius(for bounds: CGRect) -> CGFloat {
|
||||
min(bounds.width, bounds.height) / 2
|
||||
public func configure(appearance: UILabel.DefaultAppearance) {
|
||||
configureUILabel(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +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
|
||||
|
||||
extension WrappedViewLayout {
|
||||
func setupSize(widthConstraint: NSLayoutConstraint?,
|
||||
heightConstraint: NSLayoutConstraint?) {
|
||||
|
||||
if size.width.isFinite {
|
||||
widthConstraint?.constant = size.width
|
||||
widthConstraint?.isActive = true
|
||||
} else {
|
||||
widthConstraint?.isActive = false
|
||||
}
|
||||
|
||||
if size.height.isFinite {
|
||||
heightConstraint?.constant = size.height
|
||||
heightConstraint?.isActive = true
|
||||
} else {
|
||||
heightConstraint?.isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
func setupCenterYOffset(centerYConstraint: NSLayoutConstraint?,
|
||||
topConstraint: NSLayoutConstraint?,
|
||||
bottomConstraint: NSLayoutConstraint?) {
|
||||
|
||||
let centerYOffset = centerOffset.vertical
|
||||
|
||||
if centerYOffset.isFinite {
|
||||
centerYConstraint?.constant = centerYOffset
|
||||
centerYConstraint?.isActive = true
|
||||
topConstraint?.isActive = false
|
||||
bottomConstraint?.isActive = false
|
||||
|
||||
} else {
|
||||
topConstraint?.constant = insets.top
|
||||
bottomConstraint?.constant = -insets.bottom
|
||||
centerYConstraint?.isActive = false
|
||||
topConstraint?.isActive = true
|
||||
bottomConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
func setupCenterXOffset(centerXConstraint: NSLayoutConstraint?,
|
||||
leadingConstraint: NSLayoutConstraint?,
|
||||
trailingConstraint: NSLayoutConstraint?) {
|
||||
|
||||
let centerXOffset = centerOffset.horizontal
|
||||
|
||||
if centerXOffset.isFinite {
|
||||
centerXConstraint?.constant = centerXOffset
|
||||
centerXConstraint?.isActive = true
|
||||
leadingConstraint?.isActive = false
|
||||
trailingConstraint?.isActive = false
|
||||
|
||||
} else {
|
||||
leadingConstraint?.constant = insets.left
|
||||
trailingConstraint?.constant = -insets.right
|
||||
centerXConstraint?.isActive = false
|
||||
leadingConstraint?.isActive = true
|
||||
trailingConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,55 +33,57 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
|
||||
// MARK: - Constraints
|
||||
|
||||
public var leadingViewLeadingToSuperviewConstraint: NSLayoutConstraint?
|
||||
public var leadingViewTopConstraint: NSLayoutConstraint?
|
||||
public var leadingViewBottomConstraint: NSLayoutConstraint?
|
||||
public var leadingViewCenterYConstraint: NSLayoutConstraint?
|
||||
public var leadingViewWidthConstraint: NSLayoutConstraint?
|
||||
public var leadingViewHeightConstraint: NSLayoutConstraint?
|
||||
public private(set) lazy var middleViewLeadingConstraint: NSLayoutConstraint = {
|
||||
middleView.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor)
|
||||
}()
|
||||
|
||||
public var middleViewLeadingToSuperViewConstraint: NSLayoutConstraint?
|
||||
public var middleViewLeadingConstraint: NSLayoutConstraint?
|
||||
public var middleViewTopConstraint: NSLayoutConstraint?
|
||||
public var middleViewTrailingToSuperViewConstraint: NSLayoutConstraint?
|
||||
public var middleViewBottomConstraint: NSLayoutConstraint?
|
||||
public var middleViewCenterYConstraint: NSLayoutConstraint?
|
||||
public var middleViewWidthConstraint: NSLayoutConstraint?
|
||||
public var middleViewHeightConstraint: NSLayoutConstraint?
|
||||
public private(set) lazy var leadingViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: leadingView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: middleViewLeadingConstraint,
|
||||
topConstraint: leadingView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: leadingView.bottomAnchor.constraint(equalTo: bottomAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: leadingView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: leadingView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: leadingView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: leadingView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
public var trailingViewLeadingToMiddleViewConstraint: NSLayoutConstraint?
|
||||
public var trailingViewTopConstraint: NSLayoutConstraint?
|
||||
public var trailingViewTrailingToSuperviewConstraint: NSLayoutConstraint?
|
||||
public var trailingViewBottomConstraint: NSLayoutConstraint?
|
||||
public var trailingViewCenterYConstraint: NSLayoutConstraint?
|
||||
public var trailingViewWidthConstraint: NSLayoutConstraint?
|
||||
public var trailingViewHeightConstraint: NSLayoutConstraint?
|
||||
public private(set) lazy var trailingViewLeadingToMiddleViewConstraint: NSLayoutConstraint = {
|
||||
trailingView.leadingAnchor.constraint(equalTo: middleView.trailingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var middleViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: middleViewLeadingConstraint,
|
||||
trailingConstraint: trailingViewLeadingToMiddleViewConstraint,
|
||||
topConstraint: middleView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: middleView.bottomAnchor.constraint(equalTo: bottomAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: middleView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: middleView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: middleView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: middleView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
public private(set) lazy var middleViewLeadingToSuperViewConstraint: NSLayoutConstraint = {
|
||||
middleView.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var middleViewTrailingToSuperViewConstraint: NSLayoutConstraint = {
|
||||
middleView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var trailingViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: trailingViewLeadingToMiddleViewConstraint,
|
||||
trailingConstraint: trailingView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: trailingView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: trailingView.bottomAnchor.constraint(equalTo: bottomAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: middleView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: trailingView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: trailingView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: trailingView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
||||
public var leadingViewConstraints: [NSLayoutConstraint?] {
|
||||
[
|
||||
leadingViewLeadingToSuperviewConstraint,
|
||||
leadingViewTopConstraint,
|
||||
leadingViewBottomConstraint,
|
||||
leadingViewCenterYConstraint,
|
||||
leadingViewHeightConstraint,
|
||||
leadingViewWidthConstraint
|
||||
]
|
||||
}
|
||||
|
||||
public var trailingViewConstraints: [NSLayoutConstraint?] {
|
||||
[
|
||||
trailingViewLeadingToMiddleViewConstraint,
|
||||
trailingViewTopConstraint,
|
||||
trailingViewBottomConstraint,
|
||||
trailingViewTrailingToSuperviewConstraint,
|
||||
trailingViewCenterYConstraint,
|
||||
trailingViewHeightConstraint,
|
||||
trailingViewWidthConstraint,
|
||||
]
|
||||
}
|
||||
|
||||
open var isLeadingViewHidden: Bool {
|
||||
leadingView.isHidden
|
||||
}
|
||||
|
|
@ -104,60 +106,8 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
[leadingView, middleView, trailingView]
|
||||
.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
|
||||
|
||||
leadingViewLeadingToSuperviewConstraint = leadingView.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
leadingViewTopConstraint = leadingView.topAnchor.constraint(equalTo: topAnchor)
|
||||
leadingViewBottomConstraint = leadingView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
leadingViewCenterYConstraint = leadingView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
leadingViewWidthConstraint = leadingView.widthAnchor.constraint(equalToConstant: .zero)
|
||||
leadingViewHeightConstraint = leadingView.heightAnchor.constraint(equalToConstant: .zero)
|
||||
|
||||
middleViewLeadingToSuperViewConstraint = middleView.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
middleViewLeadingConstraint = middleView.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor)
|
||||
middleViewCenterYConstraint = middleView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
middleViewTopConstraint = middleView.topAnchor.constraint(equalTo: topAnchor)
|
||||
middleViewTrailingToSuperViewConstraint = middleView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
middleViewBottomConstraint = middleView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
middleViewWidthConstraint = middleView.widthAnchor.constraint(equalToConstant: .zero)
|
||||
middleViewHeightConstraint = middleView.heightAnchor.constraint(equalToConstant: .zero)
|
||||
|
||||
trailingViewLeadingToMiddleViewConstraint = trailingView.leadingAnchor.constraint(equalTo: middleView.trailingAnchor)
|
||||
trailingViewTopConstraint = trailingView.topAnchor.constraint(equalTo: topAnchor)
|
||||
trailingViewTrailingToSuperviewConstraint = trailingView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
trailingViewBottomConstraint = trailingView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
trailingViewCenterYConstraint = trailingView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
trailingViewWidthConstraint = trailingView.widthAnchor.constraint(equalToConstant: .zero)
|
||||
trailingViewHeightConstraint = trailingView.heightAnchor.constraint(equalToConstant: .zero)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
leadingViewLeadingToSuperviewConstraint,
|
||||
leadingViewTopConstraint,
|
||||
leadingViewBottomConstraint,
|
||||
|
||||
middleViewLeadingConstraint,
|
||||
|
||||
trailingViewTopConstraint,
|
||||
trailingViewBottomConstraint,
|
||||
trailingViewLeadingToMiddleViewConstraint,
|
||||
trailingViewTrailingToSuperviewConstraint,
|
||||
].compactMap { $0 })
|
||||
|
||||
NSLayoutConstraint.deactivate([
|
||||
leadingViewCenterYConstraint,
|
||||
leadingViewWidthConstraint,
|
||||
leadingViewHeightConstraint,
|
||||
|
||||
middleViewLeadingToSuperViewConstraint,
|
||||
middleViewTopConstraint,
|
||||
middleViewTrailingToSuperViewConstraint,
|
||||
middleViewBottomConstraint,
|
||||
middleViewCenterYConstraint,
|
||||
middleViewWidthConstraint,
|
||||
middleViewHeightConstraint,
|
||||
|
||||
trailingViewCenterYConstraint,
|
||||
trailingViewHeightConstraint,
|
||||
trailingViewWidthConstraint,
|
||||
].compactMap { $0 })
|
||||
[leadingViewConstraints, middleViewConstraints, trailingViewConstraints]
|
||||
.forEach { $0.update(insets: .zero, size: .infinity, centerOffset: .nan) }
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
|
@ -171,8 +121,7 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
updateLeadingViewLayout(leadingViewLayout: appearance.leadingViewAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
|
||||
updateMiddleViewLayout(containerLayout: appearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
updateMiddleViewLayout(middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
|
||||
updateTrailingViewLayout(trailingViewLayout: appearance.trailingAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
|
|
@ -183,76 +132,59 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
private func updateLeadingViewLayout(leadingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
|
||||
let leadingToSuperviewContraint: NSLayoutConstraint?
|
||||
let leadingToSuperviewContraint: NSLayoutConstraint
|
||||
let leadingViewLeftInset: CGFloat
|
||||
|
||||
if isLeadingViewHidden {
|
||||
NSLayoutConstraint.deactivate(leadingViewConstraints.compactMap { $0 })
|
||||
middleViewLeadingToSuperViewConstraint?.isActive = true
|
||||
leadingViewConstraints.deactivate()
|
||||
middleViewLeadingToSuperViewConstraint.isActive = true
|
||||
|
||||
leadingToSuperviewContraint = middleViewLeadingToSuperViewConstraint
|
||||
leadingViewLeftInset = middleViewLayout.insets.left
|
||||
|
||||
} else {
|
||||
middleViewLeadingConstraint?.constant = leadingViewLayout.insets.right + middleViewLayout.insets.left
|
||||
leadingViewLeadingToSuperviewConstraint?.isActive = true
|
||||
middleViewLeadingConstraint?.isActive = true
|
||||
middleViewLeadingToSuperViewConstraint?.isActive = false
|
||||
middleViewLeadingConstraint.constant = leadingViewLayout.insets.right + middleViewLayout.insets.left
|
||||
leadingViewConstraints.edgeConstraints.leadingConstraint.isActive = true
|
||||
middleViewConstraints.edgeConstraints.leadingConstraint.isActive = true
|
||||
middleViewLeadingToSuperViewConstraint.isActive = false
|
||||
|
||||
leadingViewLayout.setupCenterYOffset(centerYConstraint: leadingViewCenterYConstraint,
|
||||
topConstraint: leadingViewTopConstraint,
|
||||
bottomConstraint: leadingViewBottomConstraint)
|
||||
leadingViewConstraints.update(from: leadingViewLayout)
|
||||
|
||||
leadingViewLayout.setupSize(widthConstraint: leadingViewWidthConstraint,
|
||||
heightConstraint: leadingViewHeightConstraint)
|
||||
|
||||
leadingToSuperviewContraint = leadingViewLeadingToSuperviewConstraint
|
||||
leadingToSuperviewContraint = leadingViewConstraints.edgeConstraints.leadingConstraint
|
||||
leadingViewLeftInset = leadingViewLayout.insets.left
|
||||
}
|
||||
|
||||
leadingToSuperviewContraint?.constant = leadingViewLeftInset
|
||||
leadingToSuperviewContraint.constant = leadingViewLeftInset
|
||||
}
|
||||
|
||||
private func updateMiddleViewLayout(containerLayout: ViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
|
||||
middleViewLayout.setupCenterYOffset(centerYConstraint: middleViewCenterYConstraint,
|
||||
topConstraint: middleViewTopConstraint,
|
||||
bottomConstraint: middleViewBottomConstraint)
|
||||
|
||||
middleViewLayout.setupSize(widthConstraint: middleViewWidthConstraint,
|
||||
heightConstraint: middleViewHeightConstraint)
|
||||
private func updateMiddleViewLayout(middleViewLayout: WrappedViewLayout) {
|
||||
middleViewConstraints.update(from: middleViewLayout)
|
||||
}
|
||||
|
||||
private func updateTrailingViewLayout(trailingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
|
||||
let trailingToSuperviewConstraint: NSLayoutConstraint?
|
||||
let trailingToSuperviewConstraint: NSLayoutConstraint
|
||||
let trailingViewRightInset: CGFloat
|
||||
|
||||
if isTrailingViewHidden {
|
||||
NSLayoutConstraint.deactivate(trailingViewConstraints.compactMap { $0 })
|
||||
middleViewTrailingToSuperViewConstraint?.isActive = true
|
||||
trailingViewConstraints.deactivate()
|
||||
middleViewTrailingToSuperViewConstraint.isActive = true
|
||||
|
||||
trailingToSuperviewConstraint = middleViewTrailingToSuperViewConstraint
|
||||
trailingViewRightInset = middleViewLayout.insets.right
|
||||
|
||||
} else {
|
||||
trailingViewLeadingToMiddleViewConstraint?.constant = middleViewLayout.insets.right + trailingViewLayout.insets.left
|
||||
trailingViewLeadingToMiddleViewConstraint?.isActive = true
|
||||
middleViewTrailingToSuperViewConstraint?.isActive = false
|
||||
trailingViewLeadingToMiddleViewConstraint.constant = middleViewLayout.insets.right + trailingViewLayout.insets.left
|
||||
trailingViewLeadingToMiddleViewConstraint.isActive = true
|
||||
middleViewTrailingToSuperViewConstraint.isActive = false
|
||||
|
||||
trailingViewLayout.setupCenterYOffset(centerYConstraint: trailingViewCenterYConstraint,
|
||||
topConstraint: trailingViewTopConstraint,
|
||||
bottomConstraint: trailingViewBottomConstraint)
|
||||
trailingViewConstraints.update(from: trailingViewLayout)
|
||||
|
||||
trailingViewLayout.setupSize(widthConstraint: trailingViewWidthConstraint,
|
||||
heightConstraint: trailingViewWidthConstraint)
|
||||
|
||||
trailingToSuperviewConstraint = trailingViewTrailingToSuperviewConstraint
|
||||
trailingToSuperviewConstraint = trailingViewConstraints.edgeConstraints.trailingConstraint
|
||||
trailingViewRightInset = trailingViewLayout.insets.right
|
||||
}
|
||||
|
||||
trailingToSuperviewConstraint?.constant = -trailingViewRightInset
|
||||
trailingToSuperviewConstraint.constant = -trailingViewRightInset
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,18 +24,43 @@ import TIUIKitCore
|
|||
import UIKit
|
||||
|
||||
open class BasePlaceholderImageView<Placeholder: UIView>: UIImageView,
|
||||
InitializableViewProtocol {
|
||||
InitializableViewProtocol,
|
||||
WrappedViewHolder {
|
||||
|
||||
public let placeholderView = Placeholder()
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
public var placeholderConstraints: SubviewConstraints?
|
||||
// MARK: - WrappedViewHolder
|
||||
|
||||
public private(set) lazy var wrappedView = Placeholder()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
public lazy var placeholderConstraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
open override var image: UIImage? {
|
||||
get {
|
||||
super.image
|
||||
}
|
||||
set {
|
||||
placeholderView.isHidden = newValue != nil
|
||||
wrappedView.isHidden = newValue != nil
|
||||
|
||||
super.image = newValue
|
||||
}
|
||||
|
|
@ -61,29 +86,22 @@ open class BasePlaceholderImageView<Placeholder: UIView>: UIImageView,
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableViewProtocol
|
||||
|
||||
open func addViews() {
|
||||
addSubview(placeholderView)
|
||||
addSubview(wrappedView)
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
placeholderView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let placeholderConstraints = SubviewConstraints(
|
||||
centerXConstraint: placeholderView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: placeholderView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
leadingConstraint: placeholderView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
topConstraint: placeholderView.topAnchor.constraint(equalTo: topAnchor),
|
||||
trailingConstraint: placeholderView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
bottomConstraint: placeholderView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
widthConstraint: placeholderView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: placeholderView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
NSLayoutConstraint.activate(placeholderConstraints.constraints)
|
||||
NSLayoutConstraint.deactivate(placeholderConstraints.sizeConstraints)
|
||||
|
||||
self.placeholderConstraints = placeholderConstraints
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
|
|
@ -103,22 +121,7 @@ open class BasePlaceholderImageView<Placeholder: UIView>: UIImageView,
|
|||
open func configureBasePlaceholder(appearance: BaseWrappedViewHolderAppearance<UIView.DefaultWrappedAppearance, some WrappedViewLayout>) {
|
||||
configureUIView(appearance: appearance)
|
||||
|
||||
placeholderView.configureUIView(appearance: appearance.subviewAppearance)
|
||||
configurePlaceholderLayout(layout: appearance.subviewAppearance.layout)
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func configurePlaceholderLayout(layout: some WrappedViewLayout) {
|
||||
layout.setupSize(widthConstraint: placeholderConstraints?.widthConstraint,
|
||||
heightConstraint: placeholderConstraints?.heightConstraint)
|
||||
|
||||
layout.setupCenterYOffset(centerYConstraint: placeholderConstraints?.centerYConstraint,
|
||||
topConstraint: placeholderConstraints?.topConstraint,
|
||||
bottomConstraint: placeholderConstraints?.bottomConstraint)
|
||||
|
||||
layout.setupCenterXOffset(centerXConstraint: placeholderConstraints?.centerXConstraint,
|
||||
leadingConstraint: placeholderConstraints?.leadingConstraint,
|
||||
trailingConstraint: placeholderConstraints?.trailingConstraint)
|
||||
wrappedView.configureUIView(appearance: appearance.subviewAppearance)
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,46 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
public let textView = DefaultTitleSubtitleView()
|
||||
public let controlsStackView = UIStackView()
|
||||
|
||||
public var imageViewConstraints: SubviewConstraints?
|
||||
public var textViewConstraints: SubviewConstraints?
|
||||
public var controlsViewConstraints: SubviewConstraints?
|
||||
public private(set) lazy var imageViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: imageView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: imageView.bottomAnchor.constraint(equalTo: textView.topAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: imageView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: imageView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: imageView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
public private(set) lazy var textViewTopToSuperviewTopConstraint: NSLayoutConstraint = {
|
||||
textView.topAnchor.constraint(equalTo: topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var textViewBottomToSuperviewBottomConstraint: NSLayoutConstraint = {
|
||||
textView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var textViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: textView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: textView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: textView.topAnchor.constraint(equalTo: imageView.bottomAnchor),
|
||||
bottomConstraint: textView.bottomAnchor.constraint(lessThanOrEqualTo: controlsStackView.topAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: textView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: textView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: textView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: textView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
public private(set) lazy var controlsViewConstraints: SubviewConstraints = {
|
||||
SubviewConstraints(edgeConstraints: EdgeConstraints(leadingConstraint: controlsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: controlsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: controlsStackView.topAnchor.constraint(equalTo: textView.bottomAnchor),
|
||||
bottomConstraint: controlsStackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor)),
|
||||
centerConstraints: CenterConstraints(centerXConstraint: controlsStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: controlsStackView.centerYAnchor.constraint(equalTo: centerYAnchor)),
|
||||
sizeConstraints: SizeConstraints(widthConstraint: controlsStackView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: controlsStackView.heightAnchor.constraint(equalToConstant: .zero)))
|
||||
}()
|
||||
|
||||
public var keyboardDidShownObserver: NSObjectProtocol?
|
||||
public var keyboardDidHiddenObserver: NSObjectProtocol?
|
||||
|
|
@ -71,42 +108,15 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
[imageView, textView, controlsStackView]
|
||||
.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
|
||||
|
||||
imageViewConstraints = .init(
|
||||
centerXConstraint: imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
leadingConstraint: imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
topConstraint: imageView.topAnchor.constraint(equalTo: topAnchor),
|
||||
trailingConstraint: imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
bottomConstraint: imageView.bottomAnchor.constraint(equalTo: textView.topAnchor),
|
||||
widthConstraint: imageView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: imageView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
textViewConstraints = .init(
|
||||
centerXConstraint: textView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: textView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
leadingConstraint: textView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
topConstraint: textView.topAnchor.constraint(equalTo: imageView.bottomAnchor),
|
||||
trailingConstraint: textView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
bottomConstraint: textView.bottomAnchor.constraint(lessThanOrEqualTo: controlsStackView.topAnchor))
|
||||
|
||||
controlsViewConstraints = .init(
|
||||
centerXConstraint: controlsStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
leadingConstraint: controlsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
topConstraint: controlsStackView.topAnchor.constraint(equalTo: textView.bottomAnchor),
|
||||
trailingConstraint: controlsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
bottomConstraint: controlsStackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
|
||||
widthConstraint: controlsStackView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: controlsStackView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
NSLayoutConstraint.activate(
|
||||
(imageViewConstraints?.constraints ?? [])
|
||||
+ (textViewConstraints?.constraints ?? [])
|
||||
+ (controlsViewConstraints?.constraints ?? [])
|
||||
imageViewConstraints.allConstraints
|
||||
+ textViewConstraints.allConstraints
|
||||
+ controlsViewConstraints.allConstraints
|
||||
)
|
||||
|
||||
NSLayoutConstraint.deactivate(
|
||||
(imageViewConstraints?.sizeConstraints ?? [])
|
||||
+ (controlsViewConstraints?.sizeConstraints ?? [])
|
||||
imageViewConstraints.sizeConstraints.allConstraints
|
||||
+ controlsViewConstraints.sizeConstraints.allConstraints
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -179,59 +189,42 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
let multiplier = isKeyboardHidden ? 1.0 : -1.0
|
||||
|
||||
if let height = getKeyboardHeight(notification) {
|
||||
controlsViewConstraints?.bottomConstraint?.constant = multiplier * height / 2
|
||||
controlsViewConstraints.edgeConstraints.bottomConstraint.constant = multiplier * height / 2
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func configureImageViewLayout(layout: WrappedViewLayout) {
|
||||
guard !isImageViewHidden, let imageViewConstraints = imageViewConstraints else {
|
||||
NSLayoutConstraint.deactivate(imageViewConstraints?.constraints ?? [])
|
||||
guard !isImageViewHidden else {
|
||||
imageViewConstraints.deactivate()
|
||||
return
|
||||
}
|
||||
|
||||
configureLayout(layout: layout, constraints: imageViewConstraints)
|
||||
imageViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configureTextViewLayout(layout: WrappedViewLayout) {
|
||||
if isImageViewHidden {
|
||||
self.textViewConstraints?.topConstraint?.isActive = false
|
||||
self.textViewConstraints?.topConstraint = textView.topAnchor.constraint(equalTo: topAnchor)
|
||||
}
|
||||
textViewConstraints.edgeConstraints.topConstraint.isActive = !isImageViewHidden
|
||||
textViewTopToSuperviewTopConstraint.isActive = isImageViewHidden
|
||||
|
||||
if isControlsViewHidden {
|
||||
self.textViewConstraints?.bottomConstraint?.isActive = false
|
||||
self.textViewConstraints?.bottomConstraint = textView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor)
|
||||
}
|
||||
textViewConstraints.edgeConstraints.bottomConstraint.isActive = !isControlsViewHidden
|
||||
textViewBottomToSuperviewBottomConstraint.isActive = isControlsViewHidden
|
||||
|
||||
if let textViewConstraints = textViewConstraints {
|
||||
configureLayout(layout: layout, constraints: textViewConstraints)
|
||||
}
|
||||
textViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configureControlsViewLayout(layout: SpacedWrappedViewLayout) {
|
||||
guard !isControlsViewHidden, let controlsViewConstraints = controlsViewConstraints else {
|
||||
NSLayoutConstraint.deactivate(controlsViewConstraints?.constraints ?? [])
|
||||
guard !isControlsViewHidden else {
|
||||
controlsViewConstraints.deactivate()
|
||||
return
|
||||
}
|
||||
|
||||
configureLayout(layout: layout, constraints: controlsViewConstraints)
|
||||
controlsViewConstraints.update(from: layout)
|
||||
|
||||
controlsStackView.spacing = layout.spacing
|
||||
}
|
||||
|
||||
private func configureLayout(layout: WrappedViewLayout, constraints: SubviewConstraints) {
|
||||
layout.setupSize(widthConstraint: constraints.widthConstraint, heightConstraint: constraints.heightConstraint)
|
||||
|
||||
layout.setupCenterYOffset(centerYConstraint: constraints.centerYConstraint,
|
||||
topConstraint: constraints.topConstraint,
|
||||
bottomConstraint: constraints.bottomConstraint)
|
||||
|
||||
layout.setupCenterXOffset(centerXConstraint: constraints.centerXConstraint,
|
||||
leadingConstraint: constraints.leadingConstraint,
|
||||
trailingConstraint: constraints.trailingConstraint)
|
||||
}
|
||||
|
||||
private func getKeyboardHeight(_ notification: Notification) -> CGFloat? {
|
||||
guard let userInfo = notification.userInfo else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ final public class DefaultPlaceholderImageView: BasePlaceholderImageView<UIImage
|
|||
|
||||
public var placeholderImage: UIImage? {
|
||||
get {
|
||||
placeholderView.image
|
||||
wrappedView.image
|
||||
}
|
||||
set {
|
||||
placeholderView.image = newValue
|
||||
wrappedView.image = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ final public class DefaultPlaceholderImageView: BasePlaceholderImageView<UIImage
|
|||
public override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
placeholderView.contentMode = .scaleAspectFit
|
||||
wrappedView.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
// MARK: - AppearanceConfigurable
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ public final class DefaultPlaceholderView: BasePlaceholderView<UIImageView>, App
|
|||
|
||||
super.applyBaseStyle(style: style)
|
||||
|
||||
configureImageSizeConstraints(size: imageView.image?.size ?? .zero)
|
||||
guard let image = imageView.image else {
|
||||
return
|
||||
}
|
||||
|
||||
imageViewConstraints.sizeConstraints.update(from: image.size)
|
||||
}
|
||||
|
||||
// MARK: - AppearanceConfigurable
|
||||
|
|
@ -42,22 +46,6 @@ public final class DefaultPlaceholderView: BasePlaceholderView<UIImageView>, App
|
|||
public func configure(appearance: Appearance) {
|
||||
configureAppearance(appearance: appearance)
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func configureImageSizeConstraints(size: CGSize) {
|
||||
guard size != .zero else {
|
||||
return
|
||||
}
|
||||
|
||||
if size.height.isFinite, size.height > .zero {
|
||||
imageViewConstraints?.heightConstraint?.constant = size.height
|
||||
}
|
||||
|
||||
if size.width.isFinite, size.width > .zero {
|
||||
imageViewConstraints?.widthConstraint?.constant = size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Default appearance model
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class BaseButtonViewModel {
|
||||
public var title: String
|
||||
public var image: UIImage?
|
||||
public var backgroundImage: UIImage?
|
||||
|
||||
public init(title: String,
|
||||
image: UIImage? = nil,
|
||||
backgroundImage: UIImage? = nil) {
|
||||
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.backgroundImage = backgroundImage
|
||||
}
|
||||
}
|
||||
|
|
@ -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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ import TISwiftUtils
|
|||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class StatefulButton: UIButton {
|
||||
open class StatefulButton: BaseInitializableButton {
|
||||
|
||||
public enum ActivityIndicatorPosition {
|
||||
case center
|
||||
|
|
@ -75,7 +75,7 @@ open class StatefulButton: UIButton {
|
|||
|
||||
// MARK: - Background
|
||||
|
||||
private var stateAppearance: StateAppearance = [:] {
|
||||
var stateAppearance: StateAppearance = [:] {
|
||||
didSet {
|
||||
updateAppearance()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,33 +20,30 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import class UIKit.NSLayoutConstraint
|
||||
import UIKit
|
||||
|
||||
public struct SubviewConstraints {
|
||||
public var centerXConstraint: NSLayoutConstraint?
|
||||
public var centerYConstraint: NSLayoutConstraint?
|
||||
public var leadingConstraint: NSLayoutConstraint?
|
||||
public var topConstraint: NSLayoutConstraint?
|
||||
public var trailingConstraint: NSLayoutConstraint?
|
||||
public var bottomConstraint: NSLayoutConstraint?
|
||||
public var widthConstraint: NSLayoutConstraint?
|
||||
public var heightConstraint: NSLayoutConstraint?
|
||||
public struct CenterConstraints: ConstraintsSet {
|
||||
public let centerXConstraint: NSLayoutConstraint
|
||||
public let centerYConstraint: NSLayoutConstraint
|
||||
|
||||
public var constraints: [NSLayoutConstraint] {
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
public var allConstraints: [NSLayoutConstraint] {
|
||||
[
|
||||
centerXConstraint,
|
||||
leadingConstraint,
|
||||
topConstraint,
|
||||
trailingConstraint,
|
||||
bottomConstraint,
|
||||
widthConstraint,
|
||||
heightConstraint
|
||||
centerYConstraint
|
||||
]
|
||||
.compactMap { $0 }
|
||||
}
|
||||
|
||||
public var sizeConstraints: [NSLayoutConstraint] {
|
||||
[widthConstraint, heightConstraint]
|
||||
.compactMap { $0 }
|
||||
public init(centerXConstraint: NSLayoutConstraint,
|
||||
centerYConstraint: NSLayoutConstraint) {
|
||||
|
||||
self.centerXConstraint = centerXConstraint
|
||||
self.centerYConstraint = centerYConstraint
|
||||
}
|
||||
|
||||
public func update(offset: UIOffset) {
|
||||
centerXConstraint.setActiveConstantOrDeactivate(constant: offset.horizontal)
|
||||
centerYConstraint.setActiveConstantOrDeactivate(constant: offset.vertical)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit.NSLayoutConstraint
|
||||
|
||||
public protocol ConstraintsSet {
|
||||
var allConstraints: [NSLayoutConstraint] { get }
|
||||
}
|
||||
|
||||
public extension ConstraintsSet {
|
||||
func activate() {
|
||||
NSLayoutConstraint.activate(allConstraints)
|
||||
}
|
||||
|
||||
func deactivate() {
|
||||
NSLayoutConstraint.deactivate(allConstraints)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,12 +22,14 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public struct EdgeConstraints {
|
||||
public struct EdgeConstraints: ConstraintsSet {
|
||||
public let leadingConstraint: NSLayoutConstraint
|
||||
public let trailingConstraint: NSLayoutConstraint
|
||||
public let topConstraint: NSLayoutConstraint
|
||||
public let bottomConstraint: NSLayoutConstraint
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
public var allConstraints: [NSLayoutConstraint] {
|
||||
[
|
||||
leadingConstraint,
|
||||
|
|
@ -37,6 +39,20 @@ public struct EdgeConstraints {
|
|||
]
|
||||
}
|
||||
|
||||
public var horizontal: [NSLayoutConstraint] {
|
||||
[
|
||||
leadingConstraint,
|
||||
trailingConstraint
|
||||
]
|
||||
}
|
||||
|
||||
public var vertical: [NSLayoutConstraint] {
|
||||
[
|
||||
topConstraint,
|
||||
bottomConstraint
|
||||
]
|
||||
}
|
||||
|
||||
public init(leadingConstraint: NSLayoutConstraint,
|
||||
trailingConstraint: NSLayoutConstraint,
|
||||
topConstraint: NSLayoutConstraint,
|
||||
|
|
@ -48,18 +64,10 @@ public struct EdgeConstraints {
|
|||
self.bottomConstraint = bottomConstraint
|
||||
}
|
||||
|
||||
public func activate() {
|
||||
NSLayoutConstraint.activate(allConstraints)
|
||||
}
|
||||
|
||||
public func deactivate() {
|
||||
NSLayoutConstraint.deactivate(allConstraints)
|
||||
}
|
||||
|
||||
public func update(from insets: UIEdgeInsets) {
|
||||
leadingConstraint.constant = insets.left
|
||||
trailingConstraint.constant = -insets.right
|
||||
topConstraint.constant = insets.top
|
||||
bottomConstraint.constant = -insets.bottom
|
||||
leadingConstraint.setActiveConstantOrDeactivate(constant: insets.left)
|
||||
trailingConstraint.setActiveConstantOrDeactivate(constant: -insets.right)
|
||||
topConstraint.setActiveConstantOrDeactivate(constant: insets.top)
|
||||
bottomConstraint.setActiveConstantOrDeactivate(constant: -insets.bottom)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// 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.NSLayoutConstraint
|
||||
|
||||
extension NSLayoutConstraint {
|
||||
func setActiveConstantOrDeactivate(constant: CGFloat) {
|
||||
if constant.isFinite {
|
||||
self.constant = constant
|
||||
isActive = true
|
||||
} else {
|
||||
isActive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public struct SizeConstraints: ConstraintsSet {
|
||||
public let widthConstraint: NSLayoutConstraint
|
||||
public let heightConstraint: NSLayoutConstraint
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
public var allConstraints: [NSLayoutConstraint] {
|
||||
[
|
||||
widthConstraint,
|
||||
heightConstraint
|
||||
]
|
||||
}
|
||||
|
||||
public init(widthConstraint: NSLayoutConstraint,
|
||||
heightConstraint: NSLayoutConstraint) {
|
||||
|
||||
self.widthConstraint = widthConstraint
|
||||
self.heightConstraint = heightConstraint
|
||||
}
|
||||
|
||||
public func update(from size: CGSize) {
|
||||
widthConstraint.setActiveConstantOrDeactivate(constant: size.width)
|
||||
heightConstraint.setActiveConstantOrDeactivate(constant: size.height)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
public struct SubviewConstraints: ConstraintsSet {
|
||||
public var edgeConstraints: EdgeConstraints
|
||||
public var centerConstraints: CenterConstraints
|
||||
public var sizeConstraints: SizeConstraints
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
public var allConstraints: [NSLayoutConstraint] {
|
||||
edgeConstraints.allConstraints + centerConstraints.allConstraints + sizeConstraints.allConstraints
|
||||
}
|
||||
|
||||
public init(edgeConstraints: EdgeConstraints,
|
||||
centerConstraints: CenterConstraints,
|
||||
sizeConstraints: SizeConstraints) {
|
||||
|
||||
self.edgeConstraints = edgeConstraints
|
||||
self.centerConstraints = centerConstraints
|
||||
self.sizeConstraints = sizeConstraints
|
||||
}
|
||||
|
||||
func update(insets: UIEdgeInsets, size: CGSize, centerOffset: UIOffset) {
|
||||
centerConstraints.update(offset: centerOffset)
|
||||
sizeConstraints.update(from: size)
|
||||
edgeConstraints.update(from: insets)
|
||||
|
||||
for verticalConstraint in edgeConstraints.vertical {
|
||||
verticalConstraint.isActive = !centerOffset.vertical.isFinite
|
||||
}
|
||||
|
||||
for horizontalConstraint in edgeConstraints.horizontal {
|
||||
horizontalConstraint.isActive = !centerOffset.horizontal.isFinite
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WrappedViewLayout shortcut
|
||||
|
||||
func update(from layout: some WrappedViewLayout) {
|
||||
update(insets: layout.insets,
|
||||
size: layout.size,
|
||||
centerOffset: layout.centerOffset)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,16 +24,38 @@ import UIKit
|
|||
|
||||
open class CollectionTableViewCell<CollectionView: UICollectionView>: ContainerTableViewCell<CollectionView> {
|
||||
|
||||
private var contentSizeObservation: NSKeyValueObservation?
|
||||
|
||||
open override func bindViews() {
|
||||
super.bindViews()
|
||||
|
||||
if #available(iOS 16, *) {
|
||||
contentSizeObservation = wrappedView.observe(\.contentSize,
|
||||
options: [.new]) { collectionView, change in
|
||||
|
||||
if let contentSize = change.newValue, !contentSize.equalTo(collectionView.bounds.size) {
|
||||
self.invalidateIntrinsicContentSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIView Overrides
|
||||
|
||||
open override func systemLayoutSizeFitting(_ targetSize: CGSize,
|
||||
withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority,
|
||||
verticalFittingPriority: UILayoutPriority) -> CGSize {
|
||||
|
||||
if #unavailable(iOS 16.0.0) {
|
||||
wrappedView.setNeedsLayout()
|
||||
wrappedView.layoutIfNeeded()
|
||||
}
|
||||
|
||||
let cachedCollectionFrame = wrappedView.frame
|
||||
wrappedView.frame.size.width = targetSize.width - contentInsets.left - contentInsets.right
|
||||
let collectionContentHeight = wrappedView.collectionViewLayout.collectionViewContentSize.height
|
||||
wrappedView.frame = cachedCollectionFrame
|
||||
|
||||
return CGSize(width: targetSize.width,
|
||||
height: collectionContentHeight + contentInsets.top + contentInsets.bottom)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,34 @@ import UIKit
|
|||
import TIUIKitCore
|
||||
|
||||
open class ContainerCollectionViewCell<View: UIView>: UICollectionViewCell, InitializableViewProtocol, WrappedViewHolder {
|
||||
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
// MARK: - WrappedViewHolder
|
||||
|
||||
public private(set) lazy var wrappedView = createView()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
contentEdgeConstraints?.update(from: contentInsets)
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private var contentEdgeConstraints: EdgeConstraints?
|
||||
public var contentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var subviewContraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
|
@ -50,6 +67,14 @@ open class ContainerCollectionViewCell<View: UIView>: UICollectionViewCell, Init
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
|
|
@ -61,7 +86,7 @@ open class ContainerCollectionViewCell<View: UIView>: UICollectionViewCell, Init
|
|||
}
|
||||
|
||||
open func configureLayout() {
|
||||
contentEdgeConstraints = configureWrappedViewLayout()
|
||||
subviewContraints.edgeConstraints.activate()
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
|
|
|
|||
|
|
@ -24,17 +24,32 @@ import UIKit
|
|||
import TIUIKitCore
|
||||
|
||||
open class ContainerTableViewCell<View: UIView>: BaseInitializableCell, WrappedViewHolder {
|
||||
|
||||
// MARK: - WrappedViewHolder
|
||||
|
||||
public private(set) lazy var wrappedView = createView()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
contentEdgeConstraints?.update(from: contentInsets)
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private var contentEdgeConstraints: EdgeConstraints?
|
||||
public var contentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var subviewContraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
|
|
@ -44,10 +59,10 @@ open class ContainerTableViewCell<View: UIView>: BaseInitializableCell, WrappedV
|
|||
contentView.addSubview(wrappedView)
|
||||
}
|
||||
|
||||
override open func configureLayout() {
|
||||
open override func configureLayout() {
|
||||
super.configureLayout()
|
||||
|
||||
contentEdgeConstraints = configureWrappedViewLayout()
|
||||
subviewContraints.edgeConstraints.activate()
|
||||
}
|
||||
|
||||
open func createView() -> View {
|
||||
|
|
@ -57,7 +72,7 @@ open class ContainerTableViewCell<View: UIView>: BaseInitializableCell, WrappedV
|
|||
// MARK: - Open methods
|
||||
|
||||
public func configureContainerTableViewCell(appearance: BaseWrappedViewHolderAppearance<some WrappedViewAppearance, some ViewLayout>) {
|
||||
contentInsets = appearance.subviewAppearance.layout.insets
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
configureUIView(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,15 +25,29 @@ import UIKit
|
|||
|
||||
public final class ContainerView<View: UIView>: BaseInitializableView, WrappedViewHolder {
|
||||
|
||||
public var wrappedView = View()
|
||||
public private(set) lazy var wrappedView = View()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
contentEdgeConstraints?.update(from: contentInsets)
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private var contentEdgeConstraints: EdgeConstraints?
|
||||
public var contentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var subviewContraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
|
|
@ -46,14 +60,13 @@ public final class ContainerView<View: UIView>: BaseInitializableView, WrappedVi
|
|||
public override func configureLayout() {
|
||||
super.configureLayout()
|
||||
|
||||
contentEdgeConstraints = configureWrappedViewLayout()
|
||||
subviewContraints.edgeConstraints.activate()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ConfigurableView
|
||||
|
||||
extension ContainerView: ConfigurableView where View: ConfigurableView {
|
||||
|
||||
public func configure(with viewModel: View.ViewModelType) {
|
||||
wrappedView.configure(with: viewModel)
|
||||
}
|
||||
|
|
@ -69,6 +82,6 @@ extension ContainerView: AppearanceConfigurable where View: AppearanceConfigurab
|
|||
public func configure(appearance: Appearance) {
|
||||
wrappedView.configure(appearance: appearance.subviewAppearance)
|
||||
configureUIView(appearance: appearance)
|
||||
contentInsets = appearance.subviewAppearance.layout.insets
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,33 @@ import UIKit
|
|||
import TIUIKitCore
|
||||
|
||||
open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableView, InitializableViewProtocol, WrappedViewHolder {
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
// MARK: - WrappedViewHolder
|
||||
|
||||
public private(set) lazy var wrappedView = createView()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
contentEdgeConstraints?.update(from: contentInsets)
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private var contentEdgeConstraints: EdgeConstraints?
|
||||
public var contentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var subviewContraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
|
|
@ -50,6 +66,14 @@ open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableVi
|
|||
initializeView()
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableView
|
||||
|
||||
open func addViews() {
|
||||
|
|
@ -61,7 +85,7 @@ open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableVi
|
|||
}
|
||||
|
||||
open func configureLayout() {
|
||||
contentEdgeConstraints = configureWrappedViewLayout()
|
||||
subviewContraints.edgeConstraints.activate()
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,16 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit.UICollectionView
|
||||
|
||||
public extension WrappableView {
|
||||
typealias InContainerView = ContainerView<Self>
|
||||
typealias InTableCell = ContainerTableViewCell<Self>
|
||||
typealias InSeparatableTableCell = ContainerSeparatorTableViewCell<Self>
|
||||
|
||||
typealias InCollectionCell = ContainerCollectionViewCell<Self>
|
||||
}
|
||||
|
||||
public extension WrappableView where Self: UICollectionView {
|
||||
typealias InCollectionTableCell = CollectionTableViewCell<Self>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,30 +21,61 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
public protocol WrappedViewHolder {
|
||||
public protocol WrappedViewHolder: AnyObject {
|
||||
associatedtype View: UIView
|
||||
|
||||
var wrappedView: View { get }
|
||||
var contentView: UIView { get }
|
||||
var contentInsets: UIEdgeInsets { get set }
|
||||
var contentSize: CGSize { get set }
|
||||
var contentCenterOffset: UIOffset { get set }
|
||||
}
|
||||
|
||||
public extension WrappedViewHolder {
|
||||
func wrappedViewConstraints() -> EdgeConstraints {
|
||||
.init(leadingConstraint: wrappedView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
trailingConstraint: wrappedView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
topConstraint: wrappedView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
bottomConstraint: wrappedView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor))
|
||||
func wrappedViewEdgeConstraints() -> EdgeConstraints {
|
||||
EdgeConstraints(leadingConstraint: wrappedView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
trailingConstraint: wrappedView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
topConstraint: wrappedView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
bottomConstraint: wrappedView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor))
|
||||
}
|
||||
|
||||
func configureWrappedViewLayout() -> EdgeConstraints {
|
||||
func wrappedViewSizeConstraints() -> SizeConstraints {
|
||||
SizeConstraints(widthConstraint: wrappedView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: wrappedView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
}
|
||||
|
||||
func wrappedViewCenterConstraints() -> CenterConstraints {
|
||||
CenterConstraints(centerXConstraint: wrappedView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
||||
centerYConstraint: wrappedView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor))
|
||||
}
|
||||
|
||||
func configureWrappedViewLayout() -> SubviewConstraints {
|
||||
wrappedView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let contentEdgeConstraints = wrappedViewConstraints()
|
||||
let contentEdgeConstraints = wrappedViewEdgeConstraints()
|
||||
contentEdgeConstraints.activate()
|
||||
|
||||
return contentEdgeConstraints
|
||||
return SubviewConstraints(edgeConstraints: contentEdgeConstraints,
|
||||
centerConstraints: wrappedViewCenterConstraints(),
|
||||
sizeConstraints: wrappedViewSizeConstraints())
|
||||
}
|
||||
|
||||
// MARK: - SubviewConstraints shortcut
|
||||
|
||||
func update(subviewConstraints: SubviewConstraints) {
|
||||
subviewConstraints.update(insets: contentInsets,
|
||||
size: contentSize,
|
||||
centerOffset: contentCenterOffset)
|
||||
}
|
||||
|
||||
// MARK: - WrappedViewLayout shortcut
|
||||
|
||||
func updateContentLayout(from layout: some WrappedViewLayout) {
|
||||
contentInsets = layout.insets
|
||||
contentSize = layout.size
|
||||
contentCenterOffset = layout.centerOffset
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,10 +307,10 @@ extension DefaultPlaceholderImageView: Skeletonable {
|
|||
public func skeletonsChangedState(_ state: SkeletonsState) {
|
||||
switch state {
|
||||
case .shown:
|
||||
placeholderView.isHidden = false
|
||||
wrappedView.isHidden = false
|
||||
|
||||
case .hidden:
|
||||
placeholderView.isHidden = true
|
||||
wrappedView.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIElements'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ public protocol SpacedWrappedViewLayout: WrappedViewLayout {
|
|||
var spacing: CGFloat { get }
|
||||
}
|
||||
|
||||
public protocol StackLayout: SpacedWrappedViewLayout {
|
||||
var axis: NSLayoutConstraint.Axis { get }
|
||||
var distribution: UIStackView.Distribution { get }
|
||||
var alignment: UIStackView.Alignment { get }
|
||||
}
|
||||
|
||||
// MARK: - Creation methods
|
||||
|
||||
extension ViewLayout {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,18 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit.UIView
|
||||
|
||||
public protocol ConfigurableView {
|
||||
associatedtype ViewModelType
|
||||
|
||||
func configure(with _: ViewModelType)
|
||||
}
|
||||
|
||||
public extension ConfigurableView where Self: UIView {
|
||||
init(viewModel: ViewModelType) {
|
||||
self.init()
|
||||
|
||||
self.configure(with: viewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,5 +28,9 @@ public extension InitializableViewProtocol {
|
|||
bindViews()
|
||||
configureAppearance()
|
||||
localize()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidInitialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@
|
|||
//
|
||||
|
||||
public protocol InitializableViewController: InitializableViewProtocol {
|
||||
|
||||
func configureBarButtons()
|
||||
}
|
||||
|
||||
public extension InitializableViewController {
|
||||
|
||||
@available(*, unavailable, message: "Use initializeController for UIViewController instead!")
|
||||
func initializeView() {
|
||||
assertionFailure("Use \(String(describing: initializeController)) for UIViewController instead!")
|
||||
}
|
||||
|
|
@ -38,5 +39,9 @@ public extension InitializableViewController {
|
|||
configureBarButtons()
|
||||
localize()
|
||||
bindViews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidInitialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@
|
|||
/// Protocol with methods that should be called in constructor methods of view.
|
||||
public protocol InitializableViewProtocol {
|
||||
|
||||
/// Main method that should call other methods in particular order.
|
||||
func initializeView()
|
||||
var callbacks: [ViewCallbacks] { get set }
|
||||
|
||||
/// Method for adding views to current view.
|
||||
func addViews()
|
||||
|
|
|
|||
|
|
@ -152,20 +152,20 @@ open class BaseTextAttributes {
|
|||
attributedTextConfiguration: { textView.attributedText = $0 })
|
||||
}
|
||||
|
||||
open func configure(button: UIButton, with string: String? = nil) {
|
||||
open func configure(button: UIButton, with string: String? = nil, for state: UIControl.State = .normal) {
|
||||
if #available(iOS 15, *) {
|
||||
var configuration = button.configuration ?? UIButton.Configuration.plain()
|
||||
|
||||
if let title = string {
|
||||
button.setTitle(nil, for: .normal)
|
||||
button.setAttributedTitle(nil, for: .normal)
|
||||
button.setTitle(nil, for: state)
|
||||
button.setAttributedTitle(nil, for: state)
|
||||
|
||||
configuration.attributedTitle = attributedString(for: title)
|
||||
button.configuration = configuration
|
||||
}
|
||||
} else {
|
||||
button.setTitle(string, for: .normal)
|
||||
button.setTitleColor(color, for: .normal)
|
||||
button.setTitle(string, for: state)
|
||||
button.setTitleColor(color, for: state)
|
||||
|
||||
if let label = button.titleLabel {
|
||||
configure(label: label)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
open class BaseViewCallbacks<View: AnyObject>: ViewCallbacks {
|
||||
public weak var view: View?
|
||||
|
||||
public init(view: View) {
|
||||
self.view = view
|
||||
}
|
||||
|
||||
open func onDidInitialize() {}
|
||||
open func onDidLayoutSubviews() {}
|
||||
|
||||
open func withStrongView(_ unwrappedViewClosure: (View) -> Void) {
|
||||
guard let view else {
|
||||
return
|
||||
}
|
||||
|
||||
unwrappedViewClosure(view)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
public protocol ViewCallbacks {
|
||||
func onDidInitialize()
|
||||
func onDidLayoutSubviews()
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ import UIKit.UIViewController
|
|||
|
||||
open class BaseInitializableViewController: UIViewController, InitializableViewController {
|
||||
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
|
@ -34,6 +36,14 @@ open class BaseInitializableViewController: UIViewController, InitializableViewC
|
|||
initializeController()
|
||||
}
|
||||
|
||||
override open func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableController
|
||||
|
||||
open func addViews() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIKitCore'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ open class BaseInitializableWebView: WKWebView,
|
|||
InitializableViewProtocol,
|
||||
ConfigurableView {
|
||||
|
||||
public var callbacks: [ViewCallbacks] = []
|
||||
|
||||
public var stateHandler: WebViewStateHandler
|
||||
public var viewModel: WebViewModel? {
|
||||
didSet {
|
||||
|
|
@ -50,6 +52,14 @@ open class BaseInitializableWebView: WKWebView,
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
for callback in callbacks {
|
||||
callback.onDidLayoutSubviews()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - InitializableViewProtocol
|
||||
|
||||
public func addViews() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIWebView'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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 = 'TIYandexMapUtils'
|
||||
s.version = '1.47.0'
|
||||
s.version = '1.48.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' }
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ switch expirationCheckStorage.getValue() {
|
|||
case let .success(token):
|
||||
// use token
|
||||
break
|
||||
case let .failure(storageError)
|
||||
if .valueNotFound = storageError {
|
||||
case let .failure(storageError):
|
||||
if .valueNotFound == storageError {
|
||||
// token is missing or expired, request new token
|
||||
} else {
|
||||
// handle storage error
|
||||
|
|
|
|||
|
|
@ -331,10 +331,10 @@ extension DefaultPlaceholderImageView: Skeletonable {
|
|||
public func skeletonsChangedState(_ state: SkeletonsState) {
|
||||
switch state {
|
||||
case .shown:
|
||||
placeholderView.isHidden = false
|
||||
wrappedView.isHidden = false
|
||||
|
||||
case .hidden:
|
||||
placeholderView.isHidden = true
|
||||
wrappedView.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue