今回はModel-View-Presenter[MVP]アーキテクチャのデザインパターンで簡単なサンプルを作成します。
ModelではDBやAPIアクセスの処理、Presenterではその他の処理、描画はViewで行う考え方に基づき実装しました。
これから作るサンプルではModel内でRealmSwiftを使用します。
環境
・MacOS Ventura 13.0
・Xcode 14.2
・Swift version 5.7.2
今回はModel-View-Presenter[MVP]アーキテクチャのデザインパターンで簡単なサンプルを作成します。
ModelではDBやAPIアクセスの処理、Presenterではその他の処理、描画はViewで行う考え方に基づき実装しました。
これから作るサンプルではModel内でRealmSwiftを使用します。
・MacOS Ventura 13.0
・Xcode 14.2
・Swift version 5.7.2
UI関連の処理はここに記述します。
iOSの場合はViewControllerのクラス内に画面レイアウト関連の処理を行う形で良いと思います。
UIの配置やボタンクリックイベントの定義などはViewControllerに書く形で実装します。
Viewから指定された処理を行います。
DB、APIを使用しない処理の場合はPresenter内で処理を書き、使用する場合はModelに処理を委譲する形で実装しました。
Presenter内で処理し、必要に応じて結果をViewに反映させます。
TextFieldにタイトルを入力→保存ボタンをタップしてDBにデータを保存→タイトル取得ボタンタップして保存したデータを取得→上部のLabelにタイトル名を反映させるシンプルなアプリです。
ここからはプロジェクトを作成し簡単なMVPのサンプルプロジェクトを作ってみます。
プロジェクトのファイル構成は以下の様になりました。
Presenterフォルダには2種類のファイルを作成しています。
使い方については後述します。
・SampleViewController
・SampleViewPresenter
・SampleViewOutput
・SampleModel
・LabelInfo
cd /Users/ユーザー名/Documents/Projects/SampleMVP/SampleMVP.xcodeproj
cd /Users/ユーザー名/Documents/Projects/SampleMVP
pod init
open podfile
pod 'RealmSwift'
pod install
import RealmSwift
class SampleModel: NSObject {
//書き込み(更新)
func writeLabelInfo(labelTitle: String) -> Bool {
let realm = try! Realm()
let count = realm.objects(LabelInfo.self).count
if count == 0 {
//初回
let labelInfo = LabelInfo()
labelInfo.labelTitle = labelTitle
try! realm.write {
realm.add(labelInfo)
}
} else {
//2回目以降は更新処理
let info = realm.objects(LabelInfo.self).first
try! realm.write {
info?.labelTitle = labelTitle
}
}
return true
}
//読み込み
func loadLabelInfo() -> String {
var title = ""
let labelInfo = LabelInfo()
let realm = try! Realm()
let results = realm.objects(LabelInfo.self)
if results.count != 0 {
title = results[0].labelTitle
} else {
return ""
}
return title
}
}
//テーブル名LabelInfo カラム名labelTitle
class LabelInfo: Object {
@objc dynamic var labelTitle: String = ""
convenience init(labelTitle: String) {
self.init()
self.labelTitle = labelTitle
}
}
protocol SampleViewOutput: AnyObject {
func titleOutput(title: String)
}
protocol SampleViewPresenter: AnyObject {
init(viewOutput: SampleViewOutput)
func saveTitle(title: String)
func loadTitle()
}
class SamplePresenter: SampleViewPresenter {
weak var viewOutput: SampleViewOutput?
private var model: SampleModel?
required init(viewOutput: SampleViewOutput) {
self.viewOutput = viewOutput
self.model = SampleModel()
}
//タイトルの保存
func saveTitle(title: String) {
///タイトルの保存
guard self.model?.writeLabelInfo(labelTitle: title) == true else {
return
}
}
//タイトルの取得
func loadTitle() {
//保存したタイトルの取得
guard let title = self.model?.loadLabelInfo() else { return }
//取得したタイトルをViewControllerに渡す
self.viewOutput?.titleOutput(title: title)
}
}
class SampleViewController: UIViewController {
private var presenter: SamplePresenter?
private var titleInfo: LabelInfo?
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleTextField: UITextField!
@IBAction func saveAction(_ sender: Any) {
let text = titleTextField.text ?? ""
//Presenterに処理を渡す
self.presenter?.saveTitle(title: text)
}
@IBAction func getTitleAction(_ sender: Any) {
//Presenterに処理を渡す
self.presenter?.loadTitle()
}
override func viewDidLoad() {
super.viewDidLoad()
if self.presenter == nil {
self.presenter = SamplePresenter(viewOutput: self)
}
}
}
extension SampleViewController: SampleViewOutput {
func titleOutput(title: String) {
titleLabel.text = title
}
}
タイトル1を保存してタイトル2に変更しました。
いかがでしたでしょうか。
今回はMVPアーキテクチャでサンプルアプリを作ってみました。
コードを見ていただくと分かる通り、Model-View-Presenterの役割がはっきりしています。
コードの量も一部のファイルに集中しずらいのでどこに何の処理が有るのかが、わかりやすいと思います。