質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
RxSwift

RxSwiftは、Reactive ExtensionsのSwift向けの実装です。iOS開発に用いられ、リアクティブプログラミングを可能にします。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1237閲覧

RxSwift TableViewのItem追加時の更新がならない

mogiruri

総合スコア37

RxSwift

RxSwiftは、Reactive ExtensionsのSwift向けの実装です。iOS開発に用いられ、リアクティブプログラミングを可能にします。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2019/06/25 07:35

編集2019/06/26 07:31

こんにちは。RxSwiftの勉強をしており、UITableViewなど多用されるものの扱いはさらっておこうと取り組んでおります。
TodoList的なものを作成し、TabelViewにもともとかかれてある配列情報を表示させるところまではできました。

ですが、アイテムのその配列に追加した際にtableViewがreloadされず(希望通りの表示がされず)に困っております。

tableviewとアイテム追加用のviewをtabViewで分けており、回答してくださる方がなるべくわかりやすいように関係部分のコードを全て掲載いたしますので、少々長いコードになります。

Model

swift

1 2struct Item: Codable { 3 var name = String() 4 var detail = String() 5 var tag = String() 6 var memo = String() 7 var fav = Bool() 8 var cellNo = Int() 9 10 init(name: String, detail: String, tag: String, memo: String, fav: Bool, celllNo: Int) { 11 self.name = name 12 self.detail = detail 13 self.tag = tag 14 self.memo = memo 15 self.fav = fav 16 self.cellNo = celllNo 17 } 18 19 init() { 20 self.init( 21 name: "Apple", 22 detail: "red", 23 tag: "fruit", 24 memo: "", 25 fav: false, 26 celllNo: 0 27 ) 28 } 29} 30 31var items: [Item] = [Item()] { 32 didSet { 33 UserDefault.shared.saveItems(items: items) 34 } 35} 36 37struct SectionModel: Codable { 38 var list: [Item] 39} 40extension SectionModel: SectionModelType { 41 var items: [Item] { 42 return list 43 } 44 45 init(original: SectionModel, items: [Item]) { 46 self = original 47 self.list = items 48 } 49 50} 51 52var list: [SectionModel] = [SectionModel(list: items)] { 53 didSet { 54 UserDefault.shared.saveList(list: list) 55 } 56} 57

View

swift

1class MainTabViewController: UIViewController { 2 3 @IBOutlet weak var tableView: UITableView! 4 5 private let disposeBag = DisposeBag() 6 private var dataSource: RxTableViewSectionedReloadDataSource<SectionModel>! 7 private lazy var viewModel = MainTabViewModel() 8 9 override func viewDidLoad() { 10 super.viewDidLoad() 11 setupTableViewDataSource() 12 binding() 13 } 14 15 func setupTableViewDataSource() { 16 dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(configureCell: {(_, tableView, indexPath, item) in 17 let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell 18 cell.selectionStyle = .none 19 cell.backgroundColor = .clear 20 cell.configure(item: item) 21 return cell 22 }) 23 } 24 25 func binding() { 26 27 tableView.rx.itemDeleted 28 .subscribe { 29 print("delete") 30 } 31 .disposed(by: disposeBag) 32 33 viewModel._dispItems.asObservable() 34 .bind(to: tableView.rx.items(dataSource: dataSource)) 35 .disposed(by: disposeBag) 36 } 37}

ViewModel

swift

1final class MainTabViewModel { 2 3 private let userDefault: UserDefaultManager 4 let _dispItems = BehaviorRelay<[SectionModel]>(value: []) 5 6 init(userDefault: UserDefaultManager = UserDefault()) { 7 self.userDefault = userDefault 8 let newValue = _dispItems.value + list 9 _dispItems.accept(newValue) 10 } 11} 12

以下でアイテムの追加を行います。
View

swift

1class AddTabViewController: UIViewController { 2 3 @IBOutlet weak var titleLable: UILabel! 4 @IBOutlet weak var itemTextField: UITextField! 5 @IBOutlet weak var detailTextField: UITextField! 6 @IBOutlet weak var tagTextField: UITextField! 7 @IBOutlet weak var memoTextView: UITextView! 8 @IBOutlet weak var addButton: UIButton! 9 10 private lazy var viewMdoel = AddTabViewModel( 11 itemLabelObservable: itemTextField.rx.text.asObservable(), 12 detailLabelObservable: detailTextField.rx.text.asObservable(), 13 tagLabelObservable: tagTextField.rx.text.asObservable(), 14 memoLabelObservable: memoTextView.rx.text.asObservable(), 15 addButtonTapped: addButton.rx.tap.asObservable(), 16 validation: Validation() 17 ) 18 19 private let disposeBag = DisposeBag() 20 21 override func viewDidLoad() { 22 super.viewDidLoad() 23 uiSetup() 24 bindingViewModel() 25 } 26 27 func bindingViewModel() { 28 29 itemTextField.rx.text 30 .bind(to: viewMdoel.itemLabel) 31 .disposed(by: disposeBag) 32 33 detailTextField.rx.text 34 .bind(to: viewMdoel.detailLabel) 35 .disposed(by: disposeBag) 36 37 tagTextField.rx.text 38 .bind(to: viewMdoel.tagLabel) 39 .disposed(by: disposeBag) 40 41 memoTextView.rx.text 42 .bind(to: viewMdoel.memoLabel) 43 .disposed(by: disposeBag) 44 45 viewMdoel.isEnableButton 46 .bind(to: addButton.rx.isEnabled) 47 .disposed(by: disposeBag) 48 49 addButton.rx.tap 50 .subscribe(onNext: { _ in 51 self.viewMdoel.addItem() 52 }) 53 .disposed(by: disposeBag) 54 } 55}

viewModel

swift

1final class AddTabViewModel { 2 3 let validatedItem: Observable<Bool> 4 let validatedDetail: Observable<Bool> 5 let validatedTag: Observable<Bool> 6 let isEnableButton: Observable<Bool> 7 let itemLabel = BehaviorRelay<String?>(value: "") 8 let detailLabel = BehaviorRelay<String?>(value: "") 9 let tagLabel = BehaviorRelay<String?>(value: "") 10 let memoLabel = BehaviorRelay<String?>(value: "") 11 12 init( 13 itemLabelObservable: Observable<String?>, 14 detailLabelObservable: Observable<String?>, 15 tagLabelObservable: Observable<String?>, 16 memoLabelObservable: Observable<String?>, 17 addButtonTapped: Observable<Void>, 18 validation: TextValidate 19 ) { 20 21 validatedItem = itemLabelObservable 22 .flatMapLatest { (input) -> Observable<Bool> in 23 return validation 24 .validate(text: input) 25 .catchErrorJustReturn(false) 26 } 27 .share() 28 29 validatedDetail = detailLabelObservable 30 .flatMapLatest { (input) -> Observable<Bool> in 31 return validation 32 .validate(text: input) 33 .catchErrorJustReturn(false) 34 } 35 .share() 36 37 validatedTag = tagLabelObservable 38 .flatMapLatest { (input) -> Observable<Bool> in 39 return validation 40 .validate(text: input) 41 .catchErrorJustReturn(false) 42 } 43 .share() 44 45 isEnableButton = Observable.combineLatest( 46 validatedItem, 47 validatedDetail, 48 validatedTag 49 50 ) { 51 $0 && $1 && $2 52 } 53 .share() 54 55 } 56 57 func addItem() { 58 let item = Item( 59 name: itemLabel.value!, 60 detail: detailLabel.value!, 61 tag: tagLabel.value!, 62 memo: memoLabel.value!, 63 fav: false, 64 celllNo: items.count 65 ) 66 list[0].list.append(item) 67 print(list) 68 } 69 70}

確認してみたこと

とりあえずprintでcellの作成で使用する配列をみてみたところ、ここには追加されたものは入っているようでした。

そもそも、RxSwiftでtableViewを作成する場合、reloadData()がどこでどう呼ばれて更新されるのか把握していないのですが、(監視しているので、変更があれば自動的に更新されると今は解釈しています)
ViewにUIButtonを配置し、プッシュしたらreloadData()を走らせるという流れも行ったのですが、リロードしてもデータが変わることはありませんでした。

問題の部分をなかなか見つけられず先へ進めずにいます。どうか助けていただけると幸いです。
よろしくお願いいたします。

追記 解決コード

swift

