Как правильно комбинировать элементы в разделе с RxDataSource swift? - PullRequest
1 голос
/ 01 октября 2019

Мне нужно объединить сообщение чата в разделе, когда элементы отправляются в течение одной минуты.

ViewModel

.....

.scan([MessageSectionModel]()) { sectionModels, messageItem in
        var models = sectionModels

        if let lastSectionModel = sectionModels.last {
            switch lastSectionModel {
            case .incomingSection(var items):
                if messageItem.0.isIncoming {
                    items.append(messageItem.0)
                    models[models.count-1] = .incomingSection(items: items)
                } else {
                    models.append(.outcomingSection(items: [messageItem.0]))
                }

            case .outcomingSection(var items):
                if messageItem.0.isIncoming {
                    models.append(.incomingSection(items: [messageItem.0]))
                } else {
                    items.append(messageItem.0)
                    models[models.count-1] = .outcomingSection(items: items)
                }
            }
            return models
        }

        if messageItem.0.isIncoming {
            models.append(.incomingSection(items: [messageItem.0]))
        } else {
            models.append(.outcomingSection(items: [messageItem.0]))
        }
        return models
    }

.....

ViewController

....

@IBOutlet private weak var messagesTableView: UITableView!

private let disposeBag = DisposeBag()
private var dataSource: RxTableViewSectionedAnimatedDataSource<MessageSectionModel>!

private let messageHeaderReuseIdentifier = String(describing: MessageHeaderView.self)
private let messageFooterReuseIdentifier = String(describing: MessageFooterView.self)

dataSource = RxTableViewSectionedAnimatedDataSource<MessageSectionModel>(
        animationConfiguration: .init(insertAnimation: .none, reloadAnimation: .none, deleteAnimation: .none),
        configureCell: { dataSource, tableView, indexPath, item in

            switch dataSource.sectionModels[indexPath.section] {
            case .incomingSection:
                guard let cell = tableView.dequeueReusableCell(
                    withIdentifier: R.reuseIdentifier.incomingMessageTableViewCell,
                    for: indexPath
                ) else {
                    return UITableViewCell()
                }

                let isFirst = indexPath.row == dataSource[indexPath.section].items.count - 1

                cell.bind(
                    messageText: item.text,
                    isFirstInSection: isFirst
                )

                return cell
            case .userSection:
                guard let cell = tableView.dequeueReusableCell(
                    withIdentifier: R.reuseIdentifier.outcomingMessageTableViewCell,
                    for: indexPath
                ) else {
                     return UITableViewCell()
                }

                cell.bind(
                    messageText: item.text,
                    isFirstInSection: indexPath.row == dataSource[indexPath.section].items.count - 1
                )

                return cell
            }
    })

....

Элементы сообщения

....

 import Foundation
 import RxDataSources

 enum MessageSectionModel {
    case incomingSection(items: [MessageSectionItem])
    case outcomingSection(items: [MessageSectionItem])

 var lastMessageDate: Date {
    switch self {
    case .incomingSection(let items):
        return items.last?.sentDate ?? Date()
    case .outcomingSection(let items):
        return items.last?.sentDate ?? Date()
    }
   }
 }

struct MessageSectionItem {
   let userId: String
   let id: String = UUID().uuidString
   let text: String
   let sentDate: Date
  let isIncoming: Bool
}

extension MessageSectionItem: IdentifiableType {
   var identity : String {
       return id
  }
}

extension MessageSectionItem: Equatable {
   static func == (lhs: MessageSectionItem, rhs: MessageSectionItem) -> Bool {
     return lhs.identity == rhs.identity
   }
  }

extension MessageSectionModel: AnimatableSectionModelType {
   init(original: MessageSectionModel, items: [MessageSectionItem]) {
     switch original {
    case .incomingSection(let items):
        self = .incomingSection(items: items)
    case .outcomingSection(let items):
        self = .outcomingSection(items: items)
    }
 }

typealias Item = MessageSectionItem

var items: [MessageSectionItem] {
    switch self {
    case .incomingSection(let items):
        return items.map { $0 }
    case .outcomingSection(let items):
        return items.map { $0 }
    }
}

var identity: Date {
    return lastMessageDate
 }
}

....

Мой вид таблицы вращается, потому что я получаю сообщения, которые возвращаются. Я понимаю, что это моя ошибка в сканировании, потому что, когда я комментирую этот код, мои ячейки сортируются правильно, но не объединяются в разделы.

   if let lastSectionModel = sectionModels.last {
         switch lastSectionModel {
         case .incomingSection(var items):
            if messageItem.0.isIncoming {
                items.append(messageItem.0)
                models[models.count-1] = .incomingSection(items: items)
            } else {
                models.append(.outcomingSection(items: [messageItem.0]))
            }

        case .outcomingSection(var items):
            if messageItem.0.isIncoming {
                models.append(.incomingSection(items: [messageItem.0]))
            } else {
                items.append(messageItem.0)
                models[models.count-1] = .outcomingSection(items: items)
            }
        }
        return models

1 Ответ

0 голосов
/ 02 октября 2019

Я думаю, вы пытаетесь сделать слишком много за один раз и в неправильном порядке. Разбейте работу на более мелкие, каждая из которых может быть легко протестирована / проверена ... Кроме того, сначала сгруппируйте свои сообщения по времени, затем , а затем разместите их в своих разделах. Я закончил с этим:

struct MessageItem {
    let userId: String
    let id: String = UUID().uuidString
    let text: String
    let sentDate: Date
    let isIncoming: Bool
}

struct MessageGroup {
    let userId: String
    var text: String {
        return parts.map { $0.text }.joined(separator: "\n")
    }
    let isIncoming: Bool

    struct Part {
        let id: String
        let text: String
        let sentDate: Date

        init(_ messageSectionItem: MessageItem) {
            id = messageSectionItem.id
            text = messageSectionItem.text
            sentDate = messageSectionItem.sentDate
        }
    }
    var parts: [Part]

    init(from item: MessageItem) {
        userId = item.userId
        isIncoming = item.isIncoming
        parts = [Part(item)]
    }
}

enum MessageSectionModel {
    case incomingSection(items: [MessageGroup])
    case outcomingSection(items: [MessageGroup])
}

extension ObservableType where Element == MessageItem {
    func convertedToSectionModels() -> Observable<[MessageSectionModel]> {
        return
            scan(into: ([MessageGroup](), MessageGroup?.none), accumulator: groupByTime(messages:item:))
            .map(appendingLastGroup(messages:group:))
            .map(groupedByIncoming(messages:))
            .map(convertedToSectionModels(messages:))
    }
}

func groupByTime(messages: inout ([MessageGroup], MessageGroup?), item: MessageItem) {
    if let group = messages.1 {
        let lastPart = group.parts.last!
        if lastPart.sentDate.timeIntervalSince(item.sentDate) > -60 && group.userId == item.userId {
            messages.1!.parts.append(MessageGroup.Part(item))
        }
        else {
            messages.0.append(group)
            messages.1 = MessageGroup(from: item)
        }
    }
    else {
        messages.1 = MessageGroup(from: item)
    }
}

func appendingLastGroup(messages: [MessageGroup], group: MessageGroup?) -> [MessageGroup] {
    guard let group = group else { return messages }
    return messages + [group]
}

func groupedByIncoming(messages: [MessageGroup]) -> [[MessageGroup]] {
    return messages.reduce([[MessageGroup]]()) { result, message in
        guard let last = result.last else {
            return [[message]]
        }
        if last.last!.isIncoming == message.isIncoming {
            return Array(result.dropLast()) + [last + [message]]
        }
        else {
            return result + [[message]]
        }
    }
}

func convertedToSectionModels(messages: [[MessageGroup]]) -> [MessageSectionModel] {
    messages.map { messages in
        if messages.first!.isIncoming {
            return .incomingSection(items: messages)
        }
        else {
            return .outcomingSection(items: messages)
        }
    }
}
...