Ios 在UITableView swift中限制每个节的单元格选择

Ios 在UITableView swift中限制每个节的单元格选择,ios,swift,uitableview,swift3,Ios,Swift,Uitableview,Swift3,我想限制我的tableview的某些部分只允许选择一个单元格,从现在起,我的所有单元格都可以被选择,而不管它位于哪个部分 然而,有一点扭曲:我的部分是一个[array],根据不同的变量动态变化 我的每个部分本身都是一个变量,因此我可以通过编程方式对它们进行精确定位,如下所示: var section1 = [NSDictionary(objects: [NSLocalizedString("Alcohol use less than 24 hours", comment:""), 2], ED

我想限制我的tableview的某些部分只允许选择一个单元格,从现在起,我的所有单元格都可以被选择,而不管它位于哪个部分

然而,有一点扭曲:我的部分是一个[array],根据不同的变量动态变化

我的每个部分本身都是一个变量,因此我可以通过编程方式对它们进行精确定位,如下所示:

var section1 = [NSDictionary(objects: [NSLocalizedString("Alcohol use less than 24 hours", comment:""), 2],
EDIT2:有人指出我可以创建一个包含限制的var

var restrictedSections: [[NSDictionary]: Bool] {return  [section1: true,section2: true,section3: true, section4: true, section4COPI: true, section5: true, section5COPI: true, section6: false, section7: false, section8: false] }
这不能作为IndexPath引用,因此这里没有运气。。。但也许是在正确的道路上

tableView中的一些代码(为StackOverflow阅读器简化):

正如你所看到的,当我点击一个单元格时,它会添加一个值,单元格会改变背景颜色并添加一个复选标记

我需要做的是,如果有一个单元格被选中,在某些部分中,只有1个单元格可以被选中,它需要检查该部分中是否有任何单元格被选中,并取消选中,以支持用户点击的新单元格。现在我根本不知道该怎么做


感谢您对第2项的帮助,您可以这样做。取消选择后,您将更改该单元格的
accessoryType

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
  let cell = tableView.cellForRow(at: indexPath)

  for selectedIndexPath in tableview.indexPathForSelectedRow {
    if selectedIndexPath.section == indexPath.section {
      tableview.deselectRow(at: indexPath, animated: true)
      cell?.accessoryType = .none
    }
  }

  return indexPath
}

在selectedIndexPaths数组中添加新选定行的索引路径之前,可以添加一行

    self.selectedIndexPaths = self.selectedIndexPaths.filter({$0.section != indexPath.section})
    self.selectedIndexPaths.append(indexPath)

重新加载tableView时,由于cellForRow中的if-else块,同一节中先前选定的单元格的外观将发生更改。

基本上,tableView的最佳解决方案(IMHO)是为表创建视图模型,根据需要操作数据,然后在表中反映该数据。然后,尽一切可能让表对数据更改做出反应,而不是试图使用表视图本身来反映数据或状态

编辑:代码不再使用
reloadData
,而是使用
performBatchUpdates
进行更优雅的演示

我创建了一个项目,你可以找到它

视图数据包含在此处:

let pilots = "Pilots"
let crew = "Crew"
let passengers = "Passengers"

var sections: [String] = []
var multipleSelectionsAllowed: Set<String> = []

var members: [String: [String]] = [:]
var selectedMembers: Set<String> = []
数据是以编程方式创建的,请参阅下面附加的项目或完整代码

您说过部分可能会更改,因此
部分
是一个变量,我们稍后将对其进行更改

selectedMembers
包含
类型的
散列(即飞行员、机组人员或乘客及其姓名),因此它应该是唯一的。此数组将当前选择作为数据而不是索引来反映

但是,我们需要IndExpath来反映
isSelected
UI更改:好的,我们将为此使用两个函数:

typealias KeyToValues = (section: String, name: String)

func sectionNameToHash(section: String, name: String) -> String {
    let hash = section + "|" + name
    return hash
}

func hashToSectionName(hash: String) -> KeyToValues {
    let array = hash.components(separatedBy: "|")
    assert(array.count == 2)
    return (array[0], array[1])
}
另外,我发现在过去非常有用的一点是,将更改单元格外观的代码放在一个地方,并在创建或更改单元格时调用它。随着UI的更改,您不会失去同步

func updateCell(atIndexPath indexPath: IndexPath) {
    let cells = tableView.visibleCells
    for cell in cells {
        guard let path = tableView.indexPath(for: cell) else { continue }
        if path == indexPath {
            updateCell(cell, atIndexPath: indexPath)
        }
    }
}

func updateCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
    let section = sections[indexPath.section]
    guard let names = members[section] else { fatalError() }
    let name = names[indexPath.row]

    let hash = sectionNameToHash(section: section, name: name)
    let shouldBeSelected = selectedMembers.contains(hash)

    if shouldBeSelected {
        cell.accessoryType = .checkmark
        print("SELECTED", hash)
    } else {
        cell.accessoryType = .none
        print("DESELECTED", hash)
    }
}
两者都需要,因为在某些情况下,您只有一个indexPath,而没有单元格

请注意,在创建单元时使用上述方法:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

    let section = sections[indexPath.section]
    guard let names = members[section] else { fatalError() }
    let name = names[indexPath.row]

    cell.textLabel?.text = name

    updateCell(cell, atIndexPath: indexPath)
    return cell
}
当tableView检测到选择时,您将首先查看现有的选定数据,然后从数据中删除该选择,然后更新任何删除的单元格的UI:

 override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    let section = sections[indexPath.section]
    guard let names = members[section] else { fatalError() }

    let canMultipleSelect = multipleSelectionsAllowed.contains(section)

    if !canMultipleSelect, let paths = tableView.indexPathsForSelectedRows {
        for path in paths {
            if path.section == indexPath.section {
                let name = names[path.row]
                let hash = sectionNameToHash(section: section, name: name)
                selectedMembers.remove(hash)
                updateCell(atIndexPath: path)
                tableView.deselectRow(at: path, animated: true)
            }
        }
    }
    return indexPath
}
然后,处理选择方法:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let section = sections[indexPath.section]
    guard let names = members[section] else { fatalError() }
    let name = names[indexPath.row]
    let hash = sectionNameToHash(section: section, name: name)

    selectedMembers.insert(hash)
    print("SELECTED THE CELL AT", hash)
    updateCell(atIndexPath: indexPath)
}
瞧,一切都按照你的意愿进行。但是,更好的是,你可以按照你说的那样重新安排部分,并让所有内容都被正确地选择。示例代码在你选择第一行/列5秒钟后重新安排部分

