diff --git a/Examples/Example-iOS/MountainsViewController.swift b/Examples/Example-iOS/MountainsViewController.swift index 5806939..7e3b5f9 100644 --- a/Examples/Example-iOS/MountainsViewController.swift +++ b/Examples/Example-iOS/MountainsViewController.swift @@ -8,6 +8,11 @@ final class MountainsViewController: UIViewController { struct Mountain: Hashable { var name: String + var highlightedName: NSAttributedString + + func hash(into hasher: inout Hasher) { + hasher.combine(name) + } func contains(_ filter: String) -> Bool { guard !filter.isEmpty else { @@ -24,13 +29,13 @@ final class MountainsViewController: UIViewController { private lazy var dataSource = CollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, mountain in let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LabelCell.name, for: indexPath) as! LabelCell - cell.label.text = mountain.name + cell.label.attributedText = mountain.highlightedName return cell } private let allMountains: [Mountain] = mountainsRawData.components(separatedBy: .newlines).map { line in let name = line.components(separatedBy: ",")[0] - return Mountain(name: name) + return Mountain(name: name, highlightedName: NSAttributedString(string: name)) } override func viewDidLoad() { @@ -49,6 +54,10 @@ final class MountainsViewController: UIViewController { let mountains = allMountains.lazy .filter { $0.contains(filter) } .sorted { $0.name < $1.name } + .map { mountain -> Mountain in + let attrName = underlineOccurences(of: filter.lowercased(), in: NSMutableAttributedString(string: mountain.name)) + return Mountain(name: mountain.name, highlightedName: attrName) + } var snapshot = DiffableDataSourceSnapshot() snapshot.appendSections([.main]) @@ -57,6 +66,21 @@ final class MountainsViewController: UIViewController { } } +private func underlineOccurences(of searchString: String, in text: NSMutableAttributedString) -> NSMutableAttributedString { + let inputLength = text.string.count + let searchLength = searchString.count + var range = NSRange(location: 0, length: text.length) + + while range.location != NSNotFound { + range = (text.string.lowercased() as NSString).range(of: searchString, options: [], range: range) + if range.location != NSNotFound { + text.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: range.location, length: searchLength)) + range = NSRange(location: range.location + range.length, length: inputLength - (range.location + range.length)) + } + } + return text +} + extension MountainsViewController: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { search(filter: searchText) diff --git a/Sources/Internal/DiffableDataSourceCore.swift b/Sources/Internal/DiffableDataSourceCore.swift index b45b757..a95e877 100644 --- a/Sources/Internal/DiffableDataSourceCore.swift +++ b/Sources/Internal/DiffableDataSourceCore.swift @@ -68,7 +68,7 @@ final class DiffableDataSourceCore ItemIdentifierType { @@ -83,7 +83,7 @@ final class DiffableDataSourceCore { struct Item: Differentiable, Equatable { - var differenceIdentifier: ItemID + var differenceIdentifier: Int + var contentIdentifier: ItemID var isReloaded: Bool init(id: ItemID, isReloaded: Bool) { - self.differenceIdentifier = id + self.contentIdentifier = id + self.differenceIdentifier = id.hashValue self.isReloaded = isReloaded } @@ -16,7 +18,7 @@ struct SnapshotStructure { } func isContentEqual(to source: Item) -> Bool { - return !isReloaded && differenceIdentifier == source.differenceIdentifier + return !isReloaded && contentIdentifier == source.contentIdentifier } } @@ -53,7 +55,7 @@ struct SnapshotStructure { var allItemIDs: [ItemID] { return sections.lazy .flatMap { $0.elements } - .map { $0.differenceIdentifier } + .map { $0.contentIdentifier } } func items(in sectionID: SectionID, file: StaticString = #file, line: UInt = #line) -> [ItemID] { @@ -61,7 +63,7 @@ struct SnapshotStructure { specifiedSectionIsNotFound(sectionID, file: file, line: line) } - return sections[sectionIndex].elements.map { $0.differenceIdentifier } + return sections[sectionIndex].elements.map { $0.contentIdentifier } } func section(containing itemID: ItemID) -> SectionID? { @@ -270,7 +272,7 @@ private extension SnapshotStructure { func itemPositionMap() -> [ItemID: ItemPosition] { return sections.enumerated().reduce(into: [:]) { result, section in for (itemRelativeIndex, item) in section.element.elements.enumerated() { - result[item.differenceIdentifier] = ItemPosition( + result[item.contentIdentifier] = ItemPosition( item: item, itemRelativeIndex: itemRelativeIndex, section: section.element,