【问题标题】:Expand/Collapse indexed tableView cells with searchController使用 searchController 展开/折叠索引 tableView 单元格
【发布时间】:2022-01-26 10:28:42
【问题描述】:

我有一个带有不同子类别(“Algrebra”、“Biology”、“Chemistry”)的 tableView,它们通过 searchController 被索引和搜索。我想将这些子类别放在多个类别中(“紧急”、“重要”、“不重要”)并在点击时展开/折叠它们。我还希望将类别编入索引(而不是子类别),但通过 searchController 保持子类别可搜索。

我不知道如何用我的代码正确实现它。

这是我的代码:

类别控制器

class CategoryController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating {

    private var searchController = UISearchController()

let categories = ["Urgent", "Important", "Not Important"] 

let subcategories = [                                                                  
        Add(category: "Algrebra", categoryImg: #imageLiteral(resourceName: "Algebra.png")),
        Add(category: "Biology", categoryImg: #imageLiteral(resourceName: "Biology.png")),
        Add(category: "Chemistry", categoryImg: #imageLiteral(resourceName: "Chemistry.png")),
    ]
    private var sectionTitles = [String]()
    private var filteredSectionTitles = [String]()
    private var sortedCategory = [(key: String, value: [Add])]()
    private var filteredCategory = [(key: String, value: [Add])]()

  private let tableView: UITableView = {
    let table = UITableView()
    table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return table }()

  override func viewDidLoad() {
        super.viewDidLoad()
//TABLEVIEW
        tableView.rowHeight = 50
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        tableView.register(CategoryCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.sectionIndexColor = .black
        tableView.sectionIndexBackgroundColor = .lightGray
        tableView.sectionIndexTrackingBackgroundColor = .gray
        tableView.allowsMultipleSelection = false
//SEARCHCONTROLLER
        self.searchController = UISearchController(searchResultsController: nil)
        self.searchController.searchResultsUpdater = self
        self.searchController.obscuresBackgroundDuringPresentation = false
        self.searchController.searchBar.placeholder = "Search for your category"
        self.searchController.hidesNavigationBarDuringPresentation = false
        self.navigationItem.searchController = self.searchController
        self.navigationItem.hidesSearchBarWhenScrolling = false
        self.navigationItem.title = "Tasks"
        navigationController?.navigationBar.prefersLargeTitles = true
        self.searchController.searchBar.searchTextField.textColor = .label
        
        
        let groupedList = Dictionary(grouping: self.subcategories, by: { String($0.category.prefix(1)) })
        self.sortedCategory = groupedList.sorted{$0.key < $1.key}
        
        for tuple in self.sortedCategory {
            self.sectionTitles.append(tuple.key)
        }
    }
//VIEWDIDLAYOUT
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        tableView.frame = view.bounds
    }
/// TABLEVIEW
     func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if self.searchController.isActive && !self.filteredSectionTitles.isEmpty {
            return self.filteredSectionTitles[section]
        } else {
            return self.sectionTitles[section]
        }
    }
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        if self.searchController.isActive && !self.filteredSectionTitles.isEmpty {
            return self.filteredSectionTitles
        } else {
            return self.sectionTitles
        }
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        if self.searchController.isActive && !self.filteredSectionTitles.isEmpty {
            return self.filteredSectionTitles.count
        } else {
            return self.sectionTitles.count
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if self.searchController.isActive && !self.filteredCategory.isEmpty {
            return self.filteredCategory[section].value.count
        } else {
            return self.sortedCategory[section].value.count
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for:indexPath) as UITableViewCell

        cell.imageView?.contentMode = .scaleAspectFit
        if self.searchController.isActive && !self.filteredCategory.isEmpty {
            cell.textLabel?.text = self.filteredCategory[indexPath.section].value[indexPath.row].category
            cell.imageView?.image = self.filteredCategory[indexPath.section].value[indexPath.row].categoryImg
        } else {
            cell.textLabel?.text = self.sortedCategory[indexPath.section].value[indexPath.row].category
            cell.imageView?.image = self.sortedCategory[indexPath.section].value[indexPath.row].categoryImg
            
        }
        return cell
        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
        Add.details.category = (currentCell.textLabel?.text)!
        let secondVC = DateController()
        navigationController?.pushViewController(secondVC, animated: true)
        print(Add.details.category)

    }
func updateSearchResults(for searchController: UISearchController) {
    
    guard let text = self.searchController.searchBar.text else {
        return
    }
    let filteredCategory = self.sortedCategory.flatMap { $0.value.filter { $0.category.contains(text) } }
    let groupedCategory = Dictionary(grouping: filteredCategory, by: { String($0.category.prefix(1)) } )
    self.filteredCategory = []
    self.filteredCategory = groupedCategory.sorted{ $0.key < $1.key }
    
    self.filteredSectionTitles = []
    for tuple in self.filteredCategory {
        self.filteredSectionTitles.append(tuple.key)
    }
    
    self.tableView.reloadData()
}
}

CategoryCell

class CategoryCell: UITableViewCell {
    var cellImageView = UIImageView()
    var cellLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: "cell")
            
            cellImageView.translatesAutoresizingMaskIntoConstraints = false
            cellImageView.contentMode = .scaleAspectFit
            cellImageView.tintColor = .systemPink
            contentView.addSubview(cellImageView)
            
            cellLabel.translatesAutoresizingMaskIntoConstraints = false
            cellLabel.font = UIFont.systemFont(ofSize: 20)
            contentView.addSubview(cellLabel)
            
            NSLayoutConstraint.activate([
                cellImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                cellImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
                cellImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
                cellImageView.widthAnchor.constraint(equalToConstant: 44),
                
                cellLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
                cellLabel.leadingAnchor.constraint(equalTo: cellImageView.trailingAnchor, constant: 10),
                
            ])
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

     }
    
        
}

