Swift与ChatGPT联手展现宫格翻页视图魔法
在本教程中,咱们将讨论怎么运用Swift言语和ChatGPT一起创立一个具有翻页功用的宫格视图组件。经过这个实例,您将了解怎么将ChatGPT与现有的iOS项目相结合,以及怎么自定义和优化组件以满足您的需求。
开端之前
首要,咱们需求确保您现已安装了所需的依靠库和软件,包括SnapKit和UIKit。这两个库在本教程中都有用到,因此请务必确保您现已正确安装了它们。
构建宫格翻页视图组件
在完结环境设置之后,咱们将开端构建宫格翻页视图组件。首要,咱们需求定义两个协议:GridPageViewDataSource
和GridPageViewDelegate
。这两个协议分别负责供给数据和处理事件。
protocol GridPageViewDataSource: AnyObject {
func cellForItemAt(pageView: GridPageView, collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell
func numberOfItems() -> Int
}
protocol GridPageViewDelegate: AnyObject {
func pageView(_ pageView: GridPageView, didSelectItemAt indexPath: IndexPath)
func pageView(_ pageView: GridPageView, didChangeToPage page: Int)
func scrollViewDidScroll(_ scrollView: UIScrollView)
}
extension GridPageViewDelegate {
func pageView(_ pageView: GridPageView, didSelectItemAt indexPath: IndexPath) {}
func pageView(_ pageView: GridPageView, didChangeToPage page: Int) {}
func scrollViewDidScroll(_ scrollView: UIScrollView) {}
}
接下来,咱们将创立一个名为GridPageView
的自定义视图类。这个类将继承自UIView
,并包括一个UICollectionView
实例。咱们将运用SnapKit为UICollectionView
设置束缚,使其铺满整个GridPageView
。
为了完成翻页功用,咱们需求创立一个名为GridPagedFlowLayout
的自定义布局类。这个类将继承自UICollectionViewFlowLayout
,并重写相应的办法来完成翻页作用。在这个类中,咱们能够自定义列数、行数、项目距离、行距离和页距离等特点。
class GridPagedFlowLayout: UICollectionViewFlowLayout {
var columns: Int
var rows: Int
var itemSpacing: CGFloat
var lineSpacing: CGFloat
var pageSpacing: CGFloat
private var allAttributes: [UICollectionViewLayoutAttributes] = []
init(columns: Int, rows: Int, itemSpacing: CGFloat, lineSpacing: CGFloat, pageSpacing: CGFloat) {
self.columns = columns
self.rows = rows
self.itemSpacing = itemSpacing
self.lineSpacing = lineSpacing
self.pageSpacing = pageSpacing
super.init()
self.scrollDirection = .horizontal
self.minimumLineSpacing = itemSpacing
self.minimumInteritemSpacing = lineSpacing
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
let contentWidth = collectionView.bounds.width - collectionView.contentInset.left - collectionView.contentInset.right
let contentHeight = collectionView.bounds.height - collectionView.contentInset.top - collectionView.contentInset.bottom
let itemWidth = (contentWidth - CGFloat(columns - 1) * itemSpacing) / CGFloat(columns)
let itemHeight = (contentHeight - CGFloat(rows - 1) * lineSpacing) / CGFloat(rows)
itemSize = CGSize(width: itemWidth, height: itemHeight)
allAttributes = []
let totalItems = collectionView.numberOfItems(inSection: 0)
for itemIndex in 0 ..< totalItems {
let indexPath = IndexPath(item: itemIndex, section: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let page = itemIndex / (columns * rows)
let remainingIndex = itemIndex % (columns * rows)
let xPosition = CGFloat(remainingIndex % columns) * (itemWidth + itemSpacing) + CGFloat(page) * (contentWidth + pageSpacing)
let yPosition = CGFloat(remainingIndex / columns) * (itemHeight + lineSpacing)
attributes.frame = CGRect(x: xPosition, y: yPosition, width: itemWidth, height: itemHeight)
allAttributes.append(attributes)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return allAttributes.filter { rect.intersects($0.frame) }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return allAttributes[indexPath.item]
}
override var collectionViewContentSize: CGSize {
guard let collectionView = collectionView else { return .zero }
let totalPages = ceil(CGFloat(collectionView.numberOfItems(inSection: 0)) / CGFloat(columns * rows))
let width = (totalPages - 1) * pageSpacing + totalPages * collectionView.bounds.width
return CGSize(width: width, height: collectionView.bounds.height)
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
let pageWidth = collectionView.bounds.width + pageSpacing
let contentInset = collectionView.contentInset.left
let rawPageValue = (collectionView.contentOffset.x + contentInset) / pageWidth
let currentPage: CGFloat
if velocity.x > 0 {
currentPage = ceil(rawPageValue)
} else if velocity.x < 0 {
currentPage = floor(rawPageValue)
} else {
currentPage = round(rawPageValue)
}
let nextPageOffset = (currentPage * pageWidth) - contentInset
return CGPoint(x: nextPageOffset, y: proposedContentOffset.y)
}
}
GridPageView
类将包括以下主要功用:
- 注册单元格
- 从头加载数据
- 处理翻滚事件
class GridPageView: UIView {
weak var dataSource: GridPageViewDataSource?
weak var delegate: GridPageViewDelegate?
/// 列数
var columns: Int {
get {
(collectionView.collectionViewLayout as? GridPagedFlowLayout)?.columns ?? 0
}
set {
if let layout = collectionView.collectionViewLayout as? GridPagedFlowLayout {
layout.columns = newValue
layout.invalidateLayout()
}
}
}
/// 行数
var rows: Int {
get {
(collectionView.collectionViewLayout as? GridPagedFlowLayout)?.rows ?? 0
}
set {
if let layout = collectionView.collectionViewLayout as? GridPagedFlowLayout {
layout.rows = newValue
layout.invalidateLayout()
}
}
}
/// 列距离
var itemSpacing: CGFloat {
get {
(collectionView.collectionViewLayout as? GridPagedFlowLayout)?.itemSpacing ?? 0
}
set {
if let layout = collectionView.collectionViewLayout as? GridPagedFlowLayout {
layout.itemSpacing = newValue
layout.invalidateLayout()
}
}
}
/// 行距离
var lineSpacing: CGFloat {
get {
(collectionView.collectionViewLayout as? GridPagedFlowLayout)?.lineSpacing ?? 0
}
set {
if let layout = collectionView.collectionViewLayout as? GridPagedFlowLayout {
layout.lineSpacing = newValue
layout.invalidateLayout()
}
}
}
/// 页边距
var pageSpacing: CGFloat {
get {
(collectionView.collectionViewLayout as? GridPagedFlowLayout)?.pageSpacing ?? 0
}
set {
if let layout = collectionView.collectionViewLayout as? GridPagedFlowLayout {
layout.pageSpacing = newValue
layout.invalidateLayout()
}
}
}
private var displayLink: CADisplayLink?
private var targetOffset: CGFloat = 0
private var initialOffset: CGFloat = 0
private var startTime: TimeInterval = 0
private(set) var collectionView: UICollectionView!
public init(columns: Int = 4, rows: Int = 2, itemSpacing: CGFloat = 10, lineSpacing: CGFloat = 10, pageSpacing: CGFloat = 20) {
let layout = GridPagedFlowLayout(columns: columns, rows: rows, itemSpacing: itemSpacing, lineSpacing: lineSpacing, pageSpacing: pageSpacing)
super.init(frame: .zero)
setupCollectionView(with: layout)
setupStyle()
}
private func setupCollectionView(with layout: GridPagedFlowLayout) {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.isPagingEnabled = false
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .zero
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupStyle() {
addSubview(collectionView)
collectionView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
private func startDisplayLink() {
displayLink = CADisplayLink(target: self, selector: #selector(updateScrollOffset))
startTime = CACurrentMediaTime()
displayLink?.add(to: .current, forMode: .common)
}
private func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
@objc private func updateScrollOffset(displayLink: CADisplayLink) {
let elapsedTime = CACurrentMediaTime() - startTime
let duration = 0.25
let progress = min(elapsedTime / duration, 1)
let easedProgress = ease(CGFloat(progress))
let interpolatedOffset = initialOffset + (targetOffset - initialOffset) * easedProgress
if progress >= 1 {
collectionView.contentOffset = CGPoint(x: targetOffset, y: collectionView.contentOffset.y)
stopDisplayLink()
} else {
collectionView.contentOffset = CGPoint(x: interpolatedOffset, y: collectionView.contentOffset.y)
}
}
private func ease(_ t: CGFloat) -> CGFloat {
return t * t
}
private func adjustContentOffset() {
guard let collectionView = collectionView, let flowLayout = collectionView.collectionViewLayout as? GridPagedFlowLayout else { return }
let pageWidth = collectionView.bounds.width + flowLayout.pageSpacing
let contentInset = collectionView.contentInset.left
let rawPageValue = (collectionView.contentOffset.x + contentInset) / pageWidth
let currentPage = round(rawPageValue)
let totalPages = ceil(CGFloat(collectionView.numberOfItems(inSection: 0)) / CGFloat(flowLayout.columns * flowLayout.rows))
let clampedPage = max(min(currentPage, totalPages - 1), 0)
let nextPageOffset = (clampedPage * pageWidth) - contentInset
collectionView.contentOffset = CGPoint(x: nextPageOffset, y: collectionView.contentOffset.y)
}
}
// MARK: - Public
extension GridPageView {
public func registerCell<T: UICollectionViewCell>(_ cellClass: T.Type, forCellWithReuseIdentifier identifier: String) {
collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
}
public func reloadData() {
collectionView.reloadData()
stopDisplayLink()
adjustContentOffset()
}
}
// MARK: - UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate
extension GridPageView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource?.numberOfItems() ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = dataSource?.cellForItemAt(pageView: self, collectionView: collectionView, indexPath: indexPath) {
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = delegate {
delegate.pageView(self, didSelectItemAt: indexPath)
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let collectionView = scrollView as? UICollectionView, let flowLayout = collectionView.collectionViewLayout as? GridPagedFlowLayout else { return }
let pageWidth = collectionView.bounds.width + flowLayout.pageSpacing
let targetXContentOffset = targetContentOffset.pointee.x
let contentInset = collectionView.contentInset.left
let rawPageValue = (targetXContentOffset + contentInset) / pageWidth
let currentPage: CGFloat
if velocity.x > 0 {
currentPage = ceil(rawPageValue)
} else if velocity.x < 0 {
currentPage = floor(rawPageValue)
} else {
currentPage = round(rawPageValue)
}
let totalPages = ceil(CGFloat(collectionView.numberOfItems(inSection: 0)) / CGFloat(flowLayout.columns * flowLayout.rows))
let clampedPage = max(min(currentPage, totalPages - 1), 0)
let nextPageOffset = (clampedPage * pageWidth) - contentInset
targetContentOffset.pointee = scrollView.contentOffset
initialOffset = scrollView.contentOffset.x
targetOffset = nextPageOffset
startDisplayLink()
delegate?.pageView(self, didChangeToPage: Int(clampedPage))
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
}
示例代码
在完成了GridPageView
和GridPagedFlowLayout
之后,您能够运用以下示例代码来创立一个具有翻页功用的宫格视图组件:
// 创立并装备GridPageView实例
let gridPageView = GridPageView(columns: 4, rows: 2, itemSpacing: 10, lineSpacing: 10, pageSpacing: 20)
gridPageView.dataSource = self
gridPageView.delegate = self
view.addSubview(gridPageView)
// 完成GridPageViewDataSource和GridPageViewDelegate协议办法
func cellForItemAt(pageView: GridPageView, collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
// ...
}
func numberOfItems() -> Int {
// ...
}
func pageView(_ pageView: GridPageView, didSelectItemAt indexPath: IndexPath) {
// ...
}
func pageView(_ pageView: GridPageView, didChangeToPage page: Int) {
// ...
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// ...
}
总结
经过本教程,您现已学会了怎么运用Swift和ChatGPT一起创立一个具有翻页功用的宫格视图组件。现在,您能够将这个组件应用到您的项目中,以完成各种炫酷的作用。期望本教程对您有所协助。在此基础上,您还能够测验扩展和自定义组件以满足您的需求。以下是一些建议供您参考:
-
增加动画作用:为
GridPageView
中的项目增加一些过渡或翻翻滚画作用,使其在翻滚过程中显得愈加流畅。 -
支撑自定义单元格:允许用户自定义每个单元格的内容和款式,使组件更具通用性和灵活性。
-
支撑多种布局款式:除了宫格布局之外,您还能够为
GridPageView
增加列表布局、瀑布流布局等其他布局款式。 -
支撑无限翻滚:为
GridPageView
增加无限翻滚功用,运用户在到达最后一页时能够持续翻滚回到第一页。 -
集成其他功用:考虑将
GridPageView
与其他组件或功用集成,例如查找、挑选、排序等,以满足您的应用需求。
持续学习和探究这些功用,您将能够充分利用GridPageView
组件,并在您的项目中完成愈加丰厚的视觉作用。祝您编程愉快!
其他
值得一提的是,本文的草稿是由 OpenAI 的 ChatGPT 生成的。ChatGPT 是一个先进的人工智能言语模型,能够生成天然、连贯的文本。在本文的生成过程中,ChatGPT 能够了解和遵从咱们的指示,生成了高质量的技术文章。
但是,ChatGPT 也有一些局限性。例如,它可能会在某些情况下表述不清晰或重复内容。尽管如此,ChatGPT 仍然是一个非常有用的东西,能够协助咱们更快速地生成文章草稿,然后节省时间和精力。
Demo在这里