Search code examples
iosswiftuitableviewnsindexpath

Widgets overlap each other in the cells of table view swift ios


I am getting widgets either textField or textView in cells of table view based on the provided elements in the array widgetInstockArray. And getting placeholder for the widgets from the labelInstcokArray

The problem : The Cell appears with both widgets overlaped, the textField is enclosed within textView on every cell.

What I want : There should be a textField or textView in each cell but not both enclosed & overlapped simultaneously.

Here is what i have done so far:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {

var widgetInstockArray = [String]()
var labelInstockArray = [String]()
var idInstockArray = [IntegerLiteralType]()
var newLabel = UITextField()
var newTextView = UITextView()
let wTextField = "textField"
let wTextView = "textView"

@IBOutlet weak var firstTableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()
    widgetInstockArray = [wTextView, wTextField, wTextView, wTextField, wTextField, wTextView, wTextField]
    idInstockArray = [1,2,3,4,5,6,7]
    labelInstockArray = ["1ok","2ok","3ok","4ok","5ok","6ok","7ok"]
    firstTableView.delegate = self
    firstTableView.dataSource = self
}

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

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 60
}

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var Cell : UITableViewCell?

        var instockTag : Int = 1
        Cell = firstTableView.dequeueReusableCell(withIdentifier: "firstCell")! as UITableViewCell
        if widgetInstockArray.count == 0 && labelInstockArray.isEmpty {
            print("no textFields in instock")
        }
        else {
            print("widget process i in winstock loop")
            for i in widgetInstockArray {
                print("widget process i in winstock loopok so here is \(i)")
                let indexOfA = widgetInstockArray.index(of: i)
                print("her is the index of i of widgets \(String(describing: indexOfA))")
                if i.contains(wTextField)  {
                    print("textField was placed")
                    newLabel = UITextField(frame: CGRect(x: 10, y: 10, width: 200, height: 30))
                    newLabel.textColor = UIColor.gray
                    newLabel.tag = instockTag
                    print("You have a instock textLabel with tag \(newLabel.tag)")
                    if labelInstockArray.isEmpty || idInstockArray.count != labelInstockArray.count {
                        newLabel.text = " "
                    } else {
                        newLabel.placeholder = labelInstockArray[indexPath.row]
                        newLabel.layer.cornerRadius = 3
                        newLabel.layer.borderColor = UIColor.gray.cgColor
                        newLabel.layer.borderWidth = 1
                        instockTag += 1
                        print("\(widgetInstockArray.count)")
                        Cell?.contentView.addSubview(newLabel)
                    }
                }

                if i.contains(wTextView) {
                    print("textView was placed")
                    newTextView = UITextView(frame: CGRect(x:10, y: 10, width: 200, height: 60))
                    newTextView.textAlignment = NSTextAlignment.justified
                    newTextView.textColor = UIColor.black
                    newTextView.backgroundColor = UIColor.white
                    newTextView.font = UIFont.systemFont(ofSize: 15)
                    newTextView.tag = instockTag
                    print("You have a instock textView with tag \(newTextView.tag)")
                    if labelInstockArray.isEmpty || idInstockArray.count != labelInstockArray.count {
                    } else {
                        //newTextView.text = labelInstockArray[indexPath.row]
                        newTextView.layer.cornerRadius = 3
                        newTextView.layer.borderColor = UIColor.gray.cgColor
                        newTextView.layer.borderWidth = 1
                        instockTag += 1
                        print("\(widgetInstockArray.count)")
                        Cell?.contentView.addSubview(newTextView)
                    }
                }
            }
            print("could not add widgets")
        }
    return Cell!
    }

func textViewDidBeginEditing(_ textView: UITextView) {
    if newTextView.textColor == UIColor.lightGray {
        newTextView.text = nil
        newTextView.textColor = UIColor.black
    }
}

