У меня есть UITableViewController
, который выводит пользовательский UITableViewCell
'.
Эти ячейки связаны с сообщениями чата, так как конфигурация практически идентична, за исключением того, как элементы ограничены.
ячейка бота: аватар> сообщение
ячейка пользователя сообщение <аватар </em>
Iнадеялся объединить их в одной пользовательской ячейке и просто включить свойство origin
в модели, что позволило мне выбрать, какие ограничения я применяю.
Это работало для 5 или 6 сообщений, пока япровел тест с 30 сообщениями, и некоторые ячейки начали наследовать оба набора якорей или даже просто случайные свойства, которые должны быть назначены другой ячейке.
Я вижу, что ошибки указывают на то, что ограничения недействительны, и я считаю, что этоиз-за неправильной подготовки ячейки к повторному использованию.
(
"<NSLayoutConstraint:0x600002533930 UIImageView:0x7fb401514d40.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600002526990 UITextView:0x7fb40200a200'I am a Person.'.leading == UILayoutGuide:0x600003f18e00'UIViewLayoutMarginsGuide'.leading + 15 (active)>",
"<NSLayoutConstraint:0x6000025271b0 UITextView:0x7fb40200a200'I am a Person.'.trailing == UIImageView:0x7fb401514d40.leading - 15 (active)>"
)
ChatMessageCell
class ChatMessageCell: UITableViewCell {
fileprivate var content: ChatMessage? {
didSet {
guard let text = content?.text else { return }
messageView.text = text
guard let origin = content?.origin else { return }
setupSubViews(origin)
}
}
fileprivate var messageAvatar: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.layer.cornerRadius = 35 / 2
imageView.layer.masksToBounds = true
return imageView
}()
fileprivate var messageView: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isSelectable = false
textView.sizeToFit()
textView.layoutIfNeeded()
textView.contentInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
textView.layer.cornerRadius = 10
textView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setContent(as content: ChatMessage) {
self.content = content
}
override func prepareForReuse() {
content = nil
}
}
extension ChatMessageCell {
fileprivate func setupSubViews(_ origin: ChatMessageOrigin) {
let margins = contentView.layoutMarginsGuide
[messageAvatar, messageView].forEach { v in contentView.addSubview(v) }
switch origin {
case .system:
messageAvatar.image = #imageLiteral(resourceName: "large-bot-head")
messageAvatar.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.anchor(
top: margins.topAnchor, leading: messageAvatar.trailingAnchor, bottom: margins.bottomAnchor, trailing: margins.trailingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
case .user:
let userContentBG = UIColor.hexStringToUIColor(hex: "00f5ff")
messageAvatar.image = UIImage.from(color: userContentBG)
messageAvatar.anchor(
top: margins.topAnchor, trailing: margins.trailingAnchor, size: CGSize(width: 35, height: 35)
)
messageView.layer.backgroundColor = userContentBG.cgColor
messageView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMinYCorner]
messageView.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, bottom: margins.bottomAnchor, trailing: messageAvatar.leadingAnchor,
padding: UIEdgeInsets(top: 5, left: 15, bottom: 0, right: 15)
)
}
}
}
ChatController
class ChatController: UITableViewController {
lazy var viewModel: ChatViewModel = {
let viewModel = ChatViewModel()
return viewModel
}()
fileprivate let headerView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .white
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.reloadData = { [weak self] in
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
configureViewHeader()
configureTableView()
registerTableCells()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: 85, left: 0, bottom: 0, right: 0)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.history.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = viewModel.history[indexPath.row]
let cell = tableView.dequeueReusableCell(withClass: ChatMessageCell.self)
cell.setContent(as: item)
cell.layoutSubviews()
return cell
}
}
extension ChatController {
fileprivate func configureViewHeader() {
let margins = view.safeAreaLayoutGuide
view.addSubview(headerView)
headerView.anchor(
top: margins.topAnchor, leading: margins.leadingAnchor, trailing: margins.trailingAnchor,
size: CGSize(width: 0, height: 70)
)
}
fileprivate func configureTableView() {
tableView.tableFooterView = UIView()
tableView.allowsSelection = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
tableView.separatorStyle = .none
tableView.backgroundColor = UIColor.clear
}
fileprivate func registerTableCells() {
tableView.register(cellWithClass: ChatMessageCell.self)
}
}
Пример того, как виw изменения прокрутки можно увидеть здесь ....
Мои расширения применяются с
@discardableResult
func anchor(top: NSLayoutYAxisAnchor? = nil, leading: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, trailing: NSLayoutXAxisAnchor? = nil, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints {
translatesAutoresizingMaskIntoConstraints = false
var anchoredConstraints = AnchoredConstraints()
if let top = top {
anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top)
}
if let leading = leading {
anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
}
if let bottom = bottom {
anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
}
if let trailing = trailing {
anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
}
if size.width != 0 {
anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width)
}
if size.height != 0 {
anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height)
}
[anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach { $0?.isActive = true }
return anchoredConstraints
}