Swift y ChatGPT unen fuerzas para mostrar la magia de la vista de página en cuadrícula

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.

avance

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: GridPageViewDataSourcey 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 GridPageViewclase de vista personalizada llamada . Esta clase heredará de UIViewy contendrá una UICollectionViewinstancia de. Usaremos SnapKit para UICollectionViewestablecer las restricciones para que ocupe toda el área GridPageView.

Para implementar la funcionalidad de cambio de página, necesitamos crear una GridPagedFlowLayoutclase de diseño personalizada llamada . Esta clase heredará UICollectionViewFlowLayouty 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)
    }
}

复制代码

GridPageViewLa 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 GridPageViewy 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:

  1. Agregue efectos de animación: GridPageViewagregue algunos efectos de animación de transición o desplazamiento a los elementos en , para que aparezcan más suaves durante el desplazamiento.

  2. 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.

  3. Admite múltiples estilos de diseño: además del diseño de cuadrícula, también puede GridPageViewagregar otros estilos de diseño, como el diseño de lista y el diseño de flujo de cascada.

  4. Compatibilidad con desplazamiento infinito: para GridPageViewagregar 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.

  5. Integre otras funciones: considere la GridPageViewposibilidad 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 GridPageViewcomponentes 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í

Supongo que te gusta

Origin juejin.im/post/7213566227000213561
Recomendado
Clasificación