如何在原型单元格中使用 UIImageView 在 UITableView 中创建视差效果

How do I create a parallax effect in UITableView with UIImageView in their prototype cells

我正在 iOS 8.4 中使用 Swift 构建应用程序。

我有一个 UITableView 和一个包含 UILabelUIImageView 的自定义 UITableViewCell。这一切都相当简单,一切都很好。

我正在尝试创建类似于 demonstrated in this demo 的视差效果。

目前我的 tableView.cellForRowAtIndexPath

中有此代码
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell = self.tableView.dequeueReusableCellWithIdentifier("myitem", forIndexPath: indexPath) as! MixTableViewCell
    cell.img.backgroundColor = UIColor.blackColor()
    cell.title.text = self.items[indexPath.row]["title"]

    cell.img.image = UIImage(named: "Example.png")

    // ideally it would be cool to have an extension allowing the following
    // cell.img.addParallax(50) // or some other configurable offset

    return cell
}

该块存在于看起来像 class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... }

的 class 中

我也知道我可以通过 func scrollViewDidScroll 在我的 class 中收听滚动事件。

除此之外,感谢您的帮助!

我想通了!想法是在不实现任何额外库的情况下执行此操作,特别是考虑到实现的简单性。

首先...在自定义 table 视图 Cell class 中,您必须创建一个包装视图。您可以在原型单元格中 select 您的 UIImageView,然后选择 Editor > Embed in > View。将两者作为出口拖到您的 Cell 中,然后为包含视图设置 clipToBounds = true。 (还记得将约束设置为与您的图像相同。

class MyCustomCell: UITableViewCell {
    @IBOutlet weak var img: UIImageView!
    @IBOutlet weak var imgWrapper: UIView!
    override func awakeFromNib() {
        self.imgWrapper.clipsToBounds = true
    }
}

然后在您的 UITableViewController 子 class(或委托)中,实施 scrollViewDidScroll — 从这里您将不断更新 UIImageView.frame 属性。见下文:

override func scrollViewDidScroll(scrollView: UIScrollView) {
    let offsetY = self.tableView.contentOffset.y
    for cell in self.tableView.visibleCells as! [MyCustomCell] {
        let x = cell.img.frame.origin.x
        let w = cell.img.bounds.width
        let h = cell.img.bounds.height
        let y = ((offsetY - cell.frame.origin.y) / h) * 25
        cell.img.frame = CGRectMake(x, y, w, h)
    }
}

See this in action.

我对@ded 的需要包装视图的解决方案不太满意,所以我想出了另一个使用自动布局并且足够简单的解决方案。

在故事板中,您只需添加 imageView 并在 ImageView 上设置 4 个约束:

  • 通往ContentView(即Superview)= 0
  • 尾随到 ContentView(即 Superview)= 0
  • 置顶Space到ContentView(即Superview)=0
  • ImageView 高度(此处设置为 200,但无论如何都会根据单元格高度重新计算)

最后两个约束(顶部和高度)需要引用自定义 UITableViewCell 的出口(在上图中,双击最右侧列中的约束,然后显示连接检查器 - 图标是一个箭头一个圆圈)

您的 UITableViewCell 应如下所示:

class ParallaxTableViewCell: UITableViewCell {

    @IBOutlet weak var parallaxImageView: UIImageView!

    // MARK: ParallaxCell

    @IBOutlet weak var parallaxHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var parallaxTopConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()

        clipsToBounds = true
        parallaxImageView.contentMode = .ScaleAspectFill
        parallaxImageView.clipsToBounds = false
    }
  }

所以基本上,我们告诉图像尽可能多地拍摄 space,但我们将其剪裁到单元格框架。

现在您的 TableViewController 应该如下所示:

