Empty State View

I’ve been testing my app with fake data for a long time. I have a function that I call, and it creates some people and items, populated with nice pictures from unsplash, and bingo presto I can work with them in my app.

(Nice thing about this data is that I’ll be using it to create screenshots for the app store.)

Which made making an empty view not so much of a priority. But getting down to the short strokes, I needed to show the user something when the app first started, because a blank screen just won’t cut it.

(Years ago I would have been, like, whatever.)

I didn’t want to spend a lot of time on this, so I decided to try DZNEmptyDataSet. It’s a framework for just this purpose – to create an special view for when there’s no data. It’s handy because it integrates pretty quickly. I still needed to do some work, like setting up the NSAttributedStrings that were needed. But pretty soon I got it up and running, as you can see in the first screenshot.

(And I have to say it looks pretty nice!)

But I ran into problems rather quickly. Two really. One, it would flicker sometimes as it was showing up. Which did not look good at all. Also, the three lines of text were not aligned properly in the middle of the view. Sometimes. Other times it did align properly. It was very frustrating and at that point I decided to roll my own solution.

(I don’t like third party library dependencies anyway.)

Rolling my own solution using UITableView’s backgroundView actually took very little time. About the same amount of time as integrating DZNEmptyDataSet in the first place. Yikes. What took a lot more time was having it be context sensitive to whether there was no data at all, or if the data had just been filtered away by the user’s filter choices. (It was a little tricky because that information wasn’t anywhere near the same level in the code as was the empty view code.)

(And this was work I would have had to do anyway with DZNEmptyDataSet.)

But I got something working, so here are two screenshots of my own empty view, without using DZNEmptyDataSet. I could re-use my code for creating the NSAttributedStrings, so that was handy. Also, you can see that I changed the text a little. I wish I had a copywriter in the family, I’m still not completely happy with it.

UICollectionView Boilerplate

Sometimes you just need some boilerplate code. And usually you need to look it up. So in order to save some time, I put together this code for a simple UICollectionView with nicely spaced cells:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class ViewController: UIViewController, UICollectionViewDataSource /*, UICollectionViewDelegate */ {

    let kReuseIdentifier = "CollectionCellID"
    let kNumCellsPerRow:CGFloat = 3.0
    let kSpacer:CGFloat = 10.0
    var collectionView : UICollectionView!
   
    override func viewDidLoad() {
        super.viewDidLoad()

        let cellsPerRow: CGFloat = kNumCellsPerRow
        let minItemSpacing: CGFloat = kSpacer
        let containerWidth: CGFloat = self.view.bounds.width
        let itemWidth: CGFloat = (containerWidth - (cellsPerRow+1) * minItemSpacing) / cellsPerRow - 1  // -1 in case the screen width doesn't make it easy for us
        let inset = max(minItemSpacing, floor( (containerWidth - (cellsPerRow*itemWidth) - (cellsPerRow-1)*minItemSpacing) / 2 ) )
        let layout = UICollectionViewFlowLayout()
       
        layout.itemSize = CGSize(width: itemWidth, height: itemWidth)
        layout.minimumInteritemSpacing = minItemSpacing
        layout.minimumLineSpacing = minItemSpacing
        layout.sectionInset = UIEdgeInsets(top: minItemSpacing, left: inset, bottom: minItemSpacing, right: inset)

        self.collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kReuseIdentifier)
        //collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = self.view.backgroundColor
       
        self.view.addSubview(collectionView)
    }
   
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 20
    }
   
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kReuseIdentifier, for: indexPath as IndexPath)
        cell.backgroundColor = UIColor.lightGray
        cell.layer.cornerRadius = 3.0
        return cell
    }
}

Here is the UICollectionView gist for it.

Embedding View Controllers

In John Sundell’s excellent article Using child view controllers as plugins in Swift, Mr. Sundell reminds us that Apple has given us the ability to compose UIViewController objects together, allowing multiple controllers to display in one view. He also gave us two helper functions to use, one to add a child view controller and the other to remove it. Here is the the function to add a child controller to another controller:

1
2
3
4
5
func add(_ child: UIViewController) {
    addChildViewController(child)
    view.addSubview(child.view)
    child.didMove(toParentViewController: self)
}

It is definitely a handy function. When I wanted to use it, however, it fell short. My parent view controller wasn’t displaying the child view controller full screen but rather just in a view – a smaller area than the full screen. So I modified the add function to be like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func add(_ child: UIViewController, using container: UIView? = nil) {
    addChildViewController(child)
    if let container = container {
        container.addSubview(child.view)
    }
    else {
        view.addSubview(child.view)
    }
    child.didMove(toParentViewController: self)
}

func exampleUsage() {
    let parentController = MyViewController()
    // assume "MyViewController" contains a view "holder" that
    // we want the child to be placed in
    let childController = UIViewController()</p>
    parentController.add( childController, using: parentController.holder )
}

I don’t know if anyone else out there is experimenting with composing view controllers, but I hope you found my experience handy.

Gist Version of This Article