我有一个类型为“待办事项”的应用,我想在其中使用自定义按钮选中标记来检查我的项目。应用程序的结构如下所示:主列表使用UITableViewController,该UITableViewController具有带有UIButton“ checkmarkButton”和标签“ cellLabel”的自定义TableViewCell。它们的值在创建后保存到核心数据中,checkmarkButton默认状态为false,并使用value(forKeyPath :)分配给cellForRowAtindexPath中的单元格。在TableViewCell类中,我具有一个checkmarkBtnTapped函数,该函数将按钮的显示图像更改(选中/取消选中),将此图像着色为指定的颜色,并在关键数据的CoreData属性中将按钮“状态”更新为bool,以获取其关键路径,获取CoreData和reloadTableView。一些使用我的列表数组的功能以及来自核心数据或表视图的其他内容都来自UITableViewController,因此我为它们实现了委托。
问题是,当我点击checkmarkButton并使用updateBtnState时,新行的状态已更改(即,我轻击行的状态为“ false”的按钮,带有标签的实际行仍为“ false”,而新行添加了带有空标签和状态为“ true”的按钮),我想这是由于updateBtnState()方法仅引用了ManagedObjectContext而没有引用indexPath。但是当我尝试引用item作为indexPath的点而不是NSManagedObject时,由于IndexPath参数,我无法将此函数传递给TableViewCell类。下面在TableViewController.swift中,我离开了updateBtnState2(),我认为它可以解决我的问题,但在TableViewCell中的checkmarkBtnTapped()函数中不可用
TableViewController.swift
导入UIKit 导入CoreData
TableViewController类:UITableViewController,ButtonSelectionDelegate {
var list: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
let cellNib = UINib(nibName: "TableViewCell", bundle: nil)
self.tableView.register(cellNib, forCellReuseIdentifier: "cell")
}
override func viewWillAppear(_ animated: Bool) {
UIApplication.shared.statusBarStyle = .lightContent
fetch()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
}
func save(name: String, state: Bool) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Item", in: managedObjectContext)!
let Item = NSManagedObject(entity: entity, insertInto: managedObjectContext)
Item.setValue(name, forKeyPath: "name")
Item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
list.append(Item)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func fetch(){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedObjectContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Item")
do{
list = try managedObjectContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
func updateBtnState(state: Bool){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Item", in: managedObjectContext)!
let Item = NSManagedObject(entity: entity, insertInto: managedObjectContext)
Item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
} catch let error as NSError {
print("Couldnt update. \(error)")
}
}
func updateBtnState2(indexPath: IndexPath, state: Bool){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedObjectContext = appDelegate.persistentContainer.viewContext
let item = list[indexPath.row]
item.setValue(state, forKeyPath: "isChecked")
do{
try managedObjectContext.save()
list[indexPath.row] = item
} catch let error as NSError {
print("Couldnt update. \(error)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(list.count)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = list[indexPath.row]
cell.selectionDelegate = self
cell.cellLabel.text = item.value(forKeyPath: "name") as? String
cell.checkmarkButton.isSelected = item.value(forKeyPath: "isChecked") as! Bool
return cell
}
func updateTableView(){
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
@objc func addTapped(){
let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {
return
}
self.save(name: nameToSave, state: false)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default)
alert.addTextField()
alert.addAction(cancelAction)
alert.addAction(saveAction)
present(alert, animated: true)
}
TableViewCell.swift
import UIKit
protocol ButtonSelectionDelegate: class {
func fetch()
func updateTableView()
func updateBtnState(state: Bool)
}
class TableViewCell: UITableViewCell {
weak var selectionDelegate: ButtonSelectionDelegate!
@IBOutlet var checkmarkButton: UIButton!
@IBOutlet var cellLabel: UILabel!
@IBAction func checkmarkBtnTapped(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
selectionDelegate?.updateBtnState(state: true)
let image: UIImage? = UIImage(named: "done_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor( red: CGFloat(21/255.0), green: CGFloat(126/255.0), blue: CGFloat(251/255.0), alpha: CGFloat(1.0))
selectionDelegate?.fetch()
selectionDelegate?.updateTableView()
print("checkmarkButton pressed to done")
} else {
selectionDelegate?.updateBtnState(state: false)
let image: UIImage? = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor.gray
selectionDelegate?.fetch()
selectionDelegate?.updateTableView()
print("checkmarkButton pressed to undone")
}
}
override func layoutSubviews() {
super.layoutSubviews()
let image: UIImage? = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
checkmarkButton.setImage(image, for: .normal)
checkmarkButton.tintColor = UIColor.gray
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
extension UIButton {
func hasImage(named imageName: String, for state: UIControlState) -> Bool {
guard let buttonImage = image(for: state), let namedImage = UIImage(named: imageName) else {
return false
}
return UIImagePNGRepresentation(buttonImage) == UIImagePNGRepresentation(namedImage)
}
}
答案 0 :(得分:0)
使用表格和集合视图单元格的最佳方法是让单元格负责其UI的所有配置。为此,您将数据传递到单元格中,然后将正确的数据放入所需格式的正确数据中。您已经有一个自定义UITableViewCell
,因此这很容易做到……
// Make the to-do item's property names into strings so you can't mistype them later.
// The other option would be to create the subclass of NSManagedObject so the properties are directly accessible.
let isChecked = "isChecked"
let name = "name"
class TableViewCell: UITableViewCell {
// Add a property to hold the actual to-do item
var item: NSManagedObject? {
didSet {
updateCell()
}
}
// Make all outlets private so you aren't tempted to touch them from outside
@IBOutlet private var checkmarkButton: UIButton!
@IBOutlet private var cellLabel: UILabel!
// Create both images once for each cell rather than every time the image changes
let doneImage = UIImage(named: "done_icon.png")?.withRenderingMode(.alwaysTemplate)
let notDoneImage = UIImage(named: "undone_icon.png")?.withRenderingMode(.alwaysTemplate)
let doneColor = UIColor( red: CGFloat(21/255.0), green: CGFloat(126/255.0), blue: CGFloat(251/255.0), alpha: CGFloat(1.0))
let notDoneColor = UIColor.gray
private func updateCell() {
guard let item = item else { return }
cellLabel?.text = item.value(forKeyPath: name) as? String
checkmarksButton?.isSelected = item.value(forKeyPath: isChecked) as! Bool
}
@IBAction private func checkmarkBtnTapped(_ sender: UIButton) {
// Safely unwrap the to-do item
guard let item = item else { return }
sender.isSelected = !sender.isSelected
let selected = sender.isSelected
item.setValue(selected, forKeyPath: "isChecked")
checkmarkButton.setImage(selected ? doneImage : notDoneImage, for: .normal)
checkmarkButton.tintColor = selected ? doneColor : notDoneColor
print("checkmarkButton pressed to \(selected ? "done" : "undone")")
}
…
}
通过这种方式,单元可以直接更新托管对象,而不必尝试通过视图控制器重新连接到该对象。
此外,layoutSubviews
中的代码并不是真正需要的,但是如果是awakeFromNib
,则是放置它的更好位置。
完成单元格设置后,您可以摆脱这些更新按钮功能,并将cellForRowAt
更改为…
class TableViewController: UITableViewController {
…
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
let item = list[indexPath.row]
cell.item = item
return cell
}
…
}