UItableview Cell 随机或在滚动时更改数据和属性

Posted

技术标签:

【中文标题】UItableview Cell 随机或在滚动时更改数据和属性【英文标题】:UItableview Cell changes data and attributes randomly or while scrolling 【发布时间】:2020-07-01 15:23:06 【问题描述】:

我已经尝试解决这个问题好几个小时了,我知道我的代码不是最好的,但我似乎无法确定问题所在。我有一个 NSmanagedobject 数组,其中包含属性。如果“isComplete”的属性为真,我希望单元格的背景颜色为绿色。我有一个自定义视图,它创建一个新的 nsmanaged 对象并将其添加到 tableview。默认情况下,它应该添加一个白色背景单元格。我知道有很多代码需要解释,但已经过去了几个小时,我只是不明白为什么我的 tableview 加载单元格数据和配置不正确。这是我的视图控制器,里面有 tableview。

    import CoreData
    import UIKit
    var coreTasks: [NSManagedObject] = []
    var taskAdditionView:TaskAdditionView!
    let appDel : AppDelegate = UIApplication.shared.delegate as! AppDelegate
    let context: NSManagedObjectContext = appDel.persistentContainer.viewContext
    
    class HomeViewController: UIViewController 
        @IBOutlet var addTaskFunction: UIBarButtonItem!
        @IBOutlet var homeTableView: UITableView!
        var todaysdeadlineLabel: UILabel!
        
        
        @IBAction func addTaskFunctions(_ sender: UIBarButtonItem) 
        animateIn()
        addTaskFunction.isEnabled = false
        
        
    
        func animateIn() 
            taskAdditionView = TaskAdditionView() // the view where i create a new nsmanaged object
            view.addSubview(taskAdditionView)
            view.bringSubviewToFront(taskAdditionView)
            
        
        
    
        
    
        

        func tableviewsConstraints()
  
            homeTableView.translatesAutoresizingMaskIntoConstraints = false
            homeTableView.layer.cornerRadius = 4
            homeTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
            homeTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
            homeTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 120).isActive = true
            homeTableView.heightAnchor.constraint(equalToConstant: homeTableView.rowHeight * 3).isActive = true
            homeTableView.layer.borderWidth = 0.5
            homeTableView.layer.borderColor = UIColor
                .black.cgColor
            
    
        override func viewDidLoad() 
            super.viewDidLoad()
            loadTasks()
            tableviewsConstraints()
             NotificationCenter.default.addObserver(self, selector: #selector(reloadTable), name: NSNotification.Name(rawValue: "reloadTableNotification"), object: nil) 
            
            
           
            
            homeTableView.delegate = self
            homeTableView.dataSource = self
        
            
            
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge])  (didallow, error) in
               
            
        
        
        
        @objc func reloadTable(notification:Notification)
            animateOut()
            DispatchQueue.main.async 
                self.homeTableView.reloadData()
                self.addTaskFunction.isEnabled = true
                print("reloadTable() fired!")
            
     
        
 
    
    
    extension HomeViewController: UITableViewDelegate,UITableViewDataSource 
        
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
            return coreTasks.count
        
        
        
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    
            let task = coreTasks[indexPath.row] // creating a new task from the already stored task depending on the indexpath.row if indexPath.row is 3 then the task is tasks[3]
           
            let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell") as! CustomCell // setting the identifier ( we have already set in the storyboard, the class of our cells to be our custom cell)
    
         
           
            cell.setTask(task: task) // this changes the label and date text since an instance of the task contains both the task and the date
            
    
            print("CellData Task :", task.value(forKey: "isComplete") as! Bool, task.value(forKey: "name") as! String)
            
                if (task.value(forKey: "isComplete") as! Bool == true)
                    cell.labelsToYellow()
                    cell.backgroundColor = Colors.greencomplete
                    cell.selectionStyle = .none
                 

            return cell
        
            
    
        // Leading Swipe Action
        func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? 
            let task = coreTasks[indexPath.row]
            let complete = markComplete(at: indexPath)
            if !(task.value(forKey: "isComplete") as! Bool)
                return UISwipeActionsConfiguration(actions: [complete])
             else 
                return UISwipeActionsConfiguration(actions: [])
            
            
        
        
        
        // Mark as Completed Task
        func markComplete(at: IndexPath) -> UIContextualAction 
            
            let df = DateFormatter()
            df.dateFormat = "dd-MM-yyyy" // assigning the date format
            let now = df.string(from: Date()) // extracting the date with the given format
            let cell = homeTableView.cellForRow(at: at) as! CustomCell
            let task = coreTasks[at.row]
            let completeActionImage = UIImage(named: "AddTask")?.withTintColor(.white)
            let action = UIContextualAction(style: .normal, title: "Complete")  (action, view, completion) in
                
                task.setValue(!(task.value(forKey: "isComplete") as! Bool), forKey: "isComplete")
                self.homeTableView.cellForRow(at: at)?.backgroundColor = task.value(forKey: "isComplete") as! Bool ? Colors.greencomplete : .white
                cell.backgroundColor = Colors.greencomplete
                cell.labelsToYellow()
                task.setValue("Finished " + now, forKey: "date")
                do 
                    try
                    context.save()
                    self.homeTableView.reloadData()
                 catch 
                    print("Markcomplete save error")
                
                
                //            cell.displayIcons(task: task)
                completion(true)
                
            
            
            //action.image = #imageLiteral(resourceName: "AddTask")
            action.image = completeActionImage
            action.backgroundColor = Colors.greencomplete
            return action
        
        
        // Trailing Swipe Actions
        
        func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? 
            let task = coreTasks[indexPath.row]
            let important = importantAction(at: indexPath)
            let delete    = deleteAction(at: indexPath)
            
            if task.value(forKey: "isComplete") as? Bool == true 
                return UISwipeActionsConfiguration(actions: [delete])
             else 
                return UISwipeActionsConfiguration(actions: [delete,important])
            
        
        
        
        
        
        // Delete Action
        func deleteAction(at: IndexPath) -> UIContextualAction 
            // remove !!!! from coredata memory as well not just array
            let deleteActionImage = UIImage(named: "Delete")?.withTintColor(.white)
            let action = UIContextualAction(style: .destructive , title: "Delete")  (action, view, completion) in
                let objectToDelete = coreTasks.remove(at: at.row)
                context.delete(objectToDelete)
                self.homeTableView.deleteRows(at: [at], with: .automatic)
                do 
                try
                context.save()
                 catch 
                print("Problem while saving")
                
                completion(true)
            
            action.image = deleteActionImage
            action.backgroundColor = Colors.reddelete
            return action
        
        
        func loadTasks()
            
                
            
            let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
            do 
              coreTasks = try context.fetch(request) as! [NSManagedObject]
              print("loadTasks() fired!")
             catch let error as NSError 
              print("Could not fetch. \(error), \(error.userInfo)")
            
            
        
    

