【Swift】MVCについて分かりやすいサンプルが無かったのでSwiftでMVCパターンで簡易アプリ書いてみた【MVCパターン】
表題の通りです。
MVCパターンについて解説書いている記事は沢山存在しますが、
github等でソースコードは公開していないので、
初めて勉強される方など不明確な領域かと思い、
私の方でMVCパターンで簡易なアプリを開発してみました。
ソースコードについても貼っておきます。
また、UIViewController(Controller)にUITableViewDelegate
とUITableViewDataSource
を書くのではなく、ModelにUITableViewDataSource
を切り分けて実装しているので、そういった内容を求めている方もMVCパターンが分からなくてもサンプルコードが短いのでご参考いただけますと幸いです。
書籍紹介
iOSアプリ設計パターン入門
こちらはかなり分かりやすく、読みやすいです。
もしよかったら3200円の価値はあるのでぜひ購入してみてください。
簡易のアプリ機能
上記のように、アプリ開発でメインで使われるUITableViewを利用してMVCパターンを採用しました。
初期表示時に3件のセルを表示しています。
何かしらのセルを押下することで、都度セルが1件追加されます。
それだけの機能を有しております。
MVCパターンについて
MVCパターンにおける一般的なModel、View、Controllerについて先に触れ、
その後、本サンプルアプリにおける期待する役割を記載します。
■Model
システムの中でビジネスロジックを担当する。(上記書籍では「「UI に関係しない処理すべて」 と形容すべき、ざっくりした存在です 」とも記述されております。)
■View
ユーザーが直接目にするView部分の描画処理を行う。
■Controller
何かしらの入力に対する適切な処理を行うだけでなく、Model オブジェクトと Viewオブジェクトも保持します。Model オブジェクトに処理を依頼し、受け取った結果を使っ て View オブジェクトへ描画を指示します。
今回ではControllerがModelとViewの橋渡し役になります。
(ModelとViewは参照関係にないです。)
今回ではControllerがModelとViewの橋渡し役になります。
(ModelとViewは参照関係にないです。)
サンプルアプリでのModelの責務
- ドメイン(ビジネスロジックを担当する)
- TableViewのdatasourceの責務はModelにある。
- 通知でViewControllerに変更を知らせる(配列の内容を変更する、など)
- 変数などを処理する(表示用の配列など)
サンプルアプリでのViewの責務
- UIViewクラスを生成し、xibファイルと紐づける(UIViewControllerでxibファイルを持たない)
サンプルアプリでのControllerの責務
- View側の押下時の処理はControllerで設定する。
- ボタンを押下した際の処理はaddTarger:を利用してControllerからModelに処理を橋渡しする。
- 画面遷移の処理を挟むので、TableViewのdelegateの責務はここにある。
- FATな処理になりそうなら、Modelにすぐに書く。
クラス構成と解説
- TweetListModel.Swift (Model) ※class
- TweetListView.Swift (View) ※UIView
- TweetViewController(Controller) ※UIViewController
Model
まずデータの表示の為の処理と、
テーブルセルを押すと件数が増える処理をModelに記載しております。
また、UITableViewのUITableViewDatasource
のような表示するセルの個数を確定する処理(func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
)は、Controllerで担当するべき責務では無い為、Modelで対応します。
import Foundation
import UIKit
// ツイート自体のデータモデルも作る
class TweetDataModel {
let tweet: String
init(initTweetText: String) {
tweet = initTweetText
}
}
class TweetListModel: NSObject,UITableViewDataSource {
// Modelを監視するクラス
let notificationCenter = NotificationCenter()
// Modelで管理する配列に初期値を設定する。
private(set) var tweetList: [TweetDataModel] = [
TweetDataModel.init(initTweetText:"Tweet: 0番目"),
TweetDataModel.init(initTweetText:"Tweet: 1番目"),
TweetDataModel.init(initTweetText:"Tweet: 2番目")
] {
didSet{
// Modelで管理している配列に変化があった場合に呼び出されて、通知する。
notificationCenter.post(name: .init(rawValue: "changeTweetList"), object: nil, userInfo: ["list" : tweetList])
}
}
// 配列に新しいツイートを追加する。
func addTweetList() {
let tweetText = "Tweet:\(self.tweetList.count)番目"
self.tweetList.append(TweetDataModel.init(initTweetText: tweetText))
}
// MARK: UITableViewDatasoruce
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tweetList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
let tweetModel = self.tweetList[indexPath.row]
cell.textLabel?.text = tweetModel.tweet
return cell
}
}
View
TweetListView.swiftとTweetListView.xibを用意します。
参考画像を元にswiftファイルとxibファイルの紐付けも済ませてください。
基本的には以下のコードをコピペで大丈夫です。
func loadNib() 内のTweetListViewはxibファイル名を参考にしています。
なのでファイル名に応じて変更してください。
import UIKit
class TweetListView: UIView {
@IBOutlet weak var tableView: UITableView!
override init(frame: CGRect){
super.init(frame: frame)
loadNib()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
loadNib()
}
func loadNib(){
let view = Bundle.main.loadNibNamed("TweetListView", owner: self, options: nil)?.first as! UIView
view.frame = self.bounds
self.addSubview(view)
}
}
Controller
先に記述を置いていきます。
ViewControllerでTableViewのデリゲートメソッドを宣言してます。
Controllerの役割は以下の二点です。
- Viewでのユーザーアクションを検知して、Modelに処理を走らせる。
- Modelの値の変化を受けて、Viewを反映する。
import UIKit
class TweetViewController: UIViewController {
var myModel: TweetListModel? {
// セットされるたびにdidSetが動作する
didSet {
// ViewとModelとを結合し、Modelの監視を開始する
registerModel()
}
}
override func loadView() {
super.loadView()
self.view = TweetListView()
}
override func viewDidLoad() {
super.viewDidLoad()
self.myModel = TweetListModel()
settingTableView()
}
private func settingTableView () {
let tweetListView = self.view as! TweetListView
tweetListView.tableView.delegate = self
tweetListView.tableView.dataSource = self.myModel
// TableViewに表示するCellを登録する
tweetListView.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
private func registerModel() {
guard let model = myModel else { return }
// 配列が変化したらnotificationCenterで通知を受け取る。
model.notificationCenter.addObserver(forName: .init(rawValue: "changeTweetList"),
object: nil,
queue: nil,
using: {
[unowned self] notification in
let tweetListView = self.view as! TweetListView
tweetListView.tableView.reloadData()
})
}
// TableViewのセルのタップを検知して、Modelの配列追加する処理を呼び出す。
@objc func onTapTableViewCell() { myModel?.addTweetList() }
}
// TableViewを描画・処理する為に最低限必要なデリゲートメソッド、データソース
extension TweetViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Modelでタップされた時の追加処理を行う。
self.onTapTableViewCell()
}
}
}```
# 最後に
このように設計することで、Viewは描画の責務に専念し、
Modelはビジネスロジックに専念し、ControllerはView-Modelの橋渡しに徹底しております。
Discussion