はじめに
前回までは、カウンターアプリ、WebViewアプリなどのサンプルアプリを実装してきました。今回は、よく使うRxSwift系ライブラリのひとつとしてRxDataSourcesを、実装しながら見ていきたいと思います。
環境
Xcode10.3
Swift5.0.1
RxSwift 5.0.0
RxCocoa 5.0.0
<今回初めて使うライブラリ>
RxDataSources 4.0.1
準備
1.プロジェクト作成
-Xcodeを起動
-Create a new Xcode project
>Single View App
>Product Name:RxSwiftLibSample
>完了。すぐにプロジェクトを閉じます。
2.ターミナルを起動して、ディレクトリに移動
$ cd RxDataSourcesSample
3.Podfile作成/編集
$ pod init
$ vi Podfile
# platform :ios, '9.0'
target 'RxDataSourcesSample' do
use_frameworks!
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxDataSources'
end
4.ライブラリのインストール
$ pod install
5.プロジェクトを開く
.xcworkspaceから起動する
Storyboardを削除
1.Main.storyboardの削除
/Main.storyboardをDelete > Move to Trash
2.Info.plist
Info.plistを開く > Main storyboard file base nameの項目を削除(マイナスボタン)
3.AppDelegateの修正
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
/* 追加 ここから */
self.window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController(rootViewController: ViewController())
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
/* 追加 ここまで */
return true
}
4.ViewController.xibの作成
- New File > View > Save As: ViewController.xib > Create
- ViewController.xibを開く
- Placeholders > File's Ownerを選択
- ClassにViewControllerを指定
- OutletのviewとXibのViewを接続
- Build & Run > 成功でOK
実装
import Foundation
import RxSwift
import RxCocoa
import RxDataSources
class SettingsViewModel {
let items = BehaviorRelay<[SettingsSectionModel]> (value: [])
var itemsObservable: Observable<[SettingsSectionModel]> {
return items.asObservable()
}
func setup() {
updateItems()
}
func updateItems() {
let sections: [SettingsSectionModel] = [
accountSection(),
commonSection()
]
items.accept(sections)
}
private func accountSection() -> SettingsSectionModel {
let items: [SettingsItem] = [
.account,
.security,
.notification,
.contents
]
return SettingsSectionModel(model: .account, items: items)
}
private func commonSection() -> SettingsSectionModel {
let items: [SettingsItem] = [
.sounds,
.dataUsing,
.accessibility,
.description(text: "基本設定はこの端末でログインしている全てのアカウントに適用されます。")
]
return SettingsSectionModel(model: .common, items: items)
}
}
import Foundation
import UIKit
import RxDataSources
typealias SettingsSectionModel = SectionModel<SettingsSection, SettingsItem>
enum SettingsSection {
case account
case common
case other
var headerHeight: CGFloat {
return 40.0
}
var footerHeight: CGFloat {
return 1.0
}
}
enum SettingsItem {
//account section
case account
case security
case notification
case contents
//common section
case sounds
case dataUsing
case accessibility
//other
case description(text: String)
var title: String? {
switch self {
case .account:
return "アカウント"
case .security:
return "セキュリティ"
case .notification:
return "通知"
case .contents:
return "コンテンツ設定"
case .sounds:
return "サウンド設定"
case .dataUsing:
return "データ利用時の設定"
case .accessibility:
return "アクセシビリティ"
case .description:
return nil
}
}
var rowHeight: CGFloat {
switch self {
case .description:
return 72.0
default:
return 48.0
}
}
var accessoryType: UITableViewCell.AccessoryType {
switch self {
case .account, .security, .notification, .contents, .sounds, .dataUsing, .accessibility:
return .disclosureIndicator
case .description:
return .none
}
}
}
import UIKit
import RxSwift
import RxDataSources
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var disposeBag = DisposeBag()
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SettingsSectionModel>(configureCell: configureCell)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SettingsSectionModel>.ConfigureCell =
{ [weak self] (dataSource, tableView, indexPath, _) in
let item = dataSource[indexPath]
switch item {
case .account, .security, .notification, .contents, .sounds, .dataUsing, .accessibility:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = item.title
cell.accessoryType = item.accessoryType
return cell
case .description(let text):
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = text
cell.isUserInteractionEnabled = false
return cell
}
}
private var viewModel: SettingsViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setupViewController()
setupTableView()
setupViewModel()
}
private func setupViewController()
{
navigationItem.title = "設定"
}
private func setupTableView()
{
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.contentInset.bottom = 12.0
tableView.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
tableView.rx.setDelegate(self).disposed(by: disposeBag)
tableView.rx.itemSelected
.subscribe(onNext: { [weak self] indexPath in
guard let item = self?.dataSource[indexPath] else { return }
self?.tableView.deselectRow(at: indexPath, animated: true)
switch item {
case .account:
//遷移させる処理
//コンパイルエラー回避のためにbreakを書いていますが処理を書いていればbreakは入りません
break
case .security:
//遷移させる処理
break
case .notification:
//遷移させる処理
break
case .contents:
//遷移させる処理
break
case .sounds:
//遷移させる処理
break
case .dataUsing:
//遷移させる処理
break
case .accessibility:
//遷移させる処理
break
case .description:
break
}
})
.disposed(by: disposeBag)
}
private func setupViewModel()
{
viewModel = SettingsViewModel()
viewModel.items
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
viewModel.updateItems()
}
}
extension ViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let item = dataSource[indexPath]
return item.rowHeight
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let section = dataSource[section]
return section.model.headerHeight
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let section = dataSource[section]
return section.model.footerHeight
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = .clear
return headerView
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView()
footerView.backgroundColor = .clear
return footerView
}
}
lazy プロパティ
・参照されるときに初めて初期値が設定されるプロパティ
・レイジープロパティに代入する値は、変数に限らず、メソッドを呼び出して値を設定することができる
[備考]
・クロージャを使ってレイジープロパティを設定することもできる
var price = 5000
class MyItem {
lazy var price:Int = { price * 2 }()
}