21. Jul 2023iOS

Exploring our iOS toolbox - Working with UITableView & UICollectionView in Swift

If you've ever developed iOS applications using UITableView or UICollectionView, you know that even the simplest use-cases require implementing many delegate methods. To simplify the configuration and usage of UITableView and UICollectionView, we have developed GRTableViewProvider and GRCollectionViewProvider. These classes provide a set of methods and bindings that make it easy to implement multiple sections and items.

Marek VricaniOS Developer

Working with UITableView

Here's how you can use GRTableViewProvider to simplify UITableView in your iOS app development:

To do so, we first need to import GRProvider and create a struct that conforms to the Sectionable protocol within our ViewController. This struct will have a title of type String and an array of strings for its items.

import UIKit
import GRProvider

fileprivate struct Section: Sectionable {

    var title: String?
    var items: [String]

}

Within our TableViewSampleController class, we will declare both the UITableView and tableProvider, which is instantiated with the defined section struct:

class TableViewSampleController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    private let tableProvider = GRTableViewProvider<Section>()

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Table View Provider"
        
        setupTableView()
        showItems()
    }
}

In the setupTableView() method, we can easily customise the tableProvider instance according to our specific needs using GRProvider.

For example, we may want to set the estimatedHeightForRow, define an action when the user clicks on a UITableViewCell, set the heightForHeaderInSection, and configure the section header.

With our GRProvider it’s easier than ever.

private func setupTableView() {
    tableProvider.estimatedHeightForRow = 100
        
    tableProvider.configureOnItemSelected = { [unowned self] _, _, _, item in
        let alert = UIAlertController(title: "Wow!", message: "You clicked an item: \(item)", preferredStyle: .alert)
        alert.addAction(.init(title: "Cancel", style: .cancel, handler: nil))
        self.present(alert, animated: true)
    }
        
    tableProvider.configureCell = { _, tv, index, title in
        guard let cell = tv.dequeueReusableCell(fromClass: SimpleTableViewCell.self, for: index) else { return UITableViewCell() }
        cell.titleLabel.text = title
        return cell
    }
        
    tableProvider.heightForHeaderInSection = UITableView.automaticDimension
        
    tableProvider.configureSectionHeader = { _, _, _, section in
        let container = UIView()
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false

        container.addSubview(label)

        NSConstraints.activate([
            label.topAnchor.constraint(equalTo: container.topAnchor, constant: 15),
            label.leftAnchor.constraint(equalTo: container.leftAnchor, constant: 15),
            label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -15),
            label.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -15)
            ])

        label.text = section.title

        return container
    }
}

Next, we'll create the showItems() method within our ViewController, where we define the data to be displayed in the table view. Finally, we'll use the bind() method to populate the table view with the data.

private func showItems() {
    let section1 = Section(title: "Section1", items: (1...4).map { "Item \($0)" })
    let section2 = Section(title: "Section2", items: (5...8).map { "Item \($0)" })
    tableProvider.bind(to: tableView, sections: [section1, section2])
}

Pretty cool right? Let’s see how we can handle UICollectionView.

Working with UICollectionView

Using GRCollectionViewProvider to work with UICollectionView is similar to the example above. We first create a Section struct where we define the title and items. Then, we define the GRCollectionViewProvider with our Section in it.

import UIKit
import GRProvider

fileprivate struct Section: Sectionable {
    
    struct Item {
        let title: String
    }
    
    var items: [Item]
    var title: String?
              
    init(items: [Item], title: String?) {
        self.items = items
        self.title = title
    }
    
}

final class CollectionProviderViewSampleController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    
    fileprivate lazy var collectionProvider = GRCollectionViewProvider<Section>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Collection View Provider"
        
        setupCollectionView()
        showItems()
    }

}

In the setupCollectionView() method, we can customize the collectionView by configuring various settings available in the collectionProvider. For example, we can set the sectionInsets, cellSize, cell and actions to perform when a cell is selected.

Once the collectionView is configured, we can use the showItems() method to populate it with the desired data.

// Configure the collectionView
private func setupCollectionView() {
    collectionProvider.sectionInsets = .init(top: 0, left: 5, bottom: 0, right: 5)
    collectionProvider.minimumLineSpacingForSection = 5
    collectionProvider.minInteritemSpacingForSection = 5

    collectionProvider.configureCellSize = { _, cv, index, item in
        return CGSize(width: (cv.frame.width - 21) / 3, height: (cv.frame.width - 21) / 3)
    }
        
    collectionProvider.configureSupplementaryElementOfKind = { provider, cv, index, type in
        let section = provider.sections[index.section]
        let view = cv.dequeueReusableSupplementaryView(ofKind: type, fromClass: SimpleCollectionViewSupplementaryView.self, for: index)
        view.titleLabel.text = section.title
        return view
    }
        
    collectionProvider.configureCell = { _, tv, indexPath, item in
        let cell = tv.dequeueReusableCell(fromClass: SimpleCollectionViewCell.self, for: indexPath)
        cell.titleLabel.text = item.title
        return cell
    }
        
    collectionProvider.configureOnItemSelected = { [unowned self] _, _, _, item in
        let alert = UIAlertController(title: "Wow!", message: "You clicked an item: \(item)", preferredStyle: .alert)
        alert.addAction(.init(title: "Cancel", style: .cancel, handler: nil))
        self.present(alert, animated: true)
    }
}

// fill collectionView with data
private func showItems() {
    let section1 = Section(items: (1...5).map { Section.Item(title: "Item \($0)") }, title: "Section 1")
    let section2 = Section(items: (10...12).map { Section.Item(title: "Item \($0)") }, title: "Section 2")
    collectionProvider.bind(to: collectionView, sections: [section1, section2])
}

Congrats!

You have gone through the process of working with UITableView or UICollectionView using GoodProvider package.

Now you may be wondering how it works under the hood inside the app. Luckily, the package comes with samples that you can explore to gain a deeper understanding of its functionality.

If you found this package useful, be sure to check out our other packages. Who knows, you might just find another gem that can help take your app to the next level!

 

Marek VricaniOS Developer