The best way to create a desk view programmatically?
Let’s bounce straight into the coding half, however first: begin Xcode, create a brand new iOS single view app challenge, enter some identify & particulars for the challenge as common, use Swift and eventually open the ViewController.swift file straight away. Now seize your keyboard! ⌨️
Professional tip: use Cmd+Shift+O to rapidly bounce between information
I am not going to make use of interface builder on this tutorial, so how can we create views programmatically? There’s a methodology referred to as loadView that is the place it is best to add customized views to your view hierarchy. You possibly can possibility+click on the strategy identify in Xcode & learn the dialogue about loadView methodology, however let me summarize the entire thing.
We’ll use a weak property to carry a reference to our desk view. Subsequent, we override the loadView methodology & name tremendous, to be able to load the controller’s self.view property with a view object (from a nib or a storyboard file if there may be one for the controller). After that we assign our model new view to a neighborhood property, flip off system supplied format stuff, and insert our desk view into our view hierarchy. Lastly we create some actual constraints utilizing anchors & save our pointer to our weak property. Simple! 🤪
class ViewController: UIViewController {
weak var tableView: UITableView!
override func loadView() {
tremendous.loadView()
let tableView = UITableView(body: .zero, type: .plain)
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(tableView)
NSLayoutConstraint.activate([
self.view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: tableView.topAnchor),
self.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
])
self.tableView = tableView
}
}
All the time use auto format anchors to specify view constraints, if you do not know the best way to use them, test my format anchors tutorial, it is takes solely about quarter-hour to study this API, and you will not remorse it. It is an especially useful gizmo for any iOS developer! 😉
You may ask: ought to I exploit weak or sturdy properties for view references? I might say in a lot of the circumstances in case you are not overriding self.view it is best to use weak! The view hierarchy will maintain your customized view by way of a powerful reference, so there isn’t a want for silly retain cycles & reminiscence leaks. Belief me! 🤥
UITableViewDataSource fundamentals
Okay, we’ve got an empty desk view, let’s show some cells! To be able to fill our desk view with actual knowledge, we’ve got to evolve to the UITableViewDataSource protocol. By way of a easy delegate sample, we will present numerous data for the UITableView class, so it’s going to to know the way a lot sections and rows will likely be wanted, what sort of cells needs to be displayed for every row, and lots of extra little particulars.
One other factor is that UITableView is a very environment friendly class. It’s going to reuse all of the cells which can be at present not displayed on the display screen, so it’s going to eat manner much less reminiscence than a UIScrollView, if it’s a must to cope with lots of or hundreds of things. To help this habits we’ve got to register our cell class with a reuse identifier, so the underlying system will know what sort of cell is required for a particular place. ⚙️
class ViewController: UIViewController {
var objects: [String] = [
"👽", "🐱", "🐔", "🐶", "🦊", "🐵", "🐼", "🐷", "💩", "🐰",
"🤖", "🦄", "🐻", "🐲", "🦁", "💀", "🐨", "🐯", "👻", "🦖",
]
override func viewDidLoad() {
tremendous.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
self.tableView.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
return self.objects.depend
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let merchandise = self.objects[indexPath.item]
cell.textLabel?.textual content = merchandise
return cell
}
}
After including a number of strains of code to our view controller file, the desk view is now capable of show a pleasant record of emojis! We’re utilizing the built-in UITableViewCell class from UIKit, which comes actually helpful in case you are good to go together with the “iOS-system-like” cell designs. We additionally conformed to the information supply protocol, by telling what number of objects are in our part (at present there is just one part), and we configured our cell contained in the well-known cell for row at indexPath delegate methodology. 😎
Customizing desk view cells
UITableViewCell can present some primary components to show knowledge (title, element, picture in several types), however often you may want customized cells. Here’s a primary template of a customized cell subclass, I will clarify all of the strategies after the code.
class MyCell: UITableViewCell {
override init(type: UITableViewCell.CellStyle, reuseIdentifier: String?) {
tremendous.init(type: type, reuseIdentifier: reuseIdentifier)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
self.initialize()
}
func initialize() {
}
override func prepareForReuse() {
tremendous.prepareForReuse()
}
}
The init(type:reuseIdentifier)
methodology is a good place to override the cell type property if you’re going to use the default UITableViewCell programmatically, however with completely different types (there isn’t a choice to set cellStyle after the cell was initialized). For instance in the event you want a .value1
styled cell, simply go the argument on to the tremendous name. This manner you’ll be able to profit from the 4 predefined cell types.
You will additionally must implement
init(coder:)
, so it is best to create a standard initialize() perform the place you can add your customized views to the view hierarchy, like we did within the loadView methodology above. If you’re utilizing xib information & IB, you need to use the awakeFromNib methodology so as to add further type to your views by way of the usual@IBOutlet
properties (or add further views to the hierarchy as effectively). 👍
The final methodology that we’ve got to speak about is prepareForReuse
. As I discussed earlier than cells are being reused so if you wish to reset some properties, just like the background of a cell, you are able to do it right here. This methodology will likely be referred to as earlier than the cell goes to be reused.
Let’s make two new cell subclasses to mess around with.
class DetailCell: UITableViewCell {
override init(type: UITableViewCell.CellStyle, reuseIdentifier: String?) {
tremendous.init(type: .subtitle, reuseIdentifier: reuseIdentifier)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
self.initialize()
}
func initialize() {
}
override func prepareForReuse() {
tremendous.prepareForReuse()
self.textLabel?.textual content = nil
self.detailTextLabel?.textual content = nil
self.imageView?.picture = nil
}
}
Our customized cell can have an enormous picture background plus a title label within the middle of the view with a customized sized system font. Additionally I’ve added the Swift emblem as an asset to the challenge, so we will have a pleasant demo picture. 🖼
class CustomCell: UITableViewCell {
weak var coverView: UIImageView!
weak var titleLabel: UILabel!
override init(type: UITableViewCell.CellStyle, reuseIdentifier: String?) {
tremendous.init(type: type, reuseIdentifier: reuseIdentifier)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
self.initialize()
}
func initialize() {
let coverView = UIImageView(body: .zero)
coverView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(coverView)
self.coverView = coverView
let titleLabel = UILabel(body: .zero)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(titleLabel)
self.titleLabel = titleLabel
NSLayoutConstraint.activate([
self.contentView.topAnchor.constraint(equalTo: self.coverView.topAnchor),
self.contentView.bottomAnchor.constraint(equalTo: self.coverView.bottomAnchor),
self.contentView.leadingAnchor.constraint(equalTo: self.coverView.leadingAnchor),
self.contentView.trailingAnchor.constraint(equalTo: self.coverView.trailingAnchor),
self.contentView.centerXAnchor.constraint(equalTo: self.titleLabel.centerXAnchor),
self.contentView.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor),
])
self.titleLabel.font = UIFont.systemFont(ofSize: 64)
}
override func prepareForReuse() {
tremendous.prepareForReuse()
self.coverView.picture = nil
}
}
That is it, let’s begin utilizing these new cells. I will even inform you the best way to set customized top for a given cell, and the best way to deal with cell choice correctly, however first we have to get to know with one other delegate protocol. 🤝
Fundamental UITableViewDelegate tutorial
This delegate is accountable for many issues, however for now we’ll cowl just some fascinating points, like the best way to deal with cell choice & present a customized cell top for every objects contained in the desk. Here’s a fast pattern code.
class ViewController: UIViewController {
override func viewDidLoad() {
tremendous.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
self.tableView.register(DetailCell.self, forCellReuseIdentifier: "DetailCell")
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let merchandise = self.objects[indexPath.item]
cell.titleLabel.textual content = merchandise
cell.coverView.picture = UIImage(named: "Swift")
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 128
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let merchandise = self.objects[indexPath.item]
let alertController = UIAlertController(title: merchandise, message: "is in da home!", preferredStyle: .alert)
let motion = UIAlertAction(title: "Okay", type: .default) { _ in }
alertController.addAction(motion)
self.current(alertController, animated: true, completion: nil)
}
}
As you’ll be able to see I am registering my model new customized cell lessons within the viewDidLoad
methodology. I additionally modified the code contained in the cellForRowAt
indexPath methodology, so we will use the CustomCell
class as an alternative of UITableViewCells
. Do not be afraid of drive casting right here, if one thing goes incorrect at this level, your app ought to crash. 🙃
There are two delegate strategies that we’re utilizing right here. Within the first one, we’ve got to return a quantity and the system will use that top for the cells. If you wish to use completely different cell top per row, you’ll be able to obtain that too by checking indexPath property or something like that. The second is the handler for the choice. If somebody faucets on a cell, this methodology will likely be referred to as & you’ll be able to carry out some motion.
An indexPath has two fascinating properties: part & merchandise (=row)
A number of sections with headers and footers
It is doable to have a number of sections contained in the desk view, I will not go an excessive amount of into the small print, as a result of it is fairly easy. You simply have to make use of indexPaths to be able to get / set / return the correct knowledge for every part & cell.
import UIKit
class ViewController: UIViewController {
weak var tableView: UITableView!
var placeholderView = UIView(body: .zero)
var isPullingDown = false
enum Type {
case `default`
case subtitle
case customized
}
var type = Type.default
var objects: [String: [String]] = [
"Originals": ["👽", "🐱", "🐔", "🐶", "🦊", "🐵", "🐼", "🐷", "💩", "🐰","🤖", "🦄"],
"iOS 11.3": ["🐻", "🐲", "🦁", "💀"],
"iOS 12": ["🐨", "🐯", "👻", "🦖"],
]
override func loadView() {
tremendous.loadView()
let tableView = UITableView(body: .zero, type: .plain)
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(tableView)
NSLayoutConstraint.activate([
self.view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: tableView.topAnchor),
self.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
])
self.tableView = tableView
}
override func viewDidLoad() {
tremendous.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
self.tableView.register(DetailCell.self, forCellReuseIdentifier: "DetailCell")
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.separatorStyle = .singleLine
self.tableView.separatorColor = .lightGray
self.tableView.separatorInset = .zero
self.navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .refresh, goal: self, motion: #selector(self.toggleCells))
}
@objc func toggleCells() {
change self.type {
case .default:
self.type = .subtitle
case .subtitle:
self.type = .customized
case .customized:
self.type = .default
}
DispatchQueue.essential.async {
self.tableView.reloadData()
}
}
func key(for part: Int) -> String {
let keys = Array(self.objects.keys).sorted { first, final -> Bool in
if first == "Originals" {
return true
}
return first < final
}
let key = keys[section]
return key
}
func objects(in part: Int) -> [String] {
let key = self.key(for: part)
return self.objects[key]!
}
func merchandise(at indexPath: IndexPath) -> String {
let objects = self.objects(in: indexPath.part)
return objects[indexPath.item]
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return self.objects.keys.depend
}
func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
return self.objects(in: part).depend
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let merchandise = self.merchandise(at: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.titleLabel.textual content = merchandise
cell.coverView.picture = UIImage(named: "Swift")
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection part: Int) -> String? {
return self.key(for: part)
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 128
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let merchandise = self.merchandise(at: indexPath)
let alertController = UIAlertController(title: merchandise, message: "is in da home!", preferredStyle: .alert)
let motion = UIAlertAction(title: "Okay", type: .default) { _ in }
alertController.addAction(motion)
self.current(alertController, animated: true, completion: nil)
}
}
Though there may be one fascinating addition within the code snippet above. You possibly can have a customized title for each part, you simply have so as to add the titleForHeaderInSection
knowledge supply methodology. Yep, it seems like shit, however this one isn’t about good UIs. 😂
Nevertheless in case you are not glad with the format of the part titles, you’ll be able to create a customized class & use that as an alternative of the built-in ones. Right here is the best way to do a customized part header view. Right here is the implementation of the reusable view:
class HeaderView: UITableViewHeaderFooterView {
weak var titleLabel: UILabel!
override init(reuseIdentifier: String?) {
tremendous.init(reuseIdentifier: reuseIdentifier)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder: aDecoder)
self.initialize()
}
func initialize() {
let titleLabel = UILabel(body: .zero)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(titleLabel)
self.titleLabel = titleLabel
NSLayoutConstraint.activate([
self.contentView.centerXAnchor.constraint(equalTo: self.titleLabel.centerXAnchor),
self.contentView.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor),
])
self.contentView.backgroundColor = .black
self.titleLabel.font = UIFont.boldSystemFont(ofSize: 16)
self.titleLabel.textAlignment = .middle
self.titleLabel.textColor = .white
}
}
There’s just a few issues left to do, it’s a must to register your header view, similar to you probably did it for the cells. It is precisely the identical manner, besides that there’s a separate registration “pool” for the header & footer views. Lastly it’s a must to implement two extra, however comparatively easy (and acquainted) delegate strategies.
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForHeaderInSection part: Int) -> CGFloat {
return 32
}
func tableView(_ tableView: UITableView, viewForHeaderInSection part: Int) -> UIView? {
let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! HeaderView
view.titleLabel.textual content = self.key(for: part)
return view
}
}
Footers works precisely the identical as headers, you simply must implement the corresponding knowledge supply & delegate strategies to be able to help them.
You possibly can even have a number of cells in the identical desk view based mostly on the row or part index or any particular enterprise requirement. I am not going to demo this right here, as a result of I’ve a manner higher resolution for mixing and reusing cells contained in the CoreKit framework. It is already there for desk views as effectively, plus I already lined this concept in my final assortment view tutorial submit. You must test that too. 🤓
Part titles & indexes
Okay, in case your mind isn’t melted but, I will present you two extra little issues that may be fascinating for novices. The primary one relies on two extra knowledge supply strategies and it is a very nice addition for lengthy lists. (I favor search bars!) 🤯
extension ViewController: UITableViewDataSource {
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return ["1", "2", "3"]
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
}
If you’re going to implement these strategies above you’ll be able to have just a little index view in your sections in the appropriate aspect of the desk view, so the end-user will have the ability to rapidly bounce between sections. Similar to within the official contacts app. 📕
Choice vs spotlight
Cells are highlighted if you find yourself holding them down along with your finger. Cell goes to be chosen in the event you launch your finger from the cell.
Do not over-complicate this. You simply must implement two strategies in you customized cell class to make every little thing work. I favor to deselect my cells straight away, if they don’t seem to be for instance utilized by some kind of knowledge picker format. Right here is the code:
class CustomCell: UITableViewCell {
override func setSelected(_ chosen: Bool, animated: Bool) {
self.coverView.backgroundColor = chosen ? .crimson : .clear
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
self.coverView.backgroundColor = highlighted ? .blue : .clear
}
}
As you’ll be able to see, it is ridiculously straightforward, however a lot of the novices do not know the way to do that. Additionally they often overlook to reset cells earlier than the reusing logic occurs, so the record retains messing up cell states. Don’t be concerned an excessive amount of about these issues, they’re going to go away as you are going to be extra skilled with the UITableView APIs.