UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)

版本记录

版本号 时间
V1.0 2019.04.27 星期六

前言

iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
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) 首先,注册用于弹性标题和自定义菜单的自定义类。 这些是已在初始项目中实现的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现在有一些额外的功能:

  • 在顶部有一个巨大的标题显示丛林杯的标志。
  • 在这之下,有一个带有四个按钮的菜单,每个团队一个。 如果点击按钮,集合视图将重新加载相应的团队。

你已经做得很好,但你可以做得更好。 现在是时候为你的UICollectionView打造一些漂亮的视觉效果了。


Adding Stretchy, Sticky and Parallax Effects

在本UICollectionViewLayout教程的最后一部分中,您将添加以下视觉效果:

  • 1) 使header有弹性。
  • 2) 在菜单和section headers中添加粘性效果。
  • 3) 实现平滑的视差效果,使用户界面更具吸引力。

注意:UICollectionViewLayout教程的以下部分暗示了仿射变换的基本知识。

1. Affine Transforms

Core Graphics CGAffineTransform API是将视觉效果应用于UICollectionView元素的最佳方式。

由于各种原因,仿射变换非常有用:

  • 1) 它们允许您在极少数代码行中创建复杂的视觉效果,如平移,缩放和旋转,或三者的组合。
  • 2) 它们以完美的方式与UIKit组件和AutoLayout进行互操作。
  • 3) 它们可帮助您在复杂情况下保持最佳性能。

仿射变换背后的数学真的很酷。 但是,解释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) 您存储一些有用的值以避免在循环中计算它们。
  • 2) 这与此方法的先前版本相同。 您迭代所有缓存的属性。
  • 3) 重置为默认值视差parallax变换和元素属性transform
  • 4) 目前,您只需调用一种方法来更新不同类型的补充视图(supplementary views)。 您将在此代码块之后实现它。
  • 5) 检查当前属性是否属于一个单元格。 如果在布局设置中激活了视差效果,请调用方法以更新其属性。 如上所述,您将在此代码块之后实现此方法。

接下来,是时候实现上面循环中调用的两个方法了:

  • 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。 如果激活了弹性效果,请执行变换计算。
  • 3) 同样的例程。 这次执行粘性菜单的变换计算。

现在是时候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值创建CAAffineTransform转换。 最后,将translation分配给单元格的属性的transform属性。

为了实现对PlayerCell的视差效果,图像的frame应具有顶部和底部负间距。 在初始项目中,为您设置了这些约束。 您可以在Constraint检查器中查看它们(见下文)。

在构建之前,您必须修复一个最终细节。 打开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布局的简单示例,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容