如何創建,使用自己的PageLoader加載Page

作者: 冶金矿产  发布:2019-11-18

已上架,下載地址:

源碼下載:http://yunpan.cn/cFwwrT4V5rHIM  访问密码 1b97

如何創建 iOS 展開式 UITableView?

 

幾乎所有的 App 都會以導航的方式向用戶展示多個 View Controller。這些 View Controller 分別擔任不同的職責,比如在屏幕上簡單顯示一些信息,或者從用戶輸入中收集複雜數據。針對 App 的不同功能設計新的 View Controller 是一件必不可少的任務,有時候甚至是一種頗為艱鉅的任務。但是,許多時候,通過展開式 UITableView,我們卻可以不用創建新的 View Controller 就能完成同樣的任務。

顧名思義,一個展開式 UITableView 是這樣一種表視圖,它“允許”其單元格(cell)展開或者收起,顯示或者隱藏,而在一般的表視圖中,它們的單元格只能是顯示的狀態。當我們需要收集一些簡單的數據或者根據用戶的意願顯示/隱藏某些內容時,創建展開式 UITableView 是一種不錯的選擇。這樣,我們就沒有必要僅僅為了讓用戶輸入一些數據就創建新的 View Controller,無論如何我們都只需要呆在同一個 View Controller 裡面,即當前的 View Controller 中。例如,通過展開式的 cell,我們顯示或隱藏一個用於給用戶輸入信息的表單,在顯示或隱藏這個表單時,根本不需要離開當前的 View Controller。

图片 1

是否採用或者不採用展開式的 UITableView,完全取決於 App 的性質。但是,只要是通過子類化UITableViewCell和自定義xib文件的方式來定製 cell 的情況,App 的外觀就不會是什麼問題。因此,歸根結底,這只是一個需求問題。

在本教程中,我將演示一種創建展開式 UITableView 的簡單有效的方法。注意,這並不是唯一方法。最好的方法要視 App 的需要而定,我的目的只是展示一種一般化的解決方案,它在大部份情形下都是適用的。因此,請進入下一部份,看看本教程最終將實現什麼樣的效果。

特點是:繁體豎排,隱藏/顯示標點符號。

 

示例 App

我們將創建一個只有一個 View Controller (其包含有一個 TableView)的 App,在這個 App 中我們將演示如何創建一個展開式表視圖。我們將模擬一個允許用戶輸入的表單,為了演示,這個 TableView 將由 3 個 section 構成:

Personal Preferences Work Experience

每個 section 都會包含展開式 cell,這些 cell 會隱藏/顯示該 section 的其它 cell。尤其是位於 section 頂部的 cell (該 cell 能夠展開或收起):

對於 “Personal” section:

Full name: 這個 cell 用於顯示用戶的全名,當它處於展開狀態時,它下面會多出兩個子 cell,分別用於輸入名和姓。 Date of birth: 用於顯示用戶的生日。當它被展開后,會顯示一個日期選擇器(UIDatePicker),允許用戶選擇某個日期并提供一個按鈕將用戶選擇的日期返回給它上面的 cell。 Marital status: 顯示用戶的婚姻狀態:已婚或單身。當它被展開后,會顯示一個開關控件,允許用戶設置他們的婚姻狀態。

對於 “Preferences” section:

Favorite sport: 我們模擬了一個運動種類列表,用於提供給用戶,讓他們從中選擇他們所喜愛的運動。當它被展開時,會列出 4 個運動種類,當用戶選擇某個子項,這個 cell 又自動會被收起。 Favorite color: 和上面非常相似,只不過這裡顯示了一個顏色列表供用戶選擇。

對於 “Work Experience” section:

Level: 當這個 cell 被點擊并展開后,將顯示另一個包含有一個滑動條的 cell,允許用戶設置他們的工作經驗等級。這個級別用一個 0…10 之間的數字表示,我們只取這個數值的整數部份。

通過下面的動畫會看得更清楚一些:

图片 2

注意上面的例子,當我們展開 TableView 時會顯示不同類型的 cell。這些 cell 都被包含在開始項目中了,你可以下載這些代碼,項目已經完成了一些前期的準備工作。所有的 cell 都在單獨的 xib 文件進行了必要的設計,同時它們的 Custom Class 也被指定為自定義的 UITableViewCell 子類(即 CustomCell):

图片 3

在項目文件夾中,你將發現這些 cell 所使用的 xib 文件,包括:

图片 4

它們作用分別如其名稱所示,你也可以下載開始項目深入探究一番。

除了 cell,你還會發現一些已經寫好的代碼。雖然這些代碼對於實現整個示例 App 的功能來說是必不可少的,但卻不屬於本教程的核心內容,因此我會跳過這些代碼,僅僅是以現成的代碼提供在開始項目中。缺失的其餘代碼是本教程中我們最關心的內容,在接下來的教程中會以 step-by-step 的方式添加到項目中。

