Использование программно созданного подкласса UITableViewCell в Swift

Я работаю над табличным представлением с пользовательской ячейкой, которая была создана программно, без использования IB. Я посмотрел в Google и спросил в NSChat, но я все еще не могу заставить его работать. Он просто отображает представление таблицы по умолчанию. Заранее спасибо!

EventCell.swift

import UIKit

class EventCell: UITableViewCell {

    var eventName: UILabel = UILabel()
    var eventCity: UILabel = UILabel()
    var eventTime: UILabel = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        self.contentView.addSubview(eventName)
        self.contentView.addSubview(eventCity)
        self.contentView.addSubview(eventTime)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        eventName = UILabel(frame: CGRectMake(20, 10, self.bounds.size.width - 40, 25))
        eventCity = UILabel(frame: CGRectMake(0, 0, 0, 0))
        eventTime = UILabel(frame: CGRectMake(0, 0, 0, 0))

    }

}

ViewController.swift

class ViewController: UITableViewController, UITableViewDelegate {

    var events: Dictionary<String, [String]> = ["0": ["Monroe Family", "La Cañada", "8:30"]]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self;
        tableView.delegate = self;

        tableView.registerClass(EventCell.self, forCellReuseIdentifier: "EventCell")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

    }

    override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return UITableViewAutomaticDimension;
    }

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        var cellIdendifier: String = "EventCell"

        var cell: EventCell = tableView.dequeueReusableCellWithIdentifier(cellIdendifier, forIndexPath: indexPath) as EventCell
        cell = EventCell(style: .Default, reuseIdentifier: cellIdendifier)

        if let i = events[String(indexPath.row)] {
            cell.eventName.text = i[0]
            cell.eventCity.text = i[1]
            cell.eventTime.text = i[2]
        }

        cell.sizeToFit()

        return cell


    }
}

4 ответа

Решение

Хорошо, сначала несколько важных комментариев.

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

cell = EventCell(style: .Default, reuseIdentifier: cellIdendifier)

В этом нет необходимости, поскольку dequeue автоматически создает новые ячейки по мере необходимости на основе класса, который вы зарегистрировали для данного идентификатора.

Во-вторых, вы не должны использовать границы основного экрана при размещении вашего кода. Это сломается, если ваш вид таблицы не на всю ширину. Вместо этого вы можете использовать self.bounds так что это всегда относительно самой клетки.

В-третьих, вы не должны вызывать setNeedsLayout или layoutIfNeeded, потому что, если этот метод вызывается, он уже все раскладывает снова.

В-четвертых, вы должны зарегистрировать свой класс ячеек табличного представления перед настройкой источника данных табличного представления на тот случай, если UITableView каждый начинает запрашивать данные из источника данных, когда источник данных установлен.

В-пятых, два ваших подпредставления имеют размер 0,0, поэтому они все равно не появятся.

Если этот код действительно работает без сбоев, то вы создаете EventCells, потому что вы делаете принудительное приведение из результата dequeueReusableCellWithIdentifier:forIndexPath, Это означает, что у вас просто проблема с макетом / отображением / данными.

У меня такой же вопрос. Я применил совет в ответе @drewag, но были некоторые дополнительные проблемы. Ниже приведен простой пример, подтверждающий концепцию, который показывает, как использовать программно созданный UITableViewCell подкласс.

Изображение ниже, как это должно выглядеть. Желтые прямоугольники UILabel подпредставления в пользовательских ячейках.

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

Код

Вот подкласс UITableViewCell. Его работа заключается в инициализации ячейки и ее содержимого, добавлении подпредставлений и компоновке подпредставлений.

import UIKit
class MyCustomCell: UITableViewCell {

    var myLabel = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        myLabel.backgroundColor = UIColor.yellowColor()
        self.contentView.addSubview(myLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        myLabel.frame = CGRect(x: 20, y: 0, width: 70, height: 30)
    }
}

Вот код контроллера представления.

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    let myArray = ["one", "two", "three", "four"]
    let cellReuseIdendifier = "cell"


    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdendifier)

        tableView.dataSource = self
        tableView.delegate = self
    }

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier(cellReuseIdendifier, forIndexPath: indexPath) as! MyCustomCell
        cell.myLabel.text = myArray[indexPath.row]

        return cell
    }
}

Заметки:

  • Я использовал обычный UIViewController а не UITableViewController, Если вы используете UITableViewController затем удалите UITableViewDelegate а также UITableViewDataSource ссылки на протокол, так как они являются избыточными для UITableViewController, Вам также не понадобится @IBOutlet tableView линия.
  • Основная проблема, которую я нашел в исходном вопросе, была eventName = UILabel(frame: CGRectMake(...)), Вместо этого должно было быть eventName.frame = CGRect(...)

Ваш конкретный код не работает, потому что он создает новые UILabels в EventCell.layoutSubviews. Только начальные UILabels добавляются в дерево представлений, и их размеры, вероятно, равны 0.

Я предполагаю, что вы хотели обновить их фреймы, например, обновить метод класса EventCell:

override func layoutSubviews() {
    super.layoutSubviews()

    eventName.frame = CGRectMake(20, 10, self.bounds.size.width - 40, 25)
    eventCity.frame = CGRectMake(<actual size>)
    eventTime.frame = CGRectMake(<actual size>)
}

Вы можете использовать ленивую реализацию в swift, чтобы избежать необходимости заходить в layoutSubviews / просматривать танец жизненного цикла.

class EventCell: UITableViewCell {
  lazy public  var lblName = {
    return UILabel (frame: CGRectMake(10, 0, self.bounds.size.width , 40))
    }()

 lazy public  var lblCity = {
    return UILabel (frame: CGRectMake(10, 40, self.bounds.size.width , 20))
    }()
 lazy public  var lblTime = {
    return UILabel (frame: CGRectMake(self.bounds.size.width - 80, 10, , 25))
    }()


  override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)

    self.contentView.addSubview(lblName)
    self.contentView.addSubview(lblCity)
    self.contentView.addSubview(lblTime)
  }

} 
Другие вопросы по тегам