if indexPath.section == 0 && indexPath.row == 0 {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
        self.sections = [self.crew, self.pilots, self.passengers] // changed!
        tableView.reloadData()
        // all selections from the tableView are now gone
        // NOTE: none of the other data changes!
        for hash in self.selectedMembers {
            let value = self.hashToSectionName(hash: hash)
            guard
                let sectionNumber = self.sections.firstIndex(of: value.section),
                let names = self.members[value.section],
                let row = names.firstIndex(of: value.name)
            else { fatalError() }

            let indexPath = IndexPath(row: row, section: sectionNumber)
            self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
        }
    }
}
重载()删除所有选择,因此上述代码使用已知的选定成员通知tableView选择列表,即使每个选择的单元格不可见

全班

import UIKit

private final class MyCell: UITableViewCell {
    override var reuseIdentifier: String? { "cell" }
}

final class ViewController: UITableViewController {

    let pilots = "Pilots"
    let crew = "Crew"
    let passengers = "Passengers"

    var sections: [String] = []
    var multipleSelectionsAllowed: Set<String> = []

    var members: [String: [String]] = [:]
    var selectedMembers: Set<String> = []

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(MyCell.self, forCellReuseIdentifier: "cell")
        tableView.allowsMultipleSelection = true

        sections = [pilots, crew, passengers] // initial ordering of sections
        multipleSelectionsAllowed = [passengers]

        constructData()
    }

    private func constructData() {
        var array: [String] = []
        (1..<6).forEach { array.append("Pilot \($0)")}
        members[pilots] = array
        array.removeAll()

        (1..<20).forEach { array.append("Crew \($0)")}
        members[crew] = array
        array.removeAll()

        (1..<250).forEach { array.append("Passenger \($0)")}
        members[passengers] = array
    }

    // MARK: - Helpers -

    typealias KeyToValues = (section: String, name: String)

    func sectionNameToHash(section: String, name: String) -> String {
        let hash = section + "|" + name
        return hash
    }


    func hashToSectionName(hash: String) -> KeyToValues {
        let array = hash.components(separatedBy: "|")
        assert(array.count == 2)
        return (array[0], array[1])
    }

}

extension ViewController /*: UITableViewDataSource */ {

    override func numberOfSections(in: UITableView) -> Int {
        return sections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let type = sections[section]
        let count = members[type]?.count ?? 0 // could use guard here too and crash if nil
        return count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]

