You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift中TableView单元格TextField与TextView重叠及动态添加问题

Fixing Overlapping TextField/TextView in TableView Cells + Dynamic Addition

Got it, let's fix this step by step. The core issue here is that you're looping through your entire array in cellForRowAt, which adds every control type to every cell—causing that overlapping mess. We'll restructure the data and cell logic to make each cell show only one control, plus add the dynamic button functionality.

1. Create a Clear Data Model

First, replace your separate widgetInstockArray and labelInstockArray with a single array of structured items. This lets each row know exactly what control to show and what placeholder to use:

enum InputControlType {
    case textField
    case textView
}

struct InputRowItem {
    let controlType: InputControlType
    let placeholder: String
}

// Initialize your data array (replace with your existing values)
var inputRowItems: [InputRowItem] = []

2. Build a Custom TableView Cell

Make a custom cell that contains both a TextField and TextView, but keeps them hidden by default. This way we can toggle visibility based on the row's data, and avoid re-adding controls every time a cell is reused.

class InputTableViewCell: UITableViewCell {
    // Add these via Interface Builder or programmatically
    let textField: UITextField = {
        let tf = UITextField()
        tf.borderStyle = .roundedRect
        // Add constraints to pin it to cell's contentView edges (adjust padding as needed)
        tf.translatesAutoresizingMaskIntoConstraints = false
        return tf
    }()
    
    let textView: UITextView = {
        let tv = UITextView()
        tv.layer.borderWidth = 1
        tv.layer.borderColor = UIColor.systemGray4.cgColor
        tv.layer.cornerRadius = 8
        // Add same constraints as textField to ensure they overlap perfectly
        tv.translatesAutoresizingMaskIntoConstraints = false
        return tv
    }()
    
    // Optional: Add placeholder support for TextView (since it doesn't have a native one)
    private let textViewPlaceholderLabel: UILabel = {
        let label = UILabel()
        label.textColor = .systemGray
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
        setupTextViewPlaceholderObserver()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
        setupTextViewPlaceholderObserver()
    }
    
    private func setupViews() {
        contentView.addSubview(textField)
        contentView.addSubview(textView)
        contentView.addSubview(textViewPlaceholderLabel)
        
        // Pin textField to contentView
        NSLayoutConstraint.activate([
            textField.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
        ])
        
        // Pin textView to same bounds as textField
        NSLayoutConstraint.activate([
            textView.topAnchor.constraint(equalTo: textField.topAnchor),
            textView.leadingAnchor.constraint(equalTo: textField.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: textField.trailingAnchor),
            textView.bottomAnchor.constraint(equalTo: textField.bottomAnchor)
        ])
        
        // Pin placeholder label inside textView
        NSLayoutConstraint.activate([
            textViewPlaceholderLabel.topAnchor.constraint(equalTo: textView.topAnchor, constant: 8),
            textViewPlaceholderLabel.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 8)
        ])
        
        // Hide both controls initially
        textField.isHidden = true
        textView.isHidden = true
    }
    
    private func setupTextViewPlaceholderObserver() {
        textView.addObserver(self, forKeyPath: "text", options: .new, context: nil)
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "text" {
            textViewPlaceholderLabel.isHidden = !textView.text.isEmpty
        }
    }
    
    // Call this to configure the cell with a row item
    func configure(with item: InputRowItem) {
        // Hide both controls first to avoid reuse issues
        textField.isHidden = true
        textView.isHidden = true
        
        switch item.controlType {
        case .textField:
            textField.isHidden = false
            textField.placeholder = item.placeholder
        case .textView:
            textView.isHidden = false
            textViewPlaceholderLabel.text = item.placeholder
            textViewPlaceholderLabel.isHidden = !textView.text.isEmpty
        }
    }
    
    deinit {
        textView.removeObserver(self, forKeyPath: "text")
    }
}

3. Fix cellForRowAt Logic

Now, instead of looping through your entire array, just grab the item for the current row and configure the cell accordingly:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "InputCell", for: indexPath) as? InputTableViewCell else {
        return UITableViewCell()
    }
    
    let rowItem = inputRowItems[indexPath.row]
    cell.configure(with: rowItem)
    
    return cell
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return inputRowItems.count
}

4. Implement Dynamic Add Functionality

When your button is tapped, add a new InputRowItem to your data array and update the table view (using insertRows is more efficient than full reload):

@IBAction func addControlButtonTapped(_ sender: UIButton) {
    // Example: Add a TextField by default, adjust type/placeholder as needed
    let newItem = InputRowItem(controlType: .textField, placeholder: "New Input")
    inputRowItems.append(newItem)
    
    // Insert the new row with animation
    let newIndexPath = IndexPath(row: inputRowItems.count - 1, section: 0)
    tableView.insertRows(at: [newIndexPath], with: .automatic)
}

5. Key Notes

  • Cell Reuse: Always hide all controls before showing the correct one—this prevents old controls from sticking around when cells are reused.
  • TextView Placeholder: Since UITextView doesn't have a native placeholder, we added a label and observer to mimic it. You can adjust this logic to fit your design.
  • Data Initialization: Populate your inputRowItems array with your existing widgetInstockArray and labelInstockArray values (map each pair to an InputRowItem).

内容的提问来源于stack exchange,提问作者iOS Developer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:58:41