添加(数据结构)

struct Add {
    static var details: Add = Add()
    var category: String = ""
    
    func getDict() -> [String: Any] {
              let dict = ["category": self.category,
                         
                ] as [String : Any]
               return dict
         }

}

【问题讨论】:

  • 暂时忘掉你的代码,试着更好地描述你的目标——因为它不是很清楚。 “化学”是否有“分析/生物/无机/有机/物理”等子项,您想要展开/折叠“化学”并使其子项可搜索?或者,您希望搜索和显示的“Algrebra”、“Biology”、“Chemistry”items 是否在可展开的“Urgent”、“Important”、“Not Important”部分中排序?
  • 抱歉解释混乱。所以不,化学没有任何子项目,是的,这正是我想要的,我想让代数/生物学/化学可搜索并在紧急/重要/不重要部分中。 (并使 Urgent/Important/NotImportant 可索引,而不是 Algebra/Biology/Chemistry)

标签: ios swift indexing tableview expand


【解决方案1】:

应该有帮助的一些提示...

首先,让我们更改一些命名。

您将“紧急”、“重要”、“不重要”的“类别”用作部分 ...并且您的“子类别”将更准确地描述为“类别” .

我们也可以将部分视为类别状态

所以,我们将像这样创建一个enum

enum CategoryStatus: Int, CustomStringConvertible {
    case urgent
    case important
    case notimportant
    
    var description : String {
        switch self {
        case .urgent: return "Urgent"
        case .important: return "Important"
        case .notimportant: return "Not Important"
        }
    }
    
    var star : UIImage {
        switch self {
        case .urgent: return UIImage(named: "star") ?? UIImage()
        case .important: return UIImage(named: "halfstar") ?? UIImage()
        case .notimportant: return UIImage(named: "emptystar") ?? UIImage()
        }
    }
}

我们将在 Category 结构中添加一个“状态”属性:

struct MyCategory {
    var name: String = ""
    var categoryImg: UIImage = UIImage()
    var status: CategoryStatus = .important
}

现在,我们可以使用“普通语言”来完成这个过程:

  • 首先按名称对整个类别列表进行排序
  • 当我们键入搜索字符串时,我们可以通过“名称包含搜索”过滤该列表
  • 什么时候可以按状态对该列表进行分组

所以如果我们开始:

Biology : .important
Chemistry : .urgent
Algebra : .urgent

我们可以按名称排序并得到

Algebra : .urgent
Biology : .important
Chemistry : .urgent

然后按状态分组

.urgent
    Algebra
    Chemistry
.important
    Biology

如果我们在搜索字段中输入了“b”,我们将从排序后的 ALL 列表开始,并对其进行过滤:

Algebra : .urgent
Biology : .important

然后按状态分组

.urgent
    Algebra
.important
    Biology

另一个提示:不要使用“完整列表”和“过滤列表”,以及一堆

if self.searchController.isActive && !self.filteredSectionTitles.isEmpty {

块,使用单个排序、过滤和分组列表。

然后该列表将设置为 A) 完整列表(如果没有输入搜索文本)或 B) 过滤列表

这是一个完整的示例,您可以尝试一下。我使用了一堆随机主题作为类别,并为每个类别图像使用圆圈中的数字,并且我使用了星形、半星形和空星形的 png。

请注意,这只是示例代码!。它并不意味着也不应被视为“生产就绪”:

enum CategoryStatus: Int, CustomStringConvertible {
    case urgent
    case important
    case notimportant
    
