chat setting voice actor

This commit is contained in:
mh 2025-11-03 17:29:21 +08:00
parent 1cedc98838
commit 3ca9d1a516
3 changed files with 610 additions and 4 deletions

View File

@ -18,12 +18,13 @@ struct ImageRow: RowModel {
let subItems: [ImageRow]? //
let buttleItems: [ChatButtleRow]?
let chatModeItems: [ChatModeItem]?
let voiceActorItems: [VoiceActorItem]?
var isExpanded: Bool = false //
var cellReuseID: String { "ChatSwipeCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil, buttleItems: [ChatButtleRow]? = nil, chatModeItems: [ChatModeItem]? = nil) {
init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil, buttleItems: [ChatButtleRow]? = nil, chatModeItems: [ChatModeItem]? = nil, voiceActorItems: [VoiceActorItem]? = nil) {
self.icon = icon
self.title = title
self.showAvatar = showAvatar
@ -32,6 +33,7 @@ struct ImageRow: RowModel {
self.subItems = subItems
self.buttleItems = buttleItems
self.chatModeItems = chatModeItems
self.voiceActorItems = voiceActorItems
}
}
@ -50,10 +52,12 @@ class ChatSwipeCell: ChatSettingBaseCell, CellConfigurable {
return lab
}()
lazy var avatarView : UIImageView = {
var avatarView : UIImageView = {
let imgView = UIImageView()
imgView.cornerRadius = 10.5
imgView.backgroundColor = .blue
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
return imgView
}()
@ -89,6 +93,12 @@ class ChatSwipeCell: ChatSettingBaseCell, CellConfigurable {
arrowImgView.isHidden = !row.showArrow
switchControl.isHidden = !row.showSwitch
// voice actor items
if row.showAvatar, let voiceActorItems = row.voiceActorItems,
let selectedItem = voiceActorItems.first(where: { $0.isSelected }) {
avatarView.image = UIImage(named: selectedItem.avatarImage)
}
//
updateArrowState(isExpanded: row.isExpanded)

View File

@ -0,0 +1,502 @@
//
// VoiceActorContainerCell.swift
// Visual_Novel_iOS
//
// Created by mh on 2025/01/27.
//
import UIKit
import SnapKit
// MARK: -
struct VoiceActorItem {
let id: String
let name: String
let description: String
let avatarImage: String
let gender: String // "male", "female", "all"
var isSelected: Bool = false
}
// MARK: - RowModel
struct VoiceActorRow: RowModel {
let items: [VoiceActorItem]
var cellReuseID: String { "VoiceActorContainerCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat {
let filterHeight: CGFloat = 50 //
let maxRows: CGFloat = 5.5 // 5.5
let rowHeight: CGFloat = 80 //
let maxListHeight = maxRows * rowHeight
return filterHeight + maxListHeight
}
}
// MARK: - VoiceActorContainerCell
class VoiceActorContainerCell: UITableViewCell, CellConfigurable {
private var allItems: [VoiceActorItem] = []
private var filteredItems: [VoiceActorItem] = []
private var selectedItemId: String?
private var tableViewHeightConstraint: Constraint?
var selectedItemChanged: ((VoiceActorItem) -> Void)? //
// MARK: -
private lazy var filterView: VoiceActorFilterView = {
let view = VoiceActorFilterView()
view.filterChanged = { [weak self] filterType in
self?.filterItems(by: filterType)
}
return view
}()
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.separatorStyle = .none
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = UIColor(hex: "#F5F5FF")
tableView.showsVerticalScrollIndicator = true
tableView.isScrollEnabled = true
tableView.register(VoiceActorItemCell.self, forCellReuseIdentifier: "VoiceActorItemCell")
tableView.estimatedRowHeight = 80
tableView.rowHeight = 80
tableView.contentInset = .zero
tableView.scrollIndicatorInsets = .zero
tableView.showsVerticalScrollIndicator = false
tableView.bounces = false
tableView.layer.cornerRadius = 15
tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return tableView
}()
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")
}
func setupViews() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(filterView)
contentView.addSubview(tableView)
filterView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(20)
make.top.equalToSuperview().offset(2.5)
make.height.equalTo(50)
}
tableView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(20)
make.top.equalTo(filterView.snp.bottom)
make.bottom.equalToSuperview().inset(2.5)
tableViewHeightConstraint = make.height.equalTo(0).constraint
}
}
func configure(with row: RowModel) {
guard let voiceActorRow = row as? VoiceActorRow else { return }
allItems = voiceActorRow.items
//
if let selected = allItems.first(where: { $0.isSelected }) {
selectedItemId = selected.id
}
filterItems(by: .all)
}
private func filterItems(by filterType: VoiceActorFilterView.FilterType) {
var items: [VoiceActorItem]
switch filterType {
case .all:
items = allItems
case .male:
items = allItems.filter { $0.gender == "male" || $0.gender == "all" }
case .female:
items = allItems.filter { $0.gender == "female" || $0.gender == "all" }
}
//
filteredItems = items.map { item in
var mutableItem = item
mutableItem.isSelected = (item.id == selectedItemId)
return mutableItem
}
tableView.reloadData()
updateTableViewHeight()
}
private func updateTableViewHeight() {
let rowHeight: CGFloat = 80
let maxRows: CGFloat = 5.5
let displayCount = min(CGFloat(filteredItems.count), maxRows)
let height = displayCount * rowHeight
tableViewHeightConstraint?.update(offset: height)
layoutIfNeeded()
}
}
// MARK: - UITableViewDataSource & UITableViewDelegate
extension VoiceActorContainerCell: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "VoiceActorItemCell", for: indexPath) as! VoiceActorItemCell
let item = filteredItems[indexPath.row]
//
let bgColor = indexPath.row % 2 == 0 ? UIColor(hex: "#F5F5FF") : UIColor(hex: "#E8F0FF")
cell.configure(with: item, backgroundColor: bgColor)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = filteredItems[indexPath.row]
selectedItemId = item.id
//
allItems = allItems.map { var i = $0; i.isSelected = (i.id == item.id); return i }
filteredItems = filteredItems.map { var i = $0; i.isSelected = (i.id == item.id); return i }
tableView.reloadData()
//
selectedItemChanged?(item)
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? VoiceActorItemCell {
cell.isHovered = true
}
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? VoiceActorItemCell {
cell.isHovered = false
}
}
}
// MARK: - VoiceActorFilterView
class VoiceActorFilterView: UIView {
enum FilterType: String {
case all = "All"
case male = "Male"
case female = "Female"
}
var selectedFilter: FilterType = .all {
didSet {
updateButtonStates()
}
}
var filterChanged: ((FilterType) -> Void)?
lazy var filterStackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [allButton, maleButton, femaleButton])
stackView.spacing = 20
stackView.alignment = .center
// stackView
stackView.distribution = .fill
// Hugging/Compression
stackView.setContentHuggingPriority(.required, for: .horizontal)
stackView.setContentCompressionResistancePriority(.required, for: .horizontal)
return stackView
}()
private lazy var allButton: UIView = {
return createFilterButton(title: "All", tag: 0)
}()
private lazy var maleButton: UIView = {
return createFilterButton(title: "Male", tag: 1)
}()
private lazy var femaleButton: UIView = {
return createFilterButton(title: "Female", tag: 2)
}()
private var filledCircles: [UIView: UIView] = [:]
private var circleBorders: [UIView: UIView] = [:]
private var titleLabels: [UIView: UILabel] = [:]
override init(frame: CGRect) {
super.init(frame: frame)
self.cornerRadius = 15.0
setupViews()
updateButtonStates()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = UIColor(hex: "#1A1A2E")
addSubview(filterStackView)
filterStackView.snp.makeConstraints { make in
make.left.equalToSuperview().inset(12)
make.centerY.equalToSuperview()
// 12
make.right.lessThanOrEqualToSuperview().inset(12)
}
}
private func createFilterButton(title: String, tag: Int) -> UIView {
// 使 UIButton便
let containerView = UIView()
containerView.tag = tag
//
let circleView = UIView()
circleView.backgroundColor = .clear
circleView.layer.borderWidth = 2
// 60%
circleView.layer.borderColor = UIColor(white: 1, alpha: 0.6).cgColor
circleView.layer.cornerRadius = 5
containerView.addSubview(circleView)
let filledCircle = UIView()
filledCircle.backgroundColor = .white
filledCircle.layer.cornerRadius = 5
filledCircle.isHidden = true
containerView.addSubview(filledCircle)
//
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.textColor = UIColor(white: 1, alpha: 0.6) //
titleLabel.font = UIFont.systemFont(ofSize: 14)
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
containerView.addSubview(titleLabel)
// (10x10) -> (5pt) ->
circleView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.centerY.equalToSuperview()
make.width.height.equalTo(10)
}
filledCircle.snp.makeConstraints { make in
make.center.equalTo(circleView)
make.width.height.equalTo(10)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(circleView.snp.right).offset(5) // 5pt
make.right.equalToSuperview()
make.centerY.equalToSuperview()
make.top.bottom.greaterThanOrEqualToSuperview() //
}
//
containerView.setContentHuggingPriority(.required, for: .horizontal)
containerView.setContentCompressionResistancePriority(.required, for: .horizontal)
//
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(filterButtonTapped(_:)))
containerView.addGestureRecognizer(tapGesture)
containerView.isUserInteractionEnabled = true
// 便
filledCircles[containerView] = filledCircle
circleBorders[containerView] = circleView
titleLabels[containerView] = titleLabel
return containerView
}
@objc private func filterButtonTapped(_ sender: UITapGestureRecognizer) {
guard let containerView = sender.view else { return }
let filters: [FilterType] = [.all, .male, .female]
selectedFilter = filters[containerView.tag]
filterChanged?(selectedFilter)
}
private func updateButtonStates() {
let buttons = [allButton, maleButton, femaleButton]
let filters: [FilterType] = [.all, .male, .female]
for (index, button) in buttons.enumerated() {
let isSelected = filters[index] == selectedFilter
//
// 60% 60%
titleLabels[button]?.textColor = UIColor(white: 1, alpha: isSelected ? 1.0 : 0.6)
filledCircles[button]?.isHidden = !isSelected
circleBorders[button]?.layer.borderColor = UIColor(white: 1, alpha: isSelected ? 1.0 : 0.6).cgColor
}
}
}
// MARK: - VoiceActorItemCell
class VoiceActorItemCell: UITableViewCell {
private lazy var containerView: UIView = {
let view = UIView()
view.layer.cornerRadius = 12
return view
}()
private lazy var avatarImageView: UIImageView = {
let view = UIImageView()
view.cornerRadius = 27.5
view.backgroundColor = .darkText
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
return view
}()
private lazy var playButton: UIButton = {
let btn = UIButton(type: .custom)
if let playImage = UIImage(named: "voice_play") {
btn.setImage(playImage, for: .normal)
} else {
let config = UIImage.SymbolConfiguration(pointSize: 10, weight: .bold)
btn.setImage(UIImage(systemName: "play.fill", withConfiguration: config), for: .normal)
btn.tintColor = .white
}
btn.backgroundColor = UIColor(hex: "#0066FF")
btn.layer.cornerRadius = 12
btn.imageEdgeInsets = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
btn.imageView?.contentMode = .scaleAspectFit
return btn
}()
private lazy var titleLabel: UILabel = {
let lab = UILabel()
lab.textColor = UIColor(hex: "#333333")
lab.font = UIFont.systemFont(ofSize: 13, weight: .medium)
return lab
}()
private lazy var subTitleLab: UILabel = {
let lab = UILabel()
lab.textColor = UIColor(hex: "#9494C3")
lab.font = UIFont.italicSystemFont(ofSize: 11)
return lab
}()
private lazy var selectionIndicator: UIView = {
let view = UIView()
view.layer.cornerRadius = 10
view.backgroundColor = UIColor.black
return view
}()
private var hoverBorderView: UIView?
var item: VoiceActorItem?
var isHovered: Bool = false {
didSet {
updateHoverState()
}
}
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")
}
func setupViews() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(containerView)
containerView.addSubview(avatarImageView)
containerView.addSubview(playButton)
containerView.addSubview(titleLabel)
containerView.addSubview(subTitleLab)
containerView.addSubview(selectionIndicator)
containerView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.bottom.equalToSuperview().inset(2.5)
}
avatarImageView.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(10)
make.width.height.equalTo(55)
make.bottom.equalToSuperview().offset(-10)
}
playButton.snp.makeConstraints { make in
make.bottom.right.equalTo(avatarImageView).offset(2)
make.width.height.equalTo(24)
}
selectionIndicator.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().inset(10)
make.width.height.equalTo(20)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(avatarImageView.snp.right).offset(10)
make.right.equalTo(selectionIndicator.snp.left).offset(-10)
make.centerY.equalToSuperview().offset(-8)
}
subTitleLab.snp.makeConstraints { make in
make.left.right.equalTo(titleLabel)
make.top.equalTo(titleLabel.snp.bottom).offset(2)
}
// hover
hoverBorderView = UIView()
hoverBorderView?.backgroundColor = .clear
hoverBorderView?.layer.borderWidth = 2
hoverBorderView?.layer.borderColor = UIColor(hex: "#FF69B4").cgColor
hoverBorderView?.layer.cornerRadius = 14
hoverBorderView?.isHidden = true
if let hoverView = hoverBorderView {
containerView.addSubview(hoverView)
hoverView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
func configure(with item: VoiceActorItem, backgroundColor: UIColor? = nil) {
self.item = item
avatarImageView.image = UIImage(named: item.avatarImage)
titleLabel.text = item.name
subTitleLab.text = item.description
//
containerView.backgroundColor = backgroundColor ?? UIColor(hex: "#F5F5FF")
updateSelectionState(isSelected: item.isSelected)
}
func updateSelectionState(isSelected: Bool) {
selectionIndicator.backgroundColor = isSelected ? UIColor(hex: "#00CC88") : UIColor.black
}
private func updateHoverState() {
hoverBorderView?.isHidden = !isHovered
}
}

