diff --git a/CHANGELOG.md b/CHANGELOG.md index 2387df8a..884b702f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 0.9.38 +- **Add**: `BaseRxTableViewCell` is subclass of `UITableViewCell` class with support `InitializableView` and `DisposeBagHolder` protocols. +- **Add**: `ContainerTableCell` is container class that provides wrapping any `UIView` into `UITableViewCell`. +- **Add**: `BaseTappableViewModel` is simplifies interaction between view and viewModel for events of tapping. +- **Add**: `VoidTappableViewModel` is subclass of `BaseTappableViewModel` class with void payload type. + ### 0.9.37 - **Fix**: ScrollView content offset of `PaginationWrapper` for iOS 13. - **Fix**: Load more request crash of `PaginationWrapper`. diff --git a/LeadKit.podspec b/LeadKit.podspec index 047c7558..e3b2ae6b 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "0.9.37" + s.version = "0.9.38" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index b5340cff..66fb2a14 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -19,6 +19,10 @@ 4CF65D1424DD684A0006B001 /* ButtonHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF65D1324DD684A0006B001 /* ButtonHolder.swift */; }; 4CF65D1624DD69250006B001 /* UIButton+ButtonHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF65D1524DD69250006B001 /* UIButton+ButtonHolder.swift */; }; 4CF65D1824DD6C080006B001 /* ButtonHolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF65D1724DD6C080006B001 /* ButtonHolderView.swift */; }; + 52421F8D24EAB52E00948DD1 /* ContainerTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52421F8C24EAB52E00948DD1 /* ContainerTableCell.swift */; }; + 52421F8F24EAB84900948DD1 /* BaseRxTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52421F8E24EAB84900948DD1 /* BaseRxTableViewCell.swift */; }; + 52421F9424EBCFAE00948DD1 /* VoidTappableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52421F9324EBCFAE00948DD1 /* VoidTappableViewModel.swift */; }; + 52421F9624EBCFBB00948DD1 /* BaseTappableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52421F9524EBCFBB00948DD1 /* BaseTappableViewModel.swift */; }; 67051ADB1EBC7C36008EADC0 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */; }; 67051ADD1EBC7C36008EADC0 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */; }; 6713C23720AF0C4D00875921 /* NetworkOperationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6713C23620AF0C4D00875921 /* NetworkOperationState.swift */; }; @@ -556,6 +560,10 @@ 4CF65D1324DD684A0006B001 /* ButtonHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonHolder.swift; sourceTree = ""; }; 4CF65D1524DD69250006B001 /* UIButton+ButtonHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+ButtonHolder.swift"; sourceTree = ""; }; 4CF65D1724DD6C080006B001 /* ButtonHolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonHolderView.swift; sourceTree = ""; }; + 52421F8C24EAB52E00948DD1 /* ContainerTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerTableCell.swift; sourceTree = ""; }; + 52421F8E24EAB84900948DD1 /* BaseRxTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseRxTableViewCell.swift; sourceTree = ""; }; + 52421F9324EBCFAE00948DD1 /* VoidTappableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoidTappableViewModel.swift; sourceTree = ""; }; + 52421F9524EBCFBB00948DD1 /* BaseTappableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTappableViewModel.swift; sourceTree = ""; }; 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; 6713C23620AF0C4D00875921 /* NetworkOperationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkOperationState.swift; sourceTree = ""; }; 6713C23B20AF0D5900875921 /* NetworkOperationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkOperationModel.swift; sourceTree = ""; }; @@ -874,6 +882,39 @@ path = ButtonHolder; sourceTree = ""; }; + 52421F8B24EAB52E00948DD1 /* ContainerTableCell */ = { + isa = PBXGroup; + children = ( + 52421F8C24EAB52E00948DD1 /* ContainerTableCell.swift */, + ); + path = ContainerTableCell; + sourceTree = ""; + }; + 52421F9024EAB84E00948DD1 /* BaseRxTableViewCell */ = { + isa = PBXGroup; + children = ( + 52421F8E24EAB84900948DD1 /* BaseRxTableViewCell.swift */, + ); + path = BaseRxTableViewCell; + sourceTree = ""; + }; + 52421F9124EBCF6E00948DD1 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 52421F9224EBCF8600948DD1 /* TappableViewModel */, + ); + path = ViewModels; + sourceTree = ""; + }; + 52421F9224EBCF8600948DD1 /* TappableViewModel */ = { + isa = PBXGroup; + children = ( + 52421F9524EBCFBB00948DD1 /* BaseTappableViewModel.swift */, + 52421F9324EBCFAE00948DD1 /* VoidTappableViewModel.swift */, + ); + path = TappableViewModel; + sourceTree = ""; + }; 671461C41EB3396E00EAB194 /* Classes */ = { isa = PBXGroup; children = ( @@ -882,6 +923,7 @@ 6774527E2062566D0024EEEF /* DataLoading */, 671461D21EB3396E00EAB194 /* Services */, 671461D41EB3396E00EAB194 /* Views */, + 52421F9124EBCF6E00948DD1 /* ViewModels */, ); path = Classes; sourceTree = ""; @@ -919,6 +961,8 @@ 671461D41EB3396E00EAB194 /* Views */ = { isa = PBXGroup; children = ( + 52421F9024EAB84E00948DD1 /* BaseRxTableViewCell */, + 52421F8B24EAB52E00948DD1 /* ContainerTableCell */, 72005A1A2266226800ECE090 /* CustomizableButton */, 677B06B6211873E7006C947D /* BasePlaceholderView */, 67DB77672108714A001CB56B /* CollectionViewWrapperView */, @@ -2412,6 +2456,7 @@ 678D26A420692BFF00B05B93 /* TextFieldViewModelEvents.swift in Sources */, 671462801EB3396E00EAB194 /* DataRequest+Extensions.swift in Sources */, 67EB7FF8206175F700BDD9FB /* PaginationWrappable.swift in Sources */, + 52421F9624EBCFBB00948DD1 /* BaseTappableViewModel.swift in Sources */, 67990AD6213EA6A50040D195 /* ContentLoadingViewModel+Extensions.swift in Sources */, 671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 4CF65D1824DD6C080006B001 /* ButtonHolderView.swift in Sources */, @@ -2557,8 +2602,10 @@ 411073AF23466B41002DD9B9 /* UIViewController+PresentFullScreen.swift in Sources */, 671462941EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, 6741CEA920E2418B00FEC4D9 /* CollectionViewHolder.swift in Sources */, + 52421F9424EBCFAE00948DD1 /* VoidTappableViewModel.swift in Sources */, 67745279206252020024EEEF /* DataLoadingState.swift in Sources */, 671463641EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, + 52421F8F24EAB84900948DD1 /* BaseRxTableViewCell.swift in Sources */, 67EB7FDA20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, 671462481EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462C81EB3396E00EAB194 /* String+Localization.swift in Sources */, @@ -2606,6 +2653,7 @@ 678D267920691D8200B05B93 /* DataModelFieldBinding.swift in Sources */, 72AECC71224A97F100D12E7C /* SearchResultsViewController.swift in Sources */, 673CF4342063E29B00C329F6 /* TextWithButtonPlaceholder.swift in Sources */, + 52421F8D24EAB52E00948DD1 /* ContainerTableCell.swift in Sources */, 673CF4222063D90600C329F6 /* DisposeBagHolder.swift in Sources */, 67DB776D210871E8001CB56B /* BaseCollectionContentController.swift in Sources */, 82B4F8DB223903B800F6708C /* Block.swift in Sources */, diff --git a/Sources/Classes/ViewModels/TappableViewModel/BaseTappableViewModel.swift b/Sources/Classes/ViewModels/TappableViewModel/BaseTappableViewModel.swift new file mode 100644 index 00000000..c0d58183 --- /dev/null +++ b/Sources/Classes/ViewModels/TappableViewModel/BaseTappableViewModel.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2020 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 RxCocoa +import RxSwift + +open class BaseTappableViewModel { + private let tapRelay = PublishRelay() + + public var tapDriver: Driver { + tapRelay.asDriver(onErrorDriveWith: .empty()) + } + + public var tapObservable: Observable { + tapRelay.asObservable() + } + + public func bind(tapObservable: Observable) -> Disposable { + tapObservable.bind(to: tapRelay) + } + + public func tap(payload: PayloadType) { + tapRelay.accept(payload) + } +} diff --git a/Sources/Classes/ViewModels/TappableViewModel/VoidTappableViewModel.swift b/Sources/Classes/ViewModels/TappableViewModel/VoidTappableViewModel.swift new file mode 100644 index 00000000..589ab45f --- /dev/null +++ b/Sources/Classes/ViewModels/TappableViewModel/VoidTappableViewModel.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class VoidTappableViewModel: BaseTappableViewModel { + public func tap() { + tap(payload: ()) + } +} diff --git a/Sources/Classes/Views/BaseRxTableViewCell/BaseRxTableViewCell.swift b/Sources/Classes/Views/BaseRxTableViewCell/BaseRxTableViewCell.swift new file mode 100644 index 00000000..4ec4ff74 --- /dev/null +++ b/Sources/Classes/Views/BaseRxTableViewCell/BaseRxTableViewCell.swift @@ -0,0 +1,73 @@ +// +// Copyright (c) 2020 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 RxSwift + +open class BaseRxTableViewCell: UITableViewCell, InitializableView, DisposeBagHolder { + + // MARK: - Properties + + public var disposeBag = DisposeBag() + + // MARK: - Initialization + + override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .default, reuseIdentifier: reuseIdentifier) + + initializeView() + } + + @available(*, unavailable) + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + // MARK: - Override + + override open func prepareForReuse() { + super.prepareForReuse() + + disposeBag = DisposeBag() + } + + // MARK: - InitializableView + + open func addViews() { + // overriding + } + + open func bindViews() { + // overriding + } + + open func configureLayout() { + // overriding + } + + open func configureAppearance() { + selectionStyle = .none + } + + open func localize() { + // overriding + } +} diff --git a/Sources/Classes/Views/ContainerTableCell/ContainerTableCell.swift b/Sources/Classes/Views/ContainerTableCell/ContainerTableCell.swift new file mode 100644 index 00000000..c30752d0 --- /dev/null +++ b/Sources/Classes/Views/ContainerTableCell/ContainerTableCell.swift @@ -0,0 +1,79 @@ +// +// Copyright (c) 2020 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 RxSwift +import TableKit + +open class ContainerTableCell: BaseRxTableViewCell, ConfigurableCell where TView: ConfigurableView { + + // MARK: - Properties + + private let wrappedView = TView() + + open var shouldConfigureDefaultConstraints: Bool { + true + } + + open var contentInsets: UIEdgeInsets { + .zero + } + + open var contentViewBackgroundColor: UIColor { + .clear + } + + // MARK: - ConfigurableCell + + open func configure(with viewModel: TView.ViewModelType) { + disposeBag = DisposeBag() + wrappedView.configure(with: viewModel) + } + + // MARK: - InitializableView + + override open func addViews() { + super.addViews() + + contentView.addSubview(wrappedView) + } + + override open func configureLayout() { + super.configureLayout() + + if shouldConfigureDefaultConstraints { + wrappedView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(contentInsets) + } + } else { + configureCustomConstraints(forWrappedView: wrappedView) + } + } + + override open func configureAppearance() { + super.configureAppearance() + + contentView.backgroundColor = contentViewBackgroundColor + backgroundColor = contentViewBackgroundColor + } + + open func configureCustomConstraints(forWrappedView view: TView) { } +}