gpt4 book ai didi

How to select only 1 item per section using diffable datasource(如何使用不同的数据源在每个部分中只选择1个项目)

转载 作者:bug小助手 更新时间:2023-10-25 12:25:36 24 4
gpt4 key购买 nike



I have the following code

我有以下代码


final class ListViewController: UIViewController {
let viewModel: ViewModel

init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: Data Source

private func makeDataSource() -> UICollectionViewDiffableDataSource<String, SettingItem> {
let cellRegistration = UICollectionView
.CellRegistration<UICollectionViewListCell, SettingItem> { [viewModel] cell, _, settingItem in
var configutation = UIListContentConfiguration.cell()
configutation.text = viewModel.cellTitle(for: settingItem)
cell.contentConfiguration = configutation

cell.accessories = [
.checkmark(displayed: .always, options: .init(isHidden: !settingItem.isSelected))
]
}
let headerRegistration = UICollectionView
.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView
.elementKindSectionHeader) { [viewModel] supplementaryView, _, indexPath in
var configutation = UIListContentConfiguration.groupedHeader()
configutation.text = viewModel.headerTitle(in: indexPath.section)
supplementaryView.contentConfiguration = configutation
}
let dataSource = UICollectionViewDiffableDataSource<String, SettingItem>(collectionView: collectionView,
cellProvider: { collectionView, indexPath, settingItem in
collectionView
.dequeueConfiguredReusableCell(
using: cellRegistration,
for: indexPath,
item: settingItem
)
})
dataSource.supplementaryViewProvider = { collectionView, _, indexPath in
collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
}
return dataSource
}

private lazy var dataSource = makeDataSource()

// MARK: Loading a View

private func makeCollectionView() -> UICollectionView {
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .supplementary
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.backgroundColor = .systemBackground
view.translatesAutoresizingMaskIntoConstraints = false
view.delegate = self
return view
}

private lazy var collectionView = makeCollectionView()

override func viewDidLoad() {
super.viewDidLoad()

title = .localized(.settings)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done,
target: self,
action: #selector(doneButtonTapped))
navigationItem.rightBarButtonItem = doneButton

view.addSubview(collectionView)
NSLayoutConstraint.activate(
collectionView.constraints(pinningTo: view, edges: [.all])
)
viewModel.reloadContent(in: dataSource)
}

@objc
private func doneButtonTapped() {
dismiss(animated: true)
}
}

extension ListViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else { return }
// update the item
var updatedSelectedItem = selectedItem
updatedSelectedItem.isSelected.toggle()
// update snapshot
var newSnapShot = dataSource.snapshot()
newSnapShot.insertItems([updatedSelectedItem], beforeItem: selectedItem)
newSnapShot.deleteItems([selectedItem])
dataSource.apply(newSnapShot)
}


}

extension ListViewController {
@MainActor final class ViewModel {
let sections: [SettingSection]

init(sections: [SettingSection]) {
self.sections = sections
}

func cellTitle(for settingItem: SettingItem) -> String {
settingItem.name
}

func headerTitle(in section: Int) -> String {
sections[section].name
}

func reloadContent(in dataSource: UICollectionViewDiffableDataSource<String, SettingItem>) {
var snapshot = NSDiffableDataSourceSnapshot<String, SettingItem>()
snapshot.appendSections(sections.map(\.name))
sections.forEach { section in
snapshot.appendItems(section.settingItems, toSection: section.name)
}
dataSource.apply(snapshot)
}
}
}

struct SettingSection: Hashable {
let name: String
let settingItems: [SettingItem]

static let language = SettingSection(name: "Section 1", settingItems: [
SettingItem(name: "value 1", isSelected: true),
SettingItem(name: "value 2", isSelected: false)
])

static let dateFormat = SettingSection(name: "Section 2", settingItems: [
SettingItem(name: "value 3", isSelected: false),
SettingItem(name: "value 4", isSelected: false)
])
}

struct SettingItem: Hashable {
let name: String
var isSelected: Bool
}

and I need to select only 1 item per section, I have been trying but right now you can select multiple items and I don't know which is the best way, since in the didSelect I'm inserting and deleting the item to update the dataSource and display the checkmark, and that is because if I try just doing

我每个部分只需要选择1个项目,我一直在尝试,但现在您可以选择多个项目,我不知道哪一个是最好的方法,因为在didSelect中,我正在插入和删除项目以更新数据源并显示复选标记,这是因为如果我尝试只做


    var snapshot = dataSource.snapshot()
snapshot.reconfigureItems([settingItem])
dataSource.apply(snapshot, animatingDifferences: true)

It crashes because it says I'm trying to update an element that does not exist, I guess it is something related with the hash but not sure

它崩溃是因为它说我正在尝试更新一个不存在的元素,我猜它是与散列相关的东西,但不确定