到此,你已經知道我們最後的目標是什麼了,接下來就讓我們開始學習如何創建展開式的 UITableView。

截幾張圖來瞅瞅。

在上一篇博客中,我已經說明了為什麼要自己寫一個PageLoader。原因就是,Frame的GoBack只是簡單的重新加載一個頁面,並不會保存原來的頁面的狀態。

描述單元格

我將在本教程中演示的、所有與展開式 UITableView 相關的實現和技術,都基於這樣一種簡單的思路:向 App 描述每個 cell 的細節。通過這種方式我們讓 App 知道每一個 cell 到底是展開的還是收起的,是可見的還是隱藏的,每個 cell 的文字標籤顯示什麼內容,等等。實際上,整體思路都基於將屬性集進行編組,這些屬性要麼描述了每個 cell 的屬性,要麼包含了 cell 的某些數值,然後將這些屬性告訴給 App,這樣 App 才能正確地顯示它們。

在本示例程序中,我創建和使用了一個屬性集合,如下面所列。注意在真正的 App 中,你可能需要增加新的屬性,或者對某些屬性進行修改。不過,此時你只需要了解大致的情況就可以了。當然,只要你願意你可以任意修改這些屬性。我們所使用的屬性列表(plist)是這樣的:

isExpandable: 一個布爾值,標明 cell 是否能夠展開或收起。在本教程中,這是我們非常關心的重要屬性。 isExpanded: 一個布爾值,標明一個展開式 cell 是處於展開狀態還是收起狀態。頂層的 cell 默認是收起狀態,因此這個值一開始都應該設成 NO。 isVisible: 顧名思義,標明這個 cell 是否應該顯示到表格中。稍後這個屬性會扮演一個重要的角色,因為我們會根據這個屬性讓表格中的某些 cell 得到顯示。 value: 這個屬性用於保存 UI 控件的值(例如,開關控件中的婚姻狀態)。不是所有 cell 都會有這樣的控件,因此大部份 cell 的這個屬性值將保留為空。 primaryTitle: cell 主標題的顯示文本,當這個屬性不為空時,這個屬性的值會顯示到 cell 上。 secondaryTitle: cell 子標題的顯示文本,或者 cell 第二個標籤的顯示文本。 cellIdentifier: 自定義 cell 的ID,用於唯一識別當前 cell 的描述。這個 ID 不仅被 App 用於从缓存队列中弹出合适的 cell,而且还要根據这个 ID 对要顯示的 cell 進行相應的處理并指定 cell 的高度。 additionalRows: 用於表示當一個展開式單元格被展開時,它下面包含了幾個附屬的 cell。

每個 cell 都會用上面的屬性集進行描述。從 App 的角度,我們使用一個屬性列表(plist)文件來保存它們會更加輕鬆。在這個plist文件中,我們會為每個 cell 使用一個上述屬性集來進行描述,并適當地填充屬性集中的屬性值,這樣,我們將最終獲得所有 cell 的一個完整的描述,這個描述對於我們或 App 來說都很容易理解。同時我們并沒有為之編寫一行代碼。很不错吧?

現在,我們在項目中新建一個 plist 文件,然後用適當的數據來填充它。當然你也可以從這裡下載現成的.plist 文件。下載后記得將它添加到我們的開始項目中。手動設置所有 cell 的屬性會佔用大量空間,這是完全沒有必要的,同時拷貝-粘貼或者輸入所有屬性值也是一件很繁瑣的事情。
然後,讓我們來討論一下這個 plist 文件:

首先,你下載的這個文件的文件名叫做CellDescriptor.plist。它的根節點(root)是一個數組,其中的每個元素表示表格中的一個 section。也就是說這個plist文件的 root 數組中有三個元素,就跟我們想在表格中顯示的 section 的數目一樣。

每個 section 本身也是一個數組,數組中包含了該 section 中所包含的所有 cell 的描述。實際上,這些編組的屬性集在這裡用字典來進行表示,每個字典代表了一個單獨的 cell。這是一個 plist 文件的例子:

图片 5

現在,是時候來完整地回顧一下我們將要顯示到表格中的 cell 的屬性集和屬性值了。很顯然,擁有了這些 cell 描述之後,我們需要編寫用於生成、管理 cell 的代碼大大減少了。我們也不需要告訴 App 這些 cell 的各種狀態(例如,哪個單元格是可展開的,App 應當讓某個 cell 展開或收起,判斷某個 cell 是可見的還是隱藏的等等)。所有的這些信息都包含在你下載的 plist 文件裡面。

 

