Как обновить ViewModel в UITablecontroller?
Есть следующая модель на RX Swift.
protocol MessagesViewModelProtocolChanges {
var reloading: Driver<Bool>! { get }
var errorOccured: Driver<String>! { get }
var sections:BehaviorSubject<[ListSection]> {get}
func configure(bindings: MessagesViewModelBindingsChanges)
}
struct MessagesViewModelBindingsChanges {
let didTriggerRefreshControll: Driver<Void>
let didPressNewMessageButton: Driver<Void>
let didTriggerLoadMore: Driver<Void>
let didChangeSegment: Driver<Int>
let didDidSelectItem: Driver<IndexPath>
}
final class MessagesViewModelChanges:BaseViewModel, MessagesViewModelProtocolChanges {
typealias Dependency = HasUserStorage & HasMessagesStorage & HasMessagesService
var dependencies: Dependency
var messages_:PublishSubject<[Message]> = PublishSubject<[Message]>()
var sections: BehaviorSubject<[ListSection]> = BehaviorSubject<[ListSection]>(value: [])
let router: MessagesRouter
var sentMsgLoaded = false
let canLoadMore = BehaviorRelay<Bool>(value: false)
var currentStudentName: Driver<String>!
var reloading: Driver<Bool>!
var errorOccured: Driver<String>!
let messageType = BehaviorRelay<MessageType>(value: .inbox)
let pageRelay = BehaviorRelay<Int>(value: 1)
let loadingRelay = BehaviorRelay<Bool>(value: false)
init(dependencies: Dependency, router: MessagesRouter) {
self.dependencies = dependencies
self.router = router
}
func configure(bindings: MessagesViewModelBindingsChanges) {
let deps = dependencies
let activityTracker = ActivityIndicator()
let errorTracker = ErrorTracker()
let _ = bindings.didChangeSegment.map({ index -> MessageType in
if index == 0 {
return MessageType.inbox
} else {
return MessageType.sent
}
}).drive(messageType)
let messageSentNotification = NotificationCenter.default.rx
.notification(InAppNotifications.MessageSent)
.mapToVoid()
.asDriver(onErrorJustReturn: ())
let updateFromNotification = NotificationCenter.default.rx
.notification(InAppNotifications.NotificationReceived)
.mapToVoid()
.asDriverOnErrorJustComplete()
let reloadTrigger = Driver.merge(bindings.didTriggerRefreshControll,
updateFromNotification,
messageSentNotification,
messageType.mapToVoid().asDriver(onErrorJustReturn: ()))
.do(onNext: { [unowned self] in
self.pageRelay.accept(1)
self.canLoadMore.accept(false)
})
let loadMoreTrigger = bindings
.didTriggerLoadMore
.withLatestFrom(canLoadMore.asDriver()).filter({ $0 })
.withLatestFrom(loadingRelay.asDriver()).filter({ !$0 })
.map({ _ in () })
.do(onNext: { [unowned self] _ in
self.pageRelay.accept(self.pageRelay.value + 1)
})
let trigger = Driver.merge([reloadTrigger, loadMoreTrigger])
trigger.asObservable()
.withLatestFrom(pageRelay)
.withLatestFrom(messageType) { return ($0, $1) }
.observeOn(ConcurrentDispatchQueueScheduler.userInitiated)
.flatMap { (page, type) -> Observable<[Message]> in
return deps.messagesService
.messages(type: type, limit: Constants.updatesLoadingPageSize, page: page).asObservable()
.flatMap({ fetchedMessages -> Observable<[Message]> in
print("reloaded \(fetchedMessages.count) \(page)")
if page == 1 {
return deps.messagesStorage.save(messages: fetchedMessages, type: type)
} else {
return deps.messagesStorage.add(messages: fetchedMessages)
}
})
.trackActivity(activityTracker)
.trackError(errorTracker)
.catchError({ _ in return .empty() })
}
.bind { [weak self] messages in
self?.messages_.onNext(messages)
}.disposed(by: disposeBag)
activityTracker.asObservable().bind(to: loadingRelay).disposed(by: disposeBag)
reloading = activityTracker.asDriver()
self.messages_.asObservable().withLatestFrom(canLoadMore) { messages, canLoadMore in return (messages, canLoadMore) }.bind { [unowned self] data in
let messages = data.0
let canLoadMore = data.1
var sctns = [ListSection]()
print("CURRENT MESSAGES COUNT \(messages.count)")
let unreadMsgCount = messages.filter({$0.isUnread}).count
ApplicationBadgeManager.shared.unreadMsgCount = unreadMsgCount
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
if self.messageType.value == MessageType.inbox {
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: Constants.reloadSideMenuCellNotif)))
}
}
if messages.count == 0 {
sctns = [ListSection(model: EmptySectionModelName, items: [.empty])]
} else {
let items = messages.map({ ListPresentableItem.custom($0) })
sctns.append(ListSection(model: "currentMessages", items: items))
}
if sctns.count > 0 && canLoadMore {
sctns.append(ListSection(model: LoadingSectionModelName, items: [ListPresentableItem.loading]))
}
self.sections.onNext(sctns)
}.disposed(by: disposeBag)
bindings.didDidSelectItem.asObservable().withLatestFrom(sections) {
ip, sections in return (ip, sections)
}.bind { [weak self] data in
let ip = data.0
let sections = data.1
let model = sections[ip.section].model
guard model != LoadingSectionModelName, model != EmptySectionModelName else {
return
}
guard case ListPresentableItem.custom(let item) = sections[ip.section].items[ip.row],
let message = item as? Message else {
return
}
self?.router.openMessage(with: message.id, type: message.type)
}.disposed(by: disposeBag)
bindings.didPressNewMessageButton.drive(onNext: { [weak self] in
self?.router.openNewMessageScreen()
}).disposed(by: disposeBag)
errorOccured = errorTracker.asDriver().map({ $0.localizedDescription })
}
}
А так же мой Uicontroller
class MessagesController: ViewController {
let disposeBag = DisposeBag()
let willDisplayLastCellSubject = PublishSubject<Void>()
var viewModel: MessagesViewModelChanges!
var dataSource: RxTableViewSectionedReloadDataSource<ListSection>!
@IBOutlet weak var segmentControl: UISegmentedControl!
@IBOutlet weak var vSegmentContainer: UIView!
private let refreshControl = UIRefreshControl()
@IBOutlet weak var tableView: UITableView!
weak var btnWriteMessage: UIBarButtonItem!
@IBOutlet weak var cnstPickerDividerHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
addSideMenuButton()
setupViews()
setupTableView()
setupRefreshControll()
setupViewModel()
setupBindings()
NotificationCenter.default.addObserver(self, selector: #selector(reloadMSGTable), name: NSNotification.Name(rawValue: Constants.reloadMSGTableNotif) , object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBottomHairline()
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.showBottomHairline()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func reloadMSGTable() {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func setupViews() {
navigationItem.title = L10n.Messages.screenTitle
let writeButton = UIBarButtonItem(
image: Assets.Images.icWriteMessage.image, style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = writeButton
self.btnWriteMessage = writeButton
vSegmentContainer.backgroundColor = Colors.defaultSystemBarsBackground
segmentControl.setColors(with: Colors.defaultSystemBarsBackground, and: Colors.mainTheme)
segmentControl.setTitle(L10n.Messages.inboxSegmentTitle, forSegmentAt: 0)
segmentControl.setTitle(L10n.Messages.sentSegmentTitle, forSegmentAt: 1)
}
func setupTableView() {
tableView.register(LoadingCell.nib, forCellReuseIdentifier: LoadingCell.reuseIdentifier)
tableView.register(EmptyCell.nib, forCellReuseIdentifier: EmptyCell.reuseIdentifier)
tableView.register(CommonMessageCell.nib, forCellReuseIdentifier: CommonMessageCell.reuseIdentifier)
tableView.separatorStyle = .singleLine
tableView.separatorInset = UIEdgeInsets(top: 0, left: 32, bottom: 0, right: 0)
tableView.tableFooterView = UIView()
tableView.rowHeight = UITableView.automaticDimension
}
func setupRefreshControll() {
guard refreshControl.superview == nil else { return }
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
}
func setupViewModel() {
let didPressNewMessageButton = btnWriteMessage.rx.tap
.mapToVoid().asDriver(onErrorJustReturn: ())
let didTriggerRefreshControll = refreshControl.rx.controlEvent(.valueChanged)
.mapToVoid().asDriverOnErrorJustComplete()
let bindings = MessagesViewModelBindingsChanges(
didTriggerRefreshControll: didTriggerRefreshControll,
didPressNewMessageButton: didPressNewMessageButton,
didTriggerLoadMore: willDisplayLastCellSubject.asDriverOnErrorJustComplete(),
didChangeSegment: segmentControl.rx.value.asDriver(),
didDidSelectItem: tableView.rx.itemSelected.asDriver())
viewModel.configure(bindings: bindings)
}
func setupBindings() {
let dataSource = RxTableViewSectionedReloadDataSource<ListSection>(
configureCell: { _, tv, _, item -> UITableViewCell in
switch item {
case .loading:
let cell = tv.dequeueReusableCell(withIdentifier: LoadingCell.reuseIdentifier) as! LoadingCell
cell.hideSeparator()
return cell
case .custom(let item):
guard let item = item as? Message else {
fatalError("wrong type")
}
let cell = tv.dequeueReusableCell(
withIdentifier: CommonMessageCell.reuseIdentifier) as! CommonMessageCell
cell.configure(for: item)
return cell
case .empty:
let cell = tv.dequeueReusableCell(withIdentifier: EmptyCell.reuseIdentifier) as! EmptyCell
cell.hideSeparator()
return cell
}
})
self.dataSource = dataSource
tableView.rx.itemSelected.asDriver().drive(onNext: { [unowned self] indexPath in
(self.tableView.cellForRow(at: indexPath) as? CommonMessageCell)?.vUnread.isHidden = true
self.tableView.deselectRow(at: indexPath, animated: true)
}).disposed(by: disposeBag)
viewModel.sections.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.reloading.drive(onNext: { [weak self] bool in
if bool {
if self?.refreshControl.isRefreshing == false {
self?.showProgress()
}
} else {
self?.hideProgressView()
if self?.refreshControl.isRefreshing == true {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
self?.refreshControl.endRefreshing()
})
}
}
}).disposed(by: disposeBag)
viewModel.errorOccured.drive(onNext: { [weak self] error in
self?.showErrorToast(with: error)
}).disposed(by: disposeBag)
}
}
extension MessagesController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if tableView.isLast(for: indexPath) {
willDisplayLastCellSubject.onNext(())
}
}
}
Не могу понять, как мне в методе viewWillAppear вызвать рефреш всей модели(
P.S: Решил путем добавления в func setupBindings() в контроллере принудительного присвоение данных в модели по тапу. На сколько это правильно? Все работает.
tableView.rx.itemSelected.asDriver().drive(onNext: { [unowned self] indexPath in
(self.tableView.cellForRow(at: indexPath) as? CommonMessageCell)?.vUnread.isHidden = true
(self.tableView.cellForRow(at: indexPath) as? CommonMessageCell)?.lbName.font = UIFont.systemFont(ofSize: 17, weight: .medium)
(self.tableView.cellForRow(at: indexPath) as? CommonMessageCell)?.lbTitle.font = UIFont.systemFont(ofSize: 15, weight: .regular)
self.tableView.deselectRow(at: indexPath, animated: true)
}).disposed(by: disposeBag)