最後的潤色
使用 MVP 最大的好處,不只是單純的各類別耦合性降低,耦合性降低有其他設計模式都可以使用,最大的好處是在於易於維護與修改,以及擴充功能。
而筆者尤其推薦 MVP 設計模式,是 MVP 設計模式甚至可以幫助我們在開發階段時的構思。
我們在之前的練習中,已經寫了不少功能,而且都用各類不同的設計模式封裝好了,而在封裝的基礎上,也應該發現目前的 MainViewController 是有點雜亂的。
所以我們把動態排版的邏輯,再用一個PhotosView類別封裝起來。
import UIKit
class PhotosView: UIScrollView {
private var mPhotoViews :[PhotoView] = [PhotoView]()
private var mTotals :UInt = 0
private let mBoardWidth :CGFloat = 5
private var mNumX :UInt = 4
var photoViews:[PhotoView]{
return mPhotoViews
}
init(aNumX:UInt,aTotals:UInt){
mTotals = aTotals
mNumX = aNumX
for _ in 0..<mTotals {
let _photoView:PhotoView = PhotoView()
mPhotoViews.append(_photoView)
}
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
// Drawing code
self.contentSize.width = rect.width
createLayout()
self.contentSize.height = mPhotoViews.last!.frame.origin.y + mPhotoViews.last!.frame.height + mBoardWidth
}
private func getPhotoViewWidthAndHeight() -> CGSize{
let _dWidth:CGFloat = (self.contentSize.width - mBoardWidth * CGFloat(mNumX + 1)) / CGFloat(mNumX)
let _dHeight:CGFloat = _dWidth * 3 / 4
return CGSize(width: _dWidth, height: _dHeight)
}
private func createLayout(){
let _photoSize:CGSize = self.getPhotoViewWidthAndHeight()
for _index:UInt in 0..<mTotals {
let _photoView:PhotoView = mPhotoViews[Int(_index)]
let _dX:CGFloat = mBoardWidth + CGFloat(_index % mNumX) * (_photoSize.width + mBoardWidth)
let _dY:CGFloat = mBoardWidth + CGFloat(_index / mNumX) * (_photoSize.height + mBoardWidth)
_photoView.frame = CGRectMake( _dX, _dY , _photoSize.width, _photoSize.height)
self.addSubview(_photoView)
}
}
}然後,我們再建立一個新的UIViewController,取名為FlickrBroswerViewController.swift,並把AppleDelegate.swift裡的mWindow.rootViewController改為FlickrBroswerViewController
然後,再回到FlickrViewController我們開始建立由 MVP 模式下建構的程式。
現在的專案的視覺架構已經跟之前不同了,我們也不能直接把PhotoPresenter直接用在現在的 PhotosView中,而且我們還想要同時處理 InfoBarView 的文字顯示,那該怎麼做呢?
我們在建立一個新的檔案PhotosPresenter,但是別忘記繼承Presenter,然後我們在Presenter這裡面,只要好好撰寫互動的呈現部分即可:
先修改了init的建構式,要特別讓其他類別都無法塞進來,因為我們在override func onFlickrDataCompleteHandler這裡面所寫的動態排版邏輯,只適用於PhotosView而已。
其他會用到的事件,就是onThemeStyleChangeHandler而已。
下一步,InforBarView也要依據事件也改變內容該怎麼處理呢? 跟PhotosPresenter一樣的方式處理。
我們再回到 FlickrBroswerViewController,再把剛剛建立好的PhotosPresenter和InfoBarPresenter加進去,除此以外再加上Controller,讓PhotosView也能當控制器。
執行看看,現在已經有佈景主題切換的功能了!
下一步,我們要翻頁的功能!而且是在infoBarView上滑動就可以翻頁,我們該在哪邊追加功能呢?
照 MVP 的邏輯來看,翻頁的動作是Controller負責,Controller才會改變Model的數據資料。
然後再FlickrBroswerViewController找到mController.addStyleChangeController再追加下面程式:
在編譯到模擬器或實體機器看看效果。
到這階段,可以在玩看看如果 Controller 的這兩行,丟入的UIView的實體不同,會有怎樣的效果?
受歡迎的500張照片瀏覽已經完成了,我們想要再追加功能時要怎麼做呢?
我們再追加一個大圖顯示的功能吧!
在新建一個類別,不過這類別我們不是繼承UIView,而是直接繼承PhotoView即可,因為大圖顯示的功能,原本的PhotoView就已經有了。
所以我們先修改PhotoView這類別,追加一個屬性:
然後我們再完成LargePhotoView這個類別。
版型的設計我這邊就不多加詳述,說明一下這個類別的功能
有 PhotoView 的功能,可以下載圖片也可以直接把 UIImage丟進來。
追加了兩個文字功能,分別顯示照片標題與擁有者名稱。
追加了 CLOSE 的事件。
追加了照片儲存到相簿的功能按鈕。
現在,我們要實踐PhotoView點選後,然後跳出LargePhotoView要怎麼做呢?各功能的程式碼應該要寫在什麼地方?
好好思考一下!
第一步, LargePhotoView 要呈現大圖、擁有者、照片標題等較多的詳細資訊,甚至日後可能會再追加或是修改要呈現的資料,所以一定要能夠知道目前被點選的 PhotoVO為何。
所以我們先在FlickrModel追加一個屬性
只要currentPhotoVO值被改變,就廣播FlickrModel.PHOTO_SELECT_CHANGE事件。
我們再完成 Controller 的部分:
只要PhotosView一接受到PhotoView.TOUCH_UP_INSIDE的事件,就找到對應的PhotoVO並且寫進FlickrModel。
當然,我們可以再追加LargePhotoView被關閉的互動控制:
只要LargePhotoView廣播LargePhotoView.CLOSE事件,就把self.model?.currentPhotoVO清空
這樣就只剩下LargePhotoView的呈現動作,也就是LargePhotoPresenter。
我們再回到 FlickrBroswerViewController 呼叫我們剛剛寫的 MVP 吧!
在 MVP 模式之下,任何功能都可以被拆解成 Model 、View、Presenter、Controller,而且如果有需要任何修改,就是一直追加功能,而舊有的功能可以移除,也可以放著不管。
在回顧一下我們完成這功能的時候,其實在實踐 MVP 模式時,新功能的追加甚至沒有想清楚功能整體的邏輯,只是考慮到會用到什麼功能就放在哪裡而已。
越複雜的互動邏輯,越適合 MVP 來幫助思考。
最後我們看一下最終成果吧:
完成到這一步的Demo:
查看原始碼
Last updated
Was this helpful?