怎麼樣才能保存原來的頁面狀態呢?只要原來的Frame不刪除,不就可以保存原來的狀態了嗎?所以實現起來就簡單多了,每Navigate一個頁面,我們就生成一個Frame,添加到Grid中,當Back時,我們就把這個Frame刪除掉。這樣就可以達到我們的目的了。

加載單元格描述

終於可以編寫代碼了,雖然我們使用的單元格描述技術為我們節省了許多時間,但在這個項目中我們仍然免不了要編寫代碼。現在,我們已經有了用於描述 cell的 plist 文件了,接下來的事情自然是用代碼將文件內容加載到一個數組對象中。這個數組對象將在後面充當表格的數據源。

打開開始項目中的 ViewController.swift 文件,在類的頂部聲明如下屬性:

var cellDescriptors: NSMutableArray!

這個數組將用於包含所有來自于 plist 文件中的用於描述每個 cell的字典。

然後,新增一個方法,用於將文件內容加載到數組對象。我們將這個方法命名為 loadCellDescriptors()

func loadCellDescriptors() {
    if let path = NSBundle.mainBundle().pathForResource("CellDescriptor", ofType: "plist") {
        cellDescriptors = NSMutableArray(contentsOfFile: path)
    }
}

這個方法非常簡單:首先我們我們判斷指定的 plist 文件路徑在 bundle 中是否存在,如果存在我們從文件中加載一個數組并初始化 cellDescriptors 變量。
接下來就是調用這個方法,我們將在 TableView 已經配置好,並且視圖即將顯示之前(即在 TableView 已經創建並且還沒有顯示任何內容之前)調用這個方法:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    configureTableView()

    loadCellDescriptors()
}

如果在上述方法最後一行後添加 print(cellDescriptors) 一句,則運行 App 后你將看見 plist 文件的內容輸出到了控制台中。這就說明文件已經成功加載到內存了。

图片 6

通常,本節的內容應該到此結束,但這次有一點例外。我們還要補充一些對於下一節來說至關重要的內容。也許你想到了(尤其是當你檢查了CellDescriptor.plist文件之後),當 App 啟動后,并不是所有的 cell 都應該被顯示。事實上,我們根本無法得知它們是否會在同時顯示,因為它們是根據用戶的要求來進行展開和收起的。

從編程的角度,這意味著 每個 cell 的行索引不應該是常量 (這就是為什麼我們在處理每個 cell 時,將 indexPath.row 用代碼來生成)。同時,我們也不能用 cell 的行索引來遍歷數據源數組并顯示每個 cell。我們只能將可見的 cell 的行索引來提供給 App。如果將 cell 描述中標記為不可見的 cell 顯示出來,這就大錯特錯了,那會導致 App 表現異常。

基於這樣的原因,我們需要實現一個新方法,叫做 getIndicesOfVisibleRows()。這個方法的作用是顯而易見的:它只返回那些標記為可見的 cell 的行索引。在實現這個方法之前,請在類的頂部增加如下屬性:

var visibleRowsPerSection = [[Int]]()

這是一個二維數組,保存了所有 section 的可見的 cell 的行索引(一維用於表示 section,一維用於表示 cell)。

現在來實現這個方法。你也許想到了,我們會遍歷所有 cell 描述并將 isVisible 屬性為 true 的 cell 的行索引添加到二維數組中。當然,我們不得不用到嵌套循環,但這也不是什麼大問題。下面是這個方法的實現:

func getIndicesOfVisibleRows() {
    visibleRowsPerSection.removeAll()

    for currentSectionCells in cellDescriptors {
        var visibleRows = [Int]()

        for row in 0...((currentSectionCells as! [[String: AnyObject]]).count - 1) {
            if currentSectionCells[row]["isVisible"] as! Bool == true {
                visibleRows.append(row)
            }
        }

        visibleRowsPerSection.append(visibleRows)
    }
}

注意,方法的一開始就將 visibleRowsPerSection 數組的內容清空了,否則連續多次調用這個方法之後數據就不正常了。接下來的實現就一目了然了,就不用我再多說了。

這個函數的第一次調用應該在從 plist 文件加載完 cell 描述之後(我們還會在後面多次調用這個函數)。因此,回到本節實現的第一個方法,將它修改為:

func loadCellDescriptors() {
    if let path = NSBundle.mainBundle().pathForResource("CellDescriptor", ofType: "plist") {
        cellDescriptors = NSMutableArray(contentsOfFile: path)
        getIndicesOfVisibleRows()
        tblExpandable.reloadData()
    }
}

雖然 TableView 還不能正常工作,但我們已經在 App 一啟動的時候就調用了它的刷新動作,這樣就能保證在接下來的的步驟中顯示正確的 cell。

本文由88必发手机版发布于冶金矿产,转载请注明出处:如何創建,使用自己的PageLoader加載Page

关键词: