運用 contentOffset / contentInset 手把手實作 instagram 個人簡介頁面的滑動 -swift 4

張憲騰
7 min readJun 5, 2018

--

最近實作了一些 Scroll View (包含 tableView 和 collectionView ,因為他們都繼承 scrollView ,所以我等等就用 scrollView 當一個總稱。)之間的交互作用,常常在 content inset 和 content offset 和 content size 上面卡了很久,所以在這邊做個筆記,希望有遇到這些問題的人,可以有更好的概念去釐清他們兩者的差異。

那這篇文中,會分成兩個階段:

  1. 描述 content size, content offset, content inset 的區別,還有他們分別代表的意義
  2. 透過剛剛的觀念,實踐一個類似 instagram 個人頁面中可以將個人資訊上推的畫面,如下圖所示:

首先,我先用簡短的話語述說這三種的區別,contentSize 說明了這整個 scrollView 所包含的內容,content offset 則說明了偏離 scrollView 的起點多遠,而 contentInset 則是幫這個 scrollView 賦予更長或更短的能力。

如果不懂沒關係,我下面會一一解釋這一切,這邊先看幾張史丹佛大學釋出的圖片。

ContentSize

基本上我們會用到 scrollView ,代表你想要呈現的內容的 SIZE 一定大於你手機的螢幕,而在 tableView 和 collectionView 中,系統會自動幫我們計算他該有的長度與高度( dataSource 這邊會去定義 scrollView 要有幾格, scrollView 多高),所以如同先前所述, contentSize 的意義就是所有想要呈現的內容。

ContentOffset

ContentOffset 敘述了你與內容( scrollView )原點的距離,透過設定 contentOffset ,我們可以將我們目前所在的位置告訴另一個 View ,便可以做出我們要弄出的效果,簡單來說, contentOffset 賦予現在所在的位置,若是還不了解,可以照著下面的 demo 實做一次,便會稍微明瞭一點。

ContentInset

ContentInset 則賦予能力,有時候我們的 tableView 會被 tabBar 擋住,我們會使用

self.tableView.contentInset = UIEdgeInsetsMake(0, 0, tabBarHeight, 0)

來解決當下的問題,他的能力在於可以將你的 scrollView 延伸,如此我們就可以運用 contentInset 來解決任何 scrollView 被擋住的問題,當然老話,還不了解沒關係,緊接著就是我們的實作了。

像這樣下面是 collectionView 或 TableView ,而上面是一個普通的 UIView ,我該怎麼往上滑,讓他可以把 UserInfoView 往上推呢,如果不想手把手教學,可以直接在最下方下載 github 連結,

首先,在 Main.StoryBoard 裡面,我們先定義好 tableView 跟 userInfoView 的位置:

大家可以看到我的 TableView (下面的 UIView ,我用 Container View 將它包起來了)設定的高度是整個螢幕的長寬,會這麼做是因為在滑動時一直更改 tableView 的 frame ,會讓你的滑動看起來很怪,反而是先定義好他整體的高度,我們修改內部內容擺放的位置就好。

而之後再將 UserInfoView 疊在上面,我們就完成了畫面的初始化了,其他生成 cell 什麼的我就不說啦!

再來,回到我們的 tableViewController ,讓我們先看一段 code :

1~11行都只是在註冊 cell 和把他的 delegate 和 dataSource 設為自己,

12行我把 tableView 的 contentInset 的 top 設為 userInfoView 的高度,如我之前所述, contentInset 是賦予能力,他很像把你的 tableView 拉長,你可以想像你的 tableView 拉長了 userInfoViewHeight 的高度,而你的第一個 cell 也被推下來了。

到目前為止,你定義了 tableView 的大小,和 tableViewCell 一開始的位置,接下來你就要開始處理滑動的部分了,讓我們看一段 code :

ScrollViewDidScroll 運用之前會繼承 ScrollView 的 delegate ,而你在之前已經繼承 tableView 的 delegate ,所以可以擁有這個能力,只要我們滑動任何 scrollView 便會觸發他,也因此可以用它來記錄每次滑動的 contentOffset。

關於第3行可能就有點邏輯了,因為我們剛剛用 contentInset 把 scrollView 下推了一個 userInfoViewHeight 的高度,所以你的 contentOffset 從 0 變成了 -200 (負的 userProfileViewHeight 的高度),可以看到我以下的圖

因為我們被往下推了 userInfoView (200)的高度,所以我們的 contenSize 的起點變成了 (0, 0) ,也因此, tableView 左上的頂點變成了 (0, -200) ,這也是為什麼我們第三行要加上 userInfoView 的高度,讓你用 delegate 傳過去的值變成真實移動得值。

關於 delegate 的內部實作寫在 GitHub 內,這裡就不多談了。

緊接著我們要來看到 UserProfileViewController 來移動我們 UserInfoView了,大家可以先看到以下的 code :

這段 code 第二行我們先拿到每次傳進來的值,然後我們在把 userInfoView 往上推,也就是把他的 frame 慢慢變小,但要記得,因為 frame 定義的是 View 絕對位置,所以這邊他的起始高度就是 64 ( status bar 的高度 20 加上navigation bar 的高度 44 , IphoneX 會有所變化。),所以記得要把它加上去,不然在上滑會突然很快地溜上去!

以上就是這次的 contentSize , contentOffset 和 contenInset 的小小介紹,加上一個小實做,如果大家不嫌棄可以給我個鼓勵幫我拍個手啦!阿哩阿豆:)

這次實作的github: https://github.com/boompieman/InstagramScrollViewDemo

--

--