chat setting chat buttle点击效果实现

This commit is contained in:
mh 2025-10-30 19:24:23 +08:00
parent 3543d9c9e8
commit c4a34fd419
3 changed files with 378 additions and 34 deletions

View File

@ -6,9 +6,286 @@
// //
import UIKit import UIKit
import SnapKit
class ChatButtleCollectionCell: UICollectionViewCell { struct SubButtleRow: RowModel {
let subItems: [ChatButtleRow]
var cellReuseID: String { "ChatButtleCollectionCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat {
let maxDisplayCount = 4
let displayCount = min(subItems.count, maxDisplayCount)
return CGFloat(displayCount) * 58 // 40
}
}
// MARK: -
struct ChatButtleItem {
let id: String
let name: String
let price: Int?
enum Kind { case `default`, vip, premium }
enum Status { case selected, available, locked, gettable }
let kind: Kind
let status: Status
}
// MARK: - RowModel
struct ChatButtleRow: RowModel {
let id: String
let name: String
let price: Int?
enum Kind { case `default`, vip, premium }
enum Status { case selected, available, locked, gettable }
let kind: Kind
let status: Status
let items: [ChatButtleRow] = []
var cellReuseID: String { "ChatButtleCollectionCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat {
let columns: CGFloat = 3
let spacing: CGFloat = 8
let horizontalPadding: CGFloat = 40 // container 20
let availableWidth = tableWidth - horizontalPadding
_ = (availableWidth - spacing * (columns - 1)) / columns // width
let itemHeight: CGFloat = 120
let rows = ceil(CGFloat(items.count) / columns)
let displayRows = min(rows, 4.5)
let total = displayRows * itemHeight + (displayRows - 1) * spacing + 5
return total
}
}
// MARK: - Cell
class ChatButtleCollectionCell: UITableViewCell, CellConfigurable {
private var items: [ChatButtleRow] = []
private var selectedId: String?
private let widthItem: CGFloat = ((UIScreen.width * 0.84 - 60.0) / 3.0 - 1.0)
private var heightConstraint: Constraint?
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
layout.itemSize = CGSizeMake(widthItem, widthItem * 110.0 / 87.0)
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.showsVerticalScrollIndicator = false
cv.register(ChatButtleCard.self, forCellWithReuseIdentifier: "ChatButtleCard")
cv.layer.cornerRadius = 15
cv.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return cv
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.bottom.equalToSuperview().inset(2.5)
heightConstraint = make.height.equalTo(0).constraint
}
}
func configure(with row: RowModel) {
guard let r = row as? SubButtleRow else { return }
items = r.subItems
if let first = items.first(where: { $0.status == .selected || $0.status == .available }) {
selectedId = first.id
}
collectionView.reloadData()
updateHeight()
}
private func updateHeight() {
let columns: CGFloat = 3
let spacing: CGFloat = 10
let availableWidth = UIScreen.width * 0.84 - 60.0
_ = (availableWidth - spacing * (columns - 1)) / columns
let itemHeight: CGFloat = widthItem * 110.0 / 87.0
let rows = ceil(CGFloat(items.count) / columns)
let displayRows = min(rows, 4.5)
let total = displayRows * itemHeight + (displayRows - 1) * spacing + 5
heightConstraint?.update(offset: total)
layoutIfNeeded()
}
override func layoutSubviews() {
super.layoutSubviews()
updateHeight()
}
}
// MARK: - CollectionView
extension ChatButtleCollectionCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { items.count }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ChatButtleCard", for: indexPath) as! ChatButtleCard
let item = items[indexPath.item]
cell.configure(with: item, selected: item.id == selectedId)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = items[indexPath.item]
guard item.status == .available || item.status == .gettable else { return }
selectedId = item.id
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let columns: CGFloat = 3
let spacing: CGFloat = 10
let availableWidth = UIScreen.width * 0.84 - 60.0
let itemWidth = (availableWidth - spacing * (columns - 1)) / columns
return CGSize(width: itemWidth, height: widthItem * 110.0 / 87.0)
}
}
// MARK: -
//class ChatButtleCard: UICollectionViewCell {
// private lazy var container: UIView = {
// let v = UIView()
// v.backgroundColor = UIColor(hex: "#1A1A2E")
// v.layer.cornerRadius = 12
// return v
// }()
// private lazy var bubble: UIView = {
// let v = UIView()
// v.backgroundColor = .white
// v.layer.cornerRadius = 20
// return v
// }()
// private lazy var hiLabel: UILabel = {
// let l = UILabel()
// l.text = "hi"
// l.textColor = .black
// l.font = UIFont.systemFont(ofSize: 14, weight: .medium)
// return l
// }()
// private lazy var title: UILabel = {
// let l = UILabel()
// l.textColor = .white
// l.font = UIFont.systemFont(ofSize: 12, weight: .medium)
// l.textAlignment = .center
// return l
// }()
// private lazy var vipBadge: UILabel = {
// let l = UILabel()
// l.text = "VIP"
// l.textColor = .black
// l.font = UIFont.systemFont(ofSize: 10, weight: .bold)
// l.backgroundColor = UIColor(hex: "#FFD700")
// l.textAlignment = .center
// l.layer.cornerRadius = 8
// l.clipsToBounds = true
// l.isHidden = true
// return l
// }()
// private lazy var lockIcon: UIImageView = {
// let v = UIImageView(image: UIImage(systemName: "lock.fill"))
// v.tintColor = UIColor(hex: "#999999")
// v.isHidden = true
// return v
// }()
// private lazy var getButton: UIButton = {
// let b = UIButton(type: .custom)
// b.setTitle("Get", for: .normal)
// b.setTitleColor(.white, for: .normal)
// b.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .bold)
// b.backgroundColor = UIColor(hex: "#8B5CF6")
// b.layer.cornerRadius = 8
// b.isHidden = true
// return b
// }()
// private lazy var highlight: UIView = {
// let v = UIView()
// v.layer.borderWidth = 2
// v.layer.borderColor = UIColor(hex: "#8B5CF6").cgColor
// v.layer.cornerRadius = 12
// v.isHidden = true
// return v
// }()
//
// override init(frame: CGRect) {
// super.init(frame: frame)
// setup()
// }
// required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
//
// private func setup() {
// contentView.addSubview(container)
// container.addSubview(bubble)
// container.addSubview(hiLabel)
// container.addSubview(title)
// container.addSubview(vipBadge)
// container.addSubview(lockIcon)
// container.addSubview(getButton)
// container.addSubview(highlight)
//
// container.snp.makeConstraints { make in make.edges.equalToSuperview() }
// bubble.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.centerY.equalToSuperview().offset(-10)
// make.width.height.equalTo(40)
// }
// hiLabel.snp.makeConstraints { make in make.center.equalTo(bubble) }
// title.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.bottom.equalToSuperview().offset(-12)
// make.left.right.equalToSuperview().inset(6)
// }
// vipBadge.snp.makeConstraints { make in
// make.top.left.equalToSuperview().offset(8)
// make.width.equalTo(24)
// make.height.equalTo(16)
// }
// lockIcon.snp.makeConstraints { make in
// make.top.right.equalToSuperview().inset(8)
// make.width.height.equalTo(12)
// }
// getButton.snp.makeConstraints { make in
// make.centerX.equalToSuperview()
// make.bottom.equalToSuperview().offset(-8)
// make.width.equalTo(60)
// make.height.equalTo(24)
// }
// highlight.snp.makeConstraints { make in make.edges.equalToSuperview() }
// }
//
// func configure(with item: ChatButtleItem, selected: Bool) {
// title.text = item.name
// vipBadge.isHidden = item.kind != .vip
// lockIcon.isHidden = item.status != .locked
// getButton.isHidden = item.status != .gettable
// highlight.isHidden = !selected && item.status != .gettable
// if item.kind == .premium { title.textColor = UIColor(hex: "#0066FF") } else if item.kind == .vip { title.textColor = UIColor(hex: "#FF8C00") } else { title.textColor = .white }
// title.isHidden = item.status == .gettable
// }
//}
//
// ChatButtleCollectionCell.swift
// Visual_Novel_iOS
//
// Created by mh on 2025/10/30.
//
class ChatButtleCard: UICollectionViewCell {
lazy var statusImgView: UIImageView = { lazy var statusImgView: UIImageView = {
let imgView = UIImageView() let imgView = UIImageView()
imgView.backgroundColor = .blue imgView.backgroundColor = .blue
@ -43,6 +320,7 @@ class ChatButtleCollectionCell: UICollectionViewCell {
lab.text = "hi" lab.text = "hi"
lab.textColor = UIColor(hex: "#333333") lab.textColor = UIColor(hex: "#333333")
lab.font = UIFont.systemFont(ofSize: 14) lab.font = UIFont.systemFont(ofSize: 14)
lab.textAlignment = .center
return lab return lab
}() }()
@ -56,6 +334,7 @@ class ChatButtleCollectionCell: UICollectionViewCell {
lab.font = UIFont.boldSystemFont(ofSize: 12) lab.font = UIFont.boldSystemFont(ofSize: 12)
lab.text = "Default" lab.text = "Default"
lab.textColor = .white lab.textColor = .white
lab.textAlignment = .center
return lab return lab
}() }()
@ -72,7 +351,7 @@ class ChatButtleCollectionCell: UICollectionViewCell {
let stackView = UIStackView(arrangedSubviews: [stoneImgView, stoneLa]) let stackView = UIStackView(arrangedSubviews: [stoneImgView, stoneLa])
stackView.axis = .horizontal stackView.axis = .horizontal
stackView.spacing = 5 stackView.spacing = 5
stackView.alignment = .center stackView.alignment = .fill
stackView.distribution = .fill stackView.distribution = .fill
return stackView return stackView
}() }()
@ -92,7 +371,19 @@ class ChatButtleCollectionCell: UICollectionViewCell {
} }
func configure(with item: ChatButtleRow, selected: Bool) {
nameLab.text = item.name
vipImgView.isHidden = item.kind != .vip
lockImgView.isHidden = item.status != .locked
getBtn.isHidden = item.status != .gettable
// highlight.isHidden = !selected && item.status != .gettable
if item.kind == .premium { nameLab.textColor = UIColor(hex: "#0066FF") } else if item.kind == .vip { nameLab.textColor = UIColor(hex: "#FF8C00") } else { nameLab.textColor = .white }
nameLab.isHidden = item.status == .gettable
}
func setupViews() { func setupViews() {
self.backgroundColor = .gray
self.cornerRadius = 10
contentView.addSubview(vipImgView) contentView.addSubview(vipImgView)
contentView.addSubview(statusImgView) contentView.addSubview(statusImgView)
contentView.addSubview(stoneStackView) contentView.addSubview(stoneStackView)
@ -122,7 +413,7 @@ class ChatButtleCollectionCell: UICollectionViewCell {
} }
hiLab.snp.makeConstraints { make in hiLab.snp.makeConstraints { make in
make.centerX.centerX.equalToSuperview() make.centerX.centerY.equalToSuperview()
} }
lockImgView.snp.makeConstraints { make in lockImgView.snp.makeConstraints { make in

View File

@ -16,18 +16,20 @@ struct ImageRow: RowModel {
let showArrow: Bool let showArrow: Bool
let showSwitch: Bool let showSwitch: Bool
let subItems: [ImageRow]? // let subItems: [ImageRow]? //
let buttleItems: [ChatButtleRow]?
var isExpanded: Bool = false // var isExpanded: Bool = false //
var cellReuseID: String { "ChatSwipeCell" } var cellReuseID: String { "ChatSwipeCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 } func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil) { init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil, buttleItems: [ChatButtleRow]? = nil) {
self.icon = icon self.icon = icon
self.title = title self.title = title
self.showAvatar = showAvatar self.showAvatar = showAvatar
self.showArrow = showArrow self.showArrow = showArrow
self.showSwitch = showSwitch self.showSwitch = showSwitch
self.subItems = subItems self.subItems = subItems
self.buttleItems = buttleItems
} }
} }
@ -92,7 +94,7 @@ class ChatSwipeCell: ChatSettingBaseCell, CellConfigurable {
updateContainerHeight(50) updateContainerHeight(50)
} }
private func updateArrowState(isExpanded: Bool) { func updateArrowState(isExpanded: Bool) {
UIView.animate(withDuration: 0.3) { [weak self] in UIView.animate(withDuration: 0.3) { [weak self] in
self?.arrowImgView.transform = isExpanded ? CGAffineTransform(rotationAngle: .pi / 2.0) : .identity self?.arrowImgView.transform = isExpanded ? CGAffineTransform(rotationAngle: .pi / 2.0) : .identity
} }

View File

@ -35,15 +35,29 @@ class ChatSettingSwipeView: CLContainer {
// ImageRow(icon: "role_exchange_mode", title: "Sub Item 10", showAvatar: false, showArrow: false, showSwitch: false) // ImageRow(icon: "role_exchange_mode", title: "Sub Item 10", showAvatar: false, showArrow: false, showSwitch: false)
]) ])
let buttleRow = ImageRow(icon: "role_chat_buttle", title: "Chat buttle", showAvatar: false, showArrow: true, showSwitch: false, subItems: [ let buttleRow = ImageRow(icon: "role_chat_buttle", title: "Chat buttle", showAvatar: false, showArrow: true, showSwitch: false, buttleItems: [
ChatButtleRow(id: "1", name: "Default", price: nil, kind: .default, status: .selected),
ChatButtleRow(id: "2", name: "Name1", price: nil, kind: .vip, status: .available),
ChatButtleRow(id: "3", name: "Name2", price: nil, kind: .premium, status: .available),
// Row 2
ChatButtleRow(id: "4", name: "Name3", price: nil, kind: .vip, status: .locked),
ChatButtleRow(id: "5", name: "Name4", price: nil, kind: .vip, status: .locked),
ChatButtleRow(id: "6", name: "Name5", price: nil, kind: .vip, status: .locked),
// Row 3
ChatButtleRow(id: "7", name: "Name6", price: 20, kind: .premium, status: .locked),
ChatButtleRow(id: "8", name: "Name7", price: 20, kind: .premium, status: .locked),
ChatButtleRow(id: "9", name: "Name8", price: 20, kind: .premium, status: .gettable),
// Row 4
// ChatButtleRow(id: "10", name: "Name9", price: 20, kind: .premium, status: .locked),
// ChatButtleRow(id: "11", name: "Name10", price: 20, kind: .premium, status: .locked),
// ChatButtleRow(id: "12", name: "Name11", price: 20, kind: .premium, status: .locked)
]) ])
rows = [ rows = [
[modelRow, ImageRow(icon: "role_text_mode", title: "Short Text Mode", showAvatar: false, showArrow: false, showSwitch: true)], [modelRow, ImageRow(icon: "role_text_mode", title: "Short Text Mode", showAvatar: false, showArrow: false, showSwitch: true)],
[ImageRow(icon: "role_voice", title: "Voice actor", showAvatar: true, showArrow: true, showSwitch: false), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)], [ImageRow(icon: "role_voice", title: "Voice actor", showAvatar: true, showArrow: true, showSwitch: false), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)],
[TokenRow(count: "2500")], [TokenRow(count: "2500")],
[FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false), ImageRow(icon: "role_chat_buttle", title: "Chat buttle", showAvatar: false, showArrow: true, showSwitch: false)], [FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false), buttleRow],
[BackgroundRow(count: 50)], [BackgroundRow(count: 50)],
[HistoryRow(time: "", icon: "", title: "", itemCount: 30)] [HistoryRow(time: "", icon: "", title: "", itemCount: 30)]
] ]
@ -96,6 +110,7 @@ class ChatSettingSwipeView: CLContainer {
tableView.register(ChatBackgroundCell.self, forCellReuseIdentifier: "ChatBackgroundCell") tableView.register(ChatBackgroundCell.self, forCellReuseIdentifier: "ChatBackgroundCell")
tableView.register(ChatHistoryCell.self, forCellReuseIdentifier: "ChatHistoryCell") tableView.register(ChatHistoryCell.self, forCellReuseIdentifier: "ChatHistoryCell")
tableView.register(SubItemsContainerCell.self, forCellReuseIdentifier: "SubItemsContainerCell") tableView.register(SubItemsContainerCell.self, forCellReuseIdentifier: "SubItemsContainerCell")
tableView.register(ChatButtleCollectionCell.self, forCellReuseIdentifier: "ChatButtleCollectionCell")
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return tableView return tableView
}() }()
@ -176,42 +191,70 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let imageRow = rows[indexPath.section][indexPath.row] as? ImageRow, guard let imageRow = rows[indexPath.section][indexPath.row] as? ImageRow,
imageRow.showArrow, imageRow.showArrow else { return }
let subItems = imageRow.subItems, // Chat buttle使
!subItems.isEmpty else { if imageRow.title == "Chat buttle", let subItems = imageRow.buttleItems, !subItems.isEmpty {
tableView.deselectRow(at: indexPath, animated: true)
let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false
expandedStates[indexPath.section, default: [:]][indexPath.row] = !isExpanded
if !isExpanded {
// ChatButtleRow
let subItemsRow = SubButtleRow(subItems: subItems)
rows[indexPath.section].insert(subItemsRow, at: indexPath.row + 1)
let insertIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.insertRows(at: [insertIndexPath], with: .fade)
if let cell = tableView.cellForRow(at: indexPath) as? ChatSwipeCell {
cell.updateArrowState(isExpanded: true)
}
} else {
//
if indexPath.row + 1 < rows[indexPath.section].count,
rows[indexPath.section][indexPath.row + 1] is SubButtleRow {
rows[indexPath.section].remove(at: indexPath.row + 1)
let deleteIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.deleteRows(at: [deleteIndexPath], with: .fade)
if let cell = tableView.cellForRow(at: indexPath) as? ChatSwipeCell {
cell.updateArrowState(isExpanded: false)
}
}
}
return return
} }
// if let subItems = imageRow.subItems, !subItems.isEmpty {
let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false //
expandedStates[indexPath.section, default: [:]][indexPath.row] = !isExpanded let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false
expandedStates[indexPath.section, default: [:]][indexPath.row] = !isExpanded
// SubItemsContainerCell
if !isExpanded {
// SubItemsContainerCell
let subItemsRow = SubItemsRow(subItems: subItems)
rows[indexPath.section].insert(subItemsRow, at: indexPath.row + 1)
let insertIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section) // SubItemsContainerCell
tableView.beginUpdates() if !isExpanded {
tableView.insertRows(at: [insertIndexPath], with: .fade) // SubItemsContainerCell
tableView.endUpdates() let subItemsRow = SubItemsRow(subItems: subItems)
rows[indexPath.section].insert(subItemsRow, at: indexPath.row + 1)
//
tableView.reloadRows(at: [indexPath], with: .none)
} else {
// SubItemsContainerCell
if indexPath.row + 1 < rows[indexPath.section].count,
rows[indexPath.section][indexPath.row + 1] is SubItemsRow {
rows[indexPath.section].remove(at: indexPath.row + 1)
let deleteIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section) let insertIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.beginUpdates() tableView.beginUpdates()
tableView.deleteRows(at: [deleteIndexPath], with: .fade) tableView.insertRows(at: [insertIndexPath], with: .fade)
tableView.endUpdates() tableView.endUpdates()
// //
tableView.reloadRows(at: [indexPath], with: .none) tableView.reloadRows(at: [indexPath], with: .none)
} else {
// SubItemsContainerCell
if indexPath.row + 1 < rows[indexPath.section].count,
rows[indexPath.section][indexPath.row + 1] is SubItemsRow {
rows[indexPath.section].remove(at: indexPath.row + 1)
let deleteIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.beginUpdates()
tableView.deleteRows(at: [deleteIndexPath], with: .fade)
tableView.endUpdates()
//
tableView.reloadRows(at: [indexPath], with: .none)
}
} }
} }
} }
@ -227,6 +270,14 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = rows[indexPath.section][indexPath.row] let row = rows[indexPath.section][indexPath.row]
// ChatButtleRow
if let buttleRow = row as? SubButtleRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatButtleCollectionCell", for: indexPath) as! ChatButtleCollectionCell
cell.selectionStyle = .none
cell.configure(with: buttleRow)
return cell
}
// SubItemsRow // SubItemsRow
if let subItemsRow = row as? SubItemsRow { if let subItemsRow = row as? SubItemsRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubItemsContainerCell", for: indexPath) as! SubItemsContainerCell let cell = tableView.dequeueReusableCell(withIdentifier: "SubItemsContainerCell", for: indexPath) as! SubItemsContainerCell