TableView SearchBar 不起作用:索引超出范围

TableView SearchBar doesn't work: Index out of range

我知道有一些类似的问题,但它对我不起作用。我是新手,所以我按照一些教程尝试在 table 视图屏幕中创建搜索栏。 我遇到了一个问题:索引超出范围,我不明白为什么。 这是我的代码:

import UIKit

final class AllGroupsViewController: UITableViewController {

    var groups = [
        "cats",
        "birds",
        "dogs",
        "books",
        "music",
        "movies",
        "art",
        "science",
        "tech",
        "beauty",
    ]
    
    @IBOutlet var searchBar: UISearchBar!
    
    var isSearching = false
    var filteredData = [String]()
    
    var userGroups: [String] = []
    var groupSectionTitles = [String]()
    var groupsDictionary = [String: [String]]()

    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(
            nibName: "GroupCell",
            bundle: nil),
                           forCellReuseIdentifier: "groupCell")
        
        for group in groups {
            let groupKey = String(group.prefix(1))
            if var groupValues = groupsDictionary[groupKey] {
                groupValues.append(group)
                groupsDictionary[groupKey] = groupValues
            } else {
                groupsDictionary[groupKey] = [group]
            }
        }
        
        
        groupSectionTitles = [String](groupsDictionary.keys)
        groupSectionTitles = groupSectionTitles.sorted(by: { [=10=] <  })
        
    }
    
    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        return groupSectionTitles.count
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        if isSearching {
            return filteredData.count
        } else {
            return groups.count
        }
        
        let groupKey = groupSectionTitles[section]
        if let groupValues = groupsDictionary[groupKey] {
            return groupValues.count
        }
        
        return 0
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return groupSectionTitles[section]
    }
    
    override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        return groupSectionTitles
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as? GroupCell
        else { return UITableViewCell() }
        
        var currentGroup = groups[indexPath.row]
        
        let groupKey = groupSectionTitles[indexPath.section]
        if let groupValues = groupsDictionary[groupKey] {
            currentGroup = groupValues[indexPath.row]
        }
        
        if isSearching {
            currentGroup = filteredData[indexPath.row]
        } else {
            currentGroup = groups[indexPath.row]
        }
        return cell
        
        cell.configure(
            photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
            name: currentGroup)

        return cell
    }

    override func tableView(_ tableView: UITableView,
                            didSelectRowAt indexPath: IndexPath) {
        defer {
            tableView.deselectRow(at: indexPath, animated: true)
        }
        
        let groupKey = groupSectionTitles[indexPath.section]
        var currentGroup = ""
        if let groupValues = groupsDictionary[groupKey] {
            currentGroup = groupValues[indexPath.row] // here is index out of range
        }
        
        if userGroups.firstIndex(of: currentGroup) == nil {
            userGroups.append(currentGroup)
        }
        
        self.performSegue(withIdentifier: "addGroup", sender: nil)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "addGroup",
               let myGroupsViewController = segue.destination as? MyGroupsViewController {
                myGroupsViewController.groups = userGroups
        }
    }
}

extension AllGroupsViewController {
     
     func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
         filteredData = groups.filter({[=10=].lowercased().prefix(searchText.count) == searchText.lowercased()})
         isSearching = true
         tableView.reloadData()
     }
     
     func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
         isSearching = false
         searchBar.text = ""
         tableView.reloadData()
     }
 }

如果有人能帮助我,我会很高兴。还有,你能推荐一些好的教程来实现我的目标吗?

实际上问题更多的是访问组的逻辑,而不是因为添加搜索栏而导致崩溃。

例如:

override func tableView(_ tableView: UITableView,
                        numberOfRowsInSection section: Int) -> Int {
    
    if isSearching {
        return filteredData.count
    } else {
        return groups.count
    }
    
    let groupKey = groupSectionTitles[section]
    if let groupValues = groupsDictionary[groupKey] {
        return groupValues.count
    }
    
    return 0
}

在这里因为你使用 if-else 你将在搜索时 return filteredData.countgroups.count - 你不会超出此代码

所以当你不搜索时,你会 return groups.count 这是 10 这是错误的,因为你想 return 我们对哪个部分的计数都在,例如 a 应该 return 1, b 应该 return 3.

逻辑 after if-else 块应该替换 else 部分中的逻辑

现在看下两个函数:

