Expand/Collapse 使用 searchController 索引 tableView 单元格
Expand/Collapse indexed tableView cells with searchController
我有一个包含不同子类别(“Algrebra”、“Biology”、“Chemistry”)的 tableView,这些子类别可通过 searchController 进行索引和搜索。我想将这些子类别放入多个类别(“紧急”、“重要”、“不重要”)中,并在点击时 expand/collapse 它们。我还希望将类别编入索引(而不是子类别),但要保持子类别可通过 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([=11=].category.prefix(1)) })
self.sortedCategory = groupedList.sorted{[=11=].key < .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 { [=11=].value.filter { [=11=].category.contains(text) } }
let groupedCategory = Dictionary(grouping: filteredCategory, by: { String([=11=].category.prefix(1)) } )
self.filteredCategory = []
self.filteredCategory = groupedCategory.sorted{ [=11=].key < .key }
self.filteredSectionTitles = []
for tuple in self.filteredCategory {
self.filteredSectionTitles.append(tuple.key)
}
self.tableView.reloadData()
}
}
类别单元格
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
}
}
应该有帮助的一些提示...
首先,让我们更改一些命名。
您正在使用“紧急”、“重要”、“不重要”的“类别”作为部分 ...您的“子类别”会更准确描述为“类别”。
我们也可以将 Sections 视为 Category Status
所以,我们将像这样创建一个 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()
}
}
}
然后我们将向类别结构添加一个“状态”属性:
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) 过滤后的列表
这是您可以试用的完整示例。我使用了一堆随机主题作为类别,并为每个类别图像使用了圆圈中的数字,并且我使用了 star、halfstar 和 emptystar 的 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{[=18=].name < .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 { [=18=].key.description }).compactMap { String([=18=].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 { [=18=].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: { [=18=].status })
// order the grouped list by status
self.sortedByStatus = groupedList.sorted{[=18=].key.rawValue < .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
]
我有一个包含不同子类别(“Algrebra”、“Biology”、“Chemistry”)的 tableView,这些子类别可通过 searchController 进行索引和搜索。我想将这些子类别放入多个类别(“紧急”、“重要”、“不重要”)中,并在点击时 expand/collapse 它们。我还希望将类别编入索引(而不是子类别),但要保持子类别可通过 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([=11=].category.prefix(1)) })
self.sortedCategory = groupedList.sorted{[=11=].key < .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 { [=11=].value.filter { [=11=].category.contains(text) } }
let groupedCategory = Dictionary(grouping: filteredCategory, by: { String([=11=].category.prefix(1)) } )
self.filteredCategory = []
self.filteredCategory = groupedCategory.sorted{ [=11=].key < .key }
self.filteredSectionTitles = []
for tuple in self.filteredCategory {
self.filteredSectionTitles.append(tuple.key)
}
self.tableView.reloadData()
}
}
类别单元格
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
}
}
应该有帮助的一些提示...
首先,让我们更改一些命名。
您正在使用“紧急”、“重要”、“不重要”的“类别”作为部分 ...您的“子类别”会更准确描述为“类别”。
我们也可以将 Sections 视为 Category Status
所以,我们将像这样创建一个 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()
}
}
}
然后我们将向类别结构添加一个“状态”属性:
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) 过滤后的列表
这是您可以试用的完整示例。我使用了一堆随机主题作为类别,并为每个类别图像使用了圆圈中的数字,并且我使用了 star、halfstar 和 emptystar 的 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{[=18=].name < .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 { [=18=].key.description }).compactMap { String([=18=].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 { [=18=].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: { [=18=].status })
// order the grouped list by status
self.sortedByStatus = groupedList.sorted{[=18=].key.rawValue < .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
]