UITableViewCell 在滚动时重复并丢失其数据

Posted

技术标签:

【中文标题】UITableViewCell 在滚动时重复并丢失其数据【英文标题】:UITableViewCell are repeating and lost its data while scrolling 【发布时间】:2017-07-23 04:16:52 【问题描述】:

我创建了一个 Form ,我在其中使用了 Textfields 和 TextField Dropdown。当我滚动 TableView 文本字段值时,文本字段值会丢失,并且文本字段下拉值会自动设置为默认值。

这是我的代码:

    enum TextFieldTags:Int 
        case ProspectName = 10, VisitDate, VisitType, Industry, Source, DissSummary, Requirements, FollowUpDate, AnticipatedDealValue, Probability, ExpectedClosingDate, Status, NextStep, Emp, Rev, AddressLine1, AddressLine2, Country, States, City, Zip, Mobile,Fax
    

    var prospectName:String?, visitDate:String? , visitType:String?, industry:String?, source:String?, dissSummary:String?, requirements:String?, followUpDate:String?, anticipatedDealValue:String?, probability:String?, expectedClosingDate:String?, status:String?, nextStep:String?, emp:String?, rev:String?, addressLine1:String?, addressLine2:String?, country, statesVal:String?, cityVal:String?, zip:String?, mobile:String?,fax:String?

    var currentLeadMaster:WizLeadMaster?

    // MARK: - Section Data Structure

    struct Section 
        var name: String!
        var options: [Any]!
        var collapsed: Bool!
        var optionItem:[Any]!
        var icon:UIImage!


        init(name: String, options: [Any], collapsed: Bool = false,optionItem:[Any],icon:UIImage) 

            self.name = name
            self.options = options

            self.collapsed = collapsed

            self.optionItem = optionItem
            self.icon = icon


        
    

    // MARK: - Properties

    @IBOutlet weak var tblLeadDetail:UITableView?

    var dataSource:NSArray  = NSArray(objects: "Lead Summary","Address","Products","Contacts","Attachment","Follow Up","Remarks")

    var sections = [Section]()

    lazy var visitArr:NSArray = 

        let visitTypes = Handler.fetchLookUpOftype(type: LookUpType.enum_cRMVisittype)

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizLookUp

            let dict = ["system":(visitModel.system! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)

        
        return optionsArray as NSArray
    ()


    lazy var leadStatus:NSArray = 

        let visitTypes = Handler.fetchLookUpOftype(type: LookUpType.enum_cRMLeadStatus)

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizLookUp

            let dict = ["system":(visitModel.system! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)

        
        return optionsArray as NSArray
    ()


    lazy var leadSource:NSArray = 

        let visitTypes = Handler.fetchLookUpOftype(type: LookUpType.enum_cRMLeadsource)

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizLookUp

            let dict = ["system":(visitModel.system! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)

        
        return optionsArray as NSArray
    ()


    lazy var industries:NSArray = 

        let visitTypes = Handler.fetchLookUpOftype(type: LookUpType.enum_cRMCustomerType)

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizLookUp

            let dict = ["system":(visitModel.system! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)
        
        return optionsArray as NSArray
    ()

    lazy var countries:NSArray = 

        let visitTypes = Handler.countriesList()

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizCountryList

            let dict = ["system":(visitModel.lable! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)

        
        return optionsArray as NSArray
    ()

    lazy var states:NSArray = 

        let visitTypes = Handler.fetchLookUpOftype(type: LookUpType.enum_cRMTerritory)

        var optionsArray = NSMutableArray(capacity: 0)

        for visitType in visitTypes 

            let visitModel = visitType as! WizLookUp

            let dict = ["system":(visitModel.system! as String),"value":(visitModel.value! as String)]

            optionsArray.add(dict as NSDictionary)

        
        return optionsArray as NSArray
    ()



    func setStatesForCountry(_ country:String) -> Void



    


    func filterValue(_ arr:NSArray,key:String) -> Any 

        let predicate = NSPredicate(format: "self == %@", key)
        let filteredArr = arr.filtered(using: predicate)

        if (filteredArr.count == 0) 
            return ""
        

        return filteredArr[0]
    

    // MARK: - ViewDidLoad

    override func viewDidLoad() 

        super.viewDidLoad()

        let barButtonItem = UIBarButtonItem(image:UIImage(named:"save1") , style: UIBarButtonItemStyle.done, target: self, action: #selector(saveLeadInfo))

        navigationItem.rightBarButtonItem = barButtonItem

        sections = [

            Section(name: "Lead Summary", options: [["title":"Prospect Name","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.customer_name as Any,"tag":TextFieldTags.ProspectName.rawValue],
                                                    ["title":"Visit Date","type":"date","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.visited_date as Any,"tag":TextFieldTags.VisitDate.rawValue],
                                                    ["title":"Visit Type","type":"dropdown","options":visitArr.value(forKeyPath: "system") as! Array<String>,"value":filterValue(visitArr.value(forKeyPath: "value") as! NSArray, key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.visit_type)!),"tag":TextFieldTags.VisitType.rawValue],
                                                    ["title":"Industry","type":"dropdown","options":industries.value(forKeyPath: "system"),"value":filterValue(industries.value(forKeyPath: "value") as! NSArray, key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.type)!),"tag":TextFieldTags.Industry.rawValue],
                                                    ["title":"Source","type":"dropdown","options":leadSource.value(forKeyPath: "system"),"value":filterValue(leadSource.value(forKeyPath: "value") as! NSArray , key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.source)!),"tag":TextFieldTags.Source.rawValue],
                                                    ["title":"Discussion Summary","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.summary_disc as Any,"tag":TextFieldTags.DissSummary.rawValue],
                                                    ["title":"Requirements","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.requirements as Any ,"tag":TextFieldTags.Requirements.rawValue],
                                                    ["title":"Follow-Up Date","type":"date","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.followup_date as Any,"tag":TextFieldTags.FollowUpDate.rawValue],
                                                    ["title":"Anticipated Deal Value","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.anticipated_deal_value as Any,"tag":TextFieldTags.AnticipatedDealValue.rawValue],
                                                    ["title":"Probabilty","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.probability as Any,"tag":TextFieldTags.Probability.rawValue],
                                                    ["title":"Expected Closing Date","type":"date","value":currentLeadMaster?.expected_closing_date as Any,"tag":TextFieldTags.ExpectedClosingDate.rawValue],
                                                    ["title":"Status","type":"dropdown","options":leadStatus.value(forKeyPath: "system"),"value":filterValue(leadStatus.value(forKeyPath: "value") as! NSArray, key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.status)!),"tag":TextFieldTags.Status.rawValue],
                                                    ["title":"Next Step","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.next_activity_planned as Any,"tag":TextFieldTags.NextStep.rawValue],
                                                    ["title":"Emp","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.no_of_emp as Any,"tag":TextFieldTags.Emp.rawValue],
                                                    ["title":"Rev","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.annual_revenue as Any,"tag":TextFieldTags.Rev.rawValue]], collapsed: true, optionItem: [],icon:UIColor.clear.getImage(size: CGSize(width: 30, height: 30))),
            Section(name: "Address", options: [["title":"Address Line 1","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline1 as Any ,"tag":TextFieldTags.AddressLine1.rawValue],
                                               ["title":"Address Line 2","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline2 as Any,"tag":TextFieldTags.AddressLine2.rawValue],
                                               ["title":"Select Country","type":"dropdown","options":countries.value(forKeyPath: "system") as! Array<String>,"value":filterValue(visitArr.value(forKeyPath: "value") as! NSArray, key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.visit_type)!),"tag":TextFieldTags.Country.rawValue],
                                               ["title":"Select States","type":"dropdown","options":visitArr.value(forKeyPath: "system") as! Array<String>,"value":filterValue(visitArr.value(forKeyPath: "value") as! NSArray, key: ((currentLeadMaster == nil) ? "":currentLeadMaster?.visit_type)!),"tag":TextFieldTags.States.rawValue],
                                               ["title":"Select City","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline2 as Any,"tag":TextFieldTags.City.rawValue],
                                               ["title":"Zip Code","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline2 as Any,"tag":TextFieldTags.Zip.rawValue],
                                               ["title":"Mobile","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline2 as Any,"tag":TextFieldTags.Mobile.rawValue],
                                               ["title":"Fax","type":"input","value":(currentLeadMaster == nil) ? "":currentLeadMaster?.addressline2 as Any,"tag":TextFieldTags.Fax.rawValue]
                                                    ], collapsed: true, optionItem: [],icon:UIColor.clear.getImage(size: CGSize(width: 30, height: 30))),
            Section(name: "Product", options: [], collapsed: true, optionItem: [],icon:UIImage.init(named: "add")!),
            Section(name: "Contacts", options: [], collapsed: true, optionItem: [],icon:UIImage.init(named: "add")!),
            Section(name: "Attachment", options: [], collapsed: true, optionItem: [],icon:UIImage.init(named: "attach")!),
        ]

        let headerNib = UINib(nibName: "WizHeaderView", bundle: nil)
        tblLeadDetail?.register(headerNib, forHeaderFooterViewReuseIdentifier: "headerLeads")

        let textInputCell = UINib(nibName: "TextInputCell", bundle: nil)
        tblLeadDetail?.register(textInputCell, forCellReuseIdentifier: "textinputcell")

        let dropDownInputCell = UINib(nibName: "DropDownInputCell", bundle: nil)
        tblLeadDetail?.register(dropDownInputCell, forCellReuseIdentifier: "dropDownInputCell")

        let dropDownDateCell = UINib(nibName: "DropDownDateCell", bundle: nil)
        tblLeadDetail?.register(dropDownDateCell, forCellReuseIdentifier: "dropDownDateCell")

        tblLeadDetail?.reloadData()

    


    func saveLeadInfo() -> Void 

    

    // MARK: - TableView Delegates & Datasource

    func numberOfSections(in tableView: UITableView) -> Int 
        return sections.count
    


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 

        let optionsArray = sections[section].options

        return optionsArray!.count
    


    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

        let optionsArray = sections[indexPath.section].options

        let dict = optionsArray?[indexPath.row] as! Dictionary<String,Any>

        if (dict["type"] as! String  == "dropdown") 

            let cell = tableView.dequeueReusableCell(withIdentifier: "dropDownInputCell") as! DropDownInputCell


            cell.txtInput.isOptionalDropDown = false
            cell.txtInput.itemList = dict["options"] as! [String]
            cell.lblText.text = dict["title"] as? String

            cell.txtInput.selectedItem = dict["value"] as? String


            cell.txtInput.tag = (dict["tag"] as? NSInteger)!
            cell.backgroundColor = UIColor.clear
            cell.selectionStyle = UITableViewCellSelectionStyle.none
            cell.delegate = self
            return cell
        
        else if (dict["type"] as! String  == "input") 

            let cell = tableView.dequeueReusableCell(withIdentifier: "textinputcell") as! TextInputCell
            cell.lblText.text = dict["title"] as? String
            cell.selectionStyle = UITableViewCellSelectionStyle.none
            cell.backgroundColor = UIColor.clear
            cell.txtInput.text = dict["value"] as? String
             cell.txtInput.tag = (dict["tag"] as? NSInteger)!
            cell.delegate = self
            return cell
        
        else

            let cell = tableView.dequeueReusableCell(withIdentifier: "dropDownDateCell") as! DropDownDateCell
            cell.lblText.text = dict["title"] as? String
            cell.txtInput.dropDownMode = IQDropDownMode.datePicker

            if dict["value"] as? String == "" 
               cell.txtInput.setDate(Date(), animated: true)
            
            else
               cell.txtInput.selectedItem = dict["value"] as? String
            

            cell.txtInput.tag = (dict["tag"] as? NSInteger)!
            cell.selectionStyle = UITableViewCellSelectionStyle.none
            cell.backgroundColor = UIColor.clear
            cell.delegate = self
            return cell
        

    


    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        tableView.deselectRow(at: indexPath, animated: true)
//        if indexPath.row == 0 
//            performSegue(withIdentifier: "leadsummary", sender:nil)
//        
//        else
//            performSegue(withIdentifier: "leadaddress", sender:nil)
//        
    


    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 

        let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "headerLeads") as? WizHeaderView ?? WizHeaderView(reuseIdentifier: "headerLeads")

        header.delegate = self

        header.lblLeadSummary.text = sections[section].name
        header.btnArrowButton.setImage(sections[section].icon, for: UIControlState.normal)
        header.setCollapsed(sections[section].collapsed)

        header.section = section

        return header
    



    func toggleSection(_ header: WizHeaderView, section: Int) 

        let collapsed = !sections[section].collapsed

        // Toggle collapse
        sections[section].collapsed = collapsed
        header.setCollapsed(collapsed)

        // Adjust the height of the rows inside the section
        tblLeadDetail?.beginUpdates()
        for i in 0 ..< sections[section].options.count 
            tblLeadDetail?.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
        
        tblLeadDetail?.endUpdates()
    


    @IBAction func placeOrder(_ sender: Any) 

        let placeOrder = MainStoryBoard.instantiateViewController(withIdentifier: "PlaceOrder")
        navigationController?.pushViewController(placeOrder, animated: true)
    


    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 
        return 35.0
    


    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
        return 10.0
    


    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return sections[(indexPath as NSIndexPath).section].collapsed! ? 0 : 70.0
    


    func cellTextField(_with TextField: IQDropDownTextField, didSelectItem item: String?) 

        switch TextField.tag 
            case TextFieldTags.VisitType.rawValue:
                 visitType = item
            break
            case TextFieldTags.Industry.rawValue:
                industry = item
            break
            case TextFieldTags.Source.rawValue:
                source = item
            break
            case TextFieldTags.Status.rawValue:
                status = item
            break
            case TextFieldTags.Country.rawValue:
                country = item
            break
            case TextFieldTags.States.rawValue:
                statesVal = item
            break
            default: break
        
    


    func cellTextFieldDidEndEditing(_with textField: UITextField) 

        switch textField.tag 
        case TextFieldTags.ProspectName.rawValue:
            prospectName = textField.text
            break
        case TextFieldTags.DissSummary.rawValue:
            dissSummary = textField.text
            break
        case TextFieldTags.Requirements.rawValue:
            requirements = textField.text
            break
        case TextFieldTags.AnticipatedDealValue.rawValue:
            anticipatedDealValue = textField.text
            break
        case TextFieldTags.Probability.rawValue:
            probability = textField.text
            break
        case TextFieldTags.NextStep.rawValue:
            nextStep = textField.text
            break
        case TextFieldTags.Emp.rawValue:
            emp = textField.text
            break
        case TextFieldTags.Rev.rawValue:
            rev = textField.text
            break
        case TextFieldTags.AddressLine1.rawValue:
            addressLine1 = textField.text
            break
        case TextFieldTags.AddressLine2.rawValue:
            addressLine2 = textField.text
            break
        case TextFieldTags.City.rawValue:
            cityVal = textField.text
            break
        case TextFieldTags.Mobile.rawValue:
            mobile = textField.text
            break
        case TextFieldTags.Fax.rawValue:
            fax = textField.text
            break
        case TextFieldTags.Zip.rawValue:
            zip = textField.text
            break
        default: break

        

    


    func stringFromDate(_ date:Date) -> String 

        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.string(from: date)
    


    func cellTextFieldDate(_with textField: IQDropDownTextField, didSelect date: Date?) 

        switch textField.tag 
        case TextFieldTags.VisitDate.rawValue:
            visitDate = stringFromDate(date!)
            break
        case TextFieldTags.FollowUpDate.rawValue:
            followUpDate = stringFromDate(date!)
            break
        case TextFieldTags.ExpectedClosingDate.rawValue:
            expectedClosingDate = stringFromDate(date!)
            break
        default: break

        

    

extension UIColor 
    func getImage(size: CGSize) -> UIImage 
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image(actions:  rendererContext in
            self.setFill()
            rendererContext.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
        )
    

【问题讨论】:

您是否尝试过在您的UITableViewCells 中覆盖prepareForReuse() 并在出列之前清除其数据?:S 您是否实现了 UITextField 的委托方法(如 textFieldDidBeginEditing)以将您在特定文本字段中收到的任何输入存储到您的数据源? 【参考方案1】:

使用UITableView with reusable cell identifier 时,您必须在编辑完成后将UITextField 或任何其他输入字段的输入存储到您用来填充所有单元格的数据源中。因为可重用单元管理内存本身以绘制所有控件,所以当您滚动表格视图时,不能保证您的数据保持原样。

【讨论】:

【参考方案2】:

在这个if - else 表达式中

if dict["value"] as? String == "" 
   cell.txtInput.setDate(Date(), animated: true)
 else
   cell.txtInput.selectedItem = dict["value"] as? String

您正在根据字典值设置两个不同 UI 元素。 随着单元格的重复使用,这些 UI 状态将一直保留到下次更改为止。

您必须确保将每个 UI 元素设置为已定义状态以避免意外行为,因此您也需要设置其他 UI 元素。例如([default value] 只是一个占位符)。

if dict["value"] as? String == "" 
   cell.txtInput.setDate(Date(), animated: true)
   cell.txtInput.selectedItem = [default value]
 else
   cell.txtInput.setDate([default value], animated: [default value])
   cell.txtInput.selectedItem = dict["value"] as? String

如果没有唯一的default value,则必须向数据源添加属性以保持日期和所选项目的当前状态。

关于文本字段,您必须在更改文本后更新数据源,因为在cellForRow 中,文本字段总是从数据源更新。

并且 - 一如既往 - 不要在 Swift 中使用 NS(Mutable)ArrayNS(Mutable)Dictionary。您丢弃了重要的类型信息,可变集合类型与 Swift 原生类型无关。最后,您可以删除switch 表达式中的所有break 行(default 之后除外)。它们在 Swift 中是多余的。

【讨论】:

dict["value"] 不能为空。因为我已经在我的结构中设置了值 如果不能为空,为什么还要检查? 你可以看到问题的代码,我没有对cellForRowAtIndexPath中的dict["value"]做任何检查。 拥有:在最后的else 子句中(创建DropDownDateCell)。 不工作!我做了你上面建议的所有事情

以上是关于UITableViewCell 在滚动时重复并丢失其数据的主要内容,如果未能解决你的问题,请参考以下文章

滚动后 UITableViewCell 标签发生变化[重复]

UITableViewCell 图像在滚动时消失

在屏幕上滚动和返回时如何保留 UITableViewCell 的选定状态

键盘出现时如何滚动 UITableviewcell 向上滚动? [复制]

如何防止 UITableViewCell 重复和重用?

在插入 uitableViewCell 时保持滚动