Merge pull request 'feature/stack_appearance_layout' (#9) from feature/stack_appearance_layout into master

Reviewed-on: #9
This commit is contained in:
Ivan Smolin 2023-06-26 19:51:15 +03:00
commit 77559babdb
75 changed files with 1452 additions and 591 deletions

View File

@ -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`

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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
}
}

View File

@ -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' }

View File

@ -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() {

View File

@ -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 {

View File

@ -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() {

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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

View File

@ -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' }

View File

@ -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 {}

View File

@ -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()
}
}
}

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import 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
}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Touch Instinct
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
@ -20,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)
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit.NSLayoutConstraint
public protocol ConstraintsSet {
var allConstraints: [NSLayoutConstraint] { get }
}
public extension ConstraintsSet {
func activate() {
NSLayoutConstraint.activate(allConstraints)
}
func deactivate() {
NSLayoutConstraint.deactivate(allConstraints)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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() {

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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>
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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' }

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -28,5 +28,9 @@ public extension InitializableViewProtocol {
bindViews()
configureAppearance()
localize()
for callback in callbacks {
callback.onDidInitialize()
}
}
}

View File

@ -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()
}
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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() {

View File

@ -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' }

View File

@ -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() {

View File

@ -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' }

View File

@ -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' }

View File

@ -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

View File

@ -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
}
}
}