feature/bottom-sheet #11
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
### 1.51.0
|
||||
|
||||
- **Added**: `BaseModalViewController` implementing `PanModalPresentable` with additional functionality
|
||||
- **Added**: `BaseModalWrapperViewController` for wrapping `UIViewController`s with `BaseModalViewController` functionality
|
||||
|
||||
### 1.50.0
|
||||
|
||||
- **Updated**: Fix activity indicator positioning for `StatefulButton` on iOS 15+ and disabled state touch handling
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
export SRCROOT := $(shell pwd)
|
||||
|
||||
push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target
|
||||
$(call clean)
|
||||
|
||||
TISwiftUtils.target:
|
||||
MODULE_NAME="TISwiftUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TISwiftUtils.target
|
||||
|
||||
TIFoundationUtils.target: TISwiftUtils.target
|
||||
MODULE_NAME="TIFoundationUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIFoundationUtils.target
|
||||
|
||||
TICoreGraphicsUtils.target:
|
||||
MODULE_NAME="TICoreGraphicsUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TICoreGraphicsUtils.target
|
||||
|
||||
TIKeychainUtils.target: TIFoundationUtils.target
|
||||
MODULE_NAME="TIKeychainUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIKeychainUtils.target
|
||||
|
||||
TIUIKitCore.target: TISwiftUtils.target
|
||||
MODULE_NAME="TIUIKitCore" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIUIKitCore.target
|
||||
|
||||
TIUIElements.target: TIUIKitCore.target
|
||||
MODULE_NAME="TIUIElements" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIUIElements.target
|
||||
|
||||
TIWebView.target: TIUIKitCore.target
|
||||
MODULE_NAME="TIWebView" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIWebView.target
|
||||
|
||||
TIBottomSheet.target: TIUIElements.target
|
||||
MODULE_NAME="TIBottomSheet" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIBottomSheet.target
|
||||
|
||||
TISwiftUICore.target: TIUIKitCore.target
|
||||
MODULE_NAME="TISwiftUICore" ./project-scripts/push_to_podspecs.sh
|
||||
touch TISwiftUICore.target
|
||||
|
||||
TITableKitUtils.target: TIUIElements.target
|
||||
MODULE_NAME="TITableKitUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TITableKitUtils.target
|
||||
|
||||
TIDeeplink.target: TIFoundationUtils.target
|
||||
MODULE_NAME="TIDeeplink" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIDeeplink.target
|
||||
|
||||
TIDeveloperUtils.target: TIUIElements.target
|
||||
MODULE_NAME="TIDeveloperUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIDeveloperUtils.target
|
||||
|
||||
TINetworking.target: TIFoundationUtils.target
|
||||
MODULE_NAME="TINetworking" ./project-scripts/push_to_podspecs.sh
|
||||
touch TINetworking.target
|
||||
|
||||
TILogging.target:
|
||||
MODULE_NAME="TILogging" ./project-scripts/push_to_podspecs.sh
|
||||
touch TILogging.target
|
||||
|
||||
TIMoyaNetworking.target: TINetworking.target
|
||||
MODULE_NAME="TIMoyaNetworking" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIMoyaNetworking.target
|
||||
|
||||
TINetworkingCache.target: TINetworking.target
|
||||
MODULE_NAME="TINetworkingCache" ./project-scripts/push_to_podspecs.sh
|
||||
touch TINetworkingCache.target
|
||||
|
||||
TIMapUtils.target: TILogging TICoreGraphicsUtils.target
|
||||
MODULE_NAME="TIMapUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIMapUtils.target
|
||||
|
||||
TIAppleMapUtils.target: TIMapUtils.target
|
||||
MODULE_NAME="TIAppleMapUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIAppleMapUtils.target
|
||||
|
||||
TIGoogleMapUtils.target: TIMapUtils.target
|
||||
MODULE_NAME="TIGoogleMapUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIGoogleMapUtils.target
|
||||
|
||||
TIYandexMapUtils.target: TIMapUtils.target
|
||||
MODULE_NAME="TIYandexMapUtils" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIYandexMapUtils.target
|
||||
|
||||
TIPagination.target: TISwiftUtils.target
|
||||
MODULE_NAME="TIPagination" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIPagination.target
|
||||
|
||||
TIAuth.target: TIUIKitCore.target TIKeychainUtils.target
|
||||
MODULE_NAME="TIAuth" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIAuth.target
|
||||
|
||||
TIEcommerce.target: TINetworking.target TIUIElements.target
|
||||
MODULE_NAME="TIEcommerce" ./project-scripts/push_to_podspecs.sh
|
||||
touch TIEcommerce.target
|
||||
|
||||
TITextProcessing.target:
|
||||
MODULE_NAME="TITextProcessing" ./project-scripts/push_to_podspecs.sh
|
||||
touch TITextProcessing.target
|
||||
|
||||
clean:
|
||||
rm *.target
|
||||
|
|
@ -5,8 +5,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Alamofire/Alamofire.git",
|
||||
"state" : {
|
||||
"revision" : "f96b619bcb2383b43d898402283924b80e2c4bae",
|
||||
"version" : "5.4.3"
|
||||
"revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
|
||||
"version" : "5.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -32,8 +32,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/petropavel13/Cursors",
|
||||
"state" : {
|
||||
"revision" : "a1561869135e72832eff3b1e729075c56c2eebf6",
|
||||
"version" : "0.5.1"
|
||||
"revision" : "52f27b82cb1cbbc2b5fd09514c48b9c75e3b0300",
|
||||
"version" : "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -50,8 +50,17 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Moya/Moya.git",
|
||||
"state" : {
|
||||
"revision" : "9b906860e3c3c09032879465c471e6375829593f",
|
||||
"version" : "15.0.0"
|
||||
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
|
||||
"version" : "15.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "panmodal",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://git.svc.touchin.ru/TouchInstinct/PanModal",
|
||||
"state" : {
|
||||
"revision" : "ced7c1703f90746df0224b6e0d33c146d9ae4284",
|
||||
"version" : "1.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -68,8 +77,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ReactiveX/RxSwift.git",
|
||||
"state" : {
|
||||
"revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
|
||||
"version" : "6.5.0"
|
||||
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
|
||||
"version" : "6.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ let package = Package(
|
|||
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
|
||||
.library(name: "TIUIElements", targets: ["TIUIElements"]),
|
||||
.library(name: "TIWebView", targets: ["TIWebView"]),
|
||||
.library(name: "TIBottomSheet", targets: ["TIBottomSheet"]),
|
||||
|
||||
// MARK: - SwiftUI
|
||||
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
|
||||
|
|
@ -54,7 +55,8 @@ let package = Package(
|
|||
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")),
|
||||
.package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")),
|
||||
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")),
|
||||
.package(url: "https://github.com/antlr/antlr4", .upToNextMinor(from: "4.10.1"))
|
||||
.package(url: "https://github.com/antlr/antlr4", .upToNextMinor(from: "4.10.1")),
|
||||
.package(url: "https://git.svc.touchin.ru/TouchInstinct/PanModal", .upToNextMinor(from: "1.3.0"))
|
||||
],
|
||||
targets: [
|
||||
|
||||
|
|
@ -68,9 +70,16 @@ let package = Package(
|
|||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
|
||||
.target(name: "TIBottomSheet",
|
||||
dependencies: ["PanModal", "TIUIElements", "TIUIKitCore", "TISwiftUtils"],
|
||||
path: "TIBottomSheet/Sources",
|
||||
exclude: ["../TIBottomSheet.app"],
|
||||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
// MARK: - SwiftUI
|
||||
.target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"),
|
||||
.target(name: "TISwiftUICore",
|
||||
dependencies: ["TIUIKitCore", "TISwiftUtils"],
|
||||
path: "TISwiftUICore/Sources"),
|
||||
|
||||
// MARK: - Utils
|
||||
|
||||
|
|
@ -109,12 +118,12 @@ let package = Package(
|
|||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
.target(name: "TIMoyaNetworking",
|
||||
dependencies: ["TINetworking", "TIFoundationUtils", "Moya"],
|
||||
dependencies: ["TINetworking", "Moya"],
|
||||
path: "TIMoyaNetworking/Sources",
|
||||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
.target(name: "TINetworkingCache",
|
||||
dependencies: ["TIFoundationUtils", "TINetworking", "Cache"],
|
||||
dependencies: ["TINetworking", "Cache"],
|
||||
path: "TINetworkingCache/Sources",
|
||||
plugins: [.plugin(name: "TISwiftLintPlugin")]),
|
||||
|
||||
|
|
@ -134,7 +143,7 @@ let package = Package(
|
|||
.target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"),
|
||||
.target(name: "TITransitions", path: "TITransitions/Sources"),
|
||||
.target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"),
|
||||
.target(name: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "TIKeychainUtils"], path: "TIAuth/Sources"),
|
||||
.target(name: "TIAuth", dependencies: ["TIUIKitCore", "TIKeychainUtils"], path: "TIAuth/Sources"),
|
||||
.target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"),
|
||||
.target(name: "TITextProcessing",
|
||||
dependencies: [.product(name: "Antlr4", package: "antlr4")],
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ LICENSE
|
|||
- [TITextProcessing](docs/titextprocessing)
|
||||
* [TITextProcessing](docs/titextprocessing/titextprocessing.md)
|
||||
- [TIDeeplink](docs/tideeplink/deeplinks.md)
|
||||
- [TIBottomSheet](docs/tibottomsheet/tibottomsheet.md)
|
||||
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
|
||||
- [Snippets](docs/snippets.md) - useful commands and scripts for development.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAppleMapUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAuth'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
ENV["DEVELOPMENT_INSTALL"] = "true"
|
||||
|
||||
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
|
||||
|
||||
target 'TIBottomSheet' do
|
||||
platform :ios, 11
|
||||
use_frameworks!
|
||||
|
||||
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
|
||||
|
ivan.smolin marked this conversation as resolved
|
||||
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
|
||||
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
|
||||
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
|
||||
end
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// 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 TIUIElements
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
extension BaseModalViewController {
|
||||
|
||||
open class BaseAppearance: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
|
||||
|
||||
public var presentationDetents: [ModalViewPresentationDetent]
|
||||
public var dragViewState: DragView.State
|
||||
public var headerViewState: ModalHeaderView.State
|
||||
public var footerViewState: ModalFooterView<FooterContentView>.State
|
||||
|
||||
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
presentationDetents: [ModalViewPresentationDetent] = [.maxHeight],
|
||||
dragViewState: DragView.State = .hidden,
|
||||
headerViewState: ModalHeaderView.State = .hidden,
|
||||
footerViewState: ModalFooterView<FooterContentView>.State = .hidden) {
|
||||
|
||||
self.presentationDetents = presentationDetents
|
||||
self.dragViewState = dragViewState
|
||||
self.headerViewState = headerViewState
|
||||
self.footerViewState = footerViewState
|
||||
|
||||
super.init(layout: layout,
|
||||
backgroundColor: backgroundColor,
|
||||
border: border,
|
||||
shadow: shadow)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DefaultAppearance: BaseAppearance, ViewAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
//
|
||||
// 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 TISwiftUtils
|
||||
import TIUIElements
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
import PanModal
|
||||
|
||||
open class BaseModalViewController<ContentView: UIView,
|
||||
FooterContentView: UIView>: BaseInitializableViewController, PanModalPresentable {
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
||||
public let dragView = DragView()
|
||||
public let headerView = ModalHeaderView()
|
||||
private(set) public lazy var contentView = createContentView()
|
||||
private(set) public lazy var footerView = createFooterView()
|
||||
|
||||
public private(set) lazy var dragViewBottomToHeaderViewTopConstraint: NSLayoutConstraint = {
|
||||
dragView.bottomAnchor.constraint(equalTo: headerView.topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var dragViewBottomToContentViewTopConstraint: NSLayoutConstraint = {
|
||||
dragView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var dragViewConstraints: SubviewConstraints = {
|
||||
let trailingConstraint = dragView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: dragView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: dragView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
bottomConstraint: dragViewBottomToHeaderViewTopConstraint)
|
||||
|
||||
let centerXConstraint = dragView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
|
||||
let centerYConstraint = dragView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: dragView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: dragView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var headerViewToSuperviewTopConstraint: NSLayoutConstraint = {
|
||||
headerView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var headerBottomToContentTopConstraint: NSLayoutConstraint = {
|
||||
headerView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var headerViewConstraints: SubviewConstraints = {
|
||||
let leadingConstraint = headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
|
||||
let trailingConstraint = headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: dragViewBottomToHeaderViewTopConstraint,
|
||||
bottomConstraint: headerBottomToContentTopConstraint)
|
||||
|
||||
let centerXConstraint = headerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
|
||||
let centerYConstraint = headerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: headerView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: headerView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var contentViewTopToSuperviewConstraint: NSLayoutConstraint = {
|
||||
contentView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var contentViewBottomToSuperviewConstraint: NSLayoutConstraint = {
|
||||
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var contentViewConstraints: SubviewConstraints = {
|
||||
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
|
||||
let trailingConstraint = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: headerBottomToContentTopConstraint,
|
||||
bottomConstraint: contentViewBottomToSuperviewConstraint)
|
||||
|
||||
let centerXConstraint = contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
|
||||
let centerYConstraint = contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: contentView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: contentView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
а ты как-то учитываешь, что contentView.bounds может поменяться? типа мы обновили содержимое таблицы/коллекции? а ты как-то учитываешь, что contentView.bounds может поменяться? типа мы обновили содержимое таблицы/коллекции?
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var footerViewConstraints: SubviewConstraints = {
|
||||
let leadingConstraint = footerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
|
||||
let trailingConstraint = footerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: footerView.topAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
bottomConstraint: footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor))
|
||||
|
||||
let centerXConstraint = footerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
|
||||
let centerYConstraint = footerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: footerView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: footerView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
// MARK: - Modal View Controller Configuration
|
||||
|
||||
public var viewControllerAppearance: BaseAppearance = .init(backgroundColor: .white)
|
||||
|
||||
open var panScrollable: UIScrollView? {
|
||||
contentView as? UIScrollView
|
||||
}
|
||||
|
||||
public var panScrollableInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
panScrollable?.contentInset = panScrollableInsets
|
||||
}
|
||||
}
|
||||
|
||||
public var dimmedView = DimmedView()
|
||||
|
||||
public var showDragIndicator = true
|
||||
|
||||
open var headerViewHeight: CGFloat {
|
||||
let dragViewHeight = getHeight(of: dragView)
|
||||
let headerViewHeight = getHeight(of: headerView)
|
||||
let dragViewVerticalInsets = getDragViewVerticalInsets()
|
||||
let headerViewVerticalInsets = getHeaderViewVerticalInsets()
|
||||
|
||||
return dragViewHeight + headerViewHeight + dragViewVerticalInsets + headerViewVerticalInsets
|
||||
}
|
||||
|
||||
open var longFormHeight: PanModalHeight {
|
||||
let detents = getSortedDetents()
|
||||
|
||||
return detents.max()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
|
||||
}
|
||||
|
||||
open var mediumFormHeight: PanModalHeight {
|
||||
let detents = getSortedDetents()
|
||||
|
||||
if detents.count > 1 {
|
||||
return detents[1].panModalHeight(headerHeight: headerViewHeight)
|
||||
}
|
||||
|
||||
return detents.first?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
|
||||
}
|
||||
|
||||
open var shortFormHeight: PanModalHeight {
|
||||
let detents = getSortedDetents()
|
||||
|
||||
return detents.min()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
|
||||
}
|
||||
|
||||
private var keyboardDidShownObserver: NSObjectProtocol?
|
||||
private var keyboardDidHiddenObserver: NSObjectProtocol?
|
||||
|
||||
// MARK: - Life Cycle
|
||||
|
||||
deinit {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
|
||||
if let keyboardDidShownObserver {
|
||||
notificationCenter.removeObserver(keyboardDidShownObserver)
|
||||
}
|
||||
|
||||
if let keyboardDidHiddenObserver {
|
||||
notificationCenter.removeObserver(keyboardDidHiddenObserver)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BaseInitializableViewController
|
||||
|
||||
open override func addViews() {
|
||||
super.addViews()
|
||||
|
||||
view.addSubviews(dragView, headerView, contentView, footerView)
|
||||
}
|
||||
|
||||
open override func configureLayout() {
|
||||
super.configureLayout()
|
||||
|
||||
for view in [dragView, headerView, contentView, footerView] {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
configureDragViewLayout()
|
||||
configureHeaderViewLayout()
|
||||
configureContentViewLayout()
|
||||
configureFooterViewLayout()
|
||||
}
|
||||
|
||||
open override func bindViews() {
|
||||
super.bindViews()
|
||||
|
||||
keyboardDidShownObserver = NotificationCenter.default
|
||||
.addObserver(forName: UIResponder.keyboardDidShowNotification,
|
||||
object: nil,
|
||||
queue: .main) { [weak self] notification in
|
||||
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: false)
|
||||
}
|
||||
|
||||
keyboardDidHiddenObserver = NotificationCenter.default
|
||||
.addObserver(forName: UIResponder.keyboardDidHideNotification,
|
||||
object: nil,
|
||||
queue: .main) { [weak self] notification in
|
||||
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: true)
|
||||
}
|
||||
}
|
||||
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
view.configureUIView(appearance: viewControllerAppearance)
|
||||
configureDragViewAppearance()
|
||||
configureHeaderViewAppearance()
|
||||
configureFooterViewAppearance()
|
||||
}
|
||||
|
||||
// MARK: - Open Methods
|
||||
|
||||
open func createContentView() -> ContentView {
|
||||
ContentView()
|
||||
}
|
||||
|
||||
open func createFooterView() -> ModalFooterView<FooterContentView> {
|
||||
ModalFooterView<FooterContentView>()
|
||||
}
|
||||
|
||||
open func configureLayoutForKeyboard(_ notification: Notification, isKeyboardHidden: Bool) {
|
||||
guard let keyboardHeight = getKeyboardHeight(notification) else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState {
|
||||
let bottomInset = footerViewAppearance.layout.insets.add(\.bottom,
|
||||
to: \.bottom,
|
||||
of: .vertical(bottom: keyboardHeight))
|
||||
|
||||
footerViewConstraints.edgeConstraints.bottomConstraint.constant = bottomInset
|
||||
} else if let panScrollable {
|
||||
var insets = panScrollableInsets
|
||||
|
||||
if isKeyboardHidden {
|
||||
panScrollable.contentInset = insets
|
||||
} else {
|
||||
insets.bottom += keyboardHeight
|
||||
|
||||
panScrollable.contentInset = insets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func configureDragViewLayout() {
|
||||
guard case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState else {
|
||||
return
|
||||
}
|
||||
|
||||
let bottomConstraint: NSLayoutConstraint
|
||||
let bottomConstant: CGFloat
|
||||
|
||||
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
|
||||
dragViewBottomToContentViewTopConstraint.isActive = false
|
||||
bottomConstraint = dragViewBottomToHeaderViewTopConstraint
|
||||
bottomConstant = dragViewAppearance.layout.insets.add(\.bottom,
|
||||
to: \.top,
|
||||
of: headerViewAppearance.layout.insets)
|
||||
} else {
|
||||
dragViewBottomToHeaderViewTopConstraint.isActive = false
|
||||
bottomConstraint = dragViewBottomToContentViewTopConstraint
|
||||
bottomConstant = dragViewAppearance.layout.insets.bottom
|
||||
}
|
||||
|
||||
dragViewConstraints.edgeConstraints.bottomConstraint = bottomConstraint
|
||||
dragViewConstraints.update(from: dragViewAppearance.layout)
|
||||
|
||||
bottomConstraint.setActiveConstantOrDeactivate(constant: bottomConstant)
|
||||
}
|
||||
|
||||
private func configureHeaderViewLayout() {
|
||||
guard case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState else {
|
||||
return
|
||||
}
|
||||
|
||||
let topConstraint: NSLayoutConstraint
|
||||
let topConstant: CGFloat
|
||||
|
||||
if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
|
||||
dragViewBottomToContentViewTopConstraint.isActive = false
|
||||
topConstraint = dragViewBottomToHeaderViewTopConstraint
|
||||
|
||||
topConstant = dragViewAppearance.layout.insets.add(\.bottom,
|
||||
to: \.top,
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
🤔 🤔
есть другие варианты?
|
||||
of: headerViewAppearance.layout.insets)
|
||||
} else {
|
||||
dragViewBottomToHeaderViewTopConstraint.isActive = false
|
||||
topConstraint = headerViewToSuperviewTopConstraint
|
||||
|
||||
let topInset = headerViewAppearance.layout.insets.top
|
||||
topConstant = topInset.isFinite ? topInset : .zero
|
||||
}
|
||||
|
||||
headerViewConstraints.edgeConstraints.topConstraint = topConstraint
|
||||
headerViewConstraints.update(from: headerViewAppearance.layout)
|
||||
|
||||
topConstraint.setActiveConstantOrDeactivate(constant: topConstant)
|
||||
}
|
||||
|
||||
private func configureContentViewLayout() {
|
||||
let topConstraint: NSLayoutConstraint
|
||||
let topConstant: CGFloat
|
||||
|
||||
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
|
||||
dragViewBottomToContentViewTopConstraint.isActive = false
|
||||
contentViewTopToSuperviewConstraint.isActive = false
|
||||
|
||||
topConstraint = headerBottomToContentTopConstraint
|
||||
topConstant = headerViewAppearance.layout.insets.bottom
|
||||
} else if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
|
||||
contentViewTopToSuperviewConstraint.isActive = false
|
||||
headerBottomToContentTopConstraint.isActive = false
|
||||
|
||||
topConstraint = dragViewBottomToContentViewTopConstraint
|
||||
topConstant = dragViewAppearance.layout.insets.bottom
|
||||
} else {
|
||||
headerBottomToContentTopConstraint.isActive = false
|
||||
dragViewBottomToContentViewTopConstraint.isActive = false
|
||||
|
||||
topConstraint = contentViewTopToSuperviewConstraint
|
||||
topConstant = .zero
|
||||
}
|
||||
|
||||
contentViewConstraints.edgeConstraints.topConstraint = topConstraint
|
||||
let layout = UIView.DefaultWrappedLayout(insets: .horizontal(.zero)
|
||||
.vertical(top: topConstant)
|
||||
.replacingNan(with: .zero))
|
||||
contentViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configureFooterViewLayout() {
|
||||
guard case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState else {
|
||||
return
|
||||
}
|
||||
|
||||
contentViewConstraints.edgeConstraints.bottomConstraint.isActive = false
|
||||
contentViewConstraints.edgeConstraints.bottomConstraint = footerViewConstraints.edgeConstraints.topConstraint
|
||||
|
||||
footerViewConstraints.update(from: footerViewAppearance.layout)
|
||||
}
|
||||
|
||||
private func configureDragViewAppearance() {
|
||||
switch viewControllerAppearance.dragViewState {
|
||||
case .hidden:
|
||||
dragView.isHidden = true
|
||||
|
||||
case let .presented(appearance):
|
||||
dragView.configure(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
private func configureHeaderViewAppearance() {
|
||||
switch viewControllerAppearance.headerViewState {
|
||||
case .hidden:
|
||||
headerView.isHidden = true
|
||||
|
||||
case let .presented(appearance):
|
||||
headerView.configure(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
private func configureFooterViewAppearance() {
|
||||
switch viewControllerAppearance.footerViewState {
|
||||
case .hidden:
|
||||
footerView.isHidden = true
|
||||
|
||||
case let .presented(appearance):
|
||||
footerView.configureBaseWrappedViewHolder(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
private func getSortedDetents() -> [ModalViewPresentationDetent] {
|
||||
viewControllerAppearance.presentationDetents.uniqued().sorted()
|
||||
}
|
||||
|
||||
private func getHeight(of view: UIView) -> CGFloat {
|
||||
guard !view.isHidden else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
return getFittingSize(forView: view).height
|
||||
}
|
||||
|
||||
private func getDragViewVerticalInsets() -> CGFloat {
|
||||
guard case let .presented(appearance) = viewControllerAppearance.dragViewState else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
return appearance.layout.insets.vertical(onNan: .zero)
|
||||
}
|
||||
|
||||
private func getHeaderViewVerticalInsets() -> CGFloat {
|
||||
guard case let .presented(appearance) = viewControllerAppearance.headerViewState else {
|
||||
return .zero
|
||||
}
|
||||
|
||||
return appearance.layout.insets.vertical(onNan: .zero)
|
||||
}
|
||||
|
||||
private func getFittingSize(forView view: UIView) -> CGSize {
|
||||
let targetSize = CGSize(width: UIScreen.main.bounds.width,
|
||||
height: UIView.layoutFittingCompressedSize.height)
|
||||
|
||||
return view.systemLayoutSizeFitting(targetSize)
|
||||
}
|
||||
|
||||
private func getKeyboardHeight(_ notification: Notification) -> CGFloat? {
|
||||
guard let userInfo = notification.userInfo else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class DefaultModalWrapperViewController<ContentController: UIViewController>: BaseModalViewController<UIView, UIView> {
|
||||
|
||||
private(set) public var contentViewController: ContentController
|
||||
|
||||
public init(contentViewController: ContentController) {
|
||||
self.contentViewController = contentViewController
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
addChild(contentViewController)
|
||||
contentViewController.didMove(toParent: self)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required public init?(coder: NSCoder) {
|
||||
contentViewController = ContentController()
|
||||
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
open override func createContentView() -> UIView {
|
||||
contentViewController.view
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
func wrappedInBottomSheetController() -> DefaultModalWrapperViewController<UIViewController> {
|
||||
DefaultModalWrapperViewController(contentViewController: self)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 TIUIKitCore
|
||||
|
||||
extension UIViewShadow {
|
||||
public static var defaultModalViewShadow: Self {
|
||||
.init {
|
||||
Color(.black)
|
||||
Opacity(0.5)
|
||||
Offset(0, -5)
|
||||
Radius(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// 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 Foundation
|
||||
import PanModal
|
||||
|
||||
public struct ModalViewPresentationDetent: Hashable {
|
||||
|
||||
// MARK: - Default Values
|
||||
|
||||
public static var headerOnly: Self {
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
CGFLOAT_MIN? https://developer.apple.com/documentation/corefoundation/cgfloat_min
|
||||
Self(height: -.greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
public static func height(_ height: CGFloat) -> Self {
|
||||
Self(height: height)
|
||||
}
|
||||
|
||||
public static var maxHeight: Self {
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
аналогично аналогично
|
||||
Self(height: .greatestFiniteMagnitude)
|
||||
}
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
||||
public var height: CGFloat
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
|
||||
if self == .headerOnly {
|
||||
return .contentHeight(headerHeight)
|
||||
}
|
||||
|
||||
if self == .maxHeight {
|
||||
return .maxHeight
|
||||
}
|
||||
|
||||
return .contentHeight(height)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Comparable
|
||||
|
||||
extension ModalViewPresentationDetent: Comparable {
|
||||
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
|
||||
lhs.height < rhs.height
|
||||
}
|
||||
}
|
||||
|
|
@ -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 TIUIElements
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
public final class DragView: BaseInitializableView, AppearanceConfigurable {
|
||||
|
||||
// MARK: - Nested Types
|
||||
|
||||
private enum Constants {
|
||||
static var dragViewTopInset: CGFloat {
|
||||
8
|
||||
}
|
||||
|
||||
static var dragViewBorder: UIViewBorder {
|
||||
UIViewBorder(cornerRadius: dragViewSize.height / 2, roundedCorners: .allCorners)
|
||||
}
|
||||
|
||||
static var dragViewBackgroundColor: UIColor {
|
||||
.lightGray
|
||||
}
|
||||
|
||||
static var dragViewSize: CGSize {
|
||||
CGSize(width: 52, height: 7)
|
||||
}
|
||||
}
|
||||
|
||||
public enum State {
|
||||
case hidden
|
||||
case presented(Appearance)
|
||||
}
|
||||
|
||||
public final class Appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
|
||||
|
||||
public static var defaultAppearance: Self {
|
||||
Self(layout: DefaultWrappedLayout(insets: .vertical(top: Constants.dragViewTopInset),
|
||||
size: Constants.dragViewSize,
|
||||
centerOffset: .centerHorizontal()),
|
||||
backgroundColor: Constants.dragViewBackgroundColor,
|
||||
border: Constants.dragViewBorder)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppearanceConfigurable
|
||||
|
||||
public func configure(appearance: Appearance) {
|
||||
configureUIView(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIElements
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class ModalFooterView<ContentView: UIView>: ContainerView<ContentView> {
|
||||
// MARK: - Nested Types
|
||||
|
||||
public enum State {
|
||||
case hidden
|
||||
case presented(Appearance)
|
||||
}
|
||||
}
|
||||
|
||||
public extension ModalFooterView {
|
||||
|
||||
final class Appearance: BaseWrappedViewHolderAppearance<DefaultWrappedAppearance, DefaultWrappedLayout>,
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
configureModalFooter(appearance:) configureModalFooter(appearance:)
ivan.smolin
commented
может some заиспользовать? чуть меньше кода, чуть больше гибкости в передаваемом параметре может some заиспользовать? чуть меньше кода, чуть больше гибкости в передаваемом параметре
|
||||
WrappedViewHolderAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(bottom: .zero),
|
||||
size: .fixedHeight(44)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
//
|
||||
// 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 TIUIElements
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class ModalHeaderView: BaseInitializableView, AppearanceConfigurable {
|
||||
|
||||
// MARK: - Nested Types
|
||||
|
||||
public enum State {
|
||||
case hidden
|
||||
case presented(Appearance)
|
||||
}
|
||||
|
||||
public enum ContentViewState {
|
||||
case leadingButton(BaseButtonStyle)
|
||||
case trailingButton(BaseButtonStyle)
|
||||
case buttons(leading: BaseButtonStyle, trailing: BaseButtonStyle)
|
||||
case custom(view: UIView, appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>)
|
||||
|
ivan.smolin marked this conversation as resolved
ivan.smolin
commented
лучше leading, trailing лучше leading, trailing
|
||||
}
|
||||
|
||||
// MARK: - Public properties
|
||||
|
||||
public let leadingButton = StatefulButton()
|
||||
public let trailingButton = StatefulButton()
|
||||
|
||||
public private(set) lazy var leftTrailingToRightLeadingConstraint: NSLayoutConstraint = {
|
||||
leadingButton.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var leftTrailingToSuperviewTrailing: NSLayoutConstraint = {
|
||||
leadingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var leadingButtonConstraints: SubviewConstraints = {
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
а почему бы не дать поддержку размеров? а почему бы не дать поддержку размеров?
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingButton.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: leftTrailingToRightLeadingConstraint,
|
||||
topConstraint: leadingButton.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: leadingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerXConstraint = leadingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = leadingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: leadingButton.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: leadingButton.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var rightLeadingToSuperviewLeadingConstraint: NSLayoutConstraint = {
|
||||
trailingButton.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var trailingButtonConstraints: SubviewConstraints = {
|
||||
let trailingConstraint = trailingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leftTrailingToRightLeadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: trailingButton.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: trailingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerXConstraint = trailingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = trailingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: trailingButton.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: trailingButton.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public var customViewConstraints: SubviewConstraints?
|
||||
|
||||
// MARK: - BaseInitializableView
|
||||
|
||||
open override func addViews() {
|
||||
super.addViews()
|
||||
|
||||
addSubviews(leadingButton, trailingButton)
|
||||
}
|
||||
|
||||
open override func configureLayout() {
|
||||
super.configureLayout()
|
||||
|
||||
for view in [leadingButton, trailingButton] {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppearanceConfigurable
|
||||
|
||||
open func configure(appearance: Appearance) {
|
||||
configureUIView(appearance: appearance)
|
||||
|
||||
configureContentView(state: appearance.contentViewState)
|
||||
}
|
||||
|
||||
open func configureContentView(state: ContentViewState) {
|
||||
var leadingButtonStyle: BaseButtonStyle?
|
||||
var trailingButtonStyle: BaseButtonStyle?
|
||||
|
||||
switch state {
|
||||
case let .leadingButton(style):
|
||||
leadingButtonStyle = style
|
||||
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToSuperviewTrailing
|
||||
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
и тут и тут
|
||||
case let .trailingButton(style):
|
||||
trailingButtonStyle = style
|
||||
trailingButtonConstraints.edgeConstraints.leadingConstraint = rightLeadingToSuperviewLeadingConstraint
|
||||
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
```swift
customViewConstraints.centerConstraints.deactivate()
```
|
||||
case let .buttons(leading, trailing):
|
||||
leadingButtonStyle = leading
|
||||
trailingButtonStyle = trailing
|
||||
|
||||
case let .custom(view, appearance):
|
||||
configureCustomView(view, withLayout: appearance.layout)
|
||||
view.configureUIView(appearance: appearance)
|
||||
}
|
||||
|
||||
configure(buttonStyle: leadingButtonStyle, forButton: leadingButton, constraints: leadingButtonConstraints)
|
||||
configure(buttonStyle: trailingButtonStyle, forButton: trailingButton, constraints: trailingButtonConstraints)
|
||||
|
||||
if let leadingButtonStyle, let trailingButtonStyle {
|
||||
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToRightLeadingConstraint
|
||||
trailingButtonConstraints.edgeConstraints.leadingConstraint = leftTrailingToRightLeadingConstraint
|
||||
|
||||
let spacing = leadingButtonStyle.appearance.layout.insets.add(\.right,
|
||||
to: \.left,
|
||||
of: trailingButtonStyle.appearance.layout.insets)
|
||||
|
||||
leftTrailingToRightLeadingConstraint.setActiveConstantOrDeactivate(constant: spacing)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func configureCustomView(_ view: UIView, withLayout layout: WrappedViewLayout) {
|
||||
addSubview(view)
|
||||
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: view.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: view.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: view.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: view.centerYAnchor.constraint(equalTo: centerYAnchor))
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: view.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: view.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
let customViewConstraints = SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
|
||||
self.customViewConstraints = customViewConstraints
|
||||
|
||||
customViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configure(buttonStyle: BaseButtonStyle?,
|
||||
forButton button: StatefulButton,
|
||||
constraints: SubviewConstraints?) {
|
||||
|
||||
guard let buttonStyle else {
|
||||
button.isHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
button.isHidden = false
|
||||
|
||||
constraints?.update(from: buttonStyle.appearance.layout)
|
||||
|
||||
button.apply(style: buttonStyle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Appearance
|
||||
|
||||
public extension ModalHeaderView {
|
||||
|
||||
final class Appearance: BaseWrappedAppearance<DefaultWrappedLayout>, WrappedViewAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(top: .zero),
|
||||
size: .fixedHeight(44)))
|
||||
}
|
||||
|
||||
public var contentViewState: ContentViewState
|
||||
|
||||
public init(layout: DefaultWrappedLayout = .defaultLayout,
|
||||
backgroundColor: UIColor = .clear,
|
||||
border: UIViewBorder = .init(),
|
||||
shadow: UIViewShadow? = nil,
|
||||
contentViewState: ContentViewState = .leadingButton(.init())) {
|
||||
|
||||
self.contentViewState = contentViewState
|
||||
|
||||
super.init(layout: layout, backgroundColor: backgroundColor, border: border, shadow: shadow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// 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 PanModal
|
||||
|
||||
open class PassthroughDimmedView: DimmedView {
|
||||
public weak var hitTestHandlerView: UIView?
|
||||
|
||||
public var isTransparent = false
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let hitTestHandlerView else {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
return hitTestHandlerView.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
hitTestHandlerView == nil || super.point(inside: point, with: event)
|
||||
}
|
||||
|
||||
open override func onChange(dimState: DimmedView.DimState) {
|
||||
super.onChange(dimState: dimState)
|
||||
|
||||
if isTransparent {
|
||||
alpha = .zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
**/build/
|
||||
**/nef/
|
||||
LICENSE
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>launcher</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.fortysevendeg.nef</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.14</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
## gitignore nef files
|
||||
**/build/
|
||||
**/nef/
|
||||
LICENSE
|
||||
|
||||
## User data
|
||||
**/xcuserdata/
|
||||
podfile.lock
|
||||
**.DS_Store
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## CocoaPods
|
||||
**Pods**
|
||||
|
||||
## Carthage
|
||||
**Carthage**
|
||||
|
||||
## SPM
|
||||
.build
|
||||
.swiftpm
|
||||
swiftpm
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
ENV["DEVELOPMENT_INSTALL"] = "true"
|
||||
|
||||
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
|
||||
|
||||
target 'TIBottomSheet' do
|
||||
platform :ios, 11
|
||||
use_frameworks!
|
||||
|
||||
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
|
||||
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
|
||||
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
|
||||
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
|
||||
end
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*:
|
||||
# TIBottomSheet
|
||||
|
||||
TIBottomSheet содержить базовую реализацию модального котроллера и немного видоизмененную библиотеку PanModal.
|
||||
|
||||
## Базовый контроллер
|
||||
|
||||
Для создания модального котроллера можно унаследоваться от `BaseModalViewController`. Данный клас принимает два generic типа: тип основного контента, тип контента футера.
|
||||
*/
|
||||
import TIBottomSheet
|
||||
import UIKit
|
||||
|
||||
class EmptyViewController: BaseModalViewController<UIView, UIView> { }
|
||||
|
||||
/*:
|
||||
## Обертка вокруг существующего контроллера
|
||||
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `BaseModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
*/
|
||||
|
||||
import TIUIKitCore
|
||||
|
||||
final class OldMassiveViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
}
|
||||
|
||||
typealias ModalOldMassiveViewController = BaseModalWrapperViewController<OldMassiveViewController>
|
||||
|
||||
class PresentingViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
|
||||
@objc private func onButtonTapped() {
|
||||
presentPanModal(ModalOldMassiveViewController())
|
||||
}
|
||||
}
|
||||
|
||||
/*:
|
||||
## Контент модального контроллера
|
||||
|
||||
Модальный котроллер может содержать следующие элементы: `DragView`, `HeaderView`, `FooterView`. Каждый из них является опциональным и без дополнительных настроек не будет показываться.
|
||||
|
||||
DragView - небольшая view, за которую пользователь "держит" модальный контроллер
|
||||
HeaderView - контейнер, содержащий в себе кнопки назад/закрыть или какие-то другие элементы управления
|
||||
FooterView - view, располагающаяся внизу контроллера, поверх всего контента (модальный контроллер уже настроен так, чтобы при скролле в самый низ, футер не перекрывал последнюю ячейку)
|
||||
|
||||
Для настройки каждого у котроллера есть свойство `viewControllerAppearance`. Через него будет настраиваться весь контроллер. Однако стоит заметить, что котроллер не будет настраивать передаваимую вью, содержащую основной контент. Стандартно котроллер будет пытаться расположить контент так, чтобы он заполнил все пространство.
|
||||
|
||||
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
|
||||
*/
|
||||
|
||||
let customViewController = BaseModalViewController<UIView, UIView>()
|
||||
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
|
||||
$0.dragViewState = .presented(.defaultAppearance)
|
||||
$0.headerViewState = .presented(.make {
|
||||
$0.layout.size = .fixedHeight(52)
|
||||
$0.backgroundColor = .white
|
||||
$0.contentViewState = .buttonLeft(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(backgroundColor: .blue)
|
||||
])))
|
||||
})
|
||||
}
|
||||
|
||||
/*:
|
||||
## "Якори" контроллера
|
||||
|
||||
Раньше для настройки высоты контроллера необходимо было пользоваться свойствами `longFormHeight`, `shortFormHeight`. В базовом контроллере можно лишь передать список точек на которых контроллер должен будет задержаться:
|
||||
*/
|
||||
|
||||
let detentsViewController = BaseModalViewController<UIView, UIView>()
|
||||
detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnly, .height(300), .maxHeight]
|
||||
|
||||
/*:
|
||||
- headerOnly будет сам пытаться вычеслить высоту хедера и dragView, показывая только их
|
||||
- height(_) будет показывать контроллер на переданной высоте
|
||||
- maxHeight - вся высота экрана (до safeArea)
|
||||
|
||||
В данный массив не рекомендуется передавать больше 3 значений, т.к. модальное окно все равно сможет занять только 3 положения на экране.
|
||||
|
||||
## DimmedView и PassthroughDimmedView
|
||||
|
||||
Для контроля `DimmedView` (затемняющей view) есть отдельное свойство `dimmedView`. Эти классы позволяют настраивать поведение при тапе в затемнённую область и кастомизировать затемнение под ваши нужды.
|
||||
*/
|
||||
|
||||
let shadowViewController = BaseModalViewController<UIView, UIView>()
|
||||
let dimmedView = PassthroughDimmedView()
|
||||
dimmedView.hitTestHandlerView = view
|
||||
dimmedView.configureUIView(appearance: .init(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
|
||||
shadowViewController.dimmedView = dimmedView
|
||||
|
||||
/*:
|
||||
## Контроль закрытия
|
||||
|
||||
`PanModalPresentable` не умеет в настройку закрытия контроллера, делая это самостоятельно через `dismiss(animated:completion:)`. Теперь можно настроить закрытие самостоятельно через свойства: `onTapToDismiss` и `onDragToDismiss`.
|
||||
|
||||
# Взаимодействие с PanModal
|
||||
|
||||
Если нет необходимости или возможности использовать `BaseModalViewController`, вы все так же можете пользоваться протоколом `PanModalRepresentable`. Вот список изменений протокола:
|
||||
|
||||
- Открытие/закрытие модального окна теперь можно настроить с помощью свойств `onTapToDismiss` и `onDragToDismiss`
|
||||
- Можно настроить промежуточное состояние модального окна с `mediumFormHeight`
|
||||
- `DimmedView` открыт для наследования и может создаваться в `dimmedView` у
|
||||
|
||||
> Для `BaseModalViewController` все свойства из `PanModalPresentable` все также работают, т.е. вы можете их переопределять, добавлять и изменять по необходимости.
|
||||
*/
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import UIKit
|
||||
|
||||
public protocol NefPlaygroundLiveViewable {}
|
||||
extension UIView: NefPlaygroundLiveViewable {}
|
||||
extension UIViewController: NefPlaygroundLiveViewable {}
|
||||
|
||||
#if NOT_IN_PLAYGROUND
|
||||
public enum Nef {
|
||||
public enum Playground {
|
||||
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
|
||||
public static func needsIndefiniteExecution(_ state: Bool) {}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
import PlaygroundSupport
|
||||
|
||||
public enum Nef {
|
||||
public enum Playground {
|
||||
public static func liveView(_ view: NefPlaygroundLiveViewable) {
|
||||
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
|
||||
}
|
||||
|
||||
public static func needsIndefiniteExecution(_ state: Bool) {
|
||||
PlaygroundPage.current.needsIndefiniteExecution = state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import Foundation
|
||||
import XCTest
|
||||
|
||||
public extension Nef {
|
||||
|
||||
static func run<T: XCTestCase>(testCase class: T.Type) {
|
||||
startTestObserver()
|
||||
T.defaultTestSuite.run()
|
||||
}
|
||||
|
||||
static private func startTestObserver() {
|
||||
_ = testObserverInstalled
|
||||
}
|
||||
|
||||
static private var testObserverInstalled = { () -> NefTestFailObserver in
|
||||
let testObserver = NefTestFailObserver()
|
||||
XCTestObservationCenter.shared.addTestObserver(testObserver)
|
||||
return testObserver
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: enrich the output for XCTest
|
||||
fileprivate class NefTestFailObserver: NSObject, XCTestObservation {
|
||||
|
||||
private var numberOfFailedTests = 0
|
||||
|
||||
func testSuiteWillStart(_ testSuite: XCTestSuite) {
|
||||
numberOfFailedTests = 0
|
||||
}
|
||||
|
||||
func testSuiteDidFinish(_ testSuite: XCTestSuite) {
|
||||
if numberOfFailedTests > 0 {
|
||||
print("💢 Test Suite '\(testSuite.name)' finished with \(numberOfFailedTests) failed \(numberOfFailedTests > 1 ? "tests" : "test").")
|
||||
} else {
|
||||
print("🔅 Test Suite '\(testSuite.name)' finished successfully.")
|
||||
}
|
||||
}
|
||||
|
||||
func testCase(_ testCase: XCTestCase,
|
||||
didFailWithDescription description: String,
|
||||
inFile filePath: String?,
|
||||
atLine lineNumber: Int) {
|
||||
|
||||
numberOfFailedTests += 1
|
||||
print("❗️Test Fail '\(testCase.name)':\(UInt(lineNumber)): \(description.description)")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.debug.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.release.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
8BACBE8022576CAD00266845 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
11F06D2789C6CF40767861CF /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1F7782E3A7AD7291B7C09F56 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */,
|
||||
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8B39A26221D40F8700DE2643 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BACBE8422576CAD00266845 /* TIBottomSheet */,
|
||||
8B39A26C21D40F8700DE2643 /* Products */,
|
||||
1F7782E3A7AD7291B7C09F56 /* Pods */,
|
||||
11F06D2789C6CF40767861CF /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8B39A26C21D40F8700DE2643 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8BACBE8422576CAD00266845 /* TIBottomSheet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8BACBE8622576CAD00266845 /* Info.plist */,
|
||||
);
|
||||
path = TIBottomSheet;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
8BACBE7E22576CAD00266845 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
8BACBE8222576CAD00266845 /* TIBottomSheet */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */;
|
||||
buildPhases = (
|
||||
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */,
|
||||
8BACBE7E22576CAD00266845 /* Headers */,
|
||||
8BACBE7F22576CAD00266845 /* Sources */,
|
||||
8BACBE8022576CAD00266845 /* Frameworks */,
|
||||
8BACBE8122576CAD00266845 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = TIBottomSheet;
|
||||
productName = TIBottomSheet2;
|
||||
productReference = 8BACBE8322576CAD00266845 /* TIBottomSheet.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
8B39A26321D40F8700DE2643 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1200;
|
||||
ORGANIZATIONNAME = "47 Degrees";
|
||||
TargetAttributes = {
|
||||
8BACBE8222576CAD00266845 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 8B39A26221D40F8700DE2643;
|
||||
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
8BACBE8222576CAD00266845 /* TIBottomSheet */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
8BACBE8122576CAD00266845 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-TIBottomSheet-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
8BACBE7F22576CAD00266845 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
8B39A27721D40F8800DE2643 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_TESTING_SEARCH_PATHS = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8B39A27821D40F8800DE2643 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTING_SEARCH_PATHS = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8BACBE8822576CAD00266845 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_TIBottomSheet_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8BACBE8922576CAD00266845 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_TIBottomSheet_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8B39A27721D40F8800DE2643 /* Debug */,
|
||||
8B39A27821D40F8800DE2643 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8BACBE8822576CAD00266845 /* Debug */,
|
||||
8BACBE8922576CAD00266845 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:TIBottomSheet.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8BACBE8222576CAD00266845"
|
||||
BuildableName = "TIBottomSheet.framework"
|
||||
BlueprintName = "TIBottomSheet"
|
||||
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8BACBE8222576CAD00266845"
|
||||
BuildableName = "TIBottomSheet.framework"
|
||||
BlueprintName = "TIBottomSheet"
|
||||
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8BACBE8222576CAD00266845"
|
||||
BuildableName = "TIBottomSheet.framework"
|
||||
BlueprintName = "TIBottomSheet"
|
||||
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
11
TIBottomSheet/TIBottomSheet.app/Contents/MacOS/TIBottomSheet.xcworkspace/contents.xcworkspacedata
generated
Normal file
11
TIBottomSheet/TIBottomSheet.app/Contents/MacOS/TIBottomSheet.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef location = "group:TIBottomSheet.playground"></FileRef>
|
||||
<FileRef
|
||||
location = "group:TIBottomSheet.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019. The nef authors.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
workspace="TIBottomSheet.xcworkspace"
|
||||
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
|
||||
|
||||
open "`pwd`/$workspacePath/$workspace"
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
TIBottomSheet.app/Contents/MacOS/TIBottomSheet.playground
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIBottomSheet'
|
||||
s.version = '1.51.0'
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
1.50.0 1.50.0
|
||||
s.summary = 'Base models for creating bottom sheet view controllers'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'castlele' => 'nikita.semenov@touchin.ru',
|
||||
'petropavel13' => 'ivan.smolin@touchin.ru'}
|
||||
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = '11.0'
|
||||
s.swift_versions = ['5.7']
|
||||
|
||||
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 'TIUIKitCore', s.version.to_s
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
s.dependency 'PanModal', '~> 1.3.0'
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TICoreGraphicsUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'CoreGraphics drawing helpers'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIDeeplink'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIDeveloperUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell<DefaultPickerView>
|
|||
// MARK: - Open methods
|
||||
|
||||
open func updateAppearance(with appearance: FilterCellStateAppearance) {
|
||||
contentInsets = appearance.contentInsets
|
||||
wrappedContentInsets = appearance.contentInsets
|
||||
wrappedView.textColor = appearance.fontColor
|
||||
wrappedView.images = appearance.stateImages ?? [:]
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ open class DefaultFilterCollectionCell: ContainerCollectionViewCell<UILabel>, Co
|
|||
// MARK: - Open methdos
|
||||
|
||||
open func updateAppearance(with appearance: FilterCellStateAppearance) {
|
||||
contentInsets = appearance.contentInsets
|
||||
wrappedContentInsets = appearance.contentInsets
|
||||
wrappedView.textColor = appearance.fontColor
|
||||
|
||||
backgroundColor = appearance.backgroundColor
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIEcommerce'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Cart, products, promocodes, bonuses and other related actions'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIFoundationUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Set of helpers for Foundation framework classes.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIGoogleMapUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIKeychainUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Set of helpers for Keychain classes.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TILogging'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Logging for TI libraries.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMapUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMoyaNetworking'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Moya + Swagger network service.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworking'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Swagger-frendly networking layer helpers.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworkingCache'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Caching results of EndpointRequests.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIPagination'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Generic pagination component.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUICore'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
extension Array where Element: Hashable {
|
||||
public func uniqued() -> [Element] {
|
||||
Array(Set(self))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Bunch of useful helpers for Swift development.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITableKitUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITextProcessing'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -30,21 +30,21 @@ extension UIButton {
|
|||
appearance.textAttributes?
|
||||
.configure(button: self,
|
||||
with: title(for: state),
|
||||
for: state)
|
||||
for: state)
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
var config = configuration ?? .plain()
|
||||
|
||||
configureInsets(in: &config,
|
||||
contentInsets: appearance.contentLayout.contentInsets,
|
||||
titleInsets: appearance.contentLayout.titleInsets,
|
||||
imageInsets: appearance.contentLayout.imageInsets)
|
||||
contentInsets: appearance.contentLayout.contentInsets.replacingNan(with: .zero),
|
||||
titleInsets: appearance.contentLayout.titleInsets.replacingNan(with: .zero),
|
||||
imageInsets: appearance.contentLayout.imageInsets.replacingNan(with: .zero))
|
||||
|
||||
configuration = config
|
||||
} else {
|
||||
contentEdgeInsets = appearance.contentLayout.contentInsets
|
||||
titleEdgeInsets = appearance.contentLayout.titleInsets
|
||||
imageEdgeInsets = appearance.contentLayout.imageInsets
|
||||
contentEdgeInsets = appearance.contentLayout.contentInsets.replacingNan(with: .zero)
|
||||
titleEdgeInsets = appearance.contentLayout.titleInsets.replacingNan(with: .zero)
|
||||
imageEdgeInsets = appearance.contentLayout.imageInsets.replacingNan(with: .zero)
|
||||
}
|
||||
|
||||
super.configureUIView(appearance: appearance)
|
||||
|
|
@ -67,7 +67,7 @@ extension UIButton {
|
|||
currentImage
|
||||
]
|
||||
.compactMap { $0 }
|
||||
.first { !($0.size.width.isZero || $0.size.height.isZero) } != nil
|
||||
.contains { !($0.size.width.isZero || $0.size.height.isZero) }
|
||||
|
||||
if hasNonEmptyImage {
|
||||
let leadingContentInset: CGFloat
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ extension UIView {
|
|||
public var centerOffset: UIOffset
|
||||
public var insets: UIEdgeInsets
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
public init(insets: UIEdgeInsets = .nan,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan) {
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ extension UIView {
|
|||
open class BaseSpecedWrappedLayout: BaseWrappedLayout {
|
||||
public var spacing: CGFloat
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
public init(insets: UIEdgeInsets = .nan,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan,
|
||||
spacing: CGFloat = .zero) {
|
||||
|
|
@ -98,7 +98,7 @@ extension UIView {
|
|||
public var distribution: UIStackView.Distribution
|
||||
public var alignment: UIStackView.Alignment
|
||||
|
||||
public init(insets: UIEdgeInsets = .zero,
|
||||
public init(insets: UIEdgeInsets = .nan,
|
||||
size: CGSize = .infinity,
|
||||
centerOffset: UIOffset = .nan,
|
||||
spacing: CGFloat = .zero,
|
||||
|
|
|
|||
|
|
@ -27,15 +27,13 @@ open class ContainerSeparatorTableViewCell<View: UIView>: ContainerTableViewCell
|
|||
private lazy var topSeparatorView = createTopSeparator()
|
||||
private lazy var bottomSeparatorView = createBottomSeparator()
|
||||
|
||||
private var topViewLeftConstraint: NSLayoutConstraint?
|
||||
private var topViewRightConstraint: NSLayoutConstraint?
|
||||
private var topViewTopConstraint: NSLayoutConstraint?
|
||||
private var topViewHeightConstraint: NSLayoutConstraint?
|
||||
private lazy var topSeparatorConstraints: SubviewConstraints = {
|
||||
subviewConstraints(for: topSeparatorView)
|
||||
}()
|
||||
|
||||
private var bottomViewLeftConstraint: NSLayoutConstraint?
|
||||
private var bottomViewRightConstraint: NSLayoutConstraint?
|
||||
private var bottomViewBottomConstraint: NSLayoutConstraint?
|
||||
private var bottomViewHeightConstraint: NSLayoutConstraint?
|
||||
private lazy var bottomSeparatorConstraints: SubviewConstraints = {
|
||||
subviewConstraints(for: bottomSeparatorView)
|
||||
}()
|
||||
|
||||
open func createTopSeparator() -> UIView {
|
||||
.init()
|
||||
|
|
@ -93,41 +91,17 @@ open class ContainerSeparatorTableViewCell<View: UIView>: ContainerTableViewCell
|
|||
open override func configureLayout() {
|
||||
super.configureLayout()
|
||||
|
||||
if let separatorSuperview = topSeparatorView.superview {
|
||||
topViewTopConstraint = topSeparatorView.topAnchor.constraint(equalTo: separatorSuperview.topAnchor)
|
||||
topViewRightConstraint = separatorSuperview.rightAnchor.constraint(equalTo: topSeparatorView.rightAnchor)
|
||||
topViewLeftConstraint = topSeparatorView.leftAnchor.constraint(equalTo: separatorSuperview.leftAnchor)
|
||||
for view in [topSeparatorView, bottomSeparatorView] {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
topViewHeightConstraint = topSeparatorView.heightAnchor.constraint(equalToConstant: 1)
|
||||
|
||||
if let separatorSuperview = topSeparatorView.superview {
|
||||
bottomViewRightConstraint = separatorSuperview.rightAnchor.constraint(equalTo: bottomSeparatorView.rightAnchor)
|
||||
bottomViewLeftConstraint = bottomSeparatorView.leftAnchor.constraint(equalTo: separatorSuperview.leftAnchor)
|
||||
bottomViewBottomConstraint = bottomSeparatorView.bottomAnchor.constraint(equalTo: separatorSuperview.bottomAnchor)
|
||||
}
|
||||
|
||||
bottomViewHeightConstraint = bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
topViewTopConstraint,
|
||||
topViewRightConstraint,
|
||||
topViewLeftConstraint,
|
||||
topViewHeightConstraint,
|
||||
bottomViewRightConstraint,
|
||||
bottomViewLeftConstraint,
|
||||
bottomViewBottomConstraint,
|
||||
bottomViewHeightConstraint
|
||||
].compactMap { $0 })
|
||||
}
|
||||
|
||||
open override func configureAppearance() {
|
||||
super.configureAppearance()
|
||||
|
||||
[topSeparatorView, bottomSeparatorView].forEach {
|
||||
$0.isHidden = true
|
||||
$0.backgroundColor = .black
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
for view in [topSeparatorView, bottomSeparatorView] {
|
||||
view.isHidden = true
|
||||
view.backgroundColor = .black
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,20 +110,37 @@ open class ContainerSeparatorTableViewCell<View: UIView>: ContainerTableViewCell
|
|||
private func updateTopSeparator(with appearance: SeparatorAppearance) {
|
||||
topSeparatorView.configureUIView(appearance: appearance)
|
||||
|
||||
topViewHeightConstraint?.constant = appearance.layout.size.height
|
||||
|
||||
topViewTopConstraint?.constant = appearance.layout.insets.top
|
||||
topViewLeftConstraint?.constant = appearance.layout.insets.left
|
||||
topViewRightConstraint?.constant = appearance.layout.insets.right
|
||||
topSeparatorConstraints.update(from: appearance.layout)
|
||||
}
|
||||
|
||||
private func updateBottomSeparator(with appearance: SeparatorAppearance) {
|
||||
bottomSeparatorView.configureUIView(appearance: appearance)
|
||||
|
||||
bottomViewHeightConstraint?.constant = appearance.layout.size.height
|
||||
bottomSeparatorConstraints.update(from: appearance.layout)
|
||||
}
|
||||
|
||||
bottomViewBottomConstraint?.constant = appearance.layout.insets.bottom
|
||||
bottomViewLeftConstraint?.constant = appearance.layout.insets.left
|
||||
bottomViewRightConstraint?.constant = appearance.layout.insets.right
|
||||
private func subviewConstraints(for seperatorView: UIView) -> SubviewConstraints {
|
||||
let leadingConstraint = seperatorView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
|
||||
let trailingConstraint = seperatorView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
|
||||
let topConstraint = seperatorView.topAnchor.constraint(equalTo: contentView.topAnchor)
|
||||
let bottomConstraint = seperatorView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: topConstraint,
|
||||
bottomConstraint: bottomConstraint)
|
||||
|
||||
let centerXConstraint = seperatorView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
|
||||
let centerYConstraint = seperatorView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: seperatorView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: seperatorView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class BaseInitializeableScrollView: UIScrollView, 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() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func configureLayout() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func bindViews() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func configureAppearance() {
|
||||
// override in subclass
|
||||
}
|
||||
|
||||
open func localize() {
|
||||
// override in subclass
|
||||
}
|
||||
}
|
||||
|
|
@ -38,29 +38,47 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
}()
|
||||
|
||||
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)))
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: middleViewLeadingConstraint,
|
||||
topConstraint: leadingView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: leadingView.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerXConstraint = leadingView.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = leadingView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: leadingView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: leadingView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var trailingViewLeadingToMiddleViewConstraint: NSLayoutConstraint = {
|
||||
public private(set) lazy var trailingViewLeadingToMiddleConstraint: 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)))
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: middleViewLeadingConstraint,
|
||||
trailingConstraint: trailingViewLeadingToMiddleConstraint,
|
||||
topConstraint: middleView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: middleView.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerXConstraint = middleView.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = middleView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: middleView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: middleView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var middleViewLeadingToSuperViewConstraint: NSLayoutConstraint = {
|
||||
|
|
@ -72,14 +90,25 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
}()
|
||||
|
||||
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)))
|
||||
let trailingConstraint = trailingView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: trailingViewLeadingToMiddleConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: trailingView.topAnchor.constraint(equalTo: topAnchor),
|
||||
bottomConstraint: trailingView.bottomAnchor.constraint(equalTo: bottomAnchor))
|
||||
|
||||
let centerXConstraint = middleView.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = trailingView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: trailingView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: trailingView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
|
@ -105,9 +134,6 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
|
||||
[leadingView, middleView, trailingView]
|
||||
|
ivan.smolin marked this conversation as resolved
Outdated
ivan.smolin
commented
а зачем? в коде не вижу причины а зачем? в коде не вижу причины
nikita.semenov
commented
153 строка: leadingViewConstraints.edgeConstraints.leadingConstraint - опциональный констрейнт 153 строка: leadingViewConstraints.edgeConstraints.leadingConstraint - опциональный констрейнт
|
||||
.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
|
||||
|
||||
[leadingViewConstraints, middleViewConstraints, trailingViewConstraints]
|
||||
.forEach { $0.update(insets: .zero, size: .infinity, centerOffset: .nan) }
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
|
@ -118,19 +144,19 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
|
||||
configureUIView(appearance: appearance)
|
||||
|
||||
updateLeadingViewLayout(leadingViewLayout: appearance.leadingViewAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
update(leadingViewLayout: appearance.leadingViewAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
|
||||
updateMiddleViewLayout(middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
update(middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
|
||||
updateTrailingViewLayout(trailingViewLayout: appearance.trailingAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
update(trailingViewLayout: appearance.trailingAppearance.layout,
|
||||
middleViewLayout: appearance.middleViewAppearance.layout)
|
||||
}
|
||||
|
||||
// MARK: - Private methdos
|
||||
|
||||
private func updateLeadingViewLayout(leadingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
private func update(leadingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
|
||||
let leadingToSuperviewContraint: NSLayoutConstraint
|
||||
let leadingViewLeftInset: CGFloat
|
||||
|
|
@ -141,9 +167,13 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
|
||||
leadingToSuperviewContraint = middleViewLeadingToSuperViewConstraint
|
||||
leadingViewLeftInset = middleViewLayout.insets.left
|
||||
|
||||
} else {
|
||||
middleViewLeadingConstraint.constant = leadingViewLayout.insets.right + middleViewLayout.insets.left
|
||||
let middleViewLeadingConstant = leadingViewLayout.insets.add(\.left,
|
||||
to: \.right,
|
||||
of: middleViewLayout.insets,
|
||||
onNan: .zero)
|
||||
|
||||
middleViewLeadingConstraint.setActiveConstantOrDeactivate(constant: middleViewLeadingConstant)
|
||||
leadingViewConstraints.edgeConstraints.leadingConstraint.isActive = true
|
||||
middleViewConstraints.edgeConstraints.leadingConstraint.isActive = true
|
||||
middleViewLeadingToSuperViewConstraint.isActive = false
|
||||
|
|
@ -154,15 +184,16 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
leadingViewLeftInset = leadingViewLayout.insets.left
|
||||
}
|
||||
|
||||
leadingToSuperviewContraint.constant = leadingViewLeftInset
|
||||
leadingToSuperviewContraint.constant = leadingViewLeftInset.isFinite ? leadingViewLeftInset : .zero
|
||||
leadingToSuperviewContraint.isActive = true
|
||||
}
|
||||
|
||||
private func updateMiddleViewLayout(middleViewLayout: WrappedViewLayout) {
|
||||
private func update(middleViewLayout: WrappedViewLayout) {
|
||||
middleViewConstraints.update(from: middleViewLayout)
|
||||
}
|
||||
|
||||
private func updateTrailingViewLayout(trailingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
private func update(trailingViewLayout: WrappedViewLayout,
|
||||
middleViewLayout: WrappedViewLayout) {
|
||||
|
||||
let trailingToSuperviewConstraint: NSLayoutConstraint
|
||||
let trailingViewRightInset: CGFloat
|
||||
|
|
@ -173,10 +204,14 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
|
||||
trailingToSuperviewConstraint = middleViewTrailingToSuperViewConstraint
|
||||
trailingViewRightInset = middleViewLayout.insets.right
|
||||
|
||||
} else {
|
||||
trailingViewLeadingToMiddleViewConstraint.constant = middleViewLayout.insets.right + trailingViewLayout.insets.left
|
||||
trailingViewLeadingToMiddleViewConstraint.isActive = true
|
||||
let trailingViewLeadingToMiddleConstant = middleViewLayout.insets.add(\.right,
|
||||
to: \.left,
|
||||
of: trailingViewLayout.insets,
|
||||
onNan: .zero)
|
||||
|
||||
trailingViewLeadingToMiddleConstraint.setActiveConstantOrDeactivate(constant: trailingViewLeadingToMiddleConstant)
|
||||
trailingViewLeadingToMiddleConstraint.isActive = true
|
||||
middleViewTrailingToSuperViewConstraint.isActive = false
|
||||
|
||||
trailingViewConstraints.update(from: trailingViewLayout)
|
||||
|
|
@ -185,6 +220,7 @@ open class BaseListItemView<LeadingView: UIView,
|
|||
trailingViewRightInset = trailingViewLayout.insets.right
|
||||
}
|
||||
|
||||
trailingToSuperviewConstraint.constant = -trailingViewRightInset
|
||||
trailingToSuperviewConstraint.constant = trailingViewRightInset.isFinite ? -trailingViewRightInset : .zero
|
||||
trailingToSuperviewConstraint.isActive = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class PlaceholderButtonStyle {
|
||||
open class BaseButtonStyle {
|
||||
|
||||
public var titles: UIControl.StateTitles
|
||||
public var images: UIControl.StateImages
|
||||
|
|
@ -28,12 +28,12 @@ open class BasePlaceholderStyle<Appearance: ViewAppearance> {
|
|||
public var titleSubtitle: DefaultTitleSubtitleViewModel
|
||||
public var controlsViewAxis: NSLayoutConstraint.Axis
|
||||
public var appearance: Appearance
|
||||
public var buttonsStyles: [PlaceholderButtonStyle]
|
||||
public var buttonsStyles: [BaseButtonStyle]
|
||||
|
||||
public init(titleSubtitle: DefaultTitleSubtitleViewModel = .init(),
|
||||
appearance: Appearance = .defaultAppearance,
|
||||
controlsViewAxis: NSLayoutConstraint.Axis = .vertical,
|
||||
buttonsStyles: [PlaceholderButtonStyle] = []) {
|
||||
buttonsStyles: [BaseButtonStyle] = []) {
|
||||
|
||||
self.titleSubtitle = titleSubtitle
|
||||
self.appearance = appearance
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public final class DefaultPlaceholderStyle: BasePlaceholderStyle<DefaultPlacehol
|
|||
titleSubtitle: DefaultTitleSubtitleViewModel = .init(),
|
||||
appearance: DefaultPlaceholderView.Appearance = .defaultAppearance,
|
||||
controlsViewAxis: NSLayoutConstraint.Axis = .vertical,
|
||||
buttonsStyles: [PlaceholderButtonStyle] = []) {
|
||||
buttonsStyles: [BaseButtonStyle] = []) {
|
||||
|
||||
self.image = image
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public protocol PlaceholderStyle: AnyObject {
|
|||
var titleSubtitle: DefaultTitleSubtitleViewModel { get set }
|
||||
var controlsViewAxis: NSLayoutConstraint.Axis { get set }
|
||||
var appearance: PlaceholderAppearance { get set }
|
||||
var buttonsStyles: [PlaceholderButtonStyle] { get set }
|
||||
var buttonsStyles: [BaseButtonStyle] { get set }
|
||||
}
|
||||
|
||||
// MARK: - Builder methods
|
||||
|
|
@ -63,8 +63,8 @@ public extension PlaceholderStyle {
|
|||
return self
|
||||
}
|
||||
|
||||
func withButton(_ builder: ParameterClosure<PlaceholderButtonStyle>) -> Self {
|
||||
let buttonStyle = PlaceholderButtonStyle()
|
||||
func withButton(_ builder: ParameterClosure<BaseButtonStyle>) -> Self {
|
||||
let buttonStyle = BaseButtonStyle()
|
||||
|
||||
builder(buttonStyle)
|
||||
buttonsStyles.append(buttonStyle)
|
||||
|
|
@ -74,7 +74,7 @@ public extension PlaceholderStyle {
|
|||
|
||||
func withButtons(_ amount: Int,
|
||||
axis: NSLayoutConstraint.Axis,
|
||||
_ builder: (Int, PlaceholderButtonStyle) -> Void) -> Self {
|
||||
_ builder: (Int, BaseButtonStyle) -> Void) -> Self {
|
||||
|
||||
controlsViewAxis = axis
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ public extension PlaceholderStyle {
|
|||
builder(index, buttonStyle)
|
||||
|
||||
} else {
|
||||
let buttonStyle = PlaceholderButtonStyle()
|
||||
let buttonStyle = BaseButtonStyle()
|
||||
|
||||
builder(index, buttonStyle)
|
||||
buttonsStyles.append(buttonStyle)
|
||||
|
|
|
|||
|
|
@ -33,19 +33,19 @@ open class BasePlaceholderImageView<Placeholder: UIView>: UIImageView,
|
|||
|
||||
public private(set) lazy var wrappedView = Placeholder()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: placeholderConstraints)
|
||||
}
|
||||
|
|
@ -118,7 +118,9 @@ open class BasePlaceholderImageView<Placeholder: UIView>: UIImageView,
|
|||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func configureBasePlaceholder(appearance: BaseWrappedViewHolderAppearance<UIView.DefaultWrappedAppearance, some WrappedViewLayout>) {
|
||||
open func configureBasePlaceholder(appearance: BaseWrappedViewHolderAppearance<
|
||||
some BaseWrappedAppearance<some ViewLayout>,
|
||||
some WrappedViewLayout>) {
|
||||
configureUIView(appearance: appearance)
|
||||
|
||||
wrappedView.configureUIView(appearance: appearance.subviewAppearance)
|
||||
|
|
|
|||
|
|
@ -31,48 +31,78 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
public let controlsStackView = UIStackView()
|
||||
|
||||
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)))
|
||||
let 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))
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: imageView.centerYAnchor.constraint(equalTo: centerYAnchor))
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: imageView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: imageView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public private(set) lazy var textViewTopToSuperviewTopConstraint: NSLayoutConstraint = {
|
||||
textView.topAnchor.constraint(equalTo: topAnchor)
|
||||
}()
|
||||
|
||||
public private(set) lazy var textViewBottomToSuperviewBottomConstraint: NSLayoutConstraint = {
|
||||
public private(set) lazy var textViewToSuperviewBottomConstraint: 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)))
|
||||
let bottomConstraint = textView.bottomAnchor.constraint(lessThanOrEqualTo: controlsStackView.topAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: textView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingConstraint: textView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
topConstraint: textView.topAnchor.constraint(equalTo: imageView.bottomAnchor),
|
||||
bottomConstraint: bottomConstraint)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: textView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
centerYConstraint: textView.centerYAnchor.constraint(equalTo: centerYAnchor))
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: textView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: textView.heightAnchor.constraint(equalToConstant: .zero))
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
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)))
|
||||
let leadingConstraint = controlsStackView.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
let trailingConstraint = controlsStackView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
let topConstraint = controlsStackView.topAnchor.constraint(equalTo: textView.bottomAnchor)
|
||||
let bottomConstraint = controlsStackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor)
|
||||
|
||||
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
|
||||
trailingConstraint: trailingConstraint,
|
||||
topConstraint: topConstraint,
|
||||
bottomConstraint: bottomConstraint)
|
||||
|
||||
let centerXConstraint = controlsStackView.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
let centerYConstraint = controlsStackView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
|
||||
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
|
||||
centerYConstraint: centerYConstraint)
|
||||
|
||||
let heightConstraint = controlsStackView.heightAnchor.constraint(equalToConstant: .zero)
|
||||
|
||||
let sizeConstraints = SizeConstraints(widthConstraint: controlsStackView.widthAnchor.constraint(equalToConstant: .zero),
|
||||
heightConstraint: heightConstraint)
|
||||
|
||||
return SubviewConstraints(edgeConstraints: edgeConstraints,
|
||||
centerConstraints: centerConstraints,
|
||||
sizeConstraints: sizeConstraints)
|
||||
}()
|
||||
|
||||
public var keyboardDidShownObserver: NSObjectProtocol?
|
||||
public var keyboardDidHiddenObserver: NSObjectProtocol?
|
||||
private var keyboardDidShownObserver: NSObjectProtocol?
|
||||
private var keyboardDidHiddenObserver: NSObjectProtocol?
|
||||
|
||||
open var isImageViewHidden: Bool {
|
||||
imageView.isHidden
|
||||
|
|
@ -82,15 +112,15 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
controlsStackView.isHidden || controlsStackView.arrangedSubviews.isEmpty
|
||||
}
|
||||
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
if let keyboardDidShownObserver = keyboardDidShownObserver {
|
||||
NotificationCenter.default.removeObserver(keyboardDidShownObserver)
|
||||
let notificationCenter = NotificationCenter.default
|
||||
|
||||
if let keyboardDidShownObserver {
|
||||
notificationCenter.removeObserver(keyboardDidShownObserver)
|
||||
}
|
||||
|
||||
if let keyboardDidHiddenObserver = keyboardDidHiddenObserver {
|
||||
NotificationCenter.default.removeObserver(keyboardDidHiddenObserver)
|
||||
if let keyboardDidHiddenObserver {
|
||||
notificationCenter.removeObserver(keyboardDidHiddenObserver)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,25 +176,19 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func applyBaseStyle(style: BasePlaceholderStyle<some BaseAppearance<some WrappedViewAppearance> & ViewAppearance>) {
|
||||
open func applyBase(style: BasePlaceholderStyle<some BaseAppearance<some WrappedViewAppearance> & ViewAppearance>) {
|
||||
textView.configure(with: style.titleSubtitle)
|
||||
controlsStackView.axis = style.controlsViewAxis
|
||||
|
||||
style.buttonsStyles.forEach {
|
||||
let button = StatefulButton(type: .custom)
|
||||
|
||||
button.set(titles: $0.titles)
|
||||
button.set(images: $0.images)
|
||||
button.configureStatefulButton(appearance: $0.appearance)
|
||||
|
||||
if let action = $0.action {
|
||||
button.addTarget(action.target, action: action.action, for: action.event)
|
||||
}
|
||||
button.apply(style: $0)
|
||||
|
||||
controlsStackView.addArrangedSubview(button)
|
||||
}
|
||||
|
||||
configureAppearance(appearance: style.appearance)
|
||||
configureBase(appearance: style.appearance)
|
||||
}
|
||||
|
||||
open func addAction(_ action: UIButton.Action,
|
||||
|
|
@ -175,10 +199,10 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
button?.addTarget(action.target, action: action.action, for: action.event)
|
||||
}
|
||||
|
||||
open func configureAppearance(appearance: BaseAppearance<some WrappedViewAppearance>) {
|
||||
configureImageViewLayout(layout: appearance.imageViewAppearance.layout)
|
||||
configureTextViewLayout(layout: appearance.textViewAppearance.layout)
|
||||
configureControlsViewLayout(layout: appearance.controlsViewAppearance.layout)
|
||||
open func configureBase(appearance: BaseAppearance<some WrappedViewAppearance>) {
|
||||
configureImageView(layout: appearance.imageViewAppearance.layout)
|
||||
configureTextView(layout: appearance.textViewAppearance.layout)
|
||||
configureControlsView(layout: appearance.controlsViewAppearance.layout)
|
||||
|
||||
configureUIView(appearance: appearance)
|
||||
textView.configure(appearance: appearance.textViewAppearance)
|
||||
|
|
@ -195,7 +219,7 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func configureImageViewLayout(layout: WrappedViewLayout) {
|
||||
private func configureImageView(layout: WrappedViewLayout) {
|
||||
guard !isImageViewHidden else {
|
||||
imageViewConstraints.deactivate()
|
||||
return
|
||||
|
|
@ -204,17 +228,17 @@ open class BasePlaceholderView<ImageView: UIView>: BaseInitializableView {
|
|||
imageViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configureTextViewLayout(layout: WrappedViewLayout) {
|
||||
private func configureTextView(layout: WrappedViewLayout) {
|
||||
textViewConstraints.edgeConstraints.topConstraint.isActive = !isImageViewHidden
|
||||
textViewTopToSuperviewTopConstraint.isActive = isImageViewHidden
|
||||
|
||||
textViewConstraints.edgeConstraints.bottomConstraint.isActive = !isControlsViewHidden
|
||||
textViewBottomToSuperviewBottomConstraint.isActive = isControlsViewHidden
|
||||
textViewToSuperviewBottomConstraint.isActive = isControlsViewHidden
|
||||
|
||||
textViewConstraints.update(from: layout)
|
||||
}
|
||||
|
||||
private func configureControlsViewLayout(layout: SpacedWrappedViewLayout) {
|
||||
private func configureControlsView(layout: SpacedWrappedViewLayout) {
|
||||
guard !isControlsViewHidden else {
|
||||
controlsViewConstraints.deactivate()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public final class DefaultPlaceholderView: BasePlaceholderView<UIImageView>, App
|
|||
public func apply(style: DefaultPlaceholderStyle) {
|
||||
imageView.image = style.image
|
||||
|
||||
super.applyBaseStyle(style: style)
|
||||
super.applyBase(style: style)
|
||||
|
||||
guard let image = imageView.image else {
|
||||
return
|
||||
|
|
@ -44,14 +44,14 @@ public final class DefaultPlaceholderView: BasePlaceholderView<UIImageView>, App
|
|||
// MARK: - AppearanceConfigurable
|
||||
|
||||
public func configure(appearance: Appearance) {
|
||||
configureAppearance(appearance: appearance)
|
||||
configureBase(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Default appearance model
|
||||
|
||||
extension DefaultPlaceholderView {
|
||||
public final class Appearance: BaseAppearance<UIView.DefaultWrappedAppearance>, ViewAppearance {
|
||||
public extension DefaultPlaceholderView {
|
||||
final class Appearance: BaseAppearance<UIView.DefaultWrappedAppearance>, ViewAppearance {
|
||||
public static var defaultAppearance: Self {
|
||||
.init()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// 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 extension StatefulButton {
|
||||
func apply(style: BaseButtonStyle) {
|
||||
set(titles: style.titles)
|
||||
set(images: style.images)
|
||||
configureStatefulButton(appearance: style.appearance)
|
||||
|
||||
if let action = style.action {
|
||||
addTarget(action.target, action: action.action, for: action.event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
import UIKit
|
||||
|
||||
public struct CenterConstraints: ConstraintsSet {
|
||||
public let centerXConstraint: NSLayoutConstraint
|
||||
public let centerYConstraint: NSLayoutConstraint
|
||||
public var centerXConstraint: NSLayoutConstraint
|
||||
public var centerYConstraint: NSLayoutConstraint
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@
|
|||
import UIKit
|
||||
|
||||
public struct EdgeConstraints: ConstraintsSet {
|
||||
public let leadingConstraint: NSLayoutConstraint
|
||||
public let trailingConstraint: NSLayoutConstraint
|
||||
public let topConstraint: NSLayoutConstraint
|
||||
public let bottomConstraint: NSLayoutConstraint
|
||||
public var leadingConstraint: NSLayoutConstraint
|
||||
public var trailingConstraint: NSLayoutConstraint
|
||||
public var topConstraint: NSLayoutConstraint
|
||||
public var bottomConstraint: NSLayoutConstraint
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
import UIKit.NSLayoutConstraint
|
||||
|
||||
extension NSLayoutConstraint {
|
||||
public extension NSLayoutConstraint {
|
||||
func setActiveConstantOrDeactivate(constant: CGFloat) {
|
||||
if constant.isFinite {
|
||||
self.constant = constant
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
import UIKit
|
||||
|
||||
public struct SizeConstraints: ConstraintsSet {
|
||||
public let widthConstraint: NSLayoutConstraint
|
||||
public let heightConstraint: NSLayoutConstraint
|
||||
public var widthConstraint: NSLayoutConstraint
|
||||
public var heightConstraint: NSLayoutConstraint
|
||||
|
||||
// MARK: - ConstraintsSet
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ public struct SubviewConstraints: ConstraintsSet {
|
|||
}
|
||||
|
||||
public init(edgeConstraints: EdgeConstraints,
|
||||
centerConstraints: CenterConstraints,
|
||||
sizeConstraints: SizeConstraints) {
|
||||
centerConstraints: CenterConstraints,
|
||||
sizeConstraints: SizeConstraints) {
|
||||
|
||||
self.edgeConstraints = edgeConstraints
|
||||
self.centerConstraints = centerConstraints
|
||||
|
|
@ -47,21 +47,16 @@ public struct SubviewConstraints: ConstraintsSet {
|
|||
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) {
|
||||
@discardableResult
|
||||
public func update(from layout: some WrappedViewLayout) -> Self {
|
||||
update(insets: layout.insets,
|
||||
size: layout.size,
|
||||
centerOffset: layout.centerOffset)
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ open class CollectionTableViewCell<CollectionView: UICollectionView>: ContainerT
|
|||
|
||||
if #available(iOS 16, *) {
|
||||
contentSizeObservation = wrappedView.observe(\.contentSize,
|
||||
options: [.new]) { collectionView, change in
|
||||
options: [.new]) { collectionView, change in
|
||||
|
||||
if let contentSize = change.newValue, !contentSize.equalTo(collectionView.bounds.size) {
|
||||
self.invalidateIntrinsicContentSize()
|
||||
|
|
@ -52,12 +52,12 @@ open class CollectionTableViewCell<CollectionView: UICollectionView>: ContainerT
|
|||
}
|
||||
|
||||
let cachedCollectionFrame = wrappedView.frame
|
||||
wrappedView.frame.size.width = targetSize.width - contentInsets.left - contentInsets.right
|
||||
wrappedView.frame.size.width = max(targetSize.width - wrappedContentInsets.horizontal(onNan: .zero), .zero)
|
||||
let collectionContentHeight = wrappedView.collectionViewLayout.collectionViewContentSize.height
|
||||
wrappedView.frame = cachedCollectionFrame
|
||||
|
||||
return CGSize(width: targetSize.width,
|
||||
height: collectionContentHeight + contentInsets.top + contentInsets.bottom)
|
||||
height: collectionContentHeight + wrappedContentInsets.vertical(onNan: .zero))
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
|
|
|||
|
|
@ -26,24 +26,24 @@ 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 {
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
|
|
@ -98,6 +98,6 @@ open class ContainerCollectionViewCell<View: UIView>: UICollectionViewCell, Init
|
|||
}
|
||||
|
||||
open func createView() -> View {
|
||||
return View()
|
||||
View()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
open class ContainerScrollView<ContentView: UIView>: BaseInitializeableScrollView, WrappedViewHolder {
|
||||
|
||||
// MARK: - WrappedViewHolder
|
||||
|
||||
public private(set) lazy var wrappedView = createView()
|
||||
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var subviewContraints: SubviewConstraints = {
|
||||
configureWrappedViewLayout()
|
||||
}()
|
||||
|
||||
open func createView() -> ContentView {
|
||||
ContentView()
|
||||
}
|
||||
|
||||
// MARK: - InitializableViewProtocol
|
||||
|
||||
open override func addViews() {
|
||||
super.addViews()
|
||||
|
||||
addSubview(wrappedView)
|
||||
}
|
||||
}
|
||||
|
||||
extension ContainerScrollView: AppearanceConfigurable where View: AppearanceConfigurable,
|
||||
View.Appearance: WrappedViewAppearance {
|
||||
|
vladimir.makarov marked this conversation as resolved
Outdated
vladimir.makarov
commented
Тут поехало немного Тут поехало немного
ivan.smolin
commented
Автовыравнивание, я бы не фокусировался на этих мелочах Автовыравнивание, я бы не фокусировался на этих мелочах
|
||||
|
||||
public func configure(appearance: DefaultWrappedViewHolderAppearance<View.Appearance, NoLayout>) {
|
||||
configureWrappedViewHolder(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,24 +24,24 @@ 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 {
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
|
|
@ -66,14 +66,7 @@ open class ContainerTableViewCell<View: UIView>: BaseInitializableCell, WrappedV
|
|||
}
|
||||
|
||||
open func createView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
public func configureContainerTableViewCell(appearance: BaseWrappedViewHolderAppearance<some WrappedViewAppearance, some ViewLayout>) {
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
configureUIView(appearance: appearance)
|
||||
View()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,8 +75,7 @@ open class ContainerTableViewCell<View: UIView>: BaseInitializableCell, WrappedV
|
|||
extension ContainerTableViewCell: AppearanceConfigurable where View: AppearanceConfigurable,
|
||||
View.Appearance: WrappedViewAppearance {
|
||||
|
||||
public func configure(appearance: DefaultWrappedViewHolderAppearance<View.Appearance, UIView.NoLayout>) {
|
||||
configureContainerTableViewCell(appearance: appearance)
|
||||
wrappedView.configure(appearance: appearance.subviewAppearance)
|
||||
public func configure(appearance: DefaultWrappedViewHolderAppearance<View.Appearance, NoLayout>) {
|
||||
configureWrappedViewHolder(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,23 +23,23 @@
|
|||
import TIUIKitCore
|
||||
import UIKit
|
||||
|
||||
public final class ContainerView<View: UIView>: BaseInitializableView, WrappedViewHolder {
|
||||
open class ContainerView<View: UIView>: BaseInitializableView, WrappedViewHolder {
|
||||
|
||||
public private(set) lazy var wrappedView = View()
|
||||
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
|
|
@ -80,8 +80,6 @@ extension ContainerView: AppearanceConfigurable where View: AppearanceConfigurab
|
|||
public typealias Appearance = UIView.DefaultWrappedViewHolderAppearance<View.Appearance, UIView.DefaultWrappedLayout>
|
||||
|
||||
public func configure(appearance: Appearance) {
|
||||
wrappedView.configure(appearance: appearance.subviewAppearance)
|
||||
configureUIView(appearance: appearance)
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
configureWrappedViewHolder(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,26 +23,28 @@
|
|||
import UIKit
|
||||
import TIUIKitCore
|
||||
|
||||
open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableView, InitializableViewProtocol, WrappedViewHolder {
|
||||
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 {
|
||||
public var wrappedContentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentSize: CGSize = .infinity {
|
||||
public var wrappedContentSize: CGSize = .infinity {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
}
|
||||
|
||||
public var contentCenterOffset: UIOffset = .nan {
|
||||
public var wrappedContentCenterOffset: UIOffset = .nan {
|
||||
didSet {
|
||||
update(subviewConstraints: subviewContraints)
|
||||
}
|
||||
|
|
@ -97,6 +99,6 @@ open class ReusableCollectionContainerView<View: UIView>: UICollectionReusableVi
|
|||
}
|
||||
|
||||
open func createView() -> View {
|
||||
return View()
|
||||
View()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Copyright (c) 2023 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIUIKitCore
|
||||
import UIKit.UIView
|
||||
|
||||
public extension WrappedViewHolder {
|
||||
func configureBaseWrappedViewHolder(appearance: some UIView.BaseWrappedViewHolderAppearance<
|
||||
some WrappedViewAppearance,
|
||||
some ViewLayout>) {
|
||||
updateContentLayout(from: appearance.subviewAppearance.layout)
|
||||
contentView.configureUIView(appearance: appearance)
|
||||
}
|
||||
}
|
||||
|
||||
public extension WrappedViewHolder where View: AppearanceConfigurable, View.Appearance: WrappedViewAppearance {
|
||||
func configureWrappedViewHolder(appearance: some UIView.BaseWrappedViewHolderAppearance<
|
||||
View.Appearance,
|
||||
some ViewLayout>) {
|
||||
configureBaseWrappedViewHolder(appearance: appearance)
|
||||
wrappedView.configure(appearance: appearance.subviewAppearance)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,9 +28,10 @@ public protocol WrappedViewHolder: AnyObject {
|
|||
|
||||
var wrappedView: View { get }
|
||||
var contentView: UIView { get }
|
||||
var contentInsets: UIEdgeInsets { get set }
|
||||
var contentSize: CGSize { get set }
|
||||
var contentCenterOffset: UIOffset { get set }
|
||||
|
||||
var wrappedContentInsets: UIEdgeInsets { get set }
|
||||
var wrappedContentSize: CGSize { get set }
|
||||
var wrappedContentCenterOffset: UIOffset { get set }
|
||||
}
|
||||
|
||||
public extension WrappedViewHolder {
|
||||
|
|
@ -65,17 +66,17 @@ public extension WrappedViewHolder {
|
|||
// MARK: - SubviewConstraints shortcut
|
||||
|
||||
func update(subviewConstraints: SubviewConstraints) {
|
||||
subviewConstraints.update(insets: contentInsets,
|
||||
size: contentSize,
|
||||
centerOffset: contentCenterOffset)
|
||||
subviewConstraints.update(insets: wrappedContentInsets,
|
||||
size: wrappedContentSize,
|
||||
centerOffset: wrappedContentCenterOffset)
|
||||
}
|
||||
|
||||
// MARK: - WrappedViewLayout shortcut
|
||||
|
||||
func updateContentLayout(from layout: some WrappedViewLayout) {
|
||||
contentInsets = layout.insets
|
||||
contentSize = layout.size
|
||||
contentCenterOffset = layout.centerOffset
|
||||
wrappedContentInsets = layout.insets
|
||||
wrappedContentSize = layout.size
|
||||
wrappedContentCenterOffset = layout.centerOffset
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,10 +57,10 @@ let customStyle = DefaultPlaceholderStyle(
|
|||
}, buttonsStyles: [
|
||||
.init(titles: [.normal: "Reload"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: UIButton.DefaultAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
|
||||
color: .black,
|
||||
alignment: .natural,
|
||||
isMultiline: false))
|
||||
.normal: UIButton.DefaultStateAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
|
||||
color: .black,
|
||||
alignment: .natural,
|
||||
isMultiline: false))
|
||||
]))
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIElements'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -26,28 +26,40 @@ public extension UIEdgeInsets {
|
|||
|
||||
// MARK: - Factory methods
|
||||
|
||||
static var nan: Self {
|
||||
.edges(.nan)
|
||||
}
|
||||
|
||||
static func edges(_ insets: CGFloat) -> UIEdgeInsets {
|
||||
.init(top: insets, left: insets, bottom: insets, right: insets)
|
||||
}
|
||||
|
||||
static func horizontal(_ insets: CGFloat) -> UIEdgeInsets {
|
||||
.init(top: .zero, left: insets, bottom: .zero, right: insets)
|
||||
.init(top: .nan, left: insets, bottom: .nan, right: insets)
|
||||
}
|
||||
|
||||
static func vertical(_ insets: CGFloat) -> UIEdgeInsets {
|
||||
.init(top: insets, left: .zero, bottom: insets, right: .zero)
|
||||
.init(top: insets, left: .nan, bottom: insets, right: .nan)
|
||||
}
|
||||
|
||||
static func horizontal(left: CGFloat = .zero, right: CGFloat = .zero) -> UIEdgeInsets {
|
||||
.init(top: .zero, left: left, bottom: .zero, right: right)
|
||||
static func horizontal(left: CGFloat = .nan, right: CGFloat = .nan) -> UIEdgeInsets {
|
||||
.init(top: .nan, left: left, bottom: .nan, right: right)
|
||||
}
|
||||
|
||||
static func vertical(top: CGFloat = .zero, bottom: CGFloat = .zero) -> UIEdgeInsets {
|
||||
.init(top: top, left: .zero, bottom: bottom, right: .zero)
|
||||
static func vertical(top: CGFloat = .nan, bottom: CGFloat = .nan) -> UIEdgeInsets {
|
||||
.init(top: top, left: .nan, bottom: bottom, right: .nan)
|
||||
}
|
||||
|
||||
// MARK: - Instance methods
|
||||
|
||||
func vertical(onNan defaultValue: CGFloat = .nan) -> CGFloat {
|
||||
add(\.top, to: \.bottom, of: self, onNan: defaultValue)
|
||||
}
|
||||
|
||||
func horizontal(onNan defaultValue: CGFloat = .nan) -> CGFloat {
|
||||
add(\.left, to: \.right, of: self, onNan: defaultValue)
|
||||
}
|
||||
|
||||
func horizontal(_ insets: CGFloat) -> UIEdgeInsets {
|
||||
.init(top: top, left: insets, bottom: bottom, right: insets)
|
||||
}
|
||||
|
|
@ -56,11 +68,38 @@ public extension UIEdgeInsets {
|
|||
.init(top: insets, left: left, bottom: insets, right: right)
|
||||
}
|
||||
|
||||
func horizontal(left: CGFloat = .zero, right: CGFloat = .zero) -> UIEdgeInsets {
|
||||
func horizontal(left: CGFloat = .nan, right: CGFloat = .nan) -> UIEdgeInsets {
|
||||
.init(top: top, left: left, bottom: bottom, right: right)
|
||||
}
|
||||
|
||||
func vertical(top: CGFloat = .zero, bottom: CGFloat = .zero) -> UIEdgeInsets {
|
||||
func vertical(top: CGFloat = .nan, bottom: CGFloat = .nan) -> UIEdgeInsets {
|
||||
.init(top: top, left: left, bottom: bottom, right: right)
|
||||
}
|
||||
|
||||
func replacingNan(with defaultValue: CGFloat) -> Self {
|
||||
.init(top: top.isFinite ? top : defaultValue,
|
||||
left: left.isFinite ? left : defaultValue,
|
||||
bottom: bottom.isFinite ? bottom : defaultValue,
|
||||
right: right.isFinite ? right : defaultValue)
|
||||
}
|
||||
|
||||
func add(_ lhsKeyPath: KeyPath<Self, CGFloat>,
|
||||
to rhsKeyPath: KeyPath<Self, CGFloat>,
|
||||
of rhs: Self,
|
||||
onNan defaultValue: CGFloat = .nan) -> CGFloat {
|
||||
|
||||
let lhsValue = self[keyPath: lhsKeyPath]
|
||||
let rhsValue = rhs[keyPath: rhsKeyPath]
|
||||
|
||||
switch (lhsValue.isFinite, rhsValue.isFinite) {
|
||||
case (true, true):
|
||||
return lhsValue + rhsValue
|
||||
case (true, false):
|
||||
return lhsValue
|
||||
case (false, true):
|
||||
return rhsValue
|
||||
case (false, false):
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIKitCore'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers.'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIWebView'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.0'
|
||||
s.summary = 'Universal web view API'
|
||||
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIYandexMapUtils'
|
||||
s.version = '1.50.0'
|
||||
s.version = '1.51.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' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
|
||||
# TIBottomSheet
|
||||
|
||||
TIBottomSheet содержить базовую реализацию модального котроллера и немного видоизмененную библиотеку PanModal.
|
||||
|
||||
## Базовый контроллер
|
||||
|
||||
Для создания модального котроллера можно унаследоваться от `BaseModalViewController`. Данный клас принимает два generic типа: тип основного контента, тип контента футера.
|
||||
|
||||
```swift
|
||||
import TIBottomSheet
|
||||
import UIKit
|
||||
|
||||
class EmptyViewController: BaseModalViewController<UIView, UIView> { }
|
||||
```
|
||||
|
||||
## Обертка вокруг существующего контроллера
|
||||
|
||||
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `BaseModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
|
||||
|
||||
```swift
|
||||
import TIUIKitCore
|
||||
|
||||
final class OldMassiveViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
}
|
||||
|
||||
typealias ModalOldMassiveViewController = BaseModalWrapperViewController<OldMassiveViewController>
|
||||
|
||||
class PresentingViewController: BaseInitializableViewController {
|
||||
// some implementation
|
||||
|
||||
@objc private func onButtonTapped() {
|
||||
presentPanModal(ModalOldMassiveViewController())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Контент модального контроллера
|
||||
|
||||
Модальный котроллер может содержать следующие элементы: `DragView`, `HeaderView`, `FooterView`. Каждый из них является опциональным и без дополнительных настроек не будет показываться.
|
||||
|
||||
DragView - небольшая view, за которую пользователь "держит" модальный контроллер
|
||||
HeaderView - контейнер, содержащий в себе кнопки назад/закрыть или какие-то другие элементы управления
|
||||
FooterView - view, располагающаяся внизу контроллера, поверх всего контента (модальный контроллер уже настроен так, чтобы при скролле в самый низ, футер не перекрывал последнюю ячейку)
|
||||
|
||||
Для настройки каждого у котроллера есть свойство `viewControllerAppearance`. Через него будет настраиваться весь контроллер. Однако стоит заметить, что котроллер не будет настраивать передаваимую вью, содержащую основной контент. Стандартно котроллер будет пытаться расположить контент так, чтобы он заполнил все пространство.
|
||||
|
||||
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
|
||||
|
||||
```swift
|
||||
let customViewController = BaseModalViewController<UIView, UIView>()
|
||||
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
|
||||
$0.dragViewState = .presented(.defaultAppearance)
|
||||
$0.headerViewState = .presented(.make {
|
||||
$0.layout.size = .fixedHeight(52)
|
||||
$0.backgroundColor = .white
|
||||
$0.contentViewState = .buttonLeft(.init(titles: [.normal: "Close"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: .init(backgroundColor: .blue)
|
||||
])))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## "Якори" контроллера
|
||||
|
||||
Раньше для настройки высоты контроллера необходимо было пользоваться свойствами `longFormHeight`, `shortFormHeight`. В базовом контроллере можно лишь передать список точек на которых контроллер должен будет задержаться:
|
||||
|
||||
```swift
|
||||
let detentsViewController = BaseModalViewController<UIView, UIView>()
|
||||
detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnly, .height(300), .maxHeight]
|
||||
```
|
||||
|
||||
- headerOnly будет сам пытаться вычеслить высоту хедера и dragView, показывая только их
|
||||
- height(_) будет показывать контроллер на переданной высоте
|
||||
- maxHeight - вся высота экрана (до safeArea)
|
||||
|
||||
В данный массив не рекомендуется передавать больше 3 значений, т.к. модальное окно все равно сможет занять только 3 положения на экране.
|
||||
|
||||
## DimmedView и PassthroughDimmedView
|
||||
|
||||
Для контроля `DimmedView` (затемняющей view) есть отдельное свойство `dimmedView`. Эти классы позволяют настраивать поведение при тапе в затемнённую область и кастомизировать затемнение под ваши нужды.
|
||||
|
||||
```swift
|
||||
let shadowViewController = BaseModalViewController<UIView, UIView>()
|
||||
let dimmedView = PassthroughDimmedView()
|
||||
dimmedView.hitTestHandlerView = view
|
||||
dimmedView.configureUIView(appearance: .init(shadow: UIViewShadow(radius: 8,
|
||||
color: .black,
|
||||
opacity: 0.3)))
|
||||
|
||||
shadowViewController.dimmedView = dimmedView
|
||||
```
|
||||
|
||||
## Контроль закрытия
|
||||
|
||||
`PanModalPresentable` не умеет в настройку закрытия контроллера, делая это самостоятельно через `dismiss(animated:completion:)`. Теперь можно настроить закрытие самостоятельно через свойства: `onTapToDismiss` и `onDragToDismiss`.
|
||||
|
||||
# Взаимодействие с PanModal
|
||||
|
||||
Если нет необходимости или возможности использовать `BaseModalViewController`, вы все так же можете пользоваться протоколом `PanModalRepresentable`. Вот список изменений протокола:
|
||||
|
||||
- Открытие/закрытие модального окна теперь можно настроить с помощью свойств `onTapToDismiss` и `onDragToDismiss`
|
||||
- Можно настроить промежуточное состояние модального окна с `mediumFormHeight`
|
||||
- `DimmedView` открыт для наследования и может создаваться в `dimmedView` у
|
||||
|
||||
> Для `BaseModalViewController` все свойства из `PanModalPresentable` все также работают, т.е. вы можете их переопределять, добавлять и изменять по необходимости.
|
||||
|
|
@ -60,10 +60,10 @@ let customStyle = DefaultPlaceholderStyle(
|
|||
}, buttonsStyles: [
|
||||
.init(titles: [.normal: "Reload"],
|
||||
appearance: .init(stateAppearances: [
|
||||
.normal: UIButton.DefaultAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
|
||||
color: .black,
|
||||
alignment: .natural,
|
||||
isMultiline: false))
|
||||
.normal: UIButton.DefaultStateAppearance(textAttributes: .init(font: .systemFont(ofSize: 20),
|
||||
color: .black,
|
||||
alignment: .natural,
|
||||
isMultiline: false))
|
||||
]))
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -20,4 +20,5 @@ TIEcommerce
|
|||
TIWebView
|
||||
TIDeveloperUtils
|
||||
TIDeeplink
|
||||
TITextProcessing
|
||||
TITextProcessing
|
||||
TIBottomSheet
|
||||
|
|
|
|||
|
|
@ -9,14 +9,23 @@
|
|||
# Required environment variables:
|
||||
# SRCROOT - path to project folder.
|
||||
#
|
||||
# Optional environment variables:
|
||||
# MODULE_NAME - single module to push
|
||||
#
|
||||
# Examples of usage:
|
||||
# SRCROOT=`pwd` ./project-scripts/push_to_podspecs.sh
|
||||
#
|
||||
|
||||
for module_name in $(cat ${SRCROOT}/project-scripts/ordered_modules_list.txt); do
|
||||
bundle exec pod repo push https://git.svc.touchin.ru/TouchInstinct/Podspecs ${SRCROOT}/${module_name}/${module_name}.podspec "$@" --allow-warnings
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
GIT_REPO_PATH="git.svc.touchin.ru/TouchInstinct/Podspecs"
|
||||
|
vladimir.makarov marked this conversation as resolved
Outdated
vladimir.makarov
commented
А это так и должно быть, да? А это так и должно быть, да?
ivan.smolin
commented
утёк тестовый код) утёк тестовый код)
|
||||
|
||||
if [ -z "${MODULE_NAME}" ]; then
|
||||
for module_name in $(cat ${SRCROOT}/project-scripts/ordered_modules_list.txt); do
|
||||
bundle exec pod repo push "git@${GIT_REPO_PATH}.git" ${SRCROOT}/${module_name}/${module_name}.podspec "$@" --allow-warnings
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
else
|
||||
bundle exec pod repo push "git@${GIT_REPO_PATH}.git" ${SRCROOT}/${MODULE_NAME}/${MODULE_NAME}.podspec "$@" --allow-warnings
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue
TIBottomSheet тоже надо добавить