UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡單示例(二)

版本記錄

版本號 時間
V1.0 2019.04.27 星期六

前言

iOS中有關視圖控件用戶能看到的都在UIKit框架里面兴猩,用戶交互也是通過UIKit進行的。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動力學和移動效果(一)
2. UIKit框架(二) —— UIKit動力學和移動效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴張效果的實現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴張效果的實現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復使用的滑塊(二)
7. UIKit框架(七) —— 動態(tài)尺寸UITableViewCell的實現(xiàn)(一)
8. UIKit框架(八) —— 動態(tài)尺寸UITableViewCell的實現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據異步預加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據異步預加載(二)
11. UIKit框架(十一) —— UICollectionView的重用、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用跨算、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側滑式面板導航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側滑式面板導航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡單示例(一)

Step 3: Adoptng the CustomLayout

在構建和運行項目之前,您需要:

  • 使集合視圖采用CustomLayout類椭懊。
  • 使JungleCupCollectionViewController支持自定義補充視圖supplementary views诸蚕。

打開Main.storyboard并在Jungle Cup Collection View Controller Scene中選擇Collection View Flow Layout,如下所示:

接下來灾搏,打開Identity Inspector并將Custom Class更改為CustomLayout挫望,如下所示:

接下來立润,打開JungleCupCollectionViewController.swift狂窑。

添加計算屬性customLayout以避免詳細代碼重復。

您的代碼應如下所示:

var customLayout: CustomLayout? {
  return collectionView?.collectionViewLayout as? CustomLayout
}

接下來桑腮,使用以下內容替換setUpCollectionViewLayout()

 
  private func setupCollectionViewLayout() {
    guard let collectionView = collectionView,
      let customLayout = customLayout else {
        return
    }
    // 1
    collectionView.register(
        UINib(nibName: "HeaderView", bundle: nil),
        forSupplementaryViewOfKind: CustomLayout.Element.header.kind,
        withReuseIdentifier: CustomLayout.Element.header.id
    )
    collectionView.register(
        UINib(nibName: "MenuView", bundle: nil),
        forSupplementaryViewOfKind: CustomLayout.Element.menu.kind,
        withReuseIdentifier: CustomLayout.Element.menu.id
    )
    
    // 2
    customLayout.settings.itemSize = CGSize(width: collectionView.frame.width, height: 200)
    customLayout.settings.headerSize = CGSize(width: collectionView.frame.width, height: 300)
    customLayout.settings.menuSize = CGSize(width: collectionView.frame.width, height: 70)
    customLayout.settings.sectionsHeaderSize = CGSize(width: collectionView.frame.width, height: 50)
    customLayout.settings.sectionsFooterSize = CGSize(width: collectionView.frame.width, height: 50)
    customLayout.settings.isHeaderStretchy = true
    customLayout.settings.isAlphaOnHeaderActive = true
    customLayout.settings.headerOverlayMaxAlphaValue = CGFloat(0)
    customLayout.settings.isMenuSticky = true
    customLayout.settings.isSectionHeadersSticky = true
    customLayout.settings.isParallaxOnCellsEnabled = true
    customLayout.settings.maxParallaxOffset = 60
    customLayout.settings.minimumInteritemSpacing = 0
    customLayout.settings.minimumLineSpacing = 3
}

以下是上面代碼的作用:

  • 1) 首先泉哈,注冊用于彈性標題和自定義菜單的自定義類。 這些是已在初始項目中實現(xiàn)的UICollectionReusableView子類破讨。
  • 2) 最后丛晦,設置CustomLayout設置的大小,行為和間距sizes, behaviours and spacings提陶。

在構建運行應用程序之前烫沙,將以下兩個case選項添加到viewForSupplementaryElementOfKind(_:viewForSupplementaryElementOfKind:at :)以處理自定義補充視圖類型:

case CustomLayout.Element.header.kind:
  let topHeaderView = collectionView.dequeueReusableSupplementaryView(
    ofKind: kind,
    withReuseIdentifier: CustomLayout.Element.header.id,
    for: indexPath)
  return topHeaderView
      
case CustomLayout.Element.menu.kind:
  let menuView = collectionView.dequeueReusableSupplementaryView(
    ofKind: kind,
    withReuseIdentifier: CustomLayout.Element.menu.id,
    for: indexPath)
  if let menuView = menuView as? MenuView {
    menuView.delegate = self
  }
  return menuView

做得好! 這是一段漫長的旅程隙笆,但你差不多完成了锌蓄。

構建并運行項目! 您應該看到類似于以下內容的內容:

入門項目中的UICollectionView現(xiàn)在有一些額外的功能:

  • 在頂部有一個巨大的標題顯示叢林杯的標志撑柔。
  • 在這之下瘸爽,有一個帶有四個按鈕的菜單,每個團隊一個铅忿。 如果點擊按鈕剪决,集合視圖將重新加載相應的團隊。