This is the image of the behavior that I need

这就是我需要的行为的形象


This is the image of the behavior that currently I have

这是我目前所拥有的行为的图像


更多回答

"In the didSelect I'm inserting and deleting the item to update the dataSource and display the checkmark" Well, don't do that. Update the model and reconfigure all affected items.

在didSelect中,我正在插入和删除该项以更新数据源并显示复选标记“嗯,不要这样做”。更新模型并重新配置所有受影响的项目。

优秀答案推荐

This is actually a great question and there are couple of different strategies to pursue. But I will focus on only one to keep this answer short. The main problem I see is that you are including the state of your cell as a part of your model (SettingItem). The state can be anything like selected, highlighted, disabled etc... When you are using DiffableDatasource it might be better to manage the state of your items in a separate array. So you configure the collectionview such that it manages the selected/highlighted states of each cell. This recommendation is also inline with various examples/tutorials that Apple provides as I personally have not seen where the state of the cell is also part of the model. If you decide to follow this advice, your data model simplifies to this:

这实际上是一个很好的问题,有几个不同的战略需要追求。但为了简短地回答这个问题,我将只关注一个问题。我看到的主要问题是,您将计算单元的状态作为模型(SettingItem)的一部分。状态可以是选中、突出显示、禁用等。使用DiffableDatasource时,最好在单独的数组中管理项的状态。因此,您可以配置集合视图,以便它管理每个单元的选中/突出显示状态。这一建议也与苹果提供的各种示例/教程一致,因为我个人没有看到细胞状态也是模型的一部分。如果您决定遵循这个建议,您的数据模型将简化为:


    struct SettingItem: Hashable {
let name: String
}

However, this brings another problem because the moment you reload collectionview all state information in the collectionview will also be erased. This is an oversight from Apple's SDK IMHO. There are situations where you want to reload the data but you want to preserve previous selections, for example. This requires you to create a separate array/collections where you personally keep track of the state of each cell after it changes. One way to achieve is this:

然而,这带来了另一个问题,因为当您重新加载集合视图时,集合视图中的所有状态信息也将被擦除。这是苹果SDK IMHO的疏忽。例如,在某些情况下,您想要重新加载数据,但想要保留以前的选择。这需要您创建一个单独的数组/集合,您可以在其中亲自跟踪每个单元格更改后的状态。实现这一目标的一种方法是:


    // selected item per section tracker
private var stateTracker = [Int: String]()

// indexpath of each selected item tracker
private var indexPathTracker = [String: IndexPath]()

So in your func collectionView(_:didSelectItemAt:) delegate method you update the trackers and then perform deselection if there is already another item selected in that section. Something like this:

因此,在您的函数集合View(_:didSelectItemAt:)委托方法中,您可以更新跟踪器,然后在该部分中已经选择了另一个项的情况下执行取消选择。大概是这样的:


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else { return }

//check whether there was another item already selected in this section
if let nameOfCurrentlySelectedItemInThisSection = stateTracker[indexPath.section] {
//we need to unselect this item, get its indexpath from the other tracker
if let indexPathToDeselect = indexPathTracker[nameOfCurrentlySelectedItemInThisSection] {
collectionView.deselectItem(at: indexPathToDeselect, animated: false)
}
}


// update the trackers
stateTracker[indexPath.section] = selectedItem.name
indexPathTracker[selectedItem.name] = indexPath
}

You also need to handle the deselection events for example something like this:

您还需要处理取消选择事件,例如如下所示:


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//you need to remove the selection status from your trackers when a deselection occurs
if let nameOfTheItemThatWasRemoved = stateTracker.removeValue(forKey: indexPath.section) {
indexPathTracker.removeValue(forKey: nameOfTheItemThatWasRemoved)
}
}

Notice that you no longer need to reload the data source after each selection. However, you might still find that in a different situation a reload is necessary, for example because the backing database has changed. Since we delegated the visual management of the state to the collectionview, the selected information will be lost after a reload. In order to counter that you can reapply your selections to the collection view since you were keeping track of them in a separate collection:

请注意,您不再需要在每次选择后重新加载数据源。但是,您可能仍然会发现,在不同的情况下,需要重新加载,例如,因为备份数据库已更改。由于我们将状态的可视管理委托给集合视图,因此在重新加载后,选定的信息将丢失。为了应对这种情况,您可以将您的选择重新应用于集合视图,因为您在单独的集合中跟踪它们:


    ...
...
dataSource.apply(snapshot, completion: {
//notice that after a reload, the indexpath or section references in your trackers might not be applicable anymore
//if this is the case, before you apply the snapshot recalculate your indexpath references with the new data
//and update your trackers accordingly
for (eachItemName, indexPath) in indexPathTracker {
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
}
})

更多回答

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com