- 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.
This commit is contained in:
Ivan Smolin 2018-05-11 20:14:42 +03:00
parent bb7fe4f7f2
commit 7249bf66dc
16 changed files with 102 additions and 54 deletions

View File

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

View File

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

View File

@ -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 = "<group>"; };
673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeneralDataLoadingController+DefaultImplementation.swift"; sourceTree = "<group>"; };
674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+LoadingIndicator.swift"; sourceTree = "<group>"; };
6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableSection+Extensions.swift"; sourceTree = "<group>"; };
676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormattingService+MappingTransform.swift"; sourceTree = "<group>"; };
67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+PaginationWrappable.swift"; sourceTree = "<group>"; };
@ -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 = "<group>";
};
6762131520A0BAD40034EEF1 /* TableKit */ = {
isa = PBXGroup;
children = (
6762131620A0BAF70034EEF1 /* TableSection */,
671462001EB3396E00EAB194 /* TableDirector */,
);
path = TableKit;
sourceTree = "<group>";
};
6762131620A0BAF70034EEF1 /* TableSection */ = {
isa = PBXGroup;
children = (
6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */,
);
path = TableSection;
sourceTree = "<group>";
};
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 */,

View File

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

View File

@ -31,7 +31,7 @@ open class GeneralDataLoadingViewModel<ResultType>: BaseViewModel {
private let loadingModel: LoadingModel
private let loadingStateVariable = Variable<LoadingState>(.initial)
private let loadingStateRelay = BehaviorRelay<LoadingState>(value: .initial)
public let disposeBag = DisposeBag()
@ -46,7 +46,7 @@ open class GeneralDataLoadingViewModel<ResultType>: 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<ResultType>: BaseViewModel {
/// Returns driver that emits current loading state
open var loadingStateDriver: Driver<LoadingState> {
return loadingStateVariable.asDriver()
return loadingStateRelay.asDriver()
}
/// By default returns true if loading state == .result.
@ -70,10 +70,10 @@ open class GeneralDataLoadingViewModel<ResultType>: 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)
}
}

View File

