讀源碼系列(swift2048)-view篇

前言

筆者是swift自學新手,希望借助閱讀別人開源項目提升自己swift水平。文中將盡量使用文字描述來代替代碼的堆砌,建議讀者多參考源碼,以便更好理解項目。文中難免有錯誤之處,歡迎各路大牛留言指正。

項目信息

swift-2048 github地址

項目主界面

該項目可以說一個帶有實驗學習性質的項目,其中部分功能沒有實現(xiàn)或不完整。但2048游戲的基本功能均完整實現(xiàn)。筆者將分3篇文章,分別按controller、model、view的進行介紹。

本篇是最后一篇,將重點展開介紹view部分。
以往文章:
第1篇-controller篇
第2篇-model篇

正文

本文將從以下2點展開說明:

  1. 文件結構概括
  2. 游戲盤view

1.文件結構概括

筆者喜歡先從文件結構看起。該項目的view部分,有下面4個文件組成:

views/AccessoryViews.swift //輔助的views.里面含顯示得分ScoreView和用來控制作用的ControlView(未實現(xiàn)也未使用)
views/GameboardView.swift //游戲盤view
views/TileView.swift //棋子view
AppearanceProvider.swift //外觀的提供者,按規(guī)則顯示提供顏色和字體大小

2.游戲盤view(GameboardView)

GameboardView代表游戲盤,TileView代表棋子。


游戲盤和棋子

GameboardView源碼中

class GameboardView : UIView {
   ...
   var tiles: Dictionary<NSIndexPath, TileView>
   ...
}

源碼可見,GameboardView是以Dictionary結構來存儲TileView。Dictionary中的key,是NSIndexPath,實際運用中將位置坐標x y(或raw col)轉成NSIndexPath,例如:

tiles[NSIndexPath(forRow: row, inSection: col)] = tile //設置位置坐標為(row,col)上的TileView

各位移步看TileView的內部:

class TileView : UIView {
    ...
    let numberLabel : UILabel//顯示數(shù)字的label
    var value : Int = 0 {
    didSet {
        backgroundColor = delegate.tileColor(value)//棋子的背景
        numberLabel.textColor = delegate.numberColor(value)//數(shù)字的顏色
        numberLabel.text = "\(value)"http://值
        }
    }
    unowned let delegate : AppearanceProviderProtocol //顯示信息委托
    ...
}

TileView不復雜,UIView中是一個UILabel。然后數(shù)字和棋子背景因value不同而不同。顯示的規(guī)則來自AppearanceProviderProtocol。

protocol AppearanceProviderProtocol: class {
  func tileColor(value: Int) -> UIColor //根據(jù)值,返回棋子的背景顏色
  func numberColor(value: Int) -> UIColor//根據(jù)值,返回棋子的數(shù)字顏色
  func fontForNumbers() -> UIFont //返回數(shù)字使用的字體
}

該協(xié)議的的實現(xiàn),按典型的switch-case的結構,有多少情況,只要預先設定好,即可:

class AppearanceProvider: AppearanceProviderProtocol {
  func tileColor(value: Int) -> UIColor {
    switch value {
    case 2:
      return UIColor(red: 238.0/255.0, green: 228.0/255.0, blue: 218.0/255.0, alpha: 1.0)
    case 4:
      return UIColor(red: 237.0/255.0, green: 224.0/255.0, blue: 200.0/255.0, alpha: 1.0)
        ...
    }
  }
  ...
}

那開發(fā)者為何使用AppearanceProviderProtocol呢?筆者猜想,這樣可以將顯示屬性(顏色、字體)相關的邏輯從TileView的邏輯中獨立出來,便于統(tǒng)一修改和調試。


讀過上一篇model篇的讀者,會發(fā)現(xiàn)這里的GameboardView-TileView與model中的SquareGameboard-TileObject比較相似,但是2者還是有本質上的差別:

  • 相同點:2者都在各自領域(view和model)中,表示游戲盤和棋子
  • 不同點:在model中,SquareGameboard組織TileObject的方式是數(shù)組。
struct SquareGameboard<T> {
    ...
    var boardArray : [T]//實際使用過程中,泛型使用TileObject代替
    ...
}

在view中,GameboardView組織TileView的方式是Dictionary

class GameboardView : UIView {
    ...
    var tiles: Dictionary<NSIndexPath, TileView>
    ...
}

為何是這樣?筆者認為是model和view關注點不同

  • model關注整個游戲的邏輯,即游戲盤中的每一個有效的位置都要被管理起來。TileObject不僅代表有數(shù)字的棋子,也代表空棋子。所以,游戲盤中所有棋子(位置)是連續(xù)的,可以用數(shù)組表示。
  • view關注顯示,即只關心游戲盤中有數(shù)字的棋子(移動、插入等)。TileView只表示有數(shù)字的格子,不表示空棋子(空位置)。在游戲過程中,有數(shù)字的棋子數(shù)量小于游戲盤中棋子位置的總數(shù)量,而且棋子與棋子之間沒有連續(xù)的關系。故而使用數(shù)組就不適合了,而采用Dictionary,并用位置坐標(NSIndexPath)做key就比較合理。

查看GameboardView的代碼,你會發(fā)現(xiàn)GameboardView沒有配套的委托協(xié)議。
沒有委托協(xié)議意味著:只存在viewController主動調用(通知)GameboardView,反過來GameboardView不需要通知viewController。進一步講,GameboardView只負責顯示,不負責與用戶交互。(看過前面文章的讀者應該會記得,該項目的用戶交互是由滑動手勢發(fā)起的)


筆者統(tǒng)計,GameboardView被controller調用的方法是以下4個:

b.reset()
b.moveOneTile(from, to: to, value: value)
b.moveTwoTiles(from, to: to, value: value)
b.insertTile(location, value: value)

除了reset之外,其他3個方法,基本和model的協(xié)議是一樣的。因為controller就只負責以簡單方式通知view(代碼上,就是直接調用view的方法)。以移動一個棋子的委托方法為例(model委托的分析,請參見model部分的文章)

  func moveOneTile(from: (Int, Int), to: (Int, Int), value: Int) {//controller實現(xiàn)的model的委托
    assert(board != nil)//board就是GameboardView
    let b = board!
    b.moveOneTile(from, to: to, value: value)// 直接調用view的方法
  }

下面是moveOneTile方法:

  func moveOneTile(from: (Int, Int), to: (Int, Int), value: Int) {
    ...(略,檢查參數(shù)代碼)
    let (fromRow, fromCol) = from
    let (toRow, toCol) = to
    let fromKey = NSIndexPath(forRow: fromRow, inSection: fromCol)
    let toKey = NSIndexPath(forRow: toRow, inSection: toCol)
    //上面是得到棋子view的key

    guard let tile = tiles[fromKey] else {
      assert(false, "placeholder error")
    }//得到舊位置的棋子(舊位置上一定會有棋子)
    let endTile = tiles[toKey]//得到新位置的棋子,可能不存在(單棋子移動分成移動和合并2種情況。只有合并情況才有新位置上的棋子。具體見model篇)

    var finalFrame = tile.frame
    finalFrame.origin.x = tilePadding + CGFloat(toCol)*(tileWidth + tilePadding)
    finalFrame.origin.y = tilePadding + CGFloat(toRow)*(tileWidth + tilePadding)
    //舊位置的frame,計算成新位置的frame

    tiles.removeValueForKey(fromKey)
    tiles[toKey] = tile
    //然后在字典中更新,刪除舊的,在放進新的

    ...(略,移動動畫和pop動畫,有興趣可查閱源碼)
  }

用文字說明流程:

1》參數(shù)是坐標,轉成NSIndexPath,在字典中找到棋子view
2》tiles字典更新,將原來位置的棋子從字典原來位置刪除,覆蓋到新位置
3》原來位置的棋子view,計算新位置的frame,使用動畫移動到新位置。新位子原來的view刪除。如果是合并(新位置原來有格子),需要pop動畫

另外2個方法也是差不多的套路:
moveTwoTiles 移動2個格子,過程與前面基本一致:

1》參數(shù)是坐標,轉成NSIndexPath,找到棋子view(2個原來位置的棋子)
2》tiles字典更新:將一個原始棋子view從字典原來位置刪除,覆蓋到新位置。另外一個原始位置的棋子view從字典中刪除
3》計算新位置棋子的frame。然后2個原來位置的棋子,動畫移動到新的位置,然后刪除一個,只保留一個。并顯示pop動畫

insertTile 新增格子

1》參數(shù)是坐標,轉成NSIndexPath。
2》計算frame,創(chuàng)建新的TileView。加入游戲盤view,然后插入字典中
3》動畫顯示。


總結

筆者經(jīng)過分析model時的邏輯洗禮,再分析view部分時,頭腦就清晰多了。
處理model的委托時,基本就是2步:1》修改保存棋子view的字典。2》移動棋子view

(該項目的view部分還有一些處理邏輯文中沒有提到(例如游戲盤背景的顯示、棋子動畫,得分view等),有興趣的讀者可自行查看源碼了解。

非常感謝您的閱讀!您的留言、打賞、點贊、關注、分享,對筆者最大的鼓勵:P

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,179評論 4 61
  • 前言 筆者是swift自學新手,希望借助閱讀別人開源項目提升自己swift水平。文中將盡量使用文字描述來代替代碼的...
    安靜的貓咪先生閱讀 1,044評論 3 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,765評論 25 708
  • 今年國慶對于我來說卻有不同! 前天女兒過了周歲生日,哺乳期間多一個小時的哺乳假結束了!從今天開始我中午...
    芝芝1981閱讀 186評論 4 2
  • 小學二年級的時候,我就離開了老家的安樂窩——村校,轉到鄉(xiāng)鎮(zhèn)的小學去讀書了,同時也寫下了我學生生涯的唯一光環(huán)—...
    alexader狗閱讀 274評論 0 0