1// Model に以下を追加 2let dispItems = BehaviorRelay<[SectionModel]>(value: []) 3 4 5// mainTabViewModel 6final class MainTabViewModel { 7 8 private let userDefault: UserDefaultManager 9 var _dispItems = BehaviorRelay<[SectionModel]>(value: []) 10 11 init(userDefault: UserDefaultManager = UserDefault()) { 12 13 self.userDefault = userDefault 14 let newValue = list 15 dispItems.accept(newValue) 16 17 // とりあえずイコール 18 _dispItems = dispItems 19 } 20} 21 22// addTabViewModel 23 24func addItem() { 25 let item = Item( 26 name: itemLabel.value!, 27 detail: detailLabel.value!, 28 tag: tagLabel.value!, 29 memo: memoLabel.value!, 30 fav: false, 31 celllNo: items.count 32 ) 33 list[0].list.append(item) 34 print(list) 35 // Modelに定義しているものに変更した値を入れる 36 dispItems.accept(list) 37 } 38

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

TableViewに限らず、RxCocoaでストリームをバインドしたUIの表示を変化させるには、ストリームに新しい値を流す必要があります。TableViewの場合はこの時にRxCocoa内部でUITableViewDataSource等の調整を行った上で、reloadDataが呼ばれます。(正確に言うと、RxDataSourcesをお使いのようなので差分のみ更新するメソッドが呼ばれます)

ご質問のコードの場合は、 list へアイテムを追加する際に _dispItems にも新しい値を流すようにしてあげないとTableViewは更新されません。そのように修正してみてください。

投稿2019/06/26 03:16

kakajika

総合スコア3131

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mogiruri

2019/06/26 07:27

コメントありがとうございます。 現段階では汚いかもしれませんが、とりあえずの反映だけを目的とした問題の解決策として追記コードのようにしたところ値が更新されるようになりました。Modelにあるlistに追加アイテムをしただけで、値が流れるストリームの入口がVMに定義してあるのでそこが紐づいていなかったためですね。 kakajikaさんはRxSwiftについてお詳しいようなので伺いたいのですが、今後cellの削除と編集をスワイプにて行いたいので、差分の管理がやりやすくなるという現在実装途中であるRxDataSourcesを採用していますが、kakajikaさん的にはこれを推奨いたしますか? また現在の解決策として、二つの異なるView(ViewModel)からtableViewに表示する配列にアクセスするためにModelに定義しておりますが、スマートではない気がします。このような場合のよりいい手段はありますでしょうか。 一言いただけると幸いです。
kakajika

2019/06/26 10:28 編集

> 今後cellの削除と編集をスワイプにて行いたいので、差分の管理がやりやすくなるという現在実装途中であるRxDataSourcesを採用していますが、kakajikaさん的にはこれを推奨いたしますか? 要件によりますね。単純にストリームを元にリストを表示したいだけであればRxCocoaのみで十分ですし、セクションの表示やリッチなアニメーション、パフォーマンスを求めるのであればRxDataSourcesを採用するかなという感じです。もっとも、RxDataSourcesを使うことにデメリットはほとんどないと思います。単に手間の問題です。 RxDataSourcesの利点については、以下の記事がわかりやすいのでおすすめです。 https://medium.com/eureka-engineering/uicollectionview%E3%82%84uitableview%E3%81%AEreloaddata%E3%82%92%E5%91%BC%E3%81%B6%E5%BF%85%E8%A6%81%E3%81%AF%E3%81%BB%E3%81%A8%E3%82%93%E3%81%A9%E3%81%AA%E3%81%84-17a0635a54a9 > また現在の解決策として、二つの異なるView(ViewModel)からtableViewに表示する配列にアクセスするためにModelに定義しておりますが、スマートではない気がします。このような場合のよりいい手段はありますでしょうか。 今の方針で特に問題ないと思いますよ。listは各画面の状態を表すデータではないですし、Model層に置くのが妥当かと思います。強いて言うなら、グローバル変数で置くのはちょっと微妙なので何らかのクラスの中に配置した方が望ましいですね。そうしておけばModelを差し替えることによりViewModelのテストも行える(つまり、画面に関するテストまでほぼ自動化できる)ようになります。
mogiruri

2019/06/27 04:27

貴重な意見大変参考になります。 確かに公式にも書いてある通り、セクションも実装するとき更に本領を発揮してくれる感じですよね。確かに実装に当たって特に操作を必要としないのであればオーバーキルな感じもしますが、柔軟に対応できる場面が増えそうなのでとりあえず実装はしてみたいと思います。 クラスに定義する部分も、規模が大きくなってきた場合にはテストも加味しますと採用した方が良さそうですね。 大変参考になる意見とご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問