    var description : String {
        switch self {
        case .urgent: return "Urgent"
        case .important: return "Important"
        case .notimportant: return "Not Important"
        }
    }
    
    var star : UIImage {
        switch self {
        case .urgent: return UIImage(named: "star") ?? UIImage()
        case .important: return UIImage(named: "halfstar") ?? UIImage()
        case .notimportant: return UIImage(named: "emptystar") ?? UIImage()
        }
    }
}

struct MyCategory {
    var name: String = ""
    var categoryImg: UIImage = UIImage()
    var status: CategoryStatus = .important
}

class CategoryController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating {
    
    private var searchController = UISearchController()
    
    // array of ALL Categories, sorted by name
    private var sortedCategories: [MyCategory] = []
    
    // this will be either ALL items, or the filtered items
    //  grouped by Status
    private var sortedByStatus = [(key: CategoryStatus, value: [MyCategory])]()
    
    private let tableView = UITableView()
    
    private let noMatchesLabel: UILabel = {
        let v = UILabel()
        v.backgroundColor = .yellow
        v.text = "NO Matches"
        v.textAlignment = .center
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var items: [MyCategory] = []
        
        // this will be our list of MyCategory objects (they'll be sorted later)
        let itemNames: [String] = [
            "Algebra",
            "Chemistry",
            "Biology",
            "Computer Sciences",
            "Physics",
            "Earth Sciences",
            "Geology",
            "Political Science",
            "Psychology",
            "Nursing",
            "Economics",
            "Agriculture",
            "Communications",
            "Engineering",
            "Foreign Lanuages",
            "English Language",
            "Literature",
            "Libary Sciences",
            "Social Sciences",
            "Visual Arts",
        ]
        
        // create our array of MyCategory
        //  setting every 3rd one to .urgent, .important or .notimportant
        for (str, i) in zip(itemNames, 0...30) {
            let status: CategoryStatus = CategoryStatus.init(rawValue: i % 3) ?? .important
            var img: UIImage = UIImage()
            if let thisImg = UIImage(named: str) {
                img = thisImg
            } else {
                if let thisImg = UIImage(systemName: "\(i).circle") {
                    img = thisImg
                }
            }
            items.append(MyCategory(name: str, categoryImg: img, status: status))
        }
        
        // sort the full list of categories by name
        self.sortedCategories = items.sorted{$0.name < $1.name}
        
        //TABLEVIEW
        tableView.rowHeight = 50
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        tableView.sectionIndexColor = .black
        tableView.sectionIndexBackgroundColor = .lightGray
        tableView.sectionIndexTrackingBackgroundColor = .gray
        tableView.allowsMultipleSelection = false
        
        tableView.dataSource = self
        tableView.delegate = self
        
        tableView.register(CategoryCell.self, forCellReuseIdentifier: CategoryCell.reuseIdentifier)
        tableView.register(MySectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MySectionHeaderView.reuseIdentifier)
        
        //SEARCHCONTROLLER
        self.searchController = UISearchController(searchResultsController: nil)
        self.searchController.searchResultsUpdater = self
        self.searchController.obscuresBackgroundDuringPresentation = false
        self.searchController.searchBar.placeholder = "Search for your category"
        self.searchController.hidesNavigationBarDuringPresentation = false
        self.navigationItem.searchController = self.searchController
        self.navigationItem.hidesSearchBarWhenScrolling = false
        self.navigationItem.title = "Tasks"
        navigationController?.navigationBar.prefersLargeTitles = true
        self.searchController.searchBar.searchTextField.textColor = .label
        
        // add the no-matches view
        noMatchesLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(noMatchesLabel)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            tableView.topAnchor.constraint(equalTo: g.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            
            noMatchesLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
            noMatchesLabel.heightAnchor.constraint(equalToConstant: 120.0),
            noMatchesLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            noMatchesLabel.topAnchor.constraint(equalTo: tableView.frameLayoutGuide.topAnchor, constant: 40.0),
            
        ])
        
        noMatchesLabel.isHidden = true
        