View File

@ -12,7 +12,7 @@ class ChatSettingSwipeView: CLContainer {
var closeAction: (()->Void)?
var sectionTitle: [String] = ["Switch Model", "Sound", "Maximum number of response tokens Maximum number of response tokens", "Appearance", "Background", "Historical Archives"]
var sectionTitle: [String] = ["Switch Model", "Sound", "Maximum number of response tokens", "Appearance", "Background", "Historical Archives"]
var rows: [[RowModel]] = []
// section -> rowIndex -> isExpanded
@ -55,7 +55,7 @@ class ChatSettingSwipeView: CLContainer {
rows = [
[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, voiceActorItems: createVoiceActorItems()), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)],
[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, chatModeItems: createChatModeItems()), buttleRow],
[BackgroundRow(count: 50)],
@ -112,6 +112,7 @@ class ChatSettingSwipeView: CLContainer {
tableView.register(SubItemsContainerCell.self, forCellReuseIdentifier: "SubItemsContainerCell")
tableView.register(ChatButtleCollectionCell.self, forCellReuseIdentifier: "ChatButtleCollectionCell")
tableView.register(ChatModeContainerCell.self, forCellReuseIdentifier: "ChatModeContainerCell")
tableView.register(VoiceActorContainerCell.self, forCellReuseIdentifier: "VoiceActorContainerCell")
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return tableView
}()
@ -196,6 +197,37 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
tableView.deselectRow(at: indexPath, animated: true)
// Voice actor
if imageRow.title == "Voice actor", let voiceActorItems = imageRow.voiceActorItems, !voiceActorItems.isEmpty {
let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false
expandedStates[indexPath.section, default: [:]][indexPath.row] = !isExpanded
if !isExpanded {
// VoiceActorRow
let actorRow = VoiceActorRow(items: voiceActorItems)
rows[indexPath.section].insert(actorRow, at: indexPath.row + 1)
let insertIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.performBatchUpdates({
tableView.insertRows(at: [insertIndexPath], with: .fade)
tableView.reloadRows(at: [indexPath], with: .none)
}, completion: nil)
} else {
//
if indexPath.row + 1 < rows[indexPath.section].count,
rows[indexPath.section][indexPath.row + 1] is VoiceActorRow {
rows[indexPath.section].remove(at: indexPath.row + 1)
let deleteIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.performBatchUpdates({
tableView.deleteRows(at: [deleteIndexPath], with: .fade)
tableView.reloadRows(at: [indexPath], with: .none)
}, completion: nil)
}
}
return
}
// Chat Mode
if imageRow.title == "Chat Mode", let chatModeItems = imageRow.chatModeItems, !chatModeItems.isEmpty {
let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false
@ -310,6 +342,20 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = rows[indexPath.section][indexPath.row]
// VoiceActorRow
if let actorRow = row as? VoiceActorRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "VoiceActorContainerCell", for: indexPath) as! VoiceActorContainerCell
cell.selectionStyle = .none
// cell
cell.selectedItemChanged = { [weak self] item in
self?.updateVoiceActorAvatar(item, at: indexPath.section, rowIndex: indexPath.row - 1)
}
cell.configure(with: actorRow)
return cell
}
// ChatModeRow
if let modeRow = row as? ChatModeRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatModeContainerCell", for: indexPath) as! ChatModeContainerCell
@ -382,4 +428,52 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
ChatModeItem(id: "2", title: "Immersive Mode", subtitle: "Previous-generation large model", isVip: true, isSelected: false)
]
}
// MARK: - VoiceActor
private func createVoiceActorItems() -> [VoiceActorItem] {
return [
VoiceActorItem(id: "1", name: "Name 1", description: "Anime-Style Girl", avatarImage: "voice_actor_1", gender: "female", isSelected: true),
VoiceActorItem(id: "2", name: "Name 2", description: "Anime-Style Girl", avatarImage: "voice_actor_1", gender: "female", isSelected: false),
VoiceActorItem(id: "3", name: "Name 3", description: "Anime-Style Boy", avatarImage: "voice_actor_2", gender: "male", isSelected: false),
VoiceActorItem(id: "4", name: "Name 4", description: "Anime-Style Girl", avatarImage: "voice_actor_1", gender: "female", isSelected: false),
VoiceActorItem(id: "5", name: "Name 5", description: "Anime-Style Girl", avatarImage: "voice_actor_1", gender: "female", isSelected: false),
VoiceActorItem(id: "6", name: "Name 6", description: "Anime-Style Boy", avatarImage: "voice_actor_2", gender: "male", isSelected: false),
VoiceActorItem(id: "7", name: "Name 7", description: "Anime-Style Girl", avatarImage: "voice_actor_1", gender: "female", isSelected: false),
VoiceActorItem(id: "8", name: "Name 8", description: "Anime-Style Boy", avatarImage: "voice_actor_2", gender: "male", isSelected: false),
]
}
// MARK: - Voice Actor
private func updateVoiceActorAvatar(_ item: VoiceActorItem, at section: Int, rowIndex: Int) {
guard section >= 0 && section < rows.count,
rowIndex >= 0 && rowIndex < rows[section].count else { return }
//
if var imageRow = rows[section][rowIndex] as? ImageRow,
var voiceActorItems = imageRow.voiceActorItems {
// items
voiceActorItems = voiceActorItems.map { var i = $0; i.isSelected = (i.id == item.id); return i }
// ImageRow struct
let updatedRow = ImageRow(
icon: imageRow.icon,
title: imageRow.title,
showAvatar: imageRow.showAvatar,
showArrow: imageRow.showArrow,
showSwitch: imageRow.showSwitch,
subItems: imageRow.subItems,
buttleItems: imageRow.buttleItems,
chatModeItems: imageRow.chatModeItems,
voiceActorItems: voiceActorItems
)
rows[section][rowIndex] = updatedRow
}
// cell
let parentIndexPath = IndexPath(row: rowIndex, section: section)
if let cell = tableView.cellForRow(at: parentIndexPath) as? ChatSwipeCell {
// avatarView
cell.avatarView.image = UIImage(named: item.avatarImage)
}
}
}