CoreTableView module for Data-Driven-UI Architecture

CoreTableView

CoreTableView module for Data-Driven-UI Architecture

Integration

if as local package

    dependencies: [
        .package(path: "../CoreTableView")
    ],

if as shared package

    dependencies: [
        .package(name: "CoreTableView", url: "https://github.com/MosMetro-official/CoreTableView.git", from: "0.0.1")
    ],

Structure

The Package is a wrapper for BEST UITableView perfomance, that is includes DifferenceKit lib inside. Available for iOS 11+.

Usage

First of all, you need to set BaseTableView – main actor for the lib. You can host it in Xib / Storyboard file or natively by code.

IBOutlet weak private var tableView : BaseTableView!

======OR======

private lazy var tableView : BaseTableView = {
    var tableView : BaseTableView!
    if #available(iOS 13.0, *) {
        tableView = BaseTableView(frame: .zero, style: .insetGrouped)
    } else {
        tableView = BaseTableView(frame: .zero, style: .grouped)
    }
    tableView.separatorColor = .clear
    tableView.showsVerticalScrollIndicator = false
    tableView.showsHorizontalScrollIndicator = false
    return tableView
}()

Anyway, this tableView wrapper created for convenient table management in Data-Driven-UI Architecture. So this Arch is built on States, that you schould strictly type. So. your view will be setted like:

final class MyView : UIView {
    
    @IBOutlet weak private var tableView: BaseTableView!
    
    struct ViewState {
    
        struct Loading : _Loading {
            let title : String
            let descr : scting
        }
    
        struct ErrorData : _ErrorData {
            let title : String
            let descr : String
            let onRetry : (() -> Void)?
        }
    
        struct Header : _TitleHeader {
            let title : String
            let style : TitleHeaderView.Style
            let height : CGFloat
            let isInsetGrouped : Bool
            let backgroundColor : UIColor
        }
    
        struct Row : _StandartImage {
            let icon : UIImage?
            let title : String
            let onSelect : (() -> Void)
        }
        
        struct Footer : _BaseFooter {
            var text : String
            var isInsetGrouped : Bool
        }
    }
    
    public var viewState: ViewState = ViewState(state: []) {
        didSet {
            DispatchQueue.main.async {
                self.tableView.viewStateInput = self.viewState.state
            }
        }
    }
}

In this case you should STRICTLY describe all possible screen states with a table, accordingly, if the screen has several states (for example, working, error and loading), then at least 3 cells should be created in the table (actually, working, with error and with loading).
Updating your UI we offer from controller, that is preparing states for us. For example, making loading state:

    let nestedView = MyView.loadFromNib()
    
    override func loadView() {
        self.view = self.nestedView
    }

    private func makeLoadingRow() -> Element {
        return MyView.ViewState.Loading(
            title : "Loading",
            descr : "Wait a bit"
        ).toElement()
    }
    
    private func makeState() {
        let stateModel = SectionState()
        let blockModel = State(model: stateModel, elements: [
            self.makeLoadingRow()
        ])
        self.nestedView.viewState.state = [blockModel]
    }

If you want to add Header / Footer, it easy:

======SAME STUFF======

    private func makeHeader() {
        return MyView.ViewState.Header(
            title : "Header"
            style : .medium
            height : 50
            isInsetGrouped : true
            backgroundColor : .clear
        )
    }

    private func makeState() {
        let stateModel = SectionState(header: self.makeHeader())
        let blockModel = State(model: stateModel, elements: [
            self.makeLoadingRow()
        ])
        self.nestedView.viewState.state = [blockModel]
    }

Perfomance

Your UITableViewCell file will be pretty the same, but you need to implement some usefull stuff. With CoreTableView your file will be looked by that:

protocol _StandartImage : CellData {
    var title : String   { get set }
    var leftImage : UIImage? { get set }
    var separator : Bool     { get set }
    var backgroundColor: UIColor? { get }
}

extension _StandartImage {

    var backgroundColor: UIColor? { return nil }
    
    func hashValues() -> [Int] {
        return [title.hashValue, leftImage.hashValue, separator.hashValue]
    }
    
    static func calculateHeight(title: String, hasAccesory: Bool, margin: CGFloat) -> CGFloat {
        let leftMargin = 64.0
        let rightMargin = 16.0 + (hasAccesory ? ((UIScreen.main.bounds.width - 32) * 0.08) : 0)
        let topAndBottomMargin = 22.0
        
        let titleSize = title.height(withConstrainedWidth: finalWidth, font: .Body_17_Regular)
        let finalWidth = UIScreen.main.bounds.width - leftMargin - rightMargin - margin * 2
        return topAndBottomMargin + titleSize + 4 + subtitleSize + 8
    }

    
    func prepare(cell: UITableViewCell, for tableView: UITableView, indexPath: IndexPath) {
        tableView.register(StandartImageCell.nib, forCellReuseIdentifier: StandartImageCell.identifire)
        guard 
        let cell = cell as? StandartImageCell 
        else { return }
        cell.configure(with: self)
    }
    
    func cell(for tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
        guard 
        let cell = tableView.dequeueReusableCell(withIdentifier: StandartImageCell.identifire, for: indexPath) as? StandartImageCell 
        else { return .init() }
        return cell
    }
}

This protocol “YourNameCell” need to be emplimented to make your state strictly typed && predictable – so now it cannot be used in any way except for this protocol. Extension for this protocol just predicted the Cell integration to the tableView. BackgroundColor is the color of the cell’s back, cell(for) – great method, so BaseTableView will see your cell (DO NOT FORGET TO IMPLEMENT “tableView.register”).
The most important part for the best perfomance using BaseTableView is to calculate YourTableViewCell height. You can do it by yourself, if your constraints are fixed – just calculate the height and return the value. But if you have some greaterOrEqual constraint, for a label – you can calculate it by using our extesion “.height(withConstrainedWidth: CGFloat, font: UIFont)
Prepare func is called when the cell “willDisplay” so it’s configure will be BEFORE it displayed.

class StandartImageCell : UITableViewCell {
        
    @IBOutlet weak private var title : UILabel!
    @IBOutlet weak private var separator : UIView!
    @IBOutlet weak private var leftImage : UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        isUserInteractionEnabled = true
    }
    
    override func prepareForReuse() {
        self.title.text = nil
        self.title.textColor = nil
        self.leftImage.image = nil
        self.leftImage.tintColor = nil
    }
    
    public func configure(with data: _StandartImage) {
        self.title.text = data.title
        self.title.textColor = textColor
        self.leftImage.image = data.leftImage
        self.separator.isHidden = !data.separator
        self.leftImage.tintColor = imageColor
        if boldText {
            self.title.font = .Body17_bold
        }
        if let accesory = data.accesoryType {
            self.accessoryType = accesory
        }
        if let bgColor = data.backgroundColor  {
            self.backgroundColor = bgColor
        }
        self.leftImage.tintColor = data.tintColor
    }
}

So your cell schould implement method to “configure” and “eraseData”.

GitHub

View Github