Долгое время большой проблемой для разработчиков 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-файл» неактивным .
В группе Cells создайте подкласс UITableViewCell
.
На этот раз мы проверим параметр XIB, так как мы будем использовать его для настройки ячейки позже.
Далее нам нужно использовать контроллер представления, который мы создали в приложении. Откройте Main.storyboard и удалите все существующие сцены, созданные Xcode. Затем щелкните и перетащите UITableViewController
из библиотеки объектов на раскадровку и измените его на экземпляр TableViewController
.
Внедрите этот контроллер представления в 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 и добавьте элементы, как показано ниже:
Ячейка состоит из photoView
для отображения изображения, quoteTextLabel
для отображения цитаты и nameLabel
для отображения имени.
ImageView
Мы устанавливаем размер photoView
фиксированному 64 × 64, и соответственно устанавливаем ограничения.
Примечание . Ограничения ширины и высоты по умолчанию имеют приоритет 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:
и рассчитайте предполагаемую высоту для каждой строки в отдельности.
Теперь, когда вы сделали всю тяжелую работу, пришло время запустить приложение. Окончательное приложение должно выглядеть так:
Пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы или комментарии ниже .