iOS:Swift中的滚动头

我正在做一个项目,我必须为此开发一个可滚动浏览的圆形聊天对象,并使其圆头,可以水平滚动,并且当用户在视图内部/外部轻按时可以展开/折叠。

首先,我们需要创建一个我最终以某种方式将其命名为ProfileHeadsView的视图 这将是我们集合视图的容器视图,并将由用户添加为视图的子视图。 因此,此容器视图以及随后的收集视图将自动采用目标视图的宽度。


ProfileHeadsView是UIView的子类,并且具有相同名称的Xib文件。 Xib的视图是ProfileHeadsView的类型。 该视图需要一种方法来从包中加载Xib文件,并将实例返回给调用函数。 这是通过以下方法实现的。

  class func loadViewFromXibWithFrame(_ frame:CGRect)-> ProfileHeadsView { 
让viewFromXib = Bundle.main.loadNibNamed(“ ProfileHeadsView”,所有者:无,选项:无)? ProfileHeadsView
viewFromXib.clipsToBounds = true
viewFromXib.frame =框架
返回viewFromXib
}

接下来,我们为设备旋转设置了一个观察器。 这使我们能够以纵向和横向模式处理流程。 我设置了旋转观察器,并在awakeFromNib中设置了我们的collectionView(这是所有插座都将连接的点。


在查看上面介绍的代码片段之前,我们必须了解UICollectionViewFlowLayout的疯狂功能 当我们需要自定义与集合视图的行为有关的任何事情时,这是首选。

为了获得所需的行为,我们利用了单元格的z索引。 Z索引允许垂直堆叠收集视图单元格。 为此,我们重写了集合视图的流程布局的layoutAttributesForElements(in 🙂方法。 在此方法内部,我们打算

  • 计算电池堆中心
  • 获取元素的布局属性以进行修改
  • 根据堆叠因子和堆叠中心计算这些单元的新位置
  • 显式地将第一个单元格放在堆栈顶部(除非您想将其他单元格放在顶部)
  • 返回修改后的属性

下面是自从看到最后一个代码以来我们已讨论的所有代码的片段。

  var stackCenter:CGPoint = CGPoint(x:0,y:0) 
var stackFactor = CGFloat(0.1)// MIN:0 .... MAX:1
@IBOutlet弱var collectionView:UICollectionView!override func awakeFromNib(){
super.awakeFromNib()
NotificationCenter.default.addObserver(自身,选择器:#selector(旋转),名称:NSNotification.Name.UIDeviceOrientationDidChange,对象:nil)
xibCVSetup()
}
@objc private func rotation(){
collectionView.collectionViewLayout.invalidateLayout()
}
私人功能setupFlowLayout(){
如果让collectionViewFlowControl = collectionView.collectionViewLayout为? WFProfileHeadsLayout {
collectionViewFlowControl.itemSize = CGSize(宽度:collectionView.bounds.height / 2,高度:collectionView.bounds.height)
collectionView.isScrollEnabled =!collectionViewFlowControl.isCompressed
}其他{
//记录错误
}
}
覆盖func layoutAttributesForElements(在CGRect中)-> [UICollectionViewLayoutAttributes]? {
stackCenter = CGPoint(x:(self.collectionView?.bounds.width)!* 0.5,y:(self.collectionView?.bounds.height)!* 0.5)

警卫队让attributeArray = super.layoutAttributesForElements(在rect中)else {return nil}

警卫让attributesToReturn = attributesArray.map({$ 0.copy()})为? [UICollectionViewLayoutAttributes] else {return nil}

//根据stackFactor和stackCenter计算每个单元的新位置
对于属性:attributesToReturn {中的UICollectionViewLayoutAttributes
let xPosition:CGFloat = stackCenter.x +(attributes.center.x-stackCenter.x)* stackFactor
令yPosition:CGFloat = stackCenter.y +(attributes.center.y-stackCenter.y)* stackFactor
attribute.center = CGPoint(x:xPosition,y:yPosition)

//将第一个单元格放在堆栈顶部
如果attribute.indexPath.row == 0 {
attribute.alpha = 1.0
attribute.zIndex = Int(1.0)
}
其他{
//淡出其他单元格
attribute.alpha = stackFactor
//第一个下方的其他单元格
attribute.zIndex = Int(0.0)
}
}
返回attributesToReturn
}

堆栈因子决定了在视图中散布多少像元的程度。 在这种情况下,堆栈中心设置为集合视图所属的视图的中心。

我们需要复制属性数组的内容,因为我们不能直接修改接收到的数组的内容并返回相同的数组(这样做会给我警告,⚠️)。

发布此信息后,我们只需要基于堆栈因子和堆栈中心计算每个单元格的位置,并使用视图的alpha的堆栈因子值淡出单元格就可以了! 现在已经设置了所有基础知识,因为我们等待将流布局关联到我们的集合视图!

下一步? 将流布局与我们的集合视图关联并运行。 简单?

但是,嘿……我们还需要实现其他一些功能,以使其像视频中的功能一样起作用。 这些帮助我们在折叠和堆叠模式之间切换,设置滚动方向,设置集合视图的内容大小以及提供项目的布局属性。 以下代码段显示了我们的流程布局的完整代码。

 导入UIKit 

类ProfileHeadsLayout:UICollectionViewFlowLayout {
var isCompressed = true {
didSet {
stackFactor = self.isCompressed吗? CGFloat(0.1):CGFloat(1.0)
self.invalidateLayout()
}
}

var stackCenter:CGPoint = CGPoint(x:0,y:0)
var stackFactor = CGFloat(0.1)

需要初始化吗?(编码器aDecoder:NSCoder){
super.init(编码器:aDecoder)
self.scrollDirection = .horizo​​ntal
}

覆盖var collectionViewContentSize:CGSize {

var contentSize = super.collectionViewContentSize

如果self.collectionView!.bounds.size.width> contentSize.width {
contentSize.width = self.collectionView!.bounds.size.width
}

如果self.collectionView!.bounds.size.height> contentSize.height {
contentSize.height = self.collectionView!.bounds.size.height
}

返回contentSize
}

覆盖func layoutAttributesForElements(在CGRect中)-> [UICollectionViewLayoutAttributes]? {
stackCenter = CGPoint(x:(self.collectionView?.bounds.width)!* 0.5,y:(self.collectionView?.bounds.height)!* 0.5)

警卫队让attributeArray = super.layoutAttributesForElements(在rect中)else {return nil}

警卫让attributesToReturn = attributesArray.map({$ 0.copy()})为? [UICollectionViewLayoutAttributes] else {return nil}

//根据stackFactor和stackCenter计算每个单元的新位置
对于属性:attributesToReturn {中的UICollectionViewLayoutAttributes
let xPosition:CGFloat = stackCenter.x +(attributes.center.x-stackCenter.x)* stackFactor
令yPosition:CGFloat = stackCenter.y +(attributes.center.y-stackCenter.y)* stackFactor
attribute.center = CGPoint(x:xPosition,y:yPosition)

//将第一个单元格放在堆栈顶部
如果attribute.indexPath.row == 0 {
attribute.alpha = 1.0
attribute.zIndex = Int(1.0)
}
其他{
//淡出其他单元格
attribute.alpha = stackFactor
//第一个下方的其他单元格
attribute.zIndex = Int(0.0)
}
}
返回attributesToReturn
}

覆盖func layoutAttributesForItem(在indexPath:IndexPath)-> UICollectionViewLayoutAttributes? {
返回UICollectionViewLayoutAttributes(forCellWith:indexPath)
}
}

回到容器视图ProfileHeadsView,当修改流布局的isCompressed属性时,需要执行一些方法。 这是通过在我们的集合视图的performBatchUpdates(_:complete 🙂内添加以下代码行来完成的。

  collectionView.performBatchUpdates({ 
collectionViewFlowControl.isCompressed =!collectionViewFlowControl.isCompressed
},完成:无)

由于在容器视图之外点击会导致集合视图单元崩溃,因此我们需要一个可公开访问的方法来执行此操作。 这是通过使集合视图的流布局无效而实现的。

  func crashExpandedPictures(){ 
如果让collectionViewFlowControl = collectionView.collectionViewLayout为? WFProfileHeadsLayout {
如果!collectionViewFlowControl.isCompressed {
toggleCollectionViewStyle()
}
}
}

以下是我们的容器视图的完整代码。

 导入UIKit 

类ProfileHeadsView:UIView {

var profileItems:[String]? = [“一个”,“两个”,“三个”,“四个”,“五个”,“六个”,“七个”,“八个”,“九个”,“十个”]

@IBOutlet弱var collectionView:UICollectionView!


class func loadViewFromXibWithFrame(_ frame:CGRect)-> ProfileHeadsView {
让viewFromXib = Bundle.main.loadNibNamed(“ ProfileHeadsView”,所有者:无,选项:无)? ProfileHeadsView
viewFromXib.clipsToBounds = true
返回viewFromXib
}

覆盖func awakeFromNib(){
super.awakeFromNib()
NotificationCenter.default.addObserver(自身,选择器:#selector(旋转),名称:NSNotification.Name.UIDeviceOrientationDidChange,对象:nil)

//初始化视图后,让我们设置集合视图...(将连接所有插座)
xibCVSetup()
}

func crashExpandedPictures(){
如果让collectionViewFlowControl = collectionView.collectionViewLayout为? ProfileHeadsLayout {
如果!collectionViewFlowControl.isCompressed {
toggleCollectionViewStyle()
}
}
}
}

扩展程序ProfileHeadsView {

私人功能xibCVSetup(){
collectionView.delegate =自我
collectionView.dataSource =自我

collectionView?.register(UINib(nibName:“ ProfileHeadCell”,bundle:nil),forCellWithReuseIdentifier:“ ProfileHeadCell”)

setupFlowLayout()
}

私人功能setupFlowLayout(){
如果让collectionViewFlowControl = collectionView.collectionViewLayout为? ProfileHeadsLayout {
collectionViewFlowControl.itemSize = CGSize(宽度:collectionView.bounds.height / 2,高度:collectionView.bounds.height)
collectionView.isScrollEnabled =!collectionViewFlowControl.isCompressed
}
}

私人功能toggleCollectionViewStyle(){
如果让collectionViewFlowControl = collectionView.collectionViewLayout为? ProfileHeadsLayout {
//这可以消除折叠状态,同时折叠展开的单元格并重新定位到默认位置
如果!collectionViewFlowControl.isCompressed {
collectionView.contentOffset = CGPoint(x:-collectionView.contentInset.left,y:-collectionView.contentInset.top)
}
//实际压缩细胞
collectionView.performBatchUpdates({
collectionViewFlowControl.isCompressed =!collectionViewFlowControl.isCompressed
},完成:无)

//禁用压缩状态下的滚动
collectionView.isScrollEnabled =!collectionViewFlowControl.isCompressed
}
}

@objc private func rotation(){
collectionView.collectionViewLayout.invalidateLayout()
}
}

// MARK:-填充收藏夹视图并在屏幕上显示内容
扩展程序ProfileHeadsView:UICollectionViewDelegate,UICollectionViewDataSource {
//集合视图魔术方法
}

最后一件事!
要在容器外视图外部点击时折叠单元格,我们所需要做的就是重写touchesBegan(_:,with 🙂

 覆盖func touchesBegan(_ touches:Set ,事件:UIEvent?){ 
让touch = touches.first
守卫让位置= touch?.location(在self.view中)else {return}
如果!profileHeadsContainer.frame.contains(location){
搅拌器?.collapseExpandedPictures()
}
}

由于我不断增加的数字化转储,我找不到帖子的地址,该帖子的地址实现了iOS摄影画廊,例如在收藏视图上的过渡效果,这有助于我弄清楚如何实现此功能。


喜欢这个职位吗? 请点击👏按钮,以帮助其他人找到此信息。