这是我的任务添加视图

import UserNotifications
import UIKit
import CoreData

class TaskAdditionView: UIView 
    
    var importanceSegmentControl: CustomSegmentControl!
    var headerLabel:UILabel!
    var taskTextField: CustomTextField!
    var submitButton:CustomButton!
    var reminderSwitch: UISwitch!
    var datePicker: UIDatePicker!
    var dateSelected: Date?
    var importanceValue: Int16 = 0

    
    override init(frame: CGRect) 
        super.init(frame: frame)
        
     // initiliaze the view like this TaskAdditionView()
    
    
    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
    
    
    func setupSwitchPicker()
        reminderSwitch = UISwitch()
        reminderSwitch.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
        datePicker = UIDatePicker()
        datePicker.minimumDate = Date()
        datePicker.addTarget(self, action: #selector(pickingDate(sender:)), for: .valueChanged)
    
    
    @objc func pickingDate(sender: UIDatePicker)
        self.dateSelected = sender.date
        print("Date selected: \(dateSelected)")
    
    
    func setupLabel()
        headerLabel = UILabel()
        headerLabel?.text = "Add Task"
        headerLabel.textAlignment = .center
        headerLabel.textColor = .black
        headerLabel?.font = UIFont(name: "AvenirNext-Bold", size: 30.0)
        headerLabel?.backgroundColor = UIColor.clear
    

    @objc func indexChanged(control : CustomSegmentControl) 
        // This all works fine and it prints out the value of 3 on any click

        switch control.selectedIndex 
        case 0:
        importanceValue = 0
            print(importanceValue)
        case 1:
        importanceValue = 1
            print(importanceValue)

        case 2:
        importanceValue = 2
        print(importanceValue)

        default:
            break;
          //Switch
     // indexChanged for the Segmented Control

    
    func setupSegmentControl()
        importanceSegmentControl = CustomSegmentControl()
        importanceSegmentControl.addTarget(self, action: #selector(indexChanged(control:)),for: UIControl.Event.valueChanged)
        
    
    func setupButton()
        let myAttributes = [ NSAttributedString.Key.font: UIFont(name: "AvenirNext-DemiBold", size: 18.0)! , NSAttributedString.Key.foregroundColor: UIColor.white ]
        

        let myTitle = "Add"
        let myAttributedTitle = NSAttributedString(string: myTitle, attributes: myAttributes)
        submitButton = CustomButton()
        submitButton.setAttributedTitle(myAttributedTitle, for: .normal)
        submitButton.addTarget(self, action: #selector(submitFunction(sender:)), for: .touchUpInside)
    
    
    
    // Submit Function
    @objc func submitFunction(sender: CustomButton)
        print("Worked")
        submitButton.shake()
        NotificationCenter.default.post(name: NSNotification.Name("reloadTableNotification") , object: nil)
        
        addTask()
        
        if (reminderSwitch.isOn)
        setupNotification()
        print(dateSelected)
        
        NSLayoutConstraint.deactivate(self.constraints)
        removeFromSuperview()
        
    
    
    func setupTextField()
          taskTextField = CustomTextField()
    
    
    func setupConstraints()
        setupLabel()
        setupTextField()
        setupSegmentControl()
        setupButton()
        setupSwitchPicker()
        addSubview(headerLabel!)
        addSubview(importanceSegmentControl!)
        addSubview(taskTextField)
        addSubview(submitButton)
        reminderSwitch.transform = reminderSwitch.transform.rotated(by: -(.pi/2))
        addSubview(reminderSwitch)
        addSubview(datePicker)
    
    
    override func didMoveToSuperview() 
        setupConstraints()
    
    override func removeFromSuperview() 
        for view in self.subviews
            view.removeFromSuperview()
        
        NSLayoutConstraint.deactivate(self.constraints)
        
        removeAllConstraintsFromView(view: self)
    

    func addTask()
       
       
            let df = DateFormatter()
            df.dateFormat = "dd-MM-yyyy" // assigning the date format
            let now = reminderSwitch.isOn ? "Deadline " + df.string(from: dateSelected!) : df.string(from: Date()) // extracting the date with the given format
        print("Reminder Switch is ON: ", reminderSwitch.isOn)
        // Adding a task to the array
        let entity =
                NSEntityDescription.entity(forEntityName: "Tasks",
                                   in: context)!
         let newTask = NSManagedObject(entity: entity, insertInto: context)
         newTask.setValue(taskTextField.text!, forKey: "name")
         newTask.setValue(false, forKey: "isComplete")
         newTask.setValue(now, forKey: "date")
         newTask.setValue(importanceValue, forKey: "importValue")
        do 
            try
            context.save()
            coreTasks.append(newTask)
            print("addTask() fired!")
             catch 
            print("Problem while saving")
            
        
        
        

        
    func setupNotification()
        
        let currentDate = Date()
        let interval = dateSelected?.timeIntervalSince(currentDate)
        print(interval)
        let notifcation = UNMutableNotificationContent()
        notifcation.title = "Task Reminder"
        notifcation.subtitle = taskTextField.text ?? "Empty"
        notifcation.badge = 1
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: interval!, repeats: false)
        let request = UNNotificationRequest(identifier: "taskReminder", content: notifcation, trigger: trigger)
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
        
    

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        taskTextField.endEditing(true)
    


func removeAllConstraintsFromView(view: UIView)  for c in view.constraints  view.removeConstraint(c)  

extension UIView 
    func removeAllConstraints() 
        let superViewConstraints = superview?.constraints.filter $0.firstItem === self || $0.secondItem === self  ?? []
        
        superview?.removeConstraints(superViewConstraints + constraints)
    

这是我的自定义表格视图单元格

    import UIKit
import CoreData



class CustomCell: UITableViewCell 
 
    
    @IBOutlet var taskLabel: UILabel!
    @IBOutlet var dateLabel: UILabel!    


    func setTask(task: NSManagedObject )
        taskLabel.text = task.value(forKey: "name") as? String
        dateLabel.text = task.value(forKey: "date") as? String

    

    func labelsToYellow() 
        taskLabel.textColor = .white
        dateLabel.textColor = .white
    

    func labelsToBlack() 
        taskLabel.textColor = .black
        dateLabel.textColor = .black
    

理想情况下,当我通过任务添加创建一个 nsmanaged 对象类型的新任务时。我的由 nsmanagedobject 数组填充的 tableview 应该将任务添加到背景颜色为白色的单元格中,并相应地添加任务标签。我有一个上下文操作,标记任务完成并使单元格背景变为绿色。奇怪的是,它在某个时候起作用了。现在随机有时任务单元格是用绿色背景创建的,有时标签是空白的,或者当我向下或向上滚动时,所有标签都会变成绿色。非常感谢您的帮助。

【问题讨论】:

【参考方案1】:

我以前遇到过这个问题,因为 TableViewCells 被重复使用,所以无论是否为默认,您都需要确保设置背景。

因此,当您添加代码以将背景设置为绿色时,请添加 else 语句或在查询之前将单元格背景设置为白色/您的默认颜色Issue with UITableViewCells Repeating Content

【讨论】:

以上是关于UItableview Cell 随机或在滚动时更改数据和属性的主要内容,如果未能解决你的问题,请参考以下文章

UITableView Cell 不应该水平滚动 swift iOS

UITableView 有 UIScrollView in Cell 无法在 ios 模拟器 8.3 中滚动

UITableView - 在编辑模式下重用单元格

UITableView滚动性能优化

UItableView滚动到特定位置

UITableview优化