你已經做得很好檀训,但你可以做得更好柑潦。 現(xiàn)在是時候為你的UICollectionView打造一些漂亮的視覺效果了。


Adding Stretchy, Sticky and Parallax Effects

在本UICollectionViewLayout教程的最后一部分中峻凫,您將添加以下視覺效果:

  • 1) 使header有彈性妒茬。
  • 2) 在菜單和section headers中添加粘性效果。
  • 3) 實現(xiàn)平滑的視差效果蔚晨,使用戶界面更具吸引力乍钻。

注意:UICollectionViewLayout教程的以下部分暗示了仿射變換的基本知識肛循。

1. Affine Transforms

Core Graphics CGAffineTransform API是將視覺效果應用于UICollectionView元素的最佳方式。

由于各種原因银择,仿射變換非常有用:

  • 1) 它們允許您在極少數(shù)代碼行中創(chuàng)建復雜的視覺效果多糠,如平移,縮放和旋轉浩考,或三者的組合夹孔。
  • 2) 它們以完美的方式與UIKit組件和AutoLayout進行互操作。
  • 3) 它們可幫助您在復雜情況下保持最佳性能析孽。

仿射變換背后的數(shù)學真的很酷搭伤。 但是,解釋CGATransform幕后矩陣的工作方式超出了本UICollectionViewLayout教程的范圍袜瞬。

如果您對此主題感興趣怜俐,可以在 Apple’s Core Graphic Framework Documentation中找到更多詳細信息。

2. Transforming Visible Attributes

打開CustomLayout.swift并將layoutAttributesForElements(in :)更新為以下內容:

override public func layoutAttributesForElements(
  in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    guard let collectionView = collectionView else {
      return nil
    }
    visibleLayoutAttributes.removeAll(keepingCapacity: true)
    // 1
    let halfHeight = collectionViewHeight * 0.5
    let halfCellHeight = cellHeight * 0.5
    // 2
    for (type, elementInfos) in cache {
      for (indexPath, attributes) in elementInfos {
        // 3
        attributes.parallax = .identity
        attributes.transform = .identity
        // 4
        updateSupplementaryViews(
          type,
          attributes: attributes,
          collectionView: collectionView,
          indexPath: indexPath)
        if attributes.frame.intersects(rect) {
          // 5
          if type == .cell,
            settings.isParallaxOnCellsEnabled {
              updateCells(attributes, halfHeight: halfHeight, halfCellHeight: halfCellHeight)
          }
          visibleLayoutAttributes.append(attributes)
        }
      }
    }
    return visibleLayoutAttributes
}

以下是對上述情況的逐步說明:

  • 1) 您存儲一些有用的值以避免在循環(huán)中計算它們邓尤。
  • 2) 這與此方法的先前版本相同拍鲤。 您迭代所有緩存的屬性。
  • 3) 重置為默認值視差parallax變換和元素屬性transform汞扎。
  • 4) 目前季稳,您只需調用一種方法來更新不同類型的補充視圖(supplementary views)。 您將在此代碼塊之后實現(xiàn)它澈魄。
  • 5) 檢查當前屬性是否屬于一個單元格景鼠。 如果在布局設置中激活了視差效果,請調用方法以更新其屬性痹扇。 如上所述辩恼,您將在此代碼塊之后實現(xiàn)此方法璃饱。

接下來,是時候實現(xiàn)上面循環(huán)中調用的兩個方法了:

  • updateSupplementaryViews(_:attributes:collectionView:indexPath:)
  • updateCells(_:halfHeight:halfCellHeight:)

添加以下內容:

private func updateSupplementaryViews(_ type: Element,
                                      attributes: CustomLayoutAttributes, 
                                      collectionView: UICollectionView,
                                      indexPath: IndexPath) {
    // 1
    if type == .sectionHeader,
      settings.isSectionHeadersSticky {
        let upperLimit = 
           CGFloat(collectionView.numberOfItems(inSection: indexPath.section))
           * (cellHeight + settings.minimumLineSpacing)
        let menuOffset = settings.isMenuSticky ? menuSize.height : 0
        attributes.transform =  CGAffineTransform(
          translationX: 0,
          y: min(upperLimit,
          max(0, contentOffset.y - attributes.initialOrigin.y + menuOffset)))
    }
    // 2
    else if type == .header,
      settings.isHeaderStretchy {
        let updatedHeight = min(
          collectionView.frame.height,
          max(headerSize.height, headerSize.height - contentOffset.y))
        let scaleFactor = updatedHeight / headerSize.height
        let delta = (updatedHeight - headerSize.height) / 2
        let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
        let translation = CGAffineTransform(
          translationX: 0,
          y: min(contentOffset.y, headerSize.height) + delta)
        attributes.transform = scale.concatenating(translation)
        if settings.isAlphaOnHeaderActive {
          attributes.headerOverlayAlpha = min(
            settings.headerOverlayMaxAlphaValue,
            contentOffset.y / headerSize.height)
        }
    }
    // 3
    else if type == .menu,
      settings.isMenuSticky {
        attributes.transform = CGAffineTransform(
          translationX: 0,
          y: max(attributes.initialOrigin.y, contentOffset.y) - headerSize.height)
    }
  }