        // call updateSearchResults to build the initial non-filtered data
        updateSearchResults(for: searchController)
        
    }
    
    /// TABLEVIEW
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: MySectionHeaderView.reuseIdentifier) as! MySectionHeaderView
        v.imageView.image = self.sortedByStatus[section].key.star
        v.label.text = self.sortedByStatus[section].key.description
        return v
    }
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        // first char of each section title
        return (sortedByStatus.map { $0.key.description }).compactMap { String($0.prefix(1)) }
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.sortedByStatus.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.sortedByStatus[section].value.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CategoryCell.reuseIdentifier, for:indexPath) as! CategoryCell
        
        cell.cellLabel.text = self.sortedByStatus[indexPath.section].value[indexPath.row].name
        cell.cellImageView.image = self.sortedByStatus[indexPath.section].value[indexPath.row].categoryImg
        
        return cell
        
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        // get Category object from data
        let thisCategory: MyCategory = self.sortedByStatus[indexPath.section].value[indexPath.row]
        print("selected:", thisCategory.name, "status:", thisCategory.status)
        
    }
    func updateSearchResults(for searchController: UISearchController) {
        
        var filteredList: [MyCategory] = []
        
        if let text = self.searchController.searchBar.text, !text.isEmpty {
            
            // we have text to search for, so filter the list
            filteredList = self.sortedCategories.filter { $0.name.localizedCaseInsensitiveContains(text) }
            
        } else {
            
            // no text to search for, so use the full list
            filteredList = self.sortedCategories
            
        }
        
        // filteredList is now either ALL Categories (no search text entered), or
        //  ALL Categories filtered by search text
        
        // create a dictionary of items grouped by status
        let groupedList = Dictionary(grouping: filteredList, by: { $0.status })
        
        // order the grouped list by status
        self.sortedByStatus = groupedList.sorted{$0.key.rawValue < $1.key.rawValue}
        
        // show noMatchesLabel if we have NO matching Categories
        noMatchesLabel.isHidden = self.sortedByStatus.count != 0
        
        // reload the table
        self.tableView.reloadData()
        
    }
}

// simple cell with image view and label
class CategoryCell: UITableViewCell {
    
    static let reuseIdentifier: String = String(describing: self)
    
    var cellImageView = UIImageView()
    var cellLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        cellImageView.translatesAutoresizingMaskIntoConstraints = false
        cellImageView.contentMode = .scaleAspectFit
        cellImageView.tintColor = .systemPink
        contentView.addSubview(cellImageView)
        
        cellLabel.translatesAutoresizingMaskIntoConstraints = false
        cellLabel.font = UIFont.systemFont(ofSize: 20)
        contentView.addSubview(cellLabel)
        
        NSLayoutConstraint.activate([
            
            cellImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            cellImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            cellImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            cellImageView.widthAnchor.constraint(equalToConstant: 44),
            
            cellLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            cellLabel.leadingAnchor.constraint(equalTo: cellImageView.trailingAnchor, constant: 10),
            
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
    }
    
}

// simple reusable section header with image view and label
class MySectionHeaderView: UITableViewHeaderFooterView {
    
    static let reuseIdentifier: String = String(describing: self)
    
    let imageView = UIImageView()
    let label = UILabel()
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        
        imageView.contentMode = .scaleAspectFit
        label.font = .systemFont(ofSize: 20.0, weight: .bold)
        
        [imageView, label].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(v)
        }
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            
            imageView.widthAnchor.constraint(equalToConstant: 24.0),
            imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
            imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            imageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            label.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 12.0),
            label.topAnchor.constraint(equalTo: g.topAnchor),
            label.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            
        ])
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
}

这是启动时的样子:

然后,当我们输入“t”“e”“ra”时:


编辑

示例代码中的for (str, i) in zip(itemNames, 0...30) { 块只是生成一些示例项目的简单方法。

要在您的代码中使用它,您可能会执行以下操作:

let items = [
    MyCategory(name: "Algebra", categoryImg: #imageLiteral(resourceName: "Algebra.png"), status: .urgent),                                                                 
    MyCategory(name: "Biology", categoryImg: #imageLiteral(resourceName: "Biology.png"), status: .important),                                                                 
    MyCategory(name: "Chemistry", categoryImg: #imageLiteral(resourceName: "Chemistry.png"), status: .notimportant),
    // and so on                                                                 
]

【讨论】:

  • 该死的谢谢 Don,为什么我没有想到像这样使用 Enum/switch 案例,它更干净,我什至可以将类别状态添加到我的数据结构中,以防我想使用它后来^^我在我的代码中实现了你的代码,它几乎是完美的。唯一的事情是我不知道如何以非随机方式将类别(代数等...)放入状态标题中,并且旁边有一个选定的图像而不是数字和圆圈
  • 这是我无法理解/更改的这部分代码for (str, i) in zip(itemNames, 0...30) { let status: CategoryStatus = CategoryStatus.init(rawValue: i % 3) ?? .important var img: UIImage = UIImage() if let thisImg = UIImage(named: str) { img = thisImg } else { if let thisImg = UIImage(systemName: "\(i).circle") { img = thisImg }} items.append(MyCategory(name: str, categoryImg: img, status: status)) }
  • @dohoudjann - 请参阅我的答案底部的 编辑
  • 完美,你是个野兽!再次感谢您的回答和明确的解释...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-14
  • 2016-08-25
  • 2023-03-20
相关资源
最近更新 更多