Статьи

Саморазмерные ячейки с UITableView и автоматической разметкой

Долгое время большой проблемой для разработчиков iOS были пользовательские высоты и размеры для UITableView а также ячеек UICollectionView . Существовали способы достичь этого, такие как « Auto Layout », но они были «хакерскими» и не реализованы полностью.

Ранее для расчета высоты разработчики имели два варианта.

При использовании Auto Layout разработчик может создать внеэкранную ячейку, расположить содержимое в tableView:heightForRowAtIndexPath: и затем получить высоту (используя systemLayoutSizeFittingSize: .

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

С iOS 8, UITableView и UICollectionView приняли Auto Layout. В этом посте мы расскажем о том, что вам нужно знать для поддержки UITableView в ваших приложениях.

Мы создадим небольшой пример приложения, которое вы также можете найти на Github .

Активы, используемые в приложении, можно найти здесь .

Настройка проекта

В этом приложении мы будем отображать серию цитат с фотографией цитаты, если таковые имеются. Мы напишем приложение в Swift 2.1 на Xcode 7.2, но нацелены на iOS 8.1 и выше.

В Xcode, перейдите в File -> New -> Project и создайте новое приложение Single View . Это создаст новый проект с простым UIViewController с которым вы можете начать. Поскольку он нам не понадобится, удалите ViewController.swift .

После создания проекта нам нужно создать контроллеры представления и пользовательские ячейки. Это должны быть подклассы UITableViewController и UITableViewCell .

Внутри проекта создайте две новые группы (папки), View Controllers и Cells для хранения вышеперечисленного.

Давайте создадим наш View Controller. Щелкните правой кнопкой мыши группу « Просмотреть контроллеры », выберите « Новый файл» и на появившейся панели выберите « Класс касания какао» .

Создать класс

Затем создайте подкласс UITableViewController . Поскольку в приложении мы будем использовать раскадровки, вы можете оставить флажок « Также создавать XIB-файл» неактивным .

UITableViewController Подкласс

В группе Cells создайте подкласс UITableViewCell .

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

UITableViewCell Подкласс

Далее нам нужно использовать контроллер представления, который мы создали в приложении. Откройте Main.storyboard и удалите все существующие сцены, созданные Xcode. Затем щелкните и перетащите UITableViewController из библиотеки объектов на раскадровку и измените его на экземпляр TableViewController .

Изменить класс UITableViewController

Внедрите этот контроллер представления в UINavigationController , выбрав сцену TableViewController и выбрав Редактор -> Внедрить -> Контроллер навигации . Это необязательно для наших целей, но это сделает приложение лучше.

Наконец, убедитесь, что контроллер навигации является точкой входа в приложение. Выберите контроллер и в инспекторе атрибутов включите « Начальный просмотр контроллера» .

Установить начальный вид контроллера

Раскадровка должна выглядеть следующим образом:

Раскадровка

Мы настроим ячейки и рассмотрим контроллеры позже.

Далее мы создадим:

  • Модель, представляющая Котировки как подкласс NSObject
  • Другой подкласс NSObject для ViewModel который действует как источник данных для контроллера. Это упростит наши контроллеры представления и позволит нам повторно использовать код там, где это возможно. Например, если нам нужно перейти от использования tableView к collectionView , это будет тривиально.

Создайте Quote.swift в новой группе моделей и добавьте следующее:

 import Foundation class Quote: NSObject { var text: String! var imageName:String? var personName:String? convenience init(text:String!, imageName:String?, personName:String?) { self.init() self.text = text self.imageName = imageName self.personName = personName } } 

Далее мы создадим ViewModel который загружает и хранит кавычки, которые будут заполнять ViewController , выступая в качестве источника данных для UITableView . Создайте ViewModel.swift внутри группы View Controllers и добавьте следующее:

 import UIKit class ViewModel: NSObject,UITableViewDelegate, UITableViewDataSource { var quotes:[Quote] = [] override init() { super.init() self.loadQuotes() } private func loadQuotes(){ quotes = [] if let path = NSBundle.mainBundle().pathForResource("quotes", ofType: "plist"),let quotesArray = NSArray(contentsOfFile: path){ for dictionary in quotesArray { let dictionary = dictionary as! NSDictionary let text = dictionary.valueForKey("text") as? String ?? "" let imageName = dictionary.valueForKey("imageName") as? String let personName = dictionary.valueForKey("person") as? String let quote = Quote(text: text, imageName: imageName, personName: personName) quotes.append(quote) } } } // TODO: TableView datasource methods } 

Здесь мы загружаем статический набор цитат из plist-файла в нашем комплекте.

Настройка UITableViewCell и UITableView

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

Для каждой цитаты мы отобразим саму цитату, имя цитаты и фотографию человека, если он доступен.

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

Есть два подхода для решения этой проблемы:

  • Используйте разные идентификаторы повторного использования и подклассы ячеек для каждого типа цитаты, или
  • Динамически изменяйте ограничения в подклассе одной ячейки и получайте макет, который мы хотим.

Выбранный подход зависит от сложности изменений ограничений. Если вы обнаружите, что вам нужен совершенно другой набор ограничений Auto Layout для каждого класса ячеек, вам лучше с первым.

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

Откройте TableViewCell.xib и добавьте элементы, как показано ниже:

TableViewCell

Ячейка состоит из photoView для отображения изображения, quoteTextLabel для отображения цитаты и nameLabel для отображения имени.

ImageView

Мы устанавливаем размер photoView фиксированному 64 × 64, и соответственно устанавливаем ограничения.

ImageView

Примечание . Ограничения ширины и высоты по умолчанию имеют приоритет 1000. Могут быть случаи, когда ваши ограничения не работают. Если вы проверяете журналы отладки, они должны включать добавленное системой ограничение UIView-Encapsulated-Layout-Height и другие ограничения, удаленные системой. Решение состоит в том, чтобы установить для таких ограничений меньший приоритет, например 999. В этом случае мы установили ограничение высоты imageView для этого приоритета, отсюда и пунктирная линия вокруг него.

Текстовая метка цитаты

Установите шрифт и количество строк следующим образом. Мы устанавливаем количество строк равным 0 поскольку хотим, чтобы метка отображала весь текст цитаты.

Конфигурация ярлыка

Границы этикетки

При установке ограничений метки установите для приоритета Content Hugging значение 751, то есть высокое, что обеспечит изменение размера метки в соответствии с ее содержимым.

Ограничения метки

Название Метка

Поскольку имя вряд ли будет длинным и потребует нескольких строк, мы устанавливаем количество строк равным 1, а высоту – фиксированными 21 пункт.

Название Метка

Примечание : есть вероятность, что nameLabel или photoView – самый нижний вид в ячейке, в зависимости от длины цитаты. Поэтому мы устанавливаем ограничение >= для обоих соответствующих ограничений нижнего интервала.

Примечание . Важно, чтобы ограничения Auto Layout были однозначными. Они должны решаться системой

Затем перетащите из представлений, чтобы создать точки в ячейке:

 @IBOutlet weak var photoView: UIImageView! @IBOutlet weak var quoteTextLabel: UILabel! @IBOutlet weak var nameLabel: UILabel! 

Наконец, помните, как imageView является необязательным? Чтобы достичь этого, мы будем поддерживать ссылки на набор ограничений, в частности, ограничение ширины photoView и ограничение расстояния между photoView и quoteTextLabel . Обновив только эти 2 ограничения, мы можем сделать так, чтобы photoView даже не находился в ячейке.

 @IBOutlet weak var photoWidthConstraint: NSLayoutConstraint! @IBOutlet weak var photoRightMarginConstraint: NSLayoutConstraint! 

Создание точек для ограничений такое же, как и для представлений. Просто выберите ограничение в Интерфейсном Разработчике и Ctrl + клик + перетащите в редактор помощника.

После настройки ячейки пора настроить методы источника данных в ViewModel.swift для отображения содержимого.

 ... // TODO: TableView datasource methods func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return quotes.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(tableViewCellIdentifier, forIndexPath: indexPath) as! TableViewCell let quote = quotes[indexPath.row] cell.quoteTextLabel.text = quote.text cell.nameLabel.text = quote.personName if let imageName = quote.imageName where !imageName.isEmpty{ cell.photoView?.image = UIImage(named: imageName) cell.photoWidthConstraint.constant = kDefaultPhotoWidth cell.photoRightMarginConstraint.constant = kDefaultPhotoRightMargin } else { cell.photoView?.image = nil cell.photoWidthConstraint.constant = 0 cell.photoRightMarginConstraint.constant = 0 } cell.contentView.setNeedsLayout() cell.contentView.layoutIfNeeded() return cell } 

И добавьте используемую здесь переменную в TableViewCell.swift :

 import UIKit let tableViewCellIdentifier = "TableViewCell" let kDefaultPhotoWidth: CGFloat = 64 let kDefaultPhotoRightMargin: CGFloat = 8 class TableViewCell: UITableViewCell { ... } 

Источник данных заполняет каждую ячейку содержимым. Обратите внимание, как мы используем ограничения, ранее сохраненные как выходы. Прежде чем вернуть ячейку, мы можем внести изменения в эти ограничения. Если изображение отсутствует, для photoView задана ширина 0, как и для его правого поля. По photoView , мы предотвращаем photoView в ячейке.

Недостаточно просто использовать числовое значение ограничений. Мы также должны сообщить contentView представлению ячейки, что его ограничения изменились, и его необходимо contentView . Следовательно, вызовы setNeedsLayout: и layoutIfNeeded:

Примечание . Вышеуказанное необходимо только для iOS 8, а не для iOS 9.

Наконец, мы подошли к настройке tableView , где происходит вся магия.

В TableViewController.swift :

 ... var viewModel:ViewModel! override func viewDidLoad() { super.viewDidLoad() //Create the ViewModel, and set it as the delegate + datasource. self.viewModel = ViewModel() self.tableView.delegate = viewModel self.tableView.dataSource = viewModel //Register our custom cell subclass. self.tableView.registerNib(UINib(nibName: "TableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: tableViewCellIdentifier) // Self-sizing magic! self.tableView.rowHeight = UITableViewAutomaticDimension self.tableView.estimatedRowHeight = 50; //Set this to any value that works for you. } 

Все, что требуется для включения ячеек с самоопределением размера в UITableView – это две последние строки. Поскольку высота строки теперь является неявным значением, iOS сначала будет использовать estimatedRowHeight RowHeight для настройки ячеек, а затем вычислит высоту строки, прежде чем ячейки появятся на экране. Вся работа, которую мы сделали с настройкой Auto Layout, окупается здесь.

Как это работает? iOS вычисляет высоту, решая линейное уравнение, представленное ограничениями Auto Layout, используя известный набор переменных: ширину contentView ячейки (которая обычно является шириной tableView минус такие вещи, как отступы, accessoryViews т. д.) и ограничения самих себя. Эта информация является минимумом, необходимым для расчета высоты любым способом.

Так как rowHeight уже был установлен, нет необходимости реализовывать tableView:heightForRowAtIndexPath: больше.

Примечание об оценочной высоте : это просто оценка. Если ваше оценочное значение сильно отличается от фактической высоты, при прокрутке вы можете заметить некоторую скачок, поскольку iOS расширяет или сворачивает ячейку до нужной высоты. Если вам нужен детальный контроль над предполагаемой высотой, используйте метод источника данных UITableView tableView:estimatedHeightForRowAtIndexPath: и рассчитайте предполагаемую высоту для каждой строки в отдельности.

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

Финальное приложение

Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы или комментарии ниже .