ios 8 Swift-TableView con CollectionView integrado
uitableview uicollectionview (3)
Soy relativamente nuevo en la programación de iOS y he intentado algunas cosas, pero fue en vano.
Me gustaría poner un
CollectionView
dentro de un
TableViewCell
.
Puedo codificar cada uno individualmente, pero no entiendo cómo configurar y hacer referencia a cada
CollectionView
dentro de un
TableViewCell
.
He encontrado este tutorial, http://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell/ que muestra cómo se puede hacer en Objective-C, pero siempre he tenido problemas con Obj-C.
¿Alguien sabe de un tutorial rápido o puede ayudar? Estoy en el proceso de crear un proyecto / código simple que publicaré en breve para intentar ayudar.
EDITAR 1
Acabo de encontrar la versión rápida del enlace anterior. Estoy trabajando en esto ahora, pero parece demasiado complicado, modificando el AppDelegate.
https://github.com/DahanHu/DHCollectionTableView
Muchas gracias Rob
Detalles
- Xcode 10.2.1 (10E1001), Swift 5
Muestra completa
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
fileprivate var tableViewCellCoordinator: [Int: IndexPath] = [:]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.tableFooterView = UIView()
}
}
// UITableViewDataSource
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionViewTableViewCell") as! CollectionViewTableViewCell
cell.selectionStyle = .none
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
let tag = tableViewCellCoordinator.count
cell.collectionView.tag = tag
tableViewCellCoordinator[tag] = indexPath
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "section: /(section)"
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = cell as! CollectionViewTableViewCell
cell.collectionView.reloadData()
cell.collectionView.contentOffset = .zero
}
}
// UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
var text = ""
if let indexPathOfCellInTableView = tableViewCellCoordinator[collectionView.tag] {
text = "/(indexPathOfCellInTableView)"
}
cell.label.text = text + " /(indexPath)"
return cell
}
}
// UICollectionViewDelegate
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected collectionViewCell with indexPath: /(indexPath) in tableViewCell with indexPath: /(tableViewCellCoordinator[collectionView.tag]!)")
}
}
CollectionViewTableViewCell
import UIKit
class CollectionViewTableViewCell: UITableViewCell {
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout!
}
CollectionViewCell
import UIKit
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
}
Main.storyboard
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="_31582378" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="200" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="pS5-CW-ipl">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="CollectionViewTableViewCell" id="bMP-Ac-C8D" customClass="CollectionViewTableViewCell" customModule="_31582378" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="bMP-Ac-C8D" id="mcy-FO-bcc">
<rect key="frame" x="0.0" y="0.0" width="375" height="199"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="yY4-ue-1HX">
<rect key="frame" x="8" y="8" width="359" height="183"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="pPl-9q-MGc">
<size key="itemSize" width="180" height="180"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="10" minY="0.0" maxX="10" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="CollectionViewCell" id="g9z-R1-8XJ" customClass="CollectionViewCell" customModule="_31582378" customModuleProvider="target">
<rect key="frame" x="10" y="2" width="180" height="180"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="180" height="180"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rHM-Xn-vBW">
<rect key="frame" x="69" y="80" width="42" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</view>
<color key="backgroundColor" red="0.28627450980000002" green="0.56470588239999997" blue="0.8862745098" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="rHM-Xn-vBW" firstAttribute="centerX" secondItem="g9z-R1-8XJ" secondAttribute="centerX" id="AXf-f9-ruf"/>
<constraint firstItem="rHM-Xn-vBW" firstAttribute="centerY" secondItem="g9z-R1-8XJ" secondAttribute="centerY" id="gw4-Iv-7ML"/>
</constraints>
<size key="customSize" width="180" height="180"/>
<connections>
<outlet property="label" destination="rHM-Xn-vBW" id="9SL-Kv-ZtD"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<constraints>
<constraint firstItem="yY4-ue-1HX" firstAttribute="bottom" secondItem="mcy-FO-bcc" secondAttribute="bottomMargin" id="04L-lF-Idy"/>
<constraint firstItem="yY4-ue-1HX" firstAttribute="leading" secondItem="mcy-FO-bcc" secondAttribute="leadingMargin" id="Fjd-8j-qvK"/>
<constraint firstItem="yY4-ue-1HX" firstAttribute="trailing" secondItem="mcy-FO-bcc" secondAttribute="trailingMargin" id="PUa-ze-U5s"/>
<constraint firstItem="yY4-ue-1HX" firstAttribute="top" secondItem="mcy-FO-bcc" secondAttribute="topMargin" id="XX6-d1-Vgx"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="collectionView" destination="yY4-ue-1HX" id="tLL-Om-JIX"/>
<outlet property="collectionViewFlowLayout" destination="pPl-9q-MGc" id="Ftw-AT-QvP"/>
</connections>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="pS5-CW-ipl" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="3vT-w2-JGU"/>
<constraint firstItem="pS5-CW-ipl" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="eS2-Y5-fxg"/>
<constraint firstItem="pS5-CW-ipl" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="hFA-oB-bWJ"/>
<constraint firstAttribute="trailing" secondItem="pS5-CW-ipl" secondAttribute="trailing" id="yin-cp-cAP"/>
</constraints>
</view>
<connections>
<outlet property="tableView" destination="pS5-CW-ipl" id="Gfe-HE-Ub6"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="136.80000000000001" y="137.18140929535232"/>
</scene>
</scenes>
</document>
Resultados
Cree un UITableView habitual y en su UITableViewCell cree el UICollectionView. Su delegado y fuente de datos collectionView deben cumplir con ese UITableViewCell.
Solo pasa por esto
En tu ViewController
// Global Variable
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: self.view.bounds)
tableView.delegate = self
tableView.dataSource = self
self.view.addSubview(tableView)
tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "NormalCell")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 3 {
var cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as! TableViewCell
cell.backgroundColor = UIColor.groupTableViewBackgroundColor()
return cell
} else {
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("NormalCell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = "cell: /(indexPath.row)"
return cell
}
}
Como puede ver, he creado dos celdas diferentes, una TableViewCell personalizada que se devuelve solo cuando el índice de fila es 3 y una UITableViewCell básica en otros índices.
El "TableViewCell" personalizado tendrá nuestra UICollectionView. Por lo tanto, cree una subclase UITableViewCell y escriba el código a continuación.
import UIKit
class TableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
var collectionView: UICollectionView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView.backgroundColor = UIColor.clearColor()
self.addSubview(collectionView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: UICollectionViewDataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! UICollectionViewCell
if indexPath.row%2 == 0 {
cell.backgroundColor = UIColor.redColor()
} else {
cell.backgroundColor = UIColor.yellowColor()
}
return cell
}
}
Espero eso ayude.
Piense en MVC y la incrustación nunca antes mediante programación como esta. Una subclase de UIView (-> eso es realmente una celda) nunca es la clase delegada para un delegado y fuente de datos de un tableView (lo mismo para collectionView).
¿Alguna vez creaste una subclase de tableview para hacer todas las cosas programáticas en esta subclase? ¡No! lo hace en su controlador de vista, porque crea UIView en Controladores, para "controlarlos". Entonces, la forma correcta de hacerlo es:
Déjame darte un ejemplo (en la forma de "mejor comprensión" de la vieja escuela):
- Cree un ViewController con tableView y agregue (addSubview) esta collectionView a su UITableViewCell.
- Tiene un ViewController en Storyboard y también se integra un UITableView
- También está conectado con su clase ViewController como un Outlet (y también su delegado y fuente de datos)
-
En lugar de agregar un CollectionView ahora en su UITableViewCell personalizado, simplemente agregue un UIView "con contenido" (con restricciones). Más tarde usará esta vista para agregar una vista de colección como una subvista.
-
Ahora cree un nuevo UIViewController (Nuevo archivo> ...) con XIB O arrastre y suelte un nuevo UIViewController desde el panel de propiedades en su guión gráfico y cree también una clase UIViewController. No te olvides de conectarte. (haremos el primero para una mejor comprensión)
- El nuevo ViewController solo se maneja como un ViewController con un CollectionView (igual que 1. pero collectionView)
-
En este nuevo ViewController con collectionView manejas todo como de costumbre, con delegado y fuente de datos, etc.
-
AHORA : en el ViewController (primero) con tableView, crea una instancia del nuevo ViewController (con collectionView) en cada celda (cellForRowAtIndexPath), y agrega su collectionView como una subvista a la Vista actual que creó (como en el soporte de contenido), por ejemplo:
deje myViewControllerWithCollectionView = MyViewControllerWithCollectionView () myCell.contentHolderView.addSubview (myViewControllerWithCollectionView.collectionView)
Lo que también puede hacer (y tal vez la forma más nueva y mejor, nunca lo intenté, pero estoy seguro de que funcionará muy bien, es: UIContainerView).
¡Eso es! algunos consejos para ti:
-
tenga cuidado al agregar una nueva subvista en cellForRowAtIndexPath, verifique siempre si contentHolderView ya tiene un
myViewControllerWithCollectionView.collectionView
para recuperar acciones de la vista de colección, agregue un protocolo personalizado (delegado) a su vista para obtener más información. Nunca establezca delegado y fuente de datos de su collectionView a su tableViewController principal, solo deje que maneje todo en el viewcontroller correcto y envíe la información a cualquier otro viewcontroller si es necesario.