防止第一响应者在 tableview 重新加载时辞职
Preventing first responder to resign on tableview reload
我正在使用 RxSwift,目前,我有一个可以在线编辑的地址列表
I.E:用户点击按钮,单元格转换为编辑模式,本质上显示一些 UITextFields
现在的问题是,当我将我的 TextField 的输入绑定到我的模型时,TableView 被重新加载,因此键盘关闭,我也尝试过 RxAnimatableDataSource 但无济于事,它仍然在每次击键时关闭键盘。
视图模型:
class UpdateAddressViewModel: ViewModel, ViewModelType {
struct Input {
let viewDidLoad: AnyObserver<Void>
let selectItemTrigger: AnyObserver<Int>
let editButtonTrigger: AnyObserver<Int>
let editTrigger: AnyObserver<Int>
let firstNameIndexed: AnyObserver<(String, Int)>
let lastNameIndexed: AnyObserver<(String, Int)>
let address1Indexed: AnyObserver<(String, Int)>
let address2Indexed: AnyObserver<(String, Int)>
let zipIndexed: AnyObserver<(String, Int)>
let cityIndexed: AnyObserver<(String, Int)>
let stateIndexed: AnyObserver<(String, Int)>
let phoneIndexed: AnyObserver<(String, Int)>
}
struct Output {
let addresses: Driver<[AddressViewModel]>
let reloadAndScroll: Driver<(Int, Bool)>
let showMenu: Driver<Int>
let error: Driver<Error>
}
private(set) var input: Input!
private(set) var output: Output!
//Input
private let viewDidLoad = PublishSubject<Void>()
private let editButtonTrigger = PublishSubject<Int>()
private let editTrigger = PublishSubject<Int>()
private let firstNameIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let lastNameIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let address1Indexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let address2Indexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let zipIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let cityIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let stateIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let phoneIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
//Output
private let addresses = BehaviorRelay<[AddressViewModel]>(value: [AddressViewModel()])
override init() {
super.init()
observeViewDidLoad()
observeEditAddress()
.bind(to: addresses)
.disposed(by: disposeBag)
firstNameIndexed
.withLatestFrom(addresses) { firstNameIndexed, viewModels -> (String, Int, [AddressViewModel]) in
let (firstName, index) = firstNameIndexed
return (firstName, index, viewModels)
}
.map { firstName, index, viewModels in
var viewModels = viewModels
viewModels[index].address.firstName = firstName
return viewModels
}
.bind(to: addresses)
.disposed(by: disposeBag)
input = Input(
viewDidLoad: viewDidLoad.asObserver(),
selectItemTrigger: selectItemTrigger.asObserver(),
editButtonTrigger: editButtonTrigger.asObserver(),
editTrigger: editTrigger.asObserver(),
firstNameIndexed: firstNameIndexed.asObserver(),
lastNameIndexed: lastNameIndexed.asObserver(),
address1Indexed: address1Indexed.asObserver(),
address2Indexed: address2Indexed.asObserver(),
zipIndexed: zipIndexed.asObserver(),
cityIndexed: cityIndexed.asObserver(),
stateIndexed: stateIndexed.asObserver(),
phoneIndexed: phoneIndexed.asObserver()
)
output = Output(
addresses: addresses.asDriver(onErrorJustReturn: []),
reloadAndScroll: reloadAndScroll,
showMenu: editButtonTrigger.asDriverOnErrorJustComplete(),
error: errorTracker.asDriver()
)
}
private func observeViewDidLoad() {
//Loading work here
}
private func observeEditAddress() -> Observable<[AddressViewModel]> {
editTrigger
.withLatestFrom(addresses) { index, viewModels in
return (index, viewModels)
}
.map { index, viewModels in
var viewModels = viewModels
for currentIndex in viewModels.indices {
viewModels[currentIndex].isSelected = currentIndex == index
viewModels[currentIndex].isEditing = currentIndex == index
if currentIndex == index {
viewModels[currentIndex].copyAddress()
}
}
return viewModels
}
}
}
这是我的 ViewController 数据源与表视图的绑定
viewModel
.output
.addresses
.drive(tableView.rx.items) { [weak self] tableView, row, viewModel in
guard let self = self else { return UITableViewCell() }
let indexPath = IndexPath(row: row, section: 0)
if viewModel.address.rechargeId == nil && viewModel.address.shopifyId == nil {
let cell: NewAddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
return cell
} else if viewModel.isEditing {
let cell: UpdateAddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
cell
.rx
.firstName
.map { ([=11=], row) }
.bind(to: self.viewModel.input.firstNameIndexed)
.disposed(by: cell.disposeBag)
return cell
} else {
let cell: AddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
cell
.rx
.editButtonTapped
.map { _ in return row }
.bind(to: self.viewModel.input.editButtonTrigger)
.disposed(by: cell.disposeBag)
return cell
}
}
.disposed(by: disposeBag)
您需要断开导致 table 视图更新的流与更改地址的流。
例子可能是这样的:
typealias ID = Int // an example
typealias Address = String // probably needs to be more involved.
struct Input {
let updateAddress: Observable<(ID, Address)>
}
struct Output {
let cells: Observable<[ID]>
let addresses: Observable<[ID: Address]>
}
table 视图将绑定到 cells
,而每个单元格将绑定到 addresses
observable,并使用 compactMap 提取其特定信息。
这样,您可以更新特定单元格而无需重新加载 table 视图中的任何单元格。
可以在我的 RxMultiCounter example.
中找到一个完整的示例
我正在使用 RxSwift,目前,我有一个可以在线编辑的地址列表
I.E:用户点击按钮,单元格转换为编辑模式,本质上显示一些 UITextFields
现在的问题是,当我将我的 TextField 的输入绑定到我的模型时,TableView 被重新加载,因此键盘关闭,我也尝试过 RxAnimatableDataSource 但无济于事,它仍然在每次击键时关闭键盘。
视图模型:
class UpdateAddressViewModel: ViewModel, ViewModelType {
struct Input {
let viewDidLoad: AnyObserver<Void>
let selectItemTrigger: AnyObserver<Int>
let editButtonTrigger: AnyObserver<Int>
let editTrigger: AnyObserver<Int>
let firstNameIndexed: AnyObserver<(String, Int)>
let lastNameIndexed: AnyObserver<(String, Int)>
let address1Indexed: AnyObserver<(String, Int)>
let address2Indexed: AnyObserver<(String, Int)>
let zipIndexed: AnyObserver<(String, Int)>
let cityIndexed: AnyObserver<(String, Int)>
let stateIndexed: AnyObserver<(String, Int)>
let phoneIndexed: AnyObserver<(String, Int)>
}
struct Output {
let addresses: Driver<[AddressViewModel]>
let reloadAndScroll: Driver<(Int, Bool)>
let showMenu: Driver<Int>
let error: Driver<Error>
}
private(set) var input: Input!
private(set) var output: Output!
//Input
private let viewDidLoad = PublishSubject<Void>()
private let editButtonTrigger = PublishSubject<Int>()
private let editTrigger = PublishSubject<Int>()
private let firstNameIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let lastNameIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let address1Indexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let address2Indexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let zipIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let cityIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let stateIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
private let phoneIndexed = ReplaySubject<(String, Int)>.create(bufferSize: 1)
//Output
private let addresses = BehaviorRelay<[AddressViewModel]>(value: [AddressViewModel()])
override init() {
super.init()
observeViewDidLoad()
observeEditAddress()
.bind(to: addresses)
.disposed(by: disposeBag)
firstNameIndexed
.withLatestFrom(addresses) { firstNameIndexed, viewModels -> (String, Int, [AddressViewModel]) in
let (firstName, index) = firstNameIndexed
return (firstName, index, viewModels)
}
.map { firstName, index, viewModels in
var viewModels = viewModels
viewModels[index].address.firstName = firstName
return viewModels
}
.bind(to: addresses)
.disposed(by: disposeBag)
input = Input(
viewDidLoad: viewDidLoad.asObserver(),
selectItemTrigger: selectItemTrigger.asObserver(),
editButtonTrigger: editButtonTrigger.asObserver(),
editTrigger: editTrigger.asObserver(),
firstNameIndexed: firstNameIndexed.asObserver(),
lastNameIndexed: lastNameIndexed.asObserver(),
address1Indexed: address1Indexed.asObserver(),
address2Indexed: address2Indexed.asObserver(),
zipIndexed: zipIndexed.asObserver(),
cityIndexed: cityIndexed.asObserver(),
stateIndexed: stateIndexed.asObserver(),
phoneIndexed: phoneIndexed.asObserver()
)
output = Output(
addresses: addresses.asDriver(onErrorJustReturn: []),
reloadAndScroll: reloadAndScroll,
showMenu: editButtonTrigger.asDriverOnErrorJustComplete(),
error: errorTracker.asDriver()
)
}
private func observeViewDidLoad() {
//Loading work here
}
private func observeEditAddress() -> Observable<[AddressViewModel]> {
editTrigger
.withLatestFrom(addresses) { index, viewModels in
return (index, viewModels)
}
.map { index, viewModels in
var viewModels = viewModels
for currentIndex in viewModels.indices {
viewModels[currentIndex].isSelected = currentIndex == index
viewModels[currentIndex].isEditing = currentIndex == index
if currentIndex == index {
viewModels[currentIndex].copyAddress()
}
}
return viewModels
}
}
}
这是我的 ViewController 数据源与表视图的绑定
viewModel
.output
.addresses
.drive(tableView.rx.items) { [weak self] tableView, row, viewModel in
guard let self = self else { return UITableViewCell() }
let indexPath = IndexPath(row: row, section: 0)
if viewModel.address.rechargeId == nil && viewModel.address.shopifyId == nil {
let cell: NewAddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
return cell
} else if viewModel.isEditing {
let cell: UpdateAddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
cell
.rx
.firstName
.map { ([=11=], row) }
.bind(to: self.viewModel.input.firstNameIndexed)
.disposed(by: cell.disposeBag)
return cell
} else {
let cell: AddressCell = tableView.dequeueCell(for: indexPath)
cell.configure(viewModel: viewModel)
cell
.rx
.editButtonTapped
.map { _ in return row }
.bind(to: self.viewModel.input.editButtonTrigger)
.disposed(by: cell.disposeBag)
return cell
}
}
.disposed(by: disposeBag)
您需要断开导致 table 视图更新的流与更改地址的流。
例子可能是这样的:
typealias ID = Int // an example
typealias Address = String // probably needs to be more involved.
struct Input {
let updateAddress: Observable<(ID, Address)>
}
struct Output {
let cells: Observable<[ID]>
let addresses: Observable<[ID: Address]>
}
table 视图将绑定到 cells
,而每个单元格将绑定到 addresses
observable,并使用 compactMap 提取其特定信息。
这样,您可以更新特定单元格而无需重新加载 table 视图中的任何单元格。
可以在我的 RxMultiCounter example.
中找到一个完整的示例