最後的潤色

使用 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,再把剛剛建立好的PhotosPresenterInfoBarPresenter加進去,除此以外再加上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?