@ -63,7 +63,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
private var currentPlaceholderView: UIView?
private var currentPlaceholderViewTopConstraint: NSLayoutConstraint?
private let applicationCurrentyActive = Variable<Bool>(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<Cursor: ResettableRxDataSourceCursor, Deleg
retryButton.rx
.controlEvent(.touchUpInside)
.asDriver()
.drive(reloadEvent)
.drive(retryEvent)
.disposed(by: disposeBag)
delegate?.footerRetryButtonWillAppear()
@ -350,13 +350,13 @@ private extension PaginationWrapper {
}
}
var retryEvent: Binder<()> {
var retryEvent: Binder<Void> {
return Binder(self) { base, _ in
base.paginationViewModel.loadMore()
}
}
var reloadEvent: Binder<()> {
var reloadEvent: Binder<Void> {
return Binder(self) { base, _ in
base.reload()
}

View File

@ -31,14 +31,14 @@ open class RxDataLoadingModel<LoadingStateType: DataLoadingState>: DataLoadingMo
public typealias EmptyResultChecker = (ResultType) -> Bool
private let stateVariable = Variable<LoadingStateType>(.initialState)
private let stateRelay = BehaviorRelay<LoadingStateType>(value: .initialState)
var currentRequestDisposable: Disposable?
var dataSource: DataSourceType
let emptyResultChecker: EmptyResultChecker
open var stateDriver: Driver<LoadingStateType> {
return stateVariable.asDriver()
return stateRelay.asDriver()
}
public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) {
@ -86,10 +86,10 @@ open class RxDataLoadingModel<LoadingStateType: DataLoadingState>: DataLoadingMo
var state: LoadingStateType {
get {
return stateVariable.value
return stateRelay.value
}
set {
stateVariable.value = newValue
stateRelay.accept(newValue)
}
}

View File

@ -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<Int>(0)
private let requestCountRelay = BehaviorRelay(value: 0)
private var disposeBag = DisposeBag()
public let configuration: NetworkServiceConfiguration
public let sessionManager: Alamofire.SessionManager
var requestCount: Driver<Int> {
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()
}

View File

@ -47,6 +47,8 @@ internal final class TextWithButtonPlaceholder: UIView {
stackView.axis = .vertical
addSubview(stackView)
stackView.setToCenter(withSize: nil)
}
required init?(coder aDecoder: NSCoder) {

View File

@ -29,7 +29,7 @@ public final class DataModelFieldBinding<T> {
public typealias GetFieldClosure = (T) -> String?
public typealias MergeFieldClosure = (T, String?) -> T
private let modelVariable: Variable<T>
private let modelRelay: BehaviorRelay<T>
private let modelDriver: Driver<T>
private let getFieldClosure: DataModelFieldBinding<T>.GetFieldClosure
private let mergeFieldClosure: DataModelFieldBinding<T>.MergeFieldClosure
@ -37,16 +37,16 @@ public final class DataModelFieldBinding<T> {
/// 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<T>,
public init(modelRelay: BehaviorRelay<T>,
modelDriver: Driver<T>,
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<T> {
/// 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<String?>) -> 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<T> {
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<T>,
convenience init(modelRelay: BehaviorRelay<T>,
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<T>) {
self.init(modelVariable: modelVariable,
modelDriver: modelVariable.asDriver(),
/// - Parameter modelRelay: BehaviourRelay that contains data model.
convenience init(modelRelay: BehaviorRelay<T>) {
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<E>.GetFieldClosure,
mergeFieldClosure: @escaping DataModelFieldBinding<E>.MergeFieldClosure) -> DataModelFieldBinding<E> {
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<E> {
return DataModelFieldBinding(modelVariable: self)
return DataModelFieldBinding(modelRelay: self)
}
}

View File

@ -30,7 +30,7 @@ open class TextFieldViewModel<ViewEvents: TextFieldViewEvents,
/// Events that can be emitted by view model.
public let viewModelEvents: ViewModelEvents
private let viewEventsVariable = Variable<ViewEvents?>(nil)
private let viewEventsRelay = BehaviorRelay<ViewEvents?>(value: nil)
private(set) public var disposeBag = DisposeBag()
@ -44,7 +44,7 @@ open class TextFieldViewModel<ViewEvents: TextFieldViewEvents,
/// View events driver that will emit view events structure
/// when view will bind itself to the view model.
public var viewEventsDriver: Driver<ViewEvents> {
return viewEventsVariable
return viewEventsRelay
.asDriver()
.flatMap { viewEvents -> Driver<ViewEvents> in
guard let viewEvents = viewEvents else {
@ -59,7 +59,7 @@ open class TextFieldViewModel<ViewEvents: TextFieldViewEvents,
///
/// - Parameter viewEvents: View events structure.
public func bind(viewEvents: ViewEvents) {
viewEventsVariable.value = viewEvents
viewEventsRelay.accept(viewEvents)
}
/// Unbinds view from view model.

View File

@ -113,7 +113,7 @@ public extension TableDirector {
/// Clear table view and reload it within empty section
func safeClear() {
clear().append(section: TableSection()).reload()
clear().append(section: TableSection(onlyRows: [])).reload()
}
}

View File

@ -0,0 +1,18 @@
import TableKit
public extension TableSection {
/// Initializes section with rows and zero height footer and header.
///
/// - Parameter rows: Rows to insert into section.
convenience init(onlyRows rows: [Row]) {
self.init(rows: rows)
self.headerView = nil
self.footerView = nil
self.headerHeight = .leastNonzeroMagnitude
self.footerHeight = .leastNonzeroMagnitude
}
}

View File

@ -23,7 +23,7 @@
import RxCocoa
/// Protocol that describes data loading process
/// with methods reload & retry and current state driver variable.
/// with methods reload & retry and current state driver.
public protocol DataLoadingModel {
associatedtype LoadingStateType: DataLoadingState