/ corekit

UIKit + AppKit = CoreKit

This is going to be a long awaited article for those who wanted to read about my universal framework called CoreKit. Now the source code is on github, you can clone it, play with it, and if you have the courage, you can also contribute to it. I warn you there are no docs, no tutorials, just plain Swift code. If you are brave enough to run the example project, you can see that the same view controller logic drives an iOS, macOS and a tvOS application. Let me talk about this a little bit.

How to build good UI for apps?

First of all, if we're talking about UI building we should always keep in mind the following states of a view controller:


source: How to fix a bad user interface

Your app usually does something to process data, before the processing happens, you should display some placeholder to the end user. After the actual data processing begins, the state transitions to the loading state - you could also indicate the status of the process with an indicator (partial state) - in the end two things can happen: an error, or the processing finishes & your screen will present the ideal data set.

How do you achieve this stateful behaviour? You should use a state machine inside your view controller, or a coordinator to coordinate the state changes inside the view controller or simply handle all the scenarios manually. Easy peasy. This is not the interesting part anyway. The real question here is that how do you build up your UI? 4-5 states with different layout elements. You might ask the question now, should I use multiple view controllers to handle all the states? Should I use subviews to get shit done? NO.

ALWAYS use collection views

No way, this guy is crazy. Fuck him. Heeey, I know, it might sounds crazy, but this is THE ONLY WAY to deal with all the issues during UI building. First of all, forget about the collection view delegate & data source protocols and start thinking in collection view data models. A collection view consists from the following elements:


source: Collection View Basics by Apple

Collection view sections (header, cells, footer) are really good building blocks. All the elements inside a section are reusable views, which means if you scroll inside a collection view, all the elements will be reused if they are not presented on the screen (low memory overhead, not like in a simple scroll view). A collection view is a scroll view, so if you have to avoid the keyboard for input fields, that could be handled easily. Cells can set their sizes based on the content, orientation or whatever you want, this is also a huge advantage too. Inside your cells you can use traditional view components, like labels, image views even stack views, you just have to calculate the cell size & auto layout can take over the rest.

The bad (legacy) API from Apple

I mentioned that you should forget about data sources & delegates if we are talking about collection views. Now check this alternative solution of mine:


source: my previous article about this topic

Let's think with Swift. Apple had this great wwdc session back from 2014 to encourage developers to build advanced layouts with collection views. My only problem with this approach is that they over-engineered the problem. This issue can be solved without any parent-child context. Let's take a look at my approach. The base idea is to have a generic data class which can encapsulate a cell and an object to fill up the cell, now put these data class instances into section objects. Each section represents a section inside the collection view, they can have selection handlers, but induvidual data elements can override the selection behaviour too. Let's go up one more level, there is the source class which gathers together all the sections. In other words, the source class is the data source of the collection view. I also made one additional extension for the source: it contains a grid. A grid is a visual helper tool to help you calculate the width of a cell inside the collection view. This is the main architecture of CoreKit UI.
Oh, one more thing:

It works on tvOS, macOS & iOS too.

Let me explain this from a more techinical point of view. I have this protocol (CollectionViewDataType), which in the epicenter of everything. I also built an open class around this protocol (CollectionViewData) which can respond to all the delegate methods that "Apple"CollectionViewDataSource & "Apple"CollectionViewDelegate needs. There is also a CollectionViewDataSource class, which is set by the datasource of the collection view. This class will handle all the cell registration & reusing without you touching a key. Your only task is to create cells & the data objects that can feed cells & put them into a section object inside a source class. You can find the whole logic here. It's like a ~800 lines of code, pretty straightforward if you're familiar with collection views, but you shouldn't care about all of this. The only thing that you should do is:

Building reusable components

REUSABLE COMPONENTS. I truly mean it. Not like UIView subclasses, and other toys. Real reusability, with low memory overhead, rotation support, easy keyboard avoiding + animations. That's it. I don't think that you'll ever need anything else. Let's check out your part, the actual work that you supposed to do if you are working with CoreKit.

Step 1: Think about reusable components (cells). Imagine your UI & cut it into little pieces that can be reused later on. Now create your cells in a xib or programmatically from code, whatever you prefer.

import class UIKit.UILabel
import CoreKit

class TextCell : CollectionViewCell
{
	@IBOutlet weak var textLabel: UILabel!
        
    override func reset() {
        super.reset()

        self.textLabel.text = nil
    }
}

Step 2: Feed your cell with data. There is a generic CollectionViewData class which encapsulates a cell and an object (any if we're talking in Swift) together. In the case of the example project, a StringData is displayed in a TextCell with a String type. You can initialize this StringData object later on with any kind of data source, like this: StringData(item: "Hello"). It can calculate the size, configurate the cell, have a selection handler, whatever you want. Complete freedom without the bondage of the delegate pattern & the indexPath calculation madness.

import CoreKit

class StringData: CollectionViewData<TextCell, String> {

    override func initialize() {
        super.initialize()
        
        self.item = "hehehe"
        self.config = { cell, item, indexPath, grid in
            cell.textLabel.text = item
        }

        self.size = { item, indexPath, grid in
            return grid.size(height: 44)
        }
    }
}

Step 3: Somewhere inside your view controller you should setup a source and fill up with your section & data objects. As you can see you don't have to subclass the CollectionViewData generic class, you can use it directly with your cell and data object type. This level of freedom gives you the perfect power to build up anything haha.

let grid = Grid(view: self.collectionView, traitCollection: self.traitCollection)
grid.columns = 2


let source  = CollectionViewDataSource(grid: grid)
var section = CollectionViewDataSourceSection() { item, indexPath in
    print("alternative section callback")
}
        
let item = CollectionViewData<TextCell, String>(item: "Cell \(i).",
                    config: { cell, item, indexPath, grid in
                        cell.backgroundColor = .red
                        cell.textLabel.text = item
                    },
                    size: { item, indexPath, grid in
                        return grid.size(height: 64, columns: 3)
                })
        
source.sections.append(section)

self.collectionView.source = source
self.collectionView.reloadData()

Step 4: That's it. You're done. Build your app & see what happens. You can use the same code for a tvOS application or a macOS application too. I made a nice trick to elimiate the border between iOS and macOS collection view cells, also I'm calling things like AppleView, AppleImageView insteadof NSView or UIView. This is what going to happen eventually with AppKit & UIKit for sure, or if not Apple engineers are crazy bastards! #justkidding

What's next?

CoreKit written in Swift 4. That's for sure. Apple already refactored lots of API's so my Apple.swift file is going to be even smaller (or bigger if you help me hide all the AppKit/UIKit classes under the Apple alias). Right now I'm trying to focus only 3 major things inside CoreKit.

  • UI building with the help of collection views
  • Async coding with promises (based on Khanlou promise class)
  • Server connections (based on Alamofire)

If you feel like that hey this project is good enough, please support it through pull requests and new issues on github. I really would like to give the community a better tool than IGListKit and all the rubbish that currently exists. Sorry, but this is the truth. I don't want to compete with Alamofire, because that's the de-facto standard for networking, but I'd like to give people a thin framework which can do all the must have tasks without other external libraries.

PS. I have a crazy idea to make an Objective-C version of CoreKit, but I don't have enough time to work on it, or maybe I will who knows...