Collection View in Table View Cell (Swift Tutorial) - Xcode 11, iOS Development
import UIKit
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableViewCell"
private var models: [ImageModel] = []
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 250, height: 250)
layout.minimumInteritemSpacing = 5
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setCollectionView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.frame = contentView.bounds
}
private func setCollectionView() {
contentView.addSubview(collectionView)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
collectionView.delegate = self
collectionView.dataSource = self
}
func configure(with models: [ImageModel]) {
self.models = models
collectionView.reloadData()
}
}
extension CustomTableViewCell: UICollectionViewDelegate {
}
extension CustomTableViewCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else {
return UICollectionViewCell()
}
let model = models[indexPath.row]
cell.configure(with: model)
return cell
}
}
extension CustomTableViewCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 250, height: 250)
}
}
import UIKit
import Combine
class TableViewController: UIViewController {
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: CustomTableViewCell.identifier)
return tableView
}()
private let viewModel: TableViewModel
private var cancellables = Set<AnyCancellable>()
init(viewModel: TableViewModel = TableViewModel()) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setUI()
bind()
}
private func setUI() {
view.backgroundColor = .systemBackground
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
}
private func bind() {
viewModel
.imageModelsSubject
.receive(on: DispatchQueue.main)
.sink { [weak self] models in
self?.tableView.reloadData()
}
.store(in: &cancellables)
}
}
extension TableViewController: UITableViewDelegate {
}
extension TableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as? CustomTableViewCell else {
return UITableViewCell()
}
cell.configure(with: viewModel.imageModelsSubject.value)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
}
import UIKit
class CustomTableViewCell: UITableViewCell {
static let identifier = "CustomTableViewCell"
private var models: [ImageModel] = []
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 250, height: 250)
layout.minimumInteritemSpacing = 5
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setCollectionView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.frame = contentView.bounds
}
private func setCollectionView() {
contentView.addSubview(collectionView)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
collectionView.delegate = self
collectionView.dataSource = self
}
func configure(with models: [ImageModel]) {
self.models = models
collectionView.reloadData()
}
}
extension CustomTableViewCell: UICollectionViewDelegate {
}
extension CustomTableViewCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else {
return UICollectionViewCell()
}
let model = models[indexPath.row]
cell.configure(with: model)
return cell
}
}
extension CustomTableViewCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 250, height: 250)
}
}
layoutSubviews
오버라이드 함수)import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
static let identifier = "CustomCollectionViewCell"
private let label: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 38, weight: .bold)
label.textAlignment = .center
label.textColor = .label
return label
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
// let width = contentView.frame.size.width
// let height = contentView.frame.size.height
imageView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
label.frame = CGRect(x: 75, y: 200, width: 50, height: 50)
}
private func setUI() {
contentView.addSubview(label)
contentView.addSubview(imageView)
contentView.backgroundColor = .secondarySystemGroupedBackground
contentView.clipsToBounds = true
}
func configure(with model: ImageModel) {
label.text = model.text
imageView.image = UIImage(named: model.imageName)
}
}
import Foundation
import Combine
import UIKit
class TableViewModel {
let imageModelsSubject: CurrentValueSubject<[ImageModel], Never> = .init([])
private var cancellables = Set<AnyCancellable>()
init() {
sendMockData()
}
private func sendMockData() {
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
var count = 0
timer
.receive(on: DispatchQueue.global(qos: .background))
.map { _ in
count += 1
}
.sink { [weak self] _ in
if count > 7 {
timer.upstream.connect().cancel()
} else {
let text = "\(count)"
let imageName = "dog_\(count)"
let imageModel = ImageModel(text: text, imageName: imageName)
var currentValue = self?.imageModelsSubject.value ?? []
currentValue.append(imageModel)
self?.imageModelsSubject.send(currentValue)
}
}
.store(in: &cancellables)
}
}
import Foundation
struct ImageModel {
let text: String
let imageName: String
}