我有一个应用程序的类型“做清单”,我想检查我的项目在清单上与自定义按钮复选标记。应用程序的结构如下:主列表使用带有自定义TableViewCell的UIButton "checkmarkButton“和标签"cellLabel”的UIButton。它们的值在创建后保存到核心数据,checkmarkButton默认状态为false,并使用值(forKeyPath:)分配给cellForRowAtindexPath中的单元格。在TableViewCell类中,我有一个checkmarkBtnTapped函数,它更改按钮的显示图像(check/uncheck),将该图像着色到指定的颜色,并在CoreData属性中将按钮"state“更新为bool,用于其键路径,获取CoreData和reloadTableView。一些函数使用我的列表数组和来自核心数据或表视图的其他东西来自UITableViewController,所以我为它们实现了委托。
问题是,当我点击checkmarkButton并使用updateBtnState新建行时,它使用的是已更改的状态(即,我点击了状态为"false“的带有按钮的行,我实际的带标签的行仍然在"false”上,新行中添加了空标签,并添加了状态为“true”的按钮)--我猜这是由于只引用managedObjectContext而不是indexPath的updateBtnState()方法。但是,当我试图将项作为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)
}
}发布于 2018-08-09 14:19:26
使用表视图单元格和集合视图单元格的最佳方法是让该单元格负责其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
}
…
}https://stackoverflow.com/questions/51767429
复制相似问题