func textViewDidEndEditing(_ textView: UITextView) {
    if newTextView.text.isEmpty {
        for i in labelInstockArray {
        newTextView.text = i
        }
        newTextView.textColor = UIColor.lightGray
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
}

Output is overlapped :(
The textFiled is within the textview, both appear in a single cell. But I want they should not overlap and there should be only one widget in each cell.

enter image description here


UPDATE according to THIS ANSWER

What if i want to add widgets on a button click so live the widgets should be added on each click.

here is what i have done

var idWidgetIs = IntegerLiteralType()
var labelWidget = String()
var widgetName = String()

let stockArray: [StockItem] = [ StockItem(id: idWidgetIs, label: labelWidget, widget: widgetName)]

    @IBAction func textViewAct(_ sender: Any) {
    idWidgetIs += 1
    labelWidget = "placeholder"
    StockItem.init(id: idWidgetIs, label: labelWidget, widget: .textView)
    }

    @IBAction func textFieldAct(_ sender: Any) {
    idWidgetIs += 1
    labelWidget = "placeholder"
    StockItem.init(id: idWidgetIs, label: labelWidget, widget: .textField)
   }

Solution

  • As identified in this answer:

    • every time the table view requests a cell, a new text field / text view is created
    • looping through widgetInStockArray when creating a cell will result in multiple text fields and text views being added to that cell

    The sample code from the question can be restructured and improved as follows to achieve the desired result.


    Restructure stock information

    Define two types for more convenient access to the id, label and widget of a single stock item.

    enum StockWidget: String {
        case textField
        case textView
    }
    
    struct StockItem {
        let id: IntegerLiteralType
        let label: String
        let widget: StockWidget
    }
    

    Update the sample data accordingly.

    let stockArray: [StockItem] = [
        StockItem(id: 1, label: "1ok", widget: .textView),
        StockItem(id: 2, label: "2ok", widget: .textField),
        StockItem(id: 3, label: "3ok", widget: .textView),
        StockItem(id: 4, label: "4ok", widget: .textField),
        StockItem(id: 5, label: "5ok", widget: .textField),
        StockItem(id: 6, label: "6ok", widget: .textView),
        StockItem(id: 7, label: "7ok", widget: .textField)
    ]
    

    Define specific cells

    One with a UITextField:

    class TextFieldWidgetCell: UITableViewCell {
    
        let textField: UITextField
    
        override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
            let field = UITextField(frame: CGRect(x: 10, y: 10, width: 200, height: 30))
            field.layer.borderColor = UIColor.gray.cgColor
            field.layer.borderWidth = 1.0
            field.layer.cornerRadius = 3.0
            field.textColor = UIColor.gray
            textField = field
    
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            contentView.addSubview(textField)
        }
    
        override func prepareForReuse() {
            super.prepareForReuse()
    
            textField.placeholder = nil
            textField.text = nil
        }
    
    }
    

    and one with a UITextView:

    class TextViewWidgetCell: UITableViewCell {
    
        let textView: UITextView
    
        override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
            let view = UITextView(frame: CGRect(x:10, y: 10, width: 200, height: 60))
            view.backgroundColor = UIColor.white
            view.font = UIFont.systemFont(ofSize: 15)
            view.layer.borderColor = UIColor.gray.cgColor
            view.layer.borderWidth = 1.0
            view.layer.cornerRadius = 3.0
            view.textAlignment = NSTextAlignment.justified
            view.textColor = UIColor.black
            textView = view
    
            super.init(style: style, reuseIdentifier: reuseIdentifier)
    
            contentView.addSubview(textView)
        }
    
        override func prepareForReuse() {
            super.prepareForReuse()
    
            textView.text = nil
        }
    
    }
    

    Register the widget cells with the table view.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        firstTableView.dataSource = self
        firstTableView.delegate = self
    
        firstTableView.register(TextFieldWidgetCell.self, forCellReuseIdentifier: StockWidget.textField.rawValue)
        firstTableView.register(TextViewWidgetCell.self, forCellReuseIdentifier: StockWidget.textView.rawValue)
    }
    

    Adapt UITableViewDataSource implementation

    Now that the cell configuration is split off into two UITableViewCell subclasses, the implementation of tableView(_, cellForRowAt:) can be reduced a great deal.

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return stockArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let stockItem = stockArray[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: stockItem.widget.rawValue)
    
        switch stockItem.widget {
        case .textField:
            if let cell = cell as TextFieldWidgetCell {
                cell.textField.placeholder = stockItem.label
            }
        case .textView:
            if let cell = cell as TextViewWidgetCell {
                cell.textView.text = stockItem.label
            }
        }
    
        return cell
    }