override func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    guard let cell
            = tableView.dequeueReusableCell(withIdentifier: "groupCell",
                                            for: indexPath) as? GroupCell
    else { return UITableViewCell() }
    
    var currentGroup = groups[indexPath.row]
    
    let groupKey = groupSectionTitles[indexPath.section]
    if let groupValues = groupsDictionary[groupKey] {
        currentGroup = groupValues[indexPath.row]
    }
    
    if isSearching {
        currentGroup = filteredData[indexPath.row]
    } else {
        currentGroup = groups[indexPath.row]
    }
    return cell
    
    cell.configure(
        photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
        name: currentGroup)
    
    return cell
}

override func tableView(_ tableView: UITableView,
                        didSelectRowAt indexPath: IndexPath) {
    defer {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    
    let groupKey = groupSectionTitles[indexPath.section]
    var currentGroup = ""
    if let groupValues = groupsDictionary[groupKey] {
        currentGroup = groupValues[indexPath.row] // here is index out of range
    }
    
    if userGroups.firstIndex(of: currentGroup) == nil {
        userGroups.append(currentGroup)
    }
    
    self.performSegue(withIdentifier: "addGroup", sender: nil)
}

首先因为 numberOfRowsInSection return 的值错误,我们将在这些函数中出现问题。

然后我认为访问组、组部分的正确数据源的逻辑不正确。

例如:cellForRowAt indexPath 中的 currentGroup = groups[indexPath.row] 是不正确的,因为当我们只想对特定部分进行分组时,这是从 10 的组数组中获取组。

而且我看到 return cell 两次,所以第一次之后的代码不会是 运行。

所以我所做的只是重构这些功能,使其更加清晰,并添加了一些注释。

首先,我们需要记住不同的数据源:

// All the groups
var groups = [
    "cats",
    "birds",
    "dogs",
    "books",
    "music",
    "movies",
    "art",
    "science",
    "tech",
    "beauty",
]

// Checks if search is active or not
var isSearching = false

// This will hold the filtered array when searching
var filteredData = [String]()

// This will hold groups of the user
var userGroups: [String] = []

// This will hold section prefixes [a, b, c, etc]
var groupSectionTitles = [String]()

// This will hold mapping of prefixes to groups
// [a: [art], b: [beauty, books], etc]
var groupsDictionary = [String: [String]]()

上面的代码与您的代码没有什么不同,只有注释,但是我们必须保留它的可视化图像,因为这对于我们访问数据的方式很重要

接下来,我创建了这个函数来获取部分中的正确组,因为我们需要多次执行此操作

private func getGroups(in section: Int) -> [String]
{
    // The current section should be got from groupSectionTitles
    let groupKey = groupSectionTitles[section]
    
    var groupsInSection: [String] = []
    
    // Get groups for current section
    if let groupValues = groupsDictionary[groupKey] {
        groupsInSection = groupValues
    }
    
    // Change groups in section if searching
    if isSearching {
        groupsInSection = filteredData
    }
    
    return groupsInSection
}

然后我稍微重构了这些函数:

override func tableView(_ tableView: UITableView,
                        numberOfRowsInSection section: Int) -> Int
{
    if isSearching {
        return filteredData.count
    } else {
        let groupsInSection = getGroups(in: section)
        return groupsInSection.count
    }
}

override func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    guard let cell
            = tableView.dequeueReusableCell(withIdentifier: "groupCell",
                                            for: indexPath) as? GroupCell
    else { return UITableViewCell() }
    
    let groupsInSection = getGroups(in: indexPath.section)
    
    cell.configure(
        photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
        name: groupsInSection[indexPath.row])
    
    return cell
}

override func tableView(_ tableView: UITableView,
                        didSelectRowAt indexPath: IndexPath)
{
    let groupsInSection = getGroups(in: indexPath.section)
    let currentGroup = groupsInSection[indexPath.row]
    
    if userGroups.firstIndex(of: currentGroup) == nil {
        userGroups.append(currentGroup)
    }
    
    defer {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    
    self.performSegue(withIdentifier: "addGroup", sender: nil)
}

我认为现在您的崩溃问题将得到解决并且一切正常。

但是,由于您尚未连接和实施搜索委托,当 isSearching 变为 true 时可能会出现一些问题,但我认为这可能是关于使用搜索委托进行过滤的另一个问题。

教程可以看一下:

UISearchResultsController tutorial - 从第 8 分钟开始观看

UISearchBar tutorial - 从第 10 分钟开始观看

Whosebug discussion