feat: UITextView now support configuration with BaseTextAttributes

ReconfigurableView & ChangeableViewModel for non-destructing state update
WrappedViewHolder protocol with table/collection view cell implementations
This commit is contained in:
Ivan Smolin 2022-07-18 18:22:42 +03:00
parent 7e319dcb03
commit 88da2ab508
43 changed files with 598 additions and 44 deletions

View File

@ -1,5 +1,12 @@
# Changelog
### 1.23.0
- **Update**: `UITextView` now support configuration with `BaseTextAttributes`
- **Add**: `ReconfigurableView` & `ChangeableViewModel` for non-destructing state update
- **Add**: `WrappedViewHolder` protocol with table/collection view cell implementations
- **Add**: `UIViewPresenter` and `ReusableUIViewPresenter ` protocols with default implementation for proper handling view/cells reuse
### 1.22.0
- **Update**: Asynchronous request preprocessing

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "1.22.0"
s.version = "1.23.0"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKit"
s.license = "Apache License, Version 2.0"

View File

@ -1,23 +1,27 @@
# LeadKit
LeadKit is the iOS framework with a bunch of tools for rapid app development.
## Additional
This repository contains the following additional frameworks:
This repository contains the following frameworks:
- [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for Swift development.
- [TIFoundationUtils](TIFoundationUtils) - set of helpers for Foundation framework classes.
- [TIUIKitCore](TIUIKitCore) - core ui elements and protocols from LeadKit.
- [TITransitions](TITransitions) - set of custom transitions to present controller.
- [TISwiftUICore](TISwiftUICore) Core UI elements: protocols, views and helpers.
- [TIUIElements](TIUIElements) - bunch of of useful protocols and views.
- [OTPSwiftView](OTPSwiftView) - a fully customizable OTP view.
- [TISwiftUtils](TISwiftUtils) - a bunch of useful helpers for development.
- [TITableKitUtils](TITableKitUtils) - Set of helpers for TableKit classes.
- [TIFoundationUtils](TIFoundationUtils) - Set of helpers for Foundation framework classes.
- [TIKeychainUtils](TIKeychainUtils) - Set of helpers for Keychain classes.
- [TITableKitUtils](TITableKitUtils) - set of helpers for TableKit classes.
- [TIKeychainUtils](TIKeychainUtils) - set of helpers for Keychain classes.
- [TIPagination](TIPagination) - realisation of paginating items from a data source.
- [TINetworking](TINetworking) - Swagger-frendly networking layer helpers.
- [TIMoyaNetworking](TIMoyaNetworking) - Moya + Swagger network service.
- [TIAppleMapUtils](TIAppleMapUtils) - set of helpers for map objects clustering and interacting using Apple MapKit.
- [TIGoogleMapUtils](TIGoogleMapUtils) - set of helpers for map objects clustering and interacting using Google Maps SDK.
- [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK.
- [TIAuth](TIAuth) - login, registration, confirmation and other related actions
Useful docs:
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
- [Snippets](docs/snippets.md) - useful commands and scripts for development.
@ -51,3 +55,7 @@ pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z'
# ...
```
## Legacy
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils'
s.version = '1.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for Foundation framework classes.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.'
s.homepage = 'https://github.com/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 = 'TIKeychainUtils'
s.version = '1.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for Keychain classes.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for map objects clustering and interacting.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Moya + Swagger network service.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -38,11 +38,11 @@ open class DefaultTokenInterceptor<RefreshError: Error>: RequestInterceptor {
public var defaultRetryStrategy: RetryResult = .doNotRetry
public var requestModificationClosure: RequestModificationClosure?
public init(isTokenInvalidClosure: @escaping ShouldRefreshTokenClosure,
public init(shouldRefreshTokenClosure: @escaping ShouldRefreshTokenClosure,
refreshTokenClosure: @escaping RefreshTokenClosure,
requestModificationClosure: RequestModificationClosure? = nil) {
self.shouldRefreshToken = isTokenInvalidClosure
self.shouldRefreshToken = shouldRefreshTokenClosure
self.refreshTokenClosure = refreshTokenClosure
self.requestModificationClosure = requestModificationClosure
}

View File

@ -29,14 +29,14 @@ open class EndpointResponseTokenInterceptor<AE, NE>: DefaultTokenInterceptor<End
private let isTokenInvalidErrorResultClosure: IsTokenInvalidErrorResultClosure
public init(isTokenInvalidClosure: @escaping ShouldRefreshTokenClosure,
public init(shouldRefreshTokenClosure: @escaping ShouldRefreshTokenClosure,
refreshTokenClosure: @escaping RefreshTokenClosure,
isTokenInvalidErrorResultClosure: @escaping IsTokenInvalidErrorResultClosure,
requestModificationClosure: RequestModificationClosure? = nil) {
self.isTokenInvalidErrorResultClosure = isTokenInvalidErrorResultClosure
super.init(isTokenInvalidClosure: isTokenInvalidClosure,
super.init(shouldRefreshTokenClosure: shouldRefreshTokenClosure,
refreshTokenClosure: refreshTokenClosure,
requestModificationClosure: requestModificationClosure)
}

View File

@ -63,7 +63,7 @@ open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor {
switch $0 {
case let .success(modifiedRequest):
completion(.success(modifiedRequest))
case let .failure(error):
case .failure:
completion(.failure(PreprocessError.unableToSatisfyRequirements(anyOfRequired: request.security,
registeredPreprocessors: schemePreprocessors)))
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TINetworking'
s.version = '1.22.0'
s.version = '1.23.0'
s.summary = 'Swagger-frendly networking layer helpers.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Caching results of EndpointRequests.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Generic pagination component.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Core UI elements: protocols, views and helpers..'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Bunch of useful helpers for Swift development.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for TableKit classes.'
s.homepage = 'https://github.com/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 = 'TITransitions'
s.version = '1.22.0'
s.version = '1.23.0'
s.summary = 'Set of custom transitions to present controller. '
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }

View File

@ -0,0 +1,78 @@
//
// 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
import TIUIKitCore
open class ContainerCollectionViewCell<View: UIView>: UICollectionViewCell, InitializableViewProtocol, WrappedViewHolder {
// MARK: - WrappedViewHolder
public private(set) lazy var wrappedView = createView()
public var contentInsets: UIEdgeInsets = .zero {
didSet {
contentEdgeConstraints?.update(from: contentInsets)
}
}
private var contentEdgeConstraints: EdgeConstraints?
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initializeView()
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
initializeView()
}
// MARK: - InitializableView
open func addViews() {
addSubview(wrappedView)
}
open func bindViews() {
// override in subclass
}
open func configureLayout() {
contentEdgeConstraints = configureWrappedViewLayout()
}
open func configureAppearance() {
// override in subclass
}
open func localize() {
// override in subclass
}
open func createView() -> View {
return View()
}
}

View File

@ -0,0 +1,56 @@
//
// 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
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)
}
}
private var contentEdgeConstraints: EdgeConstraints?
// MARK: - InitializableView
override open func addViews() {
super.addViews()
addSubview(wrappedView)
}
override open func configureLayout() {
super.configureLayout()
contentEdgeConstraints = configureWrappedViewLayout()
}
open func createView() -> View {
return View()
}
}

View File

@ -0,0 +1,54 @@
//
// 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 EdgeConstraints {
public let leadingConstraint: NSLayoutConstraint
public let trailingConstraint: NSLayoutConstraint
public let topConstraint: NSLayoutConstraint
public let bottomConstraint: NSLayoutConstraint
public var allConstraints: [NSLayoutConstraint] {
[
leadingConstraint,
trailingConstraint,
topConstraint,
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
}
}

View File

@ -0,0 +1,78 @@
//
// 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
import TIUIKitCore
open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableView, InitializableViewProtocol, WrappedViewHolder {
// MARK: - WrappedViewHolder
public private(set) lazy var wrappedView = createView()
public var contentInsets: UIEdgeInsets = .zero {
didSet {
contentEdgeConstraints?.update(from: contentInsets)
}
}
private var contentEdgeConstraints: EdgeConstraints?
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initializeView()
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
initializeView()
}
// MARK: - InitializableView
open func addViews() {
addSubview(wrappedView)
}
open func bindViews() {
// override in subclass
}
open func configureLayout() {
contentEdgeConstraints = configureWrappedViewLayout()
}
open func configureAppearance() {
// override in subclass
}
open func localize() {
// override in subclass
}
open func createView() -> View {
return View()
}
}

View File

@ -0,0 +1,35 @@
//
// 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 TIUIKitCore
public extension WrappedViewHolder where View: ConfigurableView {
func configure(with viewModel: View.ViewModelType) {
wrappedView.configure(with: viewModel)
}
}
public extension WrappedViewHolder where View: ReconfigurableView {
func apply(change: View.ViewModelType.Change) {
wrappedView.apply(change: change)
}
}

View File

@ -0,0 +1,55 @@
//
// 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 protocol WrappedViewHolder {
associatedtype View: UIView
var wrappedView: View { get }
var contentView: UIView { get }
var contentInsets: UIEdgeInsets { 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 configureWrappedViewLayout() -> EdgeConstraints {
wrappedView.translatesAutoresizingMaskIntoConstraints = false
let contentEdgeConstraints = wrappedViewConstraints()
contentEdgeConstraints.activate()
return contentEdgeConstraints
}
}
public extension WrappedViewHolder where Self: UIView {
var contentView: UIView {
self
}
}

View File

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

View File

@ -0,0 +1,25 @@
//
// 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.
//
public protocol ChangeableViewModel {
associatedtype Change
}

View File

@ -0,0 +1,25 @@
//
// 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.
//
public protocol ReconfigurableView: ConfigurableView where ViewModelType: ChangeableViewModel {
func apply(change: ViewModelType.Change)
}

View File

@ -0,0 +1,45 @@
//
// 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.
//
open class DefaultUIViewPresenter<View: AnyObject>: ReusableUIViewPresenter{
public private(set) weak var view: View?
public init() {}
// MARK: - UIViewPresenter
open func didCompleteConfiguration(of view: View) {
self.view = view
}
// MARK: - ReusableUIViewPresenter
open func willReuse(view: View) {
if didConfigure(view: view) {
self.view = nil
}
}
open func didConfigure(view: View) -> Bool {
self.view === view
}
}

View File

@ -0,0 +1,25 @@
//
// 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.
//
public protocol ReusableUIViewPresenter: UIViewPresenter {
func willReuse(view: View)
}

View File

@ -0,0 +1,27 @@
//
// 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.
//
public protocol UIViewPresenter {
associatedtype View: AnyObject // should be stored weakly
func didCompleteConfiguration(of view: View)
}

View File

@ -20,8 +20,7 @@
// THE SOFTWARE.
//
import UIKit.UIFont
import UIKit.UIColor
import UIKit
/// Base set of attributes to configure appearance of text.
open class BaseTextAttributes {
@ -32,6 +31,18 @@ open class BaseTextAttributes {
public let lineHeightMultiple: CGFloat
public let numberOfLines: Int
open var attributedStringAttributes: [NSAttributedString.Key : Any] {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = alignment
paragraphStyle.lineHeightMultiple = lineHeightMultiple
return [
.font: font,
.foregroundColor: color,
.paragraphStyle: paragraphStyle
]
}
public init(font: UIFont,
color: UIColor,
alignment: NSTextAlignment,
@ -61,6 +72,10 @@ open class BaseTextAttributes {
configure(textContainer: textField)
}
open func configure(textView: UITextView) {
configure(textContainer: textView)
}
open func configure(button: UIButton, for state: UIControl.State) {
if let buttonLabel = button.titleLabel {
configure(label: buttonLabel)
@ -110,6 +125,14 @@ open class BaseTextAttributes {
attributedTextConfiguration: { textField.attributedText = $0 })
}
open func configure(textView: UITextView, with string: String?) {
configure(textContainer: textView,
with: string,
appearanceConfiguration: configure(textView:),
textConfiguration: { textView.text = $0 },
attributedTextConfiguration: { textView.attributedText = $0 })
}
open func configure(button: UIButton, with string: String?, for state: UIControl.State) {
configure(textContainer: button,
with: string,
@ -122,17 +145,12 @@ open class BaseTextAttributes {
}
open func attributedString(for string: String) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = alignment
paragraphStyle.lineHeightMultiple = lineHeightMultiple
NSAttributedString(string: string, attributes: attributedStringAttributes)
}
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.paragraphStyle: paragraphStyle
]
return NSAttributedString(string: string, attributes: attributes)
open func apply(in attributedString: NSMutableAttributedString, at range: NSRange? = nil) {
attributedString.addAttributes(attributedStringAttributes,
range: range ?? NSRange(location: 0, length: attributedString.length))
}
}

View File

@ -22,7 +22,7 @@
import UIKit
protocol BaseTextAttributesConfigurable {
public protocol BaseTextAttributesConfigurable {
func set(font: UIFont)
func set(color: UIColor)
func set(alignment: NSTextAlignment)
@ -55,3 +55,17 @@ extension UITextField: BaseTextAttributesConfigurable {
textAlignment = alignment
}
}
extension UITextView: BaseTextAttributesConfigurable {
public func set(font: UIFont) {
self.font = font
}
public func set(color: UIColor) {
textColor = color
}
public func set(alignment: NSTextAlignment) {
textAlignment = alignment
}
}

View File

@ -68,6 +68,10 @@ public struct ViewText {
attributes.configure(textField: textField, with: text)
}
public func configure(textView: UITextView) {
attributes.configure(textView: textView, with: text)
}
public func configure(button: UIButton, for state: UIControl.State) {
attributes.configure(button: button, with: text, for: state)
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'TIUIKitCore'
s.version = '1.22.0'
s.version = '1.23.0'
s.summary = 'Core UI elements: protocols, views and helpers.'
s.homepage = 'https://github.com/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.22.0'
s.version = '1.23.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }