diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d42ea9..a16894f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 0.7.13 +- **Update**: Migrate from `Variable` to `BehaviorRelay`. +- **Fix**: `PaginationWrapper` retry load more after fail. +- **Fix**: `safeClear` method of `TableDirector` now creates section without header and footer. +- **Add**: `TableSection` convenience initializer for section without footer and header. + ### 0.7.12 - **Add**: `UniversalMappable` protocol to have ability generate generic mapping models diff --git a/LeadKit.podspec b/LeadKit.podspec index 9f6594da..9f5a917d 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -40,7 +40,7 @@ Pod::Spec.new do |s| "Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingController+DefaultImplementation.swift", "Sources/Extensions/DataLoading/PaginationDataLoading/*", "Sources/Extensions/Support/UIScrollView+Support.swift", - "Sources/Extensions/TableDirector/*", + "Sources/Extensions/TableKit/**/*.swift", "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift", "Sources/Extensions/Drawing/UIImage/*", "Sources/Extensions/UIKit/**/*.swift", @@ -65,7 +65,7 @@ Pod::Spec.new do |s| "Sources/Extensions/NetworkService/NetworkService+ActivityIndicator-UIApplication.swift", "Sources/Extensions/DataLoading/PaginationDataLoading/*", "Sources/Extensions/Support/UIScrollView+Support.swift", - "Sources/Extensions/TableDirector/*", + "Sources/Extensions/TableKit/**/*.swift", "Sources/Extensions/UIKit/UIApplication/UIApplication+OpenUrlSupport.swift", "Sources/Extensions/UIKit/UIApplication/UIApplication+Cellular.swift", "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift", @@ -97,7 +97,7 @@ Pod::Spec.new do |s| "Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift", "Sources/Extensions/NetworkService/NetworkService+ActivityIndicator-UIApplication.swift", "Sources/Extensions/DataLoading/PaginationDataLoading/*", - "Sources/Extensions/TableDirector/*", + "Sources/Extensions/TableKit/**/*.swift", "Sources/Extensions/UIApplication/UIApplication+OpenUrlSupport.swift", "Sources/Extensions/UIApplication/UIApplication+Cellular.swift", "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift", diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index 3d14bcd8..e7db21a3 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -347,6 +347,8 @@ 675C1FB41F97CA32007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; }; 675C1FB51F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; }; 675C1FB61F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; }; + 6762131820A0BBA30034EEF1 /* TableSection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */; }; + 6762131920A0BBA30034EEF1 /* TableSection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */; }; 676B22A2206A626D002E9F8A /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */; }; 676B22A3206A626D002E9F8A /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */; }; 676B22A4206A626D002E9F8A /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */; }; @@ -710,6 +712,7 @@ 673CF4332063E29B00C329F6 /* TextWithButtonPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithButtonPlaceholder.swift; sourceTree = ""; }; 673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeneralDataLoadingController+DefaultImplementation.swift"; sourceTree = ""; }; 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+LoadingIndicator.swift"; sourceTree = ""; }; + 6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableSection+Extensions.swift"; sourceTree = ""; }; 676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormattingService+MappingTransform.swift"; sourceTree = ""; }; 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+PaginationWrappable.swift"; sourceTree = ""; }; @@ -957,7 +960,7 @@ 82F8BB161F5DDED100C1061B /* Single */, 671461FA1EB3396E00EAB194 /* String */, 671461FE1EB3396E00EAB194 /* Support */, - 671462001EB3396E00EAB194 /* TableDirector */, + 6762131520A0BAD40034EEF1 /* TableKit */, 671462021EB3396E00EAB194 /* TimeInterval */, 671462081EB3396E00EAB194 /* UIColor */, 672947E0206EA36B00AC6B6B /* UIKit */, @@ -1425,6 +1428,23 @@ path = UIActivityIndicatorView; sourceTree = ""; }; + 6762131520A0BAD40034EEF1 /* TableKit */ = { + isa = PBXGroup; + children = ( + 6762131620A0BAF70034EEF1 /* TableSection */, + 671462001EB3396E00EAB194 /* TableDirector */, + ); + path = TableKit; + sourceTree = ""; + }; + 6762131620A0BAF70034EEF1 /* TableSection */ = { + isa = PBXGroup; + children = ( + 6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */, + ); + path = TableSection; + sourceTree = ""; + }; 676B22A0206A6249002E9F8A /* NSAttributedString */ = { isa = PBXGroup; children = ( @@ -2633,6 +2653,7 @@ A676AE551F98112E001F9214 /* ObservableMappable.swift in Sources */, A6E0DDE11F8A696F002CA74E /* SeparatorRowBox.swift in Sources */, A6E0DDDE1F8A696F002CA74E /* EmptyCellRow.swift in Sources */, + 6762131820A0BBA30034EEF1 /* TableSection+Extensions.swift in Sources */, 67EB7FFD206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, 671463041EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */, 6774527420624E820024EEEF /* DataLoadingModel.swift in Sources */, @@ -2971,6 +2992,7 @@ 676B22A5206A626D002E9F8A /* NSAttributedString+Extensions.swift in Sources */, 673CF4142063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift in Sources */, 671462DB1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, + 6762131920A0BBA30034EEF1 /* TableSection+Extensions.swift in Sources */, 6774529020625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, 67EB7FC3206140E600BDD9FB /* TotalCountCursor.swift in Sources */, EF24213D2076D5CA00FA9BE6 /* NetworkServiceConfiguration.swift in Sources */, diff --git a/Podfile.lock b/Podfile.lock index 4ae1b881..441e4d61 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Alamofire (4.7.1) + - Alamofire (4.7.2) - ObjectMapper (3.1.0) - RxAlamofire (4.2.0): - RxAlamofire/Core (= 4.2.0) @@ -10,7 +10,7 @@ PODS: - RxSwift (~> 4.0) - RxSwift (4.1.2) - SwiftDate (4.5.1) - - SwiftLint (0.25.0) + - SwiftLint (0.25.1) - TableKit (2.6.0) - UIScrollView-InfiniteScroll (1.0.2) @@ -25,13 +25,13 @@ DEPENDENCIES: - UIScrollView-InfiniteScroll (~> 1.0.0) SPEC CHECKSUMS: - Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30 + Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223 ObjectMapper: 20505058f54e5c3ca69e1d6de9897d152a5369a6 RxAlamofire: 87a9c588541210cc3e4a1f843ccc3ecf3eb98b31 RxCocoa: d88ba0f1f6abf040011a9eb4b539324fc426843a RxSwift: e49536837d9901277638493ea537394d4b55f570 SwiftDate: 7b56d42a221f582047287deb256b23fc5ed49a60 - SwiftLint: e14651157288e9e01d6e1a71db7014fb5744a8ea + SwiftLint: ce933681be10c3266e82576dad676fa815a602e9 TableKit: 61880e4c13ac0ba396a308fcb1ae48f6dec8b458 UIScrollView-InfiniteScroll: c132d6d5851daff229ab4a1060ccf70a05a051c9 diff --git a/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift index 646095f3..e80a7707 100644 --- a/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift +++ b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift @@ -31,7 +31,7 @@ open class GeneralDataLoadingViewModel: BaseViewModel { private let loadingModel: LoadingModel - private let loadingStateVariable = Variable(.initial) + private let loadingStateRelay = BehaviorRelay(value: .initial) public let disposeBag = DisposeBag() @@ -46,7 +46,7 @@ open class GeneralDataLoadingViewModel: BaseViewModel { loadingModel = LoadingModel(dataSource: dataSource, emptyResultChecker: emptyResultChecker) loadingModel.stateDriver - .drive(loadingStateVariable) + .drive(loadingStateRelay) .disposed(by: disposeBag) loadingModel.reload() @@ -54,7 +54,7 @@ open class GeneralDataLoadingViewModel: BaseViewModel { /// Returns driver that emits current loading state open var loadingStateDriver: Driver { - return loadingStateVariable.asDriver() + return loadingStateRelay.asDriver() } /// By default returns true if loading state == .result. @@ -70,10 +70,10 @@ open class GeneralDataLoadingViewModel: BaseViewModel { /// Current state of loading process. private(set) public var currentLoadingState: LoadingState { get { - return loadingStateVariable.value + return loadingStateRelay.value } set { - loadingStateVariable.value = newValue + loadingStateRelay.accept(newValue) } } diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift index ab81267a..e222f183 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -63,7 +63,7 @@ final public class PaginationWrapper(true) + private let applicationCurrentyActive = BehaviorRelay(value: true) /// Initializer with table view, placeholders container view, cusor and delegate parameters. /// @@ -193,7 +193,7 @@ final public class PaginationWrapper { + var retryEvent: Binder { return Binder(self) { base, _ in base.paginationViewModel.loadMore() } } - var reloadEvent: Binder<()> { + var reloadEvent: Binder { return Binder(self) { base, _ in base.reload() } diff --git a/Sources/Classes/DataLoading/RxDataLoadingModel.swift b/Sources/Classes/DataLoading/RxDataLoadingModel.swift index d1acee29..0d3d1fbc 100644 --- a/Sources/Classes/DataLoading/RxDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/RxDataLoadingModel.swift @@ -31,14 +31,14 @@ open class RxDataLoadingModel: DataLoadingMo public typealias EmptyResultChecker = (ResultType) -> Bool - private let stateVariable = Variable(.initialState) + private let stateRelay = BehaviorRelay(value: .initialState) var currentRequestDisposable: Disposable? var dataSource: DataSourceType let emptyResultChecker: EmptyResultChecker open var stateDriver: Driver { - return stateVariable.asDriver() + return stateRelay.asDriver() } public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) { @@ -86,10 +86,10 @@ open class RxDataLoadingModel: DataLoadingMo var state: LoadingStateType { get { - return stateVariable.value + return stateRelay.value } set { - stateVariable.value = newValue + stateRelay.accept(newValue) } } diff --git a/Sources/Classes/Services/NetworkService.swift b/Sources/Classes/Services/NetworkService.swift index 607c8636..3de3ec92 100644 --- a/Sources/Classes/Services/NetworkService.swift +++ b/Sources/Classes/Services/NetworkService.swift @@ -30,17 +30,17 @@ import RxAlamofire /// Has an ability to automatically show / hide network activity indicator open class NetworkService { - /// Enable synchronization for setting variable from different thread + /// Enable synchronization for setting behaviour relay from different thread private let lock = NSRecursiveLock() - private let requestCountVariable = Variable(0) + private let requestCountRelay = BehaviorRelay(value: 0) private var disposeBag = DisposeBag() public let configuration: NetworkServiceConfiguration public let sessionManager: Alamofire.SessionManager var requestCount: Driver { - return requestCountVariable.asDriver() + return requestCountRelay.asDriver() } /// - Parameter sessionManager: Alamofire.SessionManager to use for requests @@ -98,13 +98,13 @@ private extension NetworkService { func increaseRequestCounter() { lock.lock() - requestCountVariable.value += 1 + requestCountRelay.accept(requestCountRelay.value + 1) lock.unlock() } func decreaseRequestCounter() { lock.lock() - requestCountVariable.value -= 1 + requestCountRelay.accept(requestCountRelay.value - 1) lock.unlock() } diff --git a/Sources/Classes/Views/DefaultPlaceholders/TextWithButtonPlaceholder.swift b/Sources/Classes/Views/DefaultPlaceholders/TextWithButtonPlaceholder.swift index 4ac62d2b..3cb90847 100644 --- a/Sources/Classes/Views/DefaultPlaceholders/TextWithButtonPlaceholder.swift +++ b/Sources/Classes/Views/DefaultPlaceholders/TextWithButtonPlaceholder.swift @@ -47,6 +47,8 @@ internal final class TextWithButtonPlaceholder: UIView { stackView.axis = .vertical addSubview(stackView) + + stackView.setToCenter(withSize: nil) } required init?(coder aDecoder: NSCoder) { diff --git a/Sources/Classes/Views/TextField/DataModelFieldBinding.swift b/Sources/Classes/Views/TextField/DataModelFieldBinding.swift index 03264013..09219535 100644 --- a/Sources/Classes/Views/TextField/DataModelFieldBinding.swift +++ b/Sources/Classes/Views/TextField/DataModelFieldBinding.swift @@ -29,7 +29,7 @@ public final class DataModelFieldBinding { public typealias GetFieldClosure = (T) -> String? public typealias MergeFieldClosure = (T, String?) -> T - private let modelVariable: Variable + private let modelRelay: BehaviorRelay private let modelDriver: Driver private let getFieldClosure: DataModelFieldBinding.GetFieldClosure private let mergeFieldClosure: DataModelFieldBinding.MergeFieldClosure @@ -37,16 +37,16 @@ public final class DataModelFieldBinding { /// Memberwise initializer. /// /// - Parameters: - /// - modelVariable: Variable that contains data model. + /// - modelRelay: BehaviourRelay that contains data model. /// - modelDriver: Driver that emits new data models. /// - getFieldClosure: Closure for getting field string reprerentation from data model. /// - mergeFieldClosure: Closure for merging new field value into data model. - public init(modelVariable: Variable, + public init(modelRelay: BehaviorRelay, modelDriver: Driver, getFieldClosure: @escaping GetFieldClosure, mergeFieldClosure: @escaping MergeFieldClosure) { - self.modelVariable = modelVariable + self.modelRelay = modelRelay self.modelDriver = modelDriver self.getFieldClosure = getFieldClosure self.mergeFieldClosure = mergeFieldClosure @@ -55,12 +55,12 @@ public final class DataModelFieldBinding { /// Method that merges new field values with data model. /// /// - Parameter textDriver: Driver that emits new text values. - /// - Returns: Disposable object that can be used to unsubscribe the observer from the variable. + /// - Returns: Disposable object that can be used to unsubscribe the observer from the behaviour relay. public func mergeStringToModel(from textDriver: Driver) -> Disposable { - return textDriver.map { [modelVariable, mergeFieldClosure] in - mergeFieldClosure(modelVariable.value, $0) + return textDriver.map { [modelRelay, mergeFieldClosure] in + mergeFieldClosure(modelRelay.value, $0) } - .drive(modelVariable) + .drive(modelRelay) } /// A Driver that will emit current field value. @@ -72,18 +72,18 @@ public final class DataModelFieldBinding { public extension DataModelFieldBinding { - /// Convenience initializer without modelDriver, which will be obtained from modelVariable. + /// Convenience initializer without modelDriver, which will be obtained from modelRelay. /// /// - Parameters: - /// - modelVariable: Variable that contains data model. + /// - modelRelay: BehaviourRelay that contains data model. /// - getFieldClosure: Closure for getting field string reprerentation from data model. /// - mergeFieldClosure: Closure for merging new field value into data model. - convenience init(modelVariable: Variable, + convenience init(modelRelay: BehaviorRelay, getFieldClosure: @escaping GetFieldClosure, mergeFieldClosure: @escaping MergeFieldClosure) { - self.init(modelVariable: modelVariable, - modelDriver: modelVariable.asDriver(), + self.init(modelRelay: modelRelay, + modelDriver: modelRelay.asDriver(), getFieldClosure: getFieldClosure, mergeFieldClosure: mergeFieldClosure) } @@ -94,19 +94,19 @@ public extension DataModelFieldBinding where T == String? { /// Convenience initializer for data model of string. /// - /// - Parameter modelVariable: Variable that contains data model. - convenience init(modelVariable: Variable) { - self.init(modelVariable: modelVariable, - modelDriver: modelVariable.asDriver(), + /// - Parameter modelRelay: BehaviourRelay that contains data model. + convenience init(modelRelay: BehaviorRelay) { + self.init(modelRelay: modelRelay, + modelDriver: modelRelay.asDriver(), getFieldClosure: { $0 }, mergeFieldClosure: { $1 }) } } -public extension Variable { +public extension BehaviorRelay { - /// Creates DataModelFieldBinding configured with given closures and variable itself. + /// Creates DataModelFieldBinding configured with given closures and behaviour relay itself. /// /// - Parameters: /// - getFieldClosure: Closure for getting field string reprerentation from data model. @@ -115,20 +115,20 @@ public extension Variable { func fieldBinding(getFieldClosure: @escaping DataModelFieldBinding.GetFieldClosure, mergeFieldClosure: @escaping DataModelFieldBinding.MergeFieldClosure) -> DataModelFieldBinding { - return DataModelFieldBinding(modelVariable: self, + return DataModelFieldBinding(modelRelay: self, getFieldClosure: getFieldClosure, mergeFieldClosure: mergeFieldClosure) } } -public extension Variable where Element == String? { +public extension BehaviorRelay where Element == String? { - /// Creates DataModelFieldBinding configured with variable itself. + /// Creates DataModelFieldBinding configured with behaviour relay itself. /// /// - Returns: DataModelFieldBinding instance. func fieldBinding() -> DataModelFieldBinding { - return DataModelFieldBinding(modelVariable: self) + return DataModelFieldBinding(modelRelay: self) } } diff --git a/Sources/Classes/Views/TextField/TextFieldViewModel.swift b/Sources/Classes/Views/TextField/TextFieldViewModel.swift index 8761f99d..65273e7b 100644 --- a/Sources/Classes/Views/TextField/TextFieldViewModel.swift +++ b/Sources/Classes/Views/TextField/TextFieldViewModel.swift @@ -30,7 +30,7 @@ open class TextFieldViewModel(nil) + private let viewEventsRelay = BehaviorRelay(value: nil) private(set) public var disposeBag = DisposeBag() @@ -44,7 +44,7 @@ open class TextFieldViewModel { - return viewEventsVariable + return viewEventsRelay .asDriver() .flatMap { viewEvents -> Driver in guard let viewEvents = viewEvents else { @@ -59,7 +59,7 @@ open class TextFieldViewModel