到目前我們完成了可以呈現照片的 PhotoView,還希望能夠有一個資訊導覽列,用作顯示目前的圖片訊息,所以我們再新增一個繼承UIView
的類別,取名為 InfoBarView
,並完成下面程式碼。
import UIKit
class InfoBarView: UIView {
private let mInfoLabel:UILabel = UILabel()
var infoLabel:UILabel{
return mInfoLabel
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.whiteGoldColor()
mInfoLabel.textColor = UIColor.goldColor()
mInfoLabel.font = UIFont.systemFontOfSize(20.0)
mInfoLabel.text = "Photo Information"
mInfoLabel.textAlignment = NSTextAlignment.Left
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawRect(rect: CGRect) {
mInfoLabel.frame = CGRect(x: 20, y: 0, width: rect.width - 40, height: rect.height)
self.addSubview(mInfoLabel)
}
func setText(aText:String){
mInfoLabel.text = aText
}
func setHighlight(aIsHighlight:Bool) {
if aIsHighlight {
mInfoLabel.textColor = UIColor.whiteGoldColor()
self.backgroundColor = UIColor.goldColor()
} else {
mInfoLabel.textColor = UIColor.goldColor()
self.backgroundColor = UIColor.whiteGoldColor()
}
}
}
我們建立了一個繼承自 UIView 但是又是用做文字功能的一個自訂 View,其實在最開始我們自訂 PhotoView 時,應該就有類似的感覺:
PhotoView 有著和 UIImageView 類似的功能,但是 PhotView 不是繼承 UIImageView 做出來的 View,而是繼承 UIView 來複合 UIImageView。
PhotoView 可以從外部類別實體去賦予 PhotoView 的 UIImage 做圖片的顯示。
UIImageView 可以從外部類別實體去賦予 PhotoView 的 UIImage 做圖片的顯示。
InfoBarView 有著和 UILabel 類似的功能,,但是 InfoBarView 不是繼承 UILabel 做出來的 View,而是繼承 UIView 來複合 UILabel。
InfoBarView 可以從外部類別實體去賦予 InfoBarView 的 String 做字串的顯示。
UILabel 可以從外部類別實體去賦予 UILabel 的 String 做字串的顯示。
若無法理解上述內容的,可以回顧 Swift 殿堂之路
的繼承與複合
的篇章,這也是適配器模式的主要概念。
適配器模式的概念,在於把兩個以上不同類別中相同的功能整理出來,甚至可以把這些沒有彼此直接繼承的不同類別當成同一個「類別」來使用。
有沒有發現,高光的功能,不管是資訊列InfoBarView
以及PhotoView
都有這功能,儘管他們有不同的內容,但是卻有同樣的方法名稱,而且使用的時機與情景也幾乎一樣。
在我們實際導入 Adapter 模式之前,我們先嘗試用目前的程式碼做一次操作。我們在剛剛viewDidLoad
再添加 InforBarView 的視覺物件,並修改之前範例建立的 UIButton 的 Selector。
先宣告我們要測試的InfoBarView
以及一個高亮開關mIsHighLight
。
private let mInfoView:InfoBarView = InfoBarView()
private var mIsHighLight:Bool = false
private func createDemoLayout(){
mFlickrModel.loadFlickrData(1)
for _index:UInt in 0..<10 {
let _photoView :PhotoView = PhotoView()
let _boardWidth :CGFloat = 5
let _dWidth :CGFloat = (self.view.bounds.width - _boardWidth * 5) / 4
let _dHeight :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
let _dX :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
let _dY :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
_photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)
self.view.addSubview(_photoView)
mPhotoViews.append(_photoView)
}
let _button:UIButton = UIButton()
_button.frame = CGRectMake(10, self.view.bounds.height - 30, self.view.bounds.width - 20, 25)
_button.backgroundColor = UIColor.redColor()
self.view.addSubview(_button)
//_button.addTarget(self, action: Selector("onTestLoadImageHandler"), forControlEvents: UIControlEvents.TouchUpInside)
_button.addTarget(self, action: Selector("onTestHightLightHandler"), forControlEvents: UIControlEvents.TouchUpInside)
mInfoView.frame = CGRectMake(0, self.view.bounds.height - 80, self.view.bounds.width, 20)
self.view.addSubview(mInfoView)
}
func onTestHighLightHandler(){
mIsHighLight = !mIsHighLight
mInfoView.setHighlight(mIsHighLight)
for _photoView:PhotoView in mPhotoViews {
_photoView.setHighlight(mIsHighLight)
}
}
其實,是因為在編寫InfoBarView
的時候,我們故意把設定底圖狀態方法的名稱取的跟PhotoView
的設定底圖狀態方法一樣,所以在外面的類別使用這些方法的時候,才會有統一性與一致性。
再導入 Adapter 模式,我們需要再開啟一個新的檔案,在Views
這資料夾裡再添加一個檔案HighLightable.swift
,
import Foundation
protocol HighLightable{
func setHighlight(aIsHighlight:Bool)
}
只要有需要這功能的,無論是視覺物件或是資料,都可以實做這個協定。
我們再把我們的PhotoView
以及InfoBarView
都遵從這協定。
當然,因為方法名稱我們之前都取好了,所以雖然實踐這個協定,但是程式碼內容幾乎都不用改動。
class PhotoView: UIView,HighLightable {
// 中間略
}
class InfoBarView: UIView,HighLightable {
// 中間略
}
我們來看看MainViewController.swift
裡面,我們在做這樣的修改:
private var mHighLightViews:[HighLightable] = [HighLightable]()
先把協定的HighLightable
當成類別宣告一個陣列。 然後在self.view.addSubview
的下面都分別把有實踐HighLightable
協定的物件都加進去,有PhotoView
和InfoBarView
。
private func createDemoLayout(){
mFlickrModel.loadFlickrData(1)
for _index:UInt in 0..<10 {
let _photoView :PhotoView = PhotoView()
let _boardWidth :CGFloat = 5
let _dWidth :CGFloat = (self.view.bounds.width - _boardWidth * 5) / 4
let _dHeight :CGFloat = (self.view.bounds.height - _boardWidth * 6) / 5
let _dX :CGFloat = _boardWidth + CGFloat(_index % 4) * (_dWidth + _boardWidth)
let _dY :CGFloat = 50 + CGFloat(_index / 4) * (_dHeight + _boardWidth)
_photoView.frame = CGRectMake( _dX, _dY , _dWidth, _dHeight)
self.view.addSubview(_photoView)
mHighLightViews.append(_photoView)
}
let _button:UIButton = UIButton()
_button.frame = CGRectMake(10, self.view.bounds.height - 30, self.view.bounds.width - 20, 25)
_button.backgroundColor = UIColor.redColor()
self.view.addSubview(_button)
//_button.addTarget(self, action: Selector("onTestLoadImageHandler"), forControlEvents: UIControlEvents.TouchUpInside)
_button.addTarget(self, action: Selector("onTestHighLightHandler"), forControlEvents: UIControlEvents.TouchUpInside)
let _infoView:InfoBarView = InfoBarView()
_infoView.frame = CGRectMake(0, self.view.bounds.height - 80, self.view.bounds.width, 20)
self.view.addSubview(_infoView)
mHighLightViews.append(_infoView)
}
我們再把按鈕事件給成如下:
func onTestHighLightHandler(){
mIsHighLight = !mIsHighLight
//mInfoView.setHighlight(mIsHighLight)
for _view:HighLightable in mHighLightViews {
_view.setHighlight(mIsHighLight)
}
}
是的,本來PhotoView
和InfoBarView
是不同的類別,彼此沒有上下關聯的繼承關係,所以是不可能互相做轉型的,而因為統一了介面,實踐了同樣的協定,所以可以把這些實體都當成HighLightable
的物件來做同樣的操作,而各自都可以實踐他們自己的功能。
這就是 Adapter 適配器模式。
完成到這一步的Demo: