Swift y ChatGPT unen fuerzas para mostrar la magia de la vista de página en cuadrícula
En este tutorial, exploraremos cómo usar el lenguaje Swift y ChatGPT para crear un componente de vista de cuadrícula con función de cambio de página. A través de este ejemplo, aprenderá cómo combinar ChatGPT con proyectos iOS existentes y cómo personalizar y optimizar componentes para satisfacer sus necesidades.
antes del comienzo
Primero, debemos asegurarnos de que haya instalado las dependencias y el software necesarios, incluidos SnapKit y UIKit. Ambas bibliotecas se usan en este tutorial, así que asegúrese de tenerlas instaladas correctamente.
Cree el componente de vista de página de cuadrícula
Después de completar la configuración del entorno, comenzaremos a construir el componente de vista rotatoria de cuadrícula. Primero, necesitamos definir dos protocolos: GridPageViewDataSource
y GridPageViewDelegate
. Estos dos protocolos son responsables de proporcionar datos y manejar eventos respectivamente.
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) {}
}
复制代码
A continuación, crearemos una GridPageView
clase de vista personalizada llamada . Esta clase heredará de UIView
y contendrá una UICollectionView
instancia de. Usaremos SnapKit para UICollectionView
establecer las restricciones para que ocupe toda el área GridPageView
.
Para implementar la funcionalidad de cambio de página, necesitamos crear una GridPagedFlowLayout
clase de diseño personalizada llamada . Esta clase heredará UICollectionViewFlowLayout
y anulará el método correspondiente para lograr el efecto de cambio de página. En esta clase, podemos personalizar atributos como el número de columnas, el número de filas, el espacio entre elementos, el espacio entre filas y el espacio entre páginas.
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
La clase contendrá las siguientes funciones principales:
- registrar celular
- recargar datos
- Manejar eventos de desplazamiento
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)
}
}
复制代码
Código de muestra
Después de implementar GridPageView
y GridPagedFlowLayout
, puede usar el siguiente código de muestra para crear un componente de vista de cuadrícula con función de cambio de página:
// 创建并配置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) {
// ...
}
复制代码
Resumir
A través de este tutorial, ha aprendido a usar Swift y ChatGPT para crear conjuntamente un componente de vista de cuadrícula con función de cambio de página. Ahora, puede aplicar este componente a su proyecto para lograr todo tipo de efectos geniales. Espero que este tutorial te sea útil. A partir de ahí, también puede experimentar con la ampliación y personalización de componentes para satisfacer sus necesidades. Aquí hay algunas sugerencias para su consideración:
-
Agregue efectos de animación:
GridPageView
agregue algunos efectos de animación de transición o desplazamiento a los elementos en , para que aparezcan más suaves durante el desplazamiento. -
Compatibilidad con celdas personalizadas: permita a los usuarios personalizar el contenido y el estilo de cada celda, lo que hace que el componente sea más versátil y flexible.
-
Admite múltiples estilos de diseño: además del diseño de cuadrícula, también puede
GridPageView
agregar otros estilos de diseño, como el diseño de lista y el diseño de flujo de cascada. -
Compatibilidad con desplazamiento infinito: para
GridPageView
agregar la funcionalidad de desplazamiento infinito, de modo que los usuarios puedan continuar desplazándose hacia atrás a la primera página cuando lleguen a la última página. -
Integre otras funciones: considere la
GridPageView
posibilidad de integrarse con otros componentes o funciones, como búsqueda, filtrado, clasificación, etc., para satisfacer las necesidades de su aplicación.
A medida que continúe aprendiendo y explorando estas funciones, podrá aprovechar al máximo los GridPageView
componentes y lograr efectos visuales más ricos en sus proyectos. ¡Feliz programación!
otro
Vale la pena mencionar que el borrador de este artículo fue generado por ChatGPT de OpenAI. ChatGPT es un modelo de lenguaje de inteligencia artificial avanzado capaz de generar texto natural y coherente. Durante la generación de este artículo, ChatGPT pudo entender y seguir nuestras instrucciones, generando artículos técnicos de alta calidad.
Sin embargo, ChatGPT también tiene algunas limitaciones. Por ejemplo, puede ser poco claro o repetitivo en algunos casos. Aún así, ChatGPT es una herramienta muy útil que nos ayuda a generar borradores de artículos más rápidamente, ahorrando tiempo y esfuerzo.
La demostración está aquí