實作無限分頁滾動視圖 (Scroll View) 為使用者帶來更完美的體驗和設計


本篇原文(標題:Custom UI Master Class: Infinite Paging Scroll View )刊登於作者 Medium,由 Tim Beals 所著,並授權翻譯及轉載。

所有 App 的成功,都取決於使用者是否常用這個 App(使用者留存率高 High User Retention),而成功的使用者體驗 (UX) 和界面設計對留存率就非常重要了!在設計 App 的時候,我們需要確保使用者可以利用最小限度而直覺的操作,來達到他們的目的;而且,這操作最好是一個吸引又有趣的過程。這次的教學就是希望利用客製化 UI,來達到上述的目標。

記得從這裡下載我們的範例專案。

使用情境

在這次的範例之中,我們將會製作一個無限分頁滾動視圖 (Infinite Paging Scroll View)。就如上方的動畫一樣,這個 UI 零件適用於給使用者少數預設選項去選擇的情況。你會希望透過小範圍的螢幕來顯示這些選項,並有一個預設選項已經被選取。然後,只要簡單地點擊螢幕來選取新選項,並透過 MVC 架構來傳遞就可以了。那就讓我們開始吧!

無限滾動的錯覺

在我們開始實作專案之前,先討論一下無限滾動的錯覺是如何運作的。我們想要將滾動視圖設置為水平分頁,於陣列之中呈現數據。

圖一: 想像一下有四個元素,每個元素分別有不同的顏色及編號,我們想在滾動視圖的內容視圖之中,將各個元素分別以不同頁面來顯示。一般而言,我們會將內容的尺寸設計為滾動視圖的四倍寬,使每個元素都有一個自己的頁面。不幸的是,在這樣的情況下我們並不會獲得想要的效果,因為一旦我們將視圖滾動到第四個分頁,唯一能回到第一個分頁的方法,就是將內容偏移量 (content offset) 重設為 0,這樣確實可以回到起始的分頁沒錯,但就無法做到我們想要的漂亮分頁動畫。

圖二: 要解決這個問題,我們可以修改輸入數據,讓第一和最後一個元素在相反側各有一個複製,也就是說現在我們會用六個分頁來顯示四個元素。這樣我們就會建立到一個漂亮的分頁動畫,可以從元素四滾動到元素一(或是反方向從元素一滾動到元素四)。

圖三: 分頁動畫完成後,我們的滾動視圖會在內容視圖的尾端顯示第一個元素。這時候,我們就可以趁機設定內容偏移量,讓分頁從內容視圖前面相同的元素一顯示一樣的數據。這樣的切換對於使用者來說是無法查覺的。

請注意:在這個範例中,內容分頁僅向一個方向滾動,而上述的設置則展示了雙向的無限滾動。雖然這代表我們的範例中包含了一些不必要的程式碼,但我選擇使用允許雙向滾動的邏輯來實現它,這樣一來,有需要的話你就可以在自己的專案中實現它。

第一步:基礎設定

我們建立一個繼承自 UIView 的客製化類別 InfiniteScrollView,並將背景顏色設為灰色來視覺化我們的結構 (第 20 行)。接著,我們定義兩個屬性:scrollViewtapView。設置滾動視圖 scrollView 時 (第 3-9 行),我們要刪除滾動指示器 (scroll indicator),並啟用分頁;而點擊視圖 tapView 會被標記為 lazy 變數,以便我們稍後添加點按手勢。目前,點擊視圖僅設定為透明 (第 11-15 行)。我們這樣佈置這些子視圖 (第 27-36 行):滾動視圖大小設置為視圖的一半,然後讓 tapView 填滿視圖的邊界,並置於滾動視圖的頂部。現在,使用者可以與較大的視圖進行互動,而滾動則會在較小的視圖中運作。

我們用 frame 初始化了 InfiniteScrollView,並將它加入視圖控制器的子視圖階層中後,就會看到類似這樣的東西:

第二步:更改 Datasource 及滾動視圖佈局

下一步,我們要將數據加入到 infiniteScrollView 之中,並在加入到內容視圖的標籤中顯示元素。為了達到這個目的,我們需要創建兩個屬性,第一個是公開屬性 datasource,用來接收字串陣列 (第 3-7 行),以及另一個私有屬性 _datasource,這是輸入 (input) 更改後的版本,這麼一來我們就能夠如同上一節討論一樣,將第一和最後一個元素加到陣列的對側末端 (第 9-13 行)。

接著,我們有 modifyDatasourcesetupContentView 兩個方法。讓我們先看看第一個 modifyDatasource 方法 (第 15-25 行),請注意這個方法是由 datasource 內 didSet 屬性觀察器所呼叫的,所以我們可以確保每當數據改變時,客製化視圖都會被更新。我們把 datasource 綁定到 tempInput 變數,以確認 datasource 是否為 nil,同時也確認 count 數值是否大於 2 (第 16 行)。如果不符合這些條件,很明顯我們就沒有足夠的數據來達成滾動效果。在這種情況下,我們的方法會回傳未設置的子視圖。假設數據輸入和數量都正確,我們就可以更改 datasource,我們將第一及最後一個元素放入到元組 (tuple) 之中,就可以完成這個步驟了 (第 18 行)。強制解析這些數值是安全的,因為我們在前一個步驟已經檢查了這些索引中是否有值。然後,我們將第一個元素放入 tempInput 陣列尾端,並將最後一個元素插入陣列開頭 (第 17-18 行)。為了展示所需,我們可以加入 print 指令 (第 22 行),並用更改過的數據來設置私有屬性 _datasource

讓我們繼續設計,讓 _datasource 中的屬性觀察器呼叫 setupContentView() (第 10-12 行)。這邊,我們移除所有可能先前設定過的子視圖 (第 31-34 行)。接著,我們使用可選綁定 (optional binding) 來解析 _datasource,並設定內容尺寸。就我們的情況而言,我們希望以水平的方式滾動分頁,所以內容的高度將會跟滾動視圖的高度一致,而寬度會等於滾動視圖寬度乘以 _datasource 之中元素的數量 (第 36-38 行)。為了將標籤加入到正確位置,我們以循環的方式來創建標籤,並使用 i 值來計算新的標籤原點。在將標籤添加為 scrollView 的子視圖前 (第 41-50 行),我們先使用同樣的 i 值從 datasource 下標相應字串,並將它設置為我們的標籤文字。當所有標籤都加上了適當的文字後,我們就可以設置內容偏移量來顯示第一個元素 (第 51-52 行)。

為了看看成果,我們可以加入一個 datasource 到視圖控制器的 infiniteScrollView 物件中,並執行 App。終端機所印出的輸出,顯示了更改過的 _datasource 中,第一和最後一個元素都被正確地加入了 (第 3-5 行)。

第三步:加入點擊手勢與分頁邏輯

雖然數據已經加入到滾動視圖之中,但是我們尚未能夠滾動分頁。為了達到這個效果,我們使用選擇器 (selector) 方法 didReceiveTap(sender:),將點擊手勢加入到 tapView 屬性中 (第 6-7 行)。這個方法 (第 19-28 行) 將滾動視圖的寬度加到目前內容的偏移量 x 值,來創建我們想要顯示的下一個矩形 (nextRect)。這樣,我們就能從 scrollRectToVisible 方法 (第 27 行) 獲得分頁動畫了!

當到達 datasource 末端時,我們就需要添加邏輯來設置內容偏移量,這個邏輯應在滾動動畫一完成後立刻被呼叫。如果我們設定客製化類別為滾動視圖的 delegate,我們就能夠加入覆寫方法 scrollViewDidScroll,每當滾動動畫完成時都會被呼叫。我們在初始化方法 (第 15 行) 中設定這個委派,然後在 scrollViewDidScroll 中,我們確認可查看內容的位置是否超出了 datasource 的邊界,並使用 contentOffset (第 33-41 行) 適當地重置。

現在建置並執行 App,我們應該能夠看到想要的分頁效果。

第四步:創建委派來傳遞已選取的選項

差不多完成了!InfiniteScrollView 確實達到了我們想要的效果,不過還差一步,我們想要將所選取到的選項回傳給視圖控制器,最好的方法就是使用委派模式。

首先,我們宣告 InfiniteScrollViewDelegate 協定,這協定帶有一個單一方法,將選項以輸入參數 optionChanged(to option:) 傳遞 (第 1-3 行)。然後,創建一個可選協定類型的委派屬性 (第 13 行),再創建一個 selectedOption 字串屬性來透過 didSet 屬性觀察器呼叫委派方法 (第 7-11 行)。現在每當我們設定 selectedOption,委派就會收到選擇。我們將會在兩個地方設置此屬性,一旦我們佈局了內容視圖,就可以改變內容的偏移量來顯示第一個選項。而現在我們也設定了第一個選項為 selectedOption (第 39 行)。

最後,我們可以在使用者點擊時 ── 也就是分頁動畫剛開始時 ── 設定選擇的選項。我們使用 guard 來解析 datasource、計算下一個選項的索引值、並使用它來下標 datasource,將字串的值指派給 selectedOption (第 47-51 行)。

在視圖控制器中,我們可以採用 InfiniteScrollViewDelegate 協定,並處理我們想要的傳入選項。然後,我們將視圖控制器自己設置為委派對象。讓我們建置並執行 App,我們應該能看到選項被發送到視圖控制器之中!

總結

大概就是這樣了!為了讓 UI 元件依照我們想要的方式運作,我們必須實現相當多的邏輯,但我們現在有了一些可高度重用的東西了。如果我們使用框架初始化 InfiniteScrollView,設置委派並傳遞我們想要滾動的選項陣列,你會發現我們只需三行簡單的程式碼,就可以建構到一個組件了!你也可以探索更多有趣的變化,加入滑動手勢或進行雙向滾動。你還可以研究刪除 tapView、並在 tableView 通過選擇單元格觸發點擊邏輯。希望你會看到更多有趣的組合,並在你的 App 內找到合適的地方來使用這個功能。謝謝你的閱讀!

本篇原文(標題:Custom UI Master Class: Infinite Paging Scroll View )刊登於作者 Medium,由 Tim Beals 所著,並授權翻譯及轉載。
作者簡介:Tim Beals 是一名 iOS 開發人員,擁有專業指導背景。他是軟件出版商 Roobi Creative Enterprises 的創始人。
Medium: https://medium.com/@timbeals
LinkedIn: https://www.linkedin.com/in/tim-beals/
Website: www.roobicreative.com
譯者簡介:HengJay,iOS 初學者,閒暇之餘習慣透過線上 MOOC 資源學習新的技術,喜歡 Swift 平易近人的語法也喜歡狗狗,目前參與生醫領域相關應用的 App 開發,希望分享文章的同時也能持續精進自己的基礎。

LinkedIn: https://www.linkedin.com/in/hengjiewang/
Facebook: https://www.facebook.com/hengjie.wang

此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This