        cell.textLabel?.text = name

        updateCell(cell, atIndexPath: indexPath)
        return cell
    }

    func updateCell(atIndexPath indexPath: IndexPath) {
        let cells = tableView.visibleCells
        for cell in cells {
            guard let path = tableView.indexPath(for: cell) else { continue }
            if path == indexPath {
                updateCell(cell, atIndexPath: indexPath)
            }
        }
    }

    func updateCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]

        let hash = sectionNameToHash(section: section, name: name)
        let shouldBeSelected = selectedMembers.contains(hash)

        if shouldBeSelected {
            cell.accessoryType = .checkmark
            print("SELECTED", hash)
        } else {
            cell.accessoryType = .none
            print("DESELECTED", hash)
        }
    }

}

extension ViewController /* : UITableViewDelegate */ {

    override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }

        let canMultipleSelect = multipleSelectionsAllowed.contains(section)

        if !canMultipleSelect, let paths = tableView.indexPathsForSelectedRows {
            for path in paths {
                if path.section == indexPath.section {
                    let name = names[path.row]
                    let hash = sectionNameToHash(section: section, name: name)
                    selectedMembers.remove(hash)
                    updateCell(atIndexPath: path)
                    tableView.deselectRow(at: path, animated: true)
                }
            }
        }
        return indexPath
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]
        let hash = sectionNameToHash(section: section, name: name)

        selectedMembers.insert(hash)
        print("SELECTED THE CELL AT", hash)
        updateCell(atIndexPath: indexPath)

    if indexPath.section == 0 && indexPath.row == 0 {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            self.sections = [self.crew, self.pilots, self.passengers]
            tableView.reloadData()
            // all selections from the tableView are gone

            for hash in self.selectedMembers {
                let value = self.hashToSectionName(hash: hash)
                guard
                    let sectionNumber = self.sections.firstIndex(of: value.section),
                    let names = self.members[value.section],
                    let row = names.firstIndex(of: value.name)
                else { fatalError() }

                let indexPath = IndexPath(row: row, section: sectionNumber)
                self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
            }
        }
    }
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        print("DESELECTED THE CELL AT", hash)

        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]
        let hash = sectionNameToHash(section: section, name: name)
        selectedMembers.remove(hash)

        updateCell(atIndexPath: indexPath)
    }

}
导入UIKit
私有最终类MyCell:UITableViewCell{
重写变量reuseIdentifier:字符串?{“单元格”}
}
最终类ViewController:UITableViewController{
让飞行员=“飞行员”
let crew=“crew”
让乘客=“乘客”
变量节:[字符串]=[]
var multipleSelectionsAllowed:Set=[]
变量成员:[String:[String]]=[:]
var selectedMembers:Set=[]
重写func viewDidLoad(){
super.viewDidLoad()
tableView.register(MyCell.self,forCellReuseIdentifier:“cell”)
tableView.allowsMultipleSelection=true
区段=[飞行员、机组人员、乘客]//区段的初始顺序
multipleSelectionsAllowed=[乘客]
constructData()
}
私有函数构造函数数据(){
变量数组:[String]=[]
(1..Int{
返回节数
}
重写func tableView(tableView:UITableView,numberofrowsinssection:Int)->Int{
let type=截面[截面]
let count=members[type]?.count±0//也可以在此处使用保护,如果为nil,则崩溃
返回计数
}
重写func tableView(tableView:UITableView,cellForRowAt indexath:indexPath)->UITableViewCell{
let cell=tableView.dequeueReusableCell(带有标识符:“cell”,用于:indexath)
let section=sections[indexPath.section]
guard let name=members[section]else{fatalError()}
让name=names[indexPath.row]
cell.textlab?.text=名称
updateCell(单元格,atIndexPath:indexPath)
返回单元
}
func updateCell(atIndexPath indexath:indexath){
let cells=tableView.visibleCells
细胞中的细胞{
guard let path=tableView.indexPath(for:cell)else{continue}
如果路径==indexPath{
updateCell(单元格,atIndexPath:indexPath)
}
}
}
func updateCell(cell:UITableViewCell,atIndexPath indexPath:indexPath){
let section=sections[indexPath.section]
guard let name=members[section]else{fatalError()}
让name=names[indexPath.row]
让hash=sectionnametohsh(section:section,name:name)
让我们选吧
if indexPath.section == 0 && indexPath.row == 0 {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
        self.sections = [self.crew, self.pilots, self.passengers] // changed!
        tableView.reloadData()
        // all selections from the tableView are now gone
        // NOTE: none of the other data changes!
        for hash in self.selectedMembers {
            let value = self.hashToSectionName(hash: hash)
            guard
                let sectionNumber = self.sections.firstIndex(of: value.section),
                let names = self.members[value.section],
                let row = names.firstIndex(of: value.name)
            else { fatalError() }

            let indexPath = IndexPath(row: row, section: sectionNumber)
            self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
        }
    }
}
import UIKit

private final class MyCell: UITableViewCell {
    override var reuseIdentifier: String? { "cell" }
}

final class ViewController: UITableViewController {

    let pilots = "Pilots"
    let crew = "Crew"
    let passengers = "Passengers"

    var sections: [String] = []
    var multipleSelectionsAllowed: Set<String> = []

    var members: [String: [String]] = [:]
    var selectedMembers: Set<String> = []

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(MyCell.self, forCellReuseIdentifier: "cell")
        tableView.allowsMultipleSelection = true

        sections = [pilots, crew, passengers] // initial ordering of sections
        multipleSelectionsAllowed = [passengers]

        constructData()
    }

    private func constructData() {
        var array: [String] = []
        (1..<6).forEach { array.append("Pilot \($0)")}
        members[pilots] = array
        array.removeAll()

        (1..<20).forEach { array.append("Crew \($0)")}
        members[crew] = array
        array.removeAll()

        (1..<250).forEach { array.append("Passenger \($0)")}
        members[passengers] = array
    }

    // MARK: - Helpers -

    typealias KeyToValues = (section: String, name: String)

    func sectionNameToHash(section: String, name: String) -> String {
        let hash = section + "|" + name
        return hash
    }


    func hashToSectionName(hash: String) -> KeyToValues {
        let array = hash.components(separatedBy: "|")
        assert(array.count == 2)
        return (array[0], array[1])
    }

}

extension ViewController /*: UITableViewDataSource */ {

    override func numberOfSections(in: UITableView) -> Int {
        return sections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let type = sections[section]
        let count = members[type]?.count ?? 0 // could use guard here too and crash if nil
        return count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]

        cell.textLabel?.text = name

        updateCell(cell, atIndexPath: indexPath)
        return cell
    }

    func updateCell(atIndexPath indexPath: IndexPath) {
        let cells = tableView.visibleCells
        for cell in cells {
            guard let path = tableView.indexPath(for: cell) else { continue }
            if path == indexPath {
                updateCell(cell, atIndexPath: indexPath)
            }
        }
    }

    func updateCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]

        let hash = sectionNameToHash(section: section, name: name)
        let shouldBeSelected = selectedMembers.contains(hash)

        if shouldBeSelected {
            cell.accessoryType = .checkmark
            print("SELECTED", hash)
        } else {
            cell.accessoryType = .none
            print("DESELECTED", hash)
        }
    }

}