class ParallaxTableViewController: UITableViewController {
    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return cellHeight
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! ParallaxTableViewCell
        cell.parallaxImageView.image = … // Set your image
        cell.parallaxHeightConstraint.constant = parallaxImageHeight
        cell.parallaxTopConstraint.constant = parallaxOffsetFor(tableView.contentOffset.y, cell: cell)
        return cell
    }

    // Change the ratio or enter a fixed value, whatever you need
    var cellHeight: CGFloat {
        return tableView.frame.width * 9 / 16
    }

    // Just an alias to make the code easier to read
    var imageVisibleHeight: CGFloat {
        return cellHeight
    }

    // Change this value to whatever you like (it sets how "fast" the image moves when you scroll)
    let parallaxOffsetSpeed: CGFloat = 25

    // This just makes sure that whatever the design is, there's enough image to be displayed, I let it up to you to figure out the details, but it's not a magic formula don't worry :)
    var parallaxImageHeight: CGFloat {
        let maxOffset = (sqrt(pow(cellHeight, 2) + 4 * parallaxOffsetSpeed * tableView.frame.height) - cellHeight) / 2
        return imageVisibleHeight + maxOffset
    }

    // Used when the table dequeues a cell, or when it scrolls
    func parallaxOffsetFor(newOffsetY: CGFloat, cell: UITableViewCell) -> CGFloat {
        return ((newOffsetY - cell.frame.origin.y) / parallaxImageHeight) * parallaxOffsetSpeed
    }

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let offsetY = tableView.contentOffset.y
        for cell in tableView.visibleCells as! [MyCustomTableViewCell] {
            cell.parallaxTopConstraint.constant = parallaxOffsetFor(offsetY, cell: cell)
        }
    }
}

备注:

  • 重要的是使用tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) 而不是tableView.dequeueReusableCellWithIdentifier("CellIdentifier"),否则图像不会被偏移直到你开始滚动

至此,视差 UITableViewCells 应该适用于任何布局,也可以适应 CollectionViews。

此方法适用于 table 视图和集合视图。

  • first of all create the cell for the tableview and put the image view in it.

  • set the image height slightly more than the cell height. if cell height = 160 let the image height be 200 (to make the parallax effect and you can change it accordingly)

  • put this two variable in your viewController or any class where your tableView delegate is extended

let imageHeight:CGFloat = 150.0
let OffsetSpeed: CGFloat = 25.0
  • add the following code in the same class
 func scrollViewDidScroll(scrollView: UIScrollView) {
    //  print("inside scroll")

    if let visibleCells = seriesTabelView.visibleCells as? [SeriesTableViewCell] {
        for parallaxCell in visibleCells {
            var yOffset = ((seriesTabelView.contentOffset.y - parallaxCell.frame.origin.y) / imageHeight) * OffsetSpeedTwo
            parallaxCell.offset(CGPointMake(0.0, yOffset))
        }
    }
}
  • where seriesTabelView is my UItableview

  • and now lets goto the cell of this tableView and add the following code

func offset(offset: CGPoint) {
        posterImage.frame = CGRectOffset(self.posterImage.bounds, offset.x, offset.y)
    }
  • were posterImage is my UIImageView

如果你想将此实现到 collectionView,只需将 tableView 变量更改为你的 collectionView 变量

就是这样。我不确定这是否是最好的方法。但它对我有用。希望它也适合你。如果有任何问题请告诉我

结合@ded 和@Nycen 的回答后,我找到了这个解决方案,它使用嵌入式视图,但更改了布局约束(只有其中一个):

  1. 在 Interface Builder 中将图像视图嵌入到 UIView 中。对于该视图,请在“视图”>“绘图”

  2. 中选中 [√] Clips to bounds
  3. 从图像添加以下约束以查看:左右,垂直居中,高度

  4. 调整高度限制,使图像略高于视图

  1. 对于 Align Center Y 约束,在您的 UITableViewCell 中创建一个出口

  2. 将此函数添加到您的视图控制器(UITableViewController 或 UITableViewControllerDelegate)中

    private static let screenMid = UIScreen.main.bounds.height / 2
    private func adjustParallax(for cell: MyTableCell) {
        cell.imageCenterYConstraint.constant = -(cell.frame.origin.y - MyViewController.screenMid - self.tableView.contentOffset.y) / 10
    }
    

注意:通过编辑幻数 10,您可以更改应用效果的强度,通过从等式中删除 - 符号,您可以更改效果的方向

  1. 从重复使用单元格开始调用函数:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCellId", for: indexPath) as! MyTableCell
        adjustParallax(for: cell)
        return cell
    }
    
  2. 还有当滚动发生时:

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        (self.tableView.visibleCells as! [MyTableCell]).forEach { cell in
            adjustParallax(for: cell)
        }
    }