依次記錄每個編號的注釋:

  • 1) 測試當前元素是否為section header。 然后球化,如果在布局設置中激活粘性行為促绵,則計算transform又厉。 最后將計算值分配給屬性的transform屬性菠镇。
  • 2) 與上面相同的例程,但這次檢查元素是否是top header禀梳。 如果激活了彈性效果杜窄,請執(zhí)行變換計算。
  • 3) 同樣的例程算途。 這次執(zhí)行粘性菜單的變換計算塞耕。

現(xiàn)在是時候transform集合視圖單元格了:

private func updateCells(_ attributes: CustomLayoutAttributes,
                         halfHeight: CGFloat,
                         halfCellHeight: CGFloat) {
  // 1
  let cellDistanceFromCenter = attributes.center.y - contentOffset.y - halfHeight
    
  // 2
  let parallaxOffset = -(settings.maxParallaxOffset * cellDistanceFromCenter)
    / (halfHeight + halfCellHeight)
  // 3 
  let boundedParallaxOffset = min(
    max(-settings.maxParallaxOffset, parallaxOffset),
    settings.maxParallaxOffset)
  // 4
  attributes.parallax = CGAffineTransform(translationX: 0, y: boundedParallaxOffset)
}

下面進行細分:

  • 1) 計算單元格與集合視圖中心center的距離。
  • 2) 在最大視差parallax值(在布局設置中設置)中按比例映射單元格與中心的距離
  • 3) 綁定parallaxOffset以避免視覺故障嘴瓤。
  • 4) 使用計算的視差parallax值創(chuàng)建CAAffineTransform轉換扫外。 最后莉钙,將translation分配給單元格的屬性的transform屬性。

為了實現(xiàn)對PlayerCell的視差效果筛谚,圖像的frame應具有頂部和底部負間距磁玉。 在初始項目中,為您設置了這些約束驾讲。 您可以在Constraint檢查器中查看它們(見下文)蚊伞。

在構建之前,您必須修復一個最終細節(jié)吮铭。 打開JungleCupCollectionViewController.swift时迫。 在setupCollectionViewLayout()內部更改以下值:

customLayout.settings.headerOverlayMaxAlphaValue = CGFloat(0)

為下面

customLayout.settings.headerOverlayMaxAlphaValue = CGFloat(0.6)

此值表示布局可以分配給headerView上的黑色疊加層的最大不透明度值。

構建并運行項目以欣賞所有視覺效果谓晌。 滾動吧掠拳! 滾動吧!

如果您想了解有關自定義UICollectionViewLayout的更多信息扎谎,請考慮閱讀 Collection View Programming Guide for iOS中的Creating Custom Layouts部分碳想,該部分詳細介紹了此主題烧董。

后記

本篇主要講述了基于自定義UICollectionViewLayout布局的簡單示例毁靶,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逊移,隨后出現(xiàn)的幾起案子预吆,更是在濱河造成了極大的恐慌,老刑警劉巖胳泉,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拐叉,死亡現(xiàn)場離奇詭異,居然都是意外死亡扇商,警方通過查閱死者的電腦和手機凤瘦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來案铺,“玉大人蔬芥,你說我怎么就攤上這事】睾海” “怎么了笔诵?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姑子。 經常有香客問我乎婿,道長,這世上最難降的妖魔是什么街佑? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任谢翎,我火速辦了婚禮捍靠,結果婚禮上,老公的妹妹穿的比我還像新娘森逮。我一直安慰自己剂公,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布吊宋。 她就那樣靜靜地躺著纲辽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪璃搜。 梳的紋絲不亂的頭發(fā)上拖吼,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音这吻,去河邊找鬼吊档。 笑死,一個胖子當著我的面吹牛唾糯,可吹牛的內容都是我干的怠硼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼移怯,長吁一口氣:“原來是場噩夢啊……” “哼香璃!你這毒婦竟也來了?” 一聲冷哼從身側響起舟误,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤葡秒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嵌溢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眯牧,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年赖草,在試婚紗的時候發(fā)現(xiàn)自己被綠了学少。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秧骑,死狀恐怖版确,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情腿堤,我是刑警寧澤阀坏,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站笆檀,受9級特大地震影響忌堂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜酗洒,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一士修、第九天 我趴在偏房一處隱蔽的房頂上張望枷遂。 院中可真熱鬧,春花似錦棋嘲、人聲如沸酒唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痪伦。三九已至,卻和暖如春雹锣,著一層夾襖步出監(jiān)牢的瞬間网沾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工蕊爵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辉哥,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓攒射,卻偏偏與公主長得像醋旦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子会放,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容