限制 UITableView 中每个部分的单元格选择 swift

Restrict cell selection per section in UITableView swift

我想限制我的 tableview 的某些部分只允许选择 1 个单元格,现在我的所有单元格都可以选择,无论它在哪个部分

但是有一点不同:我的部分是一个[数组],并且会根据不同的变量动态变化。

我的部分本身就是一个变量,因此我可以像这样以编程方式精确定位它们:

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 的一些代码(为 Whosebug 读者简化):

// checkmarks when tapped

    func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
        if let cell = tableView.cellForRow(at: indexPath) {
            if self.selectedIndexPaths.contains(indexPath) {

                cell.accessoryType = .none
                cell.backgroundColor = UIColor.clear
                self.selectedIndexPaths.remove(indexPath)

                if CrewMembersNumber == "1" {
                    if((indexPath).section == 0) {
                        self.section1score -= section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section3score -= section3[(indexPath).row].object(forKey: "value") as! Int
                    }

                } else if CrewMembersNumber == "2" {
                    if((indexPath).section == 0) {
                        self.section1score -= section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section2score -= section2[(indexPath).row].object(forKey: "value") as! Int
                    }
                } else if CrewMembersNumber == "3" {
                    if((indexPath).section == 0) {
                        self.section1score -= section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section5score -= section5[(indexPath).row].object(forKey: "value") as! Int
                    }

                } else {
                    // if crewmemebernumber doest return 1-2 or 3
                    if((indexPath).section == 0) {
                        self.section1score -= section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section4score -= section4[(indexPath).row].object(forKey: "value") as! Int
                    }

                }

            } else {
                cell.accessoryType = .checkmark
                cell.backgroundColor = UIColor (red:236/255.0, green: 236/255, blue: 236/255, alpha: 1.0)
                self.selectedIndexPaths.add(indexPath)

                if CrewMembersNumber == "1" {
                    if((indexPath).section == 0) {
                        self.section1score += section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section3score += section3[(indexPath).row].object(forKey: "value") as! Int
                    }

                } else if CrewMembersNumber == "2" {
                    if((indexPath).section == 0) {
                        self.section1score += section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section2score += section2[(indexPath).row].object(forKey: "value") as! Int
                    }

                } else if CrewMembersNumber == "3" {
                    if((indexPath).section == 0) {
                        self.section1score += section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section5score += section5[(indexPath).row].object(forKey: "value") as! Int
                    }
                } else {
                    // if crewmemebernumber doest return 1-2 or 3
                    if((indexPath).section == 0) {
                        self.section1score += section1[(indexPath).row].object(forKey: "value") as! Int
                    } else if((indexPath).section == 1) {
                        self.section4score += section4[(indexPath).row].object(forKey: "value") as! Int
                    }
                }
            }
            self.updateToolbarAndLabel(self.totalScore)
            self.tableView.reloadData()
        }
    }


    func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        cell.textLabel!.text = self.textForIndexPath(indexPath);
        cell.textLabel!.font = UIFont(name:"Avenir", size:19)
        cell.textLabel!.numberOfLines = 0
        cell.selectionStyle = UITableViewCellSelectionStyle.none;

        if(self.selectedIndexPaths.contains(indexPath)) {

            cell.accessoryType = .checkmark;
            cell.backgroundColor = UIColor (red:236/255.0, green: 236/255, blue: 236/255, alpha: 1.0)
        } else {
            cell.accessoryType = .none;
            cell.backgroundColor = UIColor.clear
        }
        return cell
 }

如您所见,当我点击一个单元格时,它会添加一个值,并且该单元格会更改它的背景颜色并添加一个复选标记,

我需要做的是如果有一个单元格被选中,在某些部分只能选择 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({[=10=].section != indexPath.section})
    self.selectedIndexPaths.append(indexPath)

重新加载 tableView 时,由于 cellForRow 中的 if else 块,先前在同一部分中选择的单元格的外观将发生变化。

从根本上说,table视图的最佳解决方案(恕我直言)是为您的 table 创建一个视图模型,根据需要操作数据,然后在 table 中反映该数据.然后,您尽一切可能让 table 对数据更改做出反应,而不是尝试使用 table 视图本身来反映数据或状态。

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

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

视图数据包含在这里:

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

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

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

前三个字符串常量允许我们索引到数据中,并初始化:

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

数据以编程方式创建,请参阅附件项目或下面随附的完整代码。

你说的部分可能会改变,所以sections是一个变量,我们稍后会改变它。

selectedMembers 包含 typehash(即 Pilot、Crew 或 Passenger 及其姓名,因此它应该是唯一的。此数组将反映当前的 selections,作为数据而不是 indexPaths。

但是,我们需要 indexPaths 来反映 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)
    }
}

你需要两者,因为在某些情况下你只有一个索引路径,而不是单元格。

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

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 检测到 selection 时,您将首先查看现有的 selected 数据,然后首先从数据中删除该 selection , 然后更新任何被选中的单元格的 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
}

然后,处理selection方法:

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)
}

瞧 - 一切如你所愿。但是,更好的是,您可以按照您所说的那样重新安排这些部分,并正确地 select 编辑所有内容。示例代码在您 select 第一个 row/column

后 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)
        }
    }
}

reload() 擦除所有 selections,所以上面的代码使用已知的 selected 成员通知 tableView of list of selections ,即使每个单元格都不可见。

完整class

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 \([=18=])")}
        members[pilots] = array
        array.removeAll()

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

        (1..<250).forEach { array.append("Passenger \([=18=])")}
        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)
    }

}