Swift UIStepper и UITextField внутри текста UITableView перекрываются и не могут синхронизироваться

Мне нужны UITextField и UIStepper внутри ячейки и обновлять оба, когда одно из двух меняет значение. Я достигаю своей цели также с помощью tableviewcontroller, но теперь у меня две проблемы:

1. Чтобы синхронизировать значение элементов, я создаю два массива, которые использую для хранения идентификатора тега каждой строки. Чтобы быть уверенным, что все теги уникальны, я начинаю считать с 800 для степпера и с 900 для текстового поля. Проблема в том, что эти значения всегда дублируются, когда я добавляю новую строку в таблицу. Например, если я добавляю новую строку, вместо того, чтобы найти 802 внутри массива, я нахожу 802 и 803, поэтому я не могу четко определить текст, назначенный каждому степперу.

2 - когда я обновляю значение с помощью степпера, текст в текстовом поле перекрывается с предыдущим текстом, как показано на этом изображении:

введите здесь описание изображения

Это происходит только для значения, добавленного во время создания UITable, если я добавляю значение в последнее время, этой проблемы не возникает.

Я четко понимаю, что все проблемы связаны с повторным использованием ячеек, которое Swift делает из соображений производительности, но я не могу найти решение. Мой код ниже:

    class barcodeInfo: Codable {

    var code = ""
    var quantity = 1

    init(code: String, quantity : Int) {
        self.code = code
        self.quantity = quantity
    }
}


class BarcodeTableViewController: UITableViewController, UITextFieldDelegate {
    var barcodeList = [barcodeInfo]()
    var QuantityMapping = [Int: Int]()
    var currentStepperTag = 900
    var currentQtyTextTag = 800

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.rowHeight = 50;
        self.tableView.dataSource = self
        self.tableView.delegate = self
        barcodeList = [barcodeInfo(code:"test", quantity: 4),barcodeInfo(code:"test1", quantity: 10)]
    }


    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        var isBackSpace = Int32()

        if let char = string.cString(using: String.Encoding.utf8) {
            isBackSpace = strcmp(char, "\\b")
        }
        if (isBackSpace != -92) {
            if string.rangeOfCharacter(from: NSCharacterSet.decimalDigits) == nil {
                return false
            }
        }

        guard let textFieldText = textField.text,
            let rangeOfTextToReplace = Range(range, in: textFieldText) else {
                return false
        }
        let substringToReplace = textFieldText[rangeOfTextToReplace]
        let count = textFieldText.count - substringToReplace.count + string.count
        return count <= 5
    }



    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        print(barcodeList.count)
        return barcodeList.count
    }

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

        let currentBarcode = barcodeList[indexPath.row]
        print(currentBarcode)

        let cell = tableView.dequeueReusableCell(withIdentifier: "barcodeCell", for: indexPath) as UITableViewCell
        cell.selectionStyle = .none

        // cell.allowsSelection = false
        cell.textLabel?.text = currentBarcode.code

        let customStepper = UIStepper (frame:CGRect(x: self.view.frame.width - 225 , y: 5, width: 100, height: 20))
        //let customStepper = UIStepper()
        customStepper.autorepeat = true
        // Add a function handler to be called when UIStepper value changes
        customStepper.addTarget(self, action: #selector(stepperValueChanged(_:)), for: .valueChanged)
        customStepper.tag = currentStepperTag
        customStepper.maximumValue = 99999
        customStepper.minimumValue = 1
        customStepper.wraps = false
        customStepper.value = Double(currentBarcode.quantity)
        cell.contentView.addSubview(customStepper)

        customStepper.translatesAutoresizingMaskIntoConstraints = false
        customStepper.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
        customStepper.widthAnchor.constraint(equalToConstant: 94).isActive = true
        customStepper.heightAnchor.constraint(equalToConstant: 29).isActive = true
        customStepper.rightAnchor.constraint(equalTo: cell.rightAnchor, constant: -30).isActive = true

        let quantityTextField = UITextField(frame: CGRect(x: self.view.frame.width - 300, y: 5, width: 50, height: 20))

        //let quantityTextField = UITextField()
        quantityTextField.delegate = self
        quantityTextField.smartInsertDeleteType = UITextSmartInsertDeleteType.no
        quantityTextField.tag = currentQtyTextTag
        quantityTextField.text = String(Int(customStepper.value))
        cell.contentView.addSubview(quantityTextField)

        quantityTextField.translatesAutoresizingMaskIntoConstraints = false
        quantityTextField.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
        quantityTextField.widthAnchor.constraint(equalToConstant: 60).isActive = true
        quantityTextField.heightAnchor.constraint(equalToConstant: 21).isActive = true
        quantityTextField.rightAnchor.constraint(equalTo: cell.rightAnchor, constant: -125).isActive = true

        QuantityMapping[currentStepperTag] = currentQtyTextTag
        print("currentQtyTextTag is now \(Int(currentQtyTextTag))")
        print("currentStepperTag is now \(Int(currentStepperTag))")
        currentQtyTextTag += 1
        currentStepperTag += 1

        return cell
    }


    @IBAction private func stepperValueChanged(_ sender:UIStepper!){
        print(QuantityMapping)
        var tagDecrement = 800
        let textTag = QuantityMapping[sender.tag] ?? 0
        if let currentQuantityTextField = self.view.viewWithTag(textTag) as? UITextField {
            currentQuantityTextField.text = String(Int(sender.value))
            if(textTag >= 900){
                                 tagDecrement = 900
                             }
        //let itmIdx = ((textTag - tagDecrement)-2)
          let  itmIdx = 1
                  self.barcodeList[itmIdx].quantity = Int(sender.value)
        }
        print("sender tag: \(Int(sender.tag))")
        print("textTag \(Int(textTag))")
        print("UIStepper is now \(Int(sender.value))")
    }

person Marco    schedule 09.11.2019    source источник
comment
Учтите, что каждый вызов cellForRowAt — а этот метод вызывается очень часто — добавляет новый степпер и текстовое поле в повторно используемую ячейку. Настоятельно рекомендуется проектировать пользовательский интерфейс как пользовательскую ячейку прототипа в Interface Builder.   -  person vadian    schedule 09.11.2019
comment
Для каждого addSubview в вашей ячейке вы должны либо проверить, существует ли он уже, и просто изменить его, но не добавлять снова, либо всегда удалять старый перед его добавлением.   -  person Chris    schedule 09.11.2019


Ответы (1)


Таким образом, решение для вашего текстового просмотра может быть

cell.contentview.subviews.map(if $0.isKindOf(UITextView.self) { $0.removeFromSuperview() })

затем сделайте свой addSubview (textView) после этого

Сделайте это, хотя для других добавлены подпредставления - с правильной проверкой типа, конечно

person Chris    schedule 09.11.2019
comment
Большое спасибо! это работает! только вопрос, не контрпродуктивно ли удалять и пересоздавать все компоненты каждый раз при перерисовке ячейки!? есть лучший способ справиться с этим? например, поддерживать индекс и обновлять ячейку, если она существует? - person Marco; 09.11.2019
comment
да, было бы лучше, если бы вы просто изменили существующие подпредставления. вот так: cell.contentview.subviews.map(if $0.isKindOf(UITextView.self) {$0.text = newText}) - person Chris; 09.11.2019