Merge branch 'master' into feature/alerts_api

# Conflicts:
#	CHANGELOG.md
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
This commit is contained in:
Nikita Semenov 2022-07-26 13:02:35 +03:00
commit 7b79d6b250
25 changed files with 580 additions and 26 deletions

View File

@ -4,6 +4,13 @@
- **Add**: `AlertFactory` for presenting alerts in SwiftUI and UIKit.
### 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,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

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

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

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