extension ViewController /* : UITableViewDelegate */ {

    override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }

        let canMultipleSelect = multipleSelectionsAllowed.contains(section)

        if !canMultipleSelect, let paths = tableView.indexPathsForSelectedRows {
            for path in paths {
                if path.section == indexPath.section {
                    let name = names[path.row]
                    let hash = sectionNameToHash(section: section, name: name)
                    selectedMembers.remove(hash)
                    updateCell(atIndexPath: path)
                    tableView.deselectRow(at: path, animated: true)
                }
            }
        }
        return indexPath
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]
        let hash = sectionNameToHash(section: section, name: name)

        selectedMembers.insert(hash)
        print("SELECTED THE CELL AT", hash)
        updateCell(atIndexPath: indexPath)

    if indexPath.section == 0 && indexPath.row == 0 {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            self.sections = [self.crew, self.pilots, self.passengers]
            tableView.reloadData()
            // all selections from the tableView are gone

            for hash in self.selectedMembers {
                let value = self.hashToSectionName(hash: hash)
                guard
                    let sectionNumber = self.sections.firstIndex(of: value.section),
                    let names = self.members[value.section],
                    let row = names.firstIndex(of: value.name)
                else { fatalError() }

                let indexPath = IndexPath(row: row, section: sectionNumber)
                self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
            }
        }
    }
    }

    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        print("DESELECTED THE CELL AT", hash)

        let section = sections[indexPath.section]
        guard let names = members[section] else { fatalError() }
        let name = names[indexPath.row]
        let hash = sectionNameToHash(section: section, name: name)
        selectedMembers.remove(hash)

        updateCell(atIndexPath: indexPath)
    }

}