MVP 模式中,最重要的是 Model,由於 MVP 中,會廣泛的用到觀察者模式來完成彼此間的溝通,為了簡化程式碼,我們透過 LionEvents 中的 EventDispatcher 來實踐。
不管是透過 Notification
或是 KVO
或是 LionEvents
都可以。
打開FlickrModel
,我們再讓他繼承EventDispatcher
。
import LionEvents
class FlickrModel:EventDispatcher {
//...略
}
下一步,我們來好好思考我們需要這App有怎樣的互動:
所以我們會需要 FlickrModel
能夠廣播/通知上述這四種不同的時機。
static let FLICKR_DATA_COMPLETE :String = "flickrDataComplete"
static let PHOTO_SELECT_CHANGE :String = "photoSelectChange"
static let THEME_STYLE_CHANGE :String = "themeStyleChange"
static let PAGE_INDEX_CHANGE :String = "pageIndexChange"
我們先前在 FlickrModel
設計好的變數在這邊就會派上用場了, 在 OOP 的主要思想下,封裝與權限控制是基本,所以 FlickrModel 裡面我們原本設定了不少變數,但是都是私有的,只有在有必要時才會開啟。
所以我們在去想其他類別會去讀取的變數與資料有什麼?再分別依序開啟:
var perPage:UInt{
return mPerPage
}
var currentPageIndex:UInt{
set{
mCurrentPageIndex = newValue
let _event:Event = Event(aType: FlickrModel.PAGE_INDEX_CHANGE)
dispatchEvent(_event)
self.loadFlickrData(mCurrentPageIndex)
}
get{
return mCurrentPageIndex
}
}
var isNight:Bool{
get{
return mIsNight
}
set{
mIsNight = newValue
let _event:Event = Event(aType: FlickrModel.THEME_STYLE_CHANGE)
dispatchEvent(_event)
}
}
我們會透過 FlickrModel 來記錄儲存現在這隻 App 所要呈現的狀態,包含了
而在切換佈景主題以及被更改目前是第幾頁的時候,會再廣播相對應的事件FlickrModel.PAGE_INDEX_CHANGE
、FlickrModel.THEME_STYLE_CHANGE
。
而 PhotoModel裡原本的FLICKR_DATA_COMPLETE
通知,除了保留原有功能以外,我們再追加事件的廣播。
private func postFlickrDataCompleteNotification(aPageIndex:UInt){
let _userObject:[NSObject:AnyObject] = ["pageIndex":aPageIndex]
NSNotificationCenter.defaultCenter().postNotificationName(FlickrModel.FLICKR_DATA_COMPLETE, object: self, userInfo:_userObject )
let _event:Event = Event(aType: FlickrModel.FLICKR_DATA_COMPLETE)
_event.information = aPageIndex
self.dispatchEvent(_event)
}
然後,我們再建立一個Presenter
的分組,放一個 Presenter
類別,MVP 模式下的 P ,指的是Presenter
,他主要功能是用做視覺互動:
先建立一個 Presenter 的類別,而且不繼承任何類別,在宣告一個 weak 弱引用的 FlickrModel,在建構式時,就連同 view 的屬性一起設好,因為沒有 view,也沒有Presenter
存在的必要了。
然後再對 model 設定 set 與 get,只要 model 被設定有值之後,就立刻註冊偵聽需要用到的四個事件,並且先寫好空的事件函數。
import Foundation
import UIKit
import LionEvents
class Presenter {
private weak var mModel:FlickrModel?
var model:FlickrModel? {
get{
return mModel
}
set{
mModel = newValue
mModel?.addEventListener(FlickrModel.FLICKR_DATA_COMPLETE, onFlickrDataCompleteHandler)
mModel?.addEventListener(FlickrModel.PHOTO_SELECT_CHANGE, onPhotoSelectChangeHandler)
mModel?.addEventListener(FlickrModel.THEME_STYLE_CHANGE, onThemeStyleChangeHandler)
mModel?.addEventListener(FlickrModel.PAGE_INDEX_CHANGE, onPageIndexChangeHandler)
}
}
var view:UIView
init(aView:UIView){
self.view = aView
}
deinit{
mModel?.removeEventListener(FlickrModel.FLICKR_DATA_COMPLETE)
mModel?.removeEventListener(FlickrModel.PHOTO_SELECT_CHANGE)
mModel?.removeEventListener(FlickrModel.THEME_STYLE_CHANGE)
mModel?.removeEventListener(FlickrModel.PAGE_INDEX_CHANGE)
}
func onFlickrDataCompleteHandler(aEvent:Event){
}
func onPageIndexChangeHandler(aEvent:Event){
}
func onThemeStyleChangeHandler(aEvent:Event){
}
func onPhotoSelectChangeHandler (aEvent: Event) {
}
}
上述四個事件函數,為了要讓其他繼承的類別可以複寫,所以權限記得至少要開到 internal。
這樣,我們就可以針對於 PhotoView 專門寫一個 PhotoPresenter ,繼承Presenter
類別,讓他完成功能事件內的事情:
import Foundation
import LionEvents
class PhotoPresenter: Presenter {
override func onThemeStyleChangeHandler(aEvent: Event) {
if let _photoView:PhotoView = self.view as? PhotoView{
_photoView.setHighlight(self.model!.isNight)
}
}
}
然後我們再修改一下 MainViewController.swift
的 viewDidLoad
的部分,並建立一個新的函數以方便整理:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
//createDemoLayout()
createDemoLayoutSecond()
createMVPDemo()
}
private func createMVPDemo(){
for _photoView:PhotoView in mPhotoViews {
let _photoPresenter:PhotoPresenter = PhotoPresenter(aView:_photoView)
_photoPresenter.model = mFlickrModel
}
}
追加一個 createMVPDemo
的函數,然後把PhotoPresenter
都建立起來,不要忘記給PhotoPresenter
設定指定的 model。
再修改一下原本的onPhotoViewTouchHandler
:
private func onPhotoViewTouchHandler(aEvent:Event){
//print("\(aEvent.type),target:\(aEvent.target),currentTarget:\(aEvent.currentTarget)")
if let _:PhotoView = aEvent.target as? PhotoView {
//_photoView.setHighlight(true)
mFlickrModel.isNight = !mFlickrModel.isNight
}
}
現在每次點擊,我們並不是直接去更改 PhotoView 是否要高光顯示,而是透過 FlickrModel
裡的isNight
屬性的更改,讓PhotoPresenter
知道所屬的PhotoView
的需要被改變。
大家應該有發現到,原本的 MainViewController.swift
似乎變得十分雜亂。
其實 cocoa
的 MVC 模式,UIViewController
不只是單純的MVC
下的Controller
而已,還包含了Dynamic Layout
的功能,尤其我們的畫面全部都是使用Hard Coding
產生的時候,我們並沒有特別把MainViewController
裡面會用到的 View
另外寫成一個類別,所以現在的MainViewController
不只是只有單純的 Controller 功能,還包含了視覺排版Dynamic Layout
,我們都寫進了這個的類別裡面了。
為了讓 MVP 這個功能獨立化,我們再建立一個專屬於這個應用程式所用的 Controller
,我們新建一個Controller.swift
,不過這個Controller
我們要的是完全乾淨的只有Controller
功能的,所以也不需要去繼承任何類別。
import Foundation
import LionEvents
class Controller {
weak var model:FlickrModel?
init(aModel:FlickrModel){
model = aModel
}
}
然後我們再幫Controller建立他的新功能:讓只要是繼承自UIView
的類別,都可以當該功能的控制器:
func addStyleChangeController(aView:UIView){
let _swipeGesture:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("onStyleSwipeHndler:"))
_swipeGesture.direction = [ UISwipeGestureRecognizerDirection.Left, UISwipeGestureRecognizerDirection.Right]
aView.addGestureRecognizer(_swipeGesture)
}
@objc private func onStyleSwipeHndler(aSender:UISwipeGestureRecognizer) {
self.model!.isNight = !self.model!.isNight
}
再回到MainViewController
裡面,先再幫Controller設定一個全域變數:
private var mController:Controller?
再找到createMVPDemo
並且追加下面程式:
private func createMVPDemo(){
for _photoView:PhotoView in mPhotoViews {
let _photoPresenter:PhotoPresenter = PhotoPresenter(aView:_photoView)
_photoPresenter.model = mFlickrModel
}
mController = Controller(aModel: mFlickrModel)
mController!.addStyleChangeController(self.view)
}
在模擬器上執行測試,你們會發現左右滑動,也可以直接更改整個畫面的佈景風格了!
花盡了心思,把這個功能寫好了,目前只會覺得一個簡單的功能把它拆得很多類別,這樣到底有什麼好處呢?
下一個步驟,就是他最大的好處要展現了!!
完成到這一步的Demo: