尝试过滤字典时出现问题 - 索引超出范围错误

时间:2017-08-09 20:33:56

标签: ios swift uitableview dictionary

我有一个日记应用程序,它有一个名为Entry的对象。它有自己的Swift文件Entry.swift,这些日记条目使用字典数组保存。

我向UITableViewController添加了一个搜索栏,每当我输入一封信时,应用程序在调用tableView.reloadData()后崩溃。我认为这与过滤器错误地返回名为entries的词典数组有关,当调用tableView.reloadData()时,dequeueReusableCell上的两个标签都无法填充因为字典数组中的信息格式错误。

Entry.swift

//
//  Entry.swift
//  Journal
//
//  Created by handje on 6/17/17.
//  Copyright © 2017 Rob Hand. All rights reserved.
//

import Foundation

class Entry {
    static fileprivate let titleKey = "title"
    static fileprivate let bodyTextKey = "bodyText"
    static fileprivate let dateKey = "date"

    var title: String
    var bodyText: String
    var date: String

    init(title: String, bodyText: String, date: String ) {
        self.title = title
        self.bodyText = bodyText
        self.date = date
    }

    func dictionaryRepresentation() -> [String: Any] {
        return [Entry.titleKey: title, Entry.bodyTextKey: bodyText, Entry.dateKey: date]
    }

    convenience init?(dictionary: [String: Any]) {
        guard let title = dictionary[Entry.titleKey] as? String,
            let bodyText = dictionary[Entry.bodyTextKey] as? String, let date = dictionary[Entry.dateKey] as? String else { return nil
        }
        self.init(title: title, bodyText: bodyText, date: date)
    }
}

extension Entry: Equatable {
    static func == (lhs:Entry, rhs:Entry) -> Bool {
    return
        lhs.title == rhs.title && 
        lhs.bodyText == rhs.bodyText
    }
}

EntryListTableViewController.swift

//
//  EntryListTableViewController.swift
//  Journal
//
//  Created by handje on 6/17/17.
//  Copyright © 2017 Rob Hand. All rights reserved.
//

import UIKit

class EntryListTableViewCell: UITableViewCell {
    @IBOutlet weak var dreamTitle: UILabel!
    @IBOutlet weak var dreamDate: UILabel!
}

extension EntryListTableViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        filterContentForSearchText(searchText: searchController.searchBar.text!)
    }
}

class EntryListTableViewController: UITableViewController {
    @IBOutlet weak var searchBar: UISearchBar!

    var dreamTitle: UILabel!
    let searchController = UISearchController(searchResultsController: nil)
    let dreams = EntryController.shared.entries
    var filteredDreams = [Entry]()

    func filterContentForSearchText(searchText: String, scope: String = "All") {
        let filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
        tableView.reloadData()

        print(filteredDreams)
    }

    override func viewDidLoad() {
        //cell setup
        super.viewDidLoad()
        let backgroundImage = UIImage(named: "DreamPageLucidity.jpg")
        let imageView = UIImageView(image: backgroundImage)
        imageView.contentMode = .scaleAspectFill
        self.tableView.backgroundView = imageView
        tableView.separatorInset = .zero
        tableView.separatorColor = UIColor.lightGray

        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        definesPresentationContext = true
        tableView.tableHeaderView = searchController.searchBar
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.tableView.reloadData()
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return EntryController.shared.entries.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "entryCell", for: indexPath) as! EntryListTableViewCell
        let entry: Entry
        if searchController.isActive && searchController.searchBar.text != "" {
            entry = filteredDreams[indexPath.row] /////////ERROR HERE///////

        } else {
            entry = EntryController.shared.entries[indexPath.row]
        }

        cell.dreamTitle.text = entry.title
        cell.dreamDate.text = entry.date
        if cell.dreamTitle.text == "" {
            cell.dreamTitle.text = "Untitled Dream"
        }
        return cell
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            let entry = EntryController.shared.entries[indexPath.row]
            EntryController.shared.deleteEntry(entry: entry)
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let detailVC = segue.destination as? EntryDetailViewController

        guard let indexPath = tableView.indexPathForSelectedRow else { return }
        let entry = EntryController.shared.entries[indexPath.row]

        detailVC?.entry = entry 
    }
}

EntryContoller.swift

//
//  EntryController.swift
//  Journal
//
//  Created by handje on 6/17/17.
//  Copyright © 2017 Rob Hand. All rights reserved.
//

import Foundation

class EntryController {
    var entries = [Entry]()
    static fileprivate let entriesKey = "entriesKey"
    static let shared = EntryController()

    init() {
        load()
    }

    // MARK: - CRUD 

    func addNewEntryWith(title: String, bodyText: String, date: String) {
        let entry = Entry(title: title, bodyText: bodyText, date: date)
        entries.append(entry)
        save()
    }

    func updateEntry(entry: Entry, title: String, bodyText: String, date: String) {
        entry.title = title
        entry.bodyText = bodyText
        save()
    }

    // Set up search bar  
    func deleteEntry(entry: Entry) {
        guard let index = entries.index(of: entry) else { return }
        entries.remove(at: index)
        save()
    }

    // MARK: - save/load UserDefaults

    private func save() {
        let entryDictionaries = entries.map {$0.dictionaryRepresentation()}
        UserDefaults.standard.set(entryDictionaries, forKey: EntryController.entriesKey)
    }

    private func load() {
        guard let entryDictionaries = UserDefaults.standard.object(forKey: EntryController.entriesKey) as? [[String: Any]] else { return }
        entries = entryDictionaries.flatMap ({ Entry(dictionary: $0) })
    }
}

2 个答案:

答案 0 :(得分:2)

我认为

存在问题
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return EntryController.shared.entries.count
    }

你应该在这里检查一下,如果搜索控制器处于活动状态,那么从filteredDreams返回计数,或者从EntryController.shared.entries.count返回计数,(根据您的确切实施更改代码)类似的东西:

if searchController.isActive && searchController.searchBar.text != "" {
            return filterDreams.count
   } else {
            return EntryController.shared.entries.count
 }

答案 1 :(得分:0)

您返回的列表大于委托函数numberOfRowsInSection中的已过滤列表

在搜索时试试这个:

    func filterContentForSearchText(searchText: String, scope: String = "All") {
            // update the list that is a class property, you were creating a new one

            if searchText.isEmpty {
                filteredDreams = EntryController.shared.entries
            } else {
                filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
            }    
            tableView.reloadData()
        }
numberOfRowsInSection

中的

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // use the filtered list to determine count
        return filteredDreams.count
}

为了更安全,你可以返回一个空单元而不是崩溃:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard indexPath.row < filteredDreams.count else { return UITableViewCell() }
       // your code here 
    }
相关问题