Latest 0.7.3
License MIT
Platforms ios 11.0, requires ARC
Dependencies Firebase, Firebase/Firestore, Firebase/Storage

🧢 Ballcap-iOS


Ballcap is a database schema design framework for Cloud Firestore.

Why Ballcap

Cloud Firestore is a great schema-less and flexible database that can handle data. However, its flexibility can create many bugs in development. Ballcap can assign schemas to Cloud Firestore to visualize data structures. This plays a very important role when developing as a team.

Inspired by

Please donate to continue development.


☑️ Firestore’s document schema with Swift Codable
☑️ Of course type safety.
☑️ It seamlessly works with Firestore and Storage.

Requirements ❗️

Installation ⚙


  • Insert pod 'Ballcap' to your Podfile.
  • Run pod install.

If you have a Feature Request, please post an issue.


Document scheme

You must conform to the Codable and Modelable protocols to define Scheme.

struct Model: Codable, Equatable, Modelable {
    var number: Int = 0
    var string: String = "Ballcap"


The document is initialized as follows:

let document: Document<Model> = Document()

print( // 0
print( // "Ballcap"

// KeyPath
print(document[.number]) // 0
print(document[.string]) // "Ballcap"


Considering the extensibility of DB, it is recommended to provide a method of version control.

// in AppDelegate


Ballcap has a cache internally.When using the cache, use Batch instead of WriteBatch.

// save

// update

// delete

// Batch
let batch: Batch = Batch() document)
batch.update(document: document)
batch.delete(document: document)

You can get data by using the get function.

Document<Model>.get(id: "DOCUMENT_ID", completion: { (document, error) in

The next get function gets data in favor of the cache. If there is no cached data, it gets from the server.

let document: Document<Model> = Document("DOCUMENT_ID")
document.get { (document, error) in

Why data is optional?

In CloudFirestore, DocumentReference does not necessarily have data. There are cases where there is no data under the following conditions.

  1. If no data is stored in DocumentReference.
  2. If data was acquired using Source.cache from DocumentReference, but there is no data in cache.

Ballcap recommends that developers unwrap if they can determine that there is data.

It is also possible to access the cache without using the network.

let document: Document<Model> = Document(id: "DOCUMENT_ID")
print(document.cache?.number) // 0
print(document.cache?.string) // "Ballcap"

Custom properties

Ballcap is preparing custom property to correspond to FieldValue.


Property for handling FieldValue.serverTimestamp()

struct Model: Codable, Equatable {
    let serverValue: ServerTimestamp
    let localValue: ServerTimestamp
let model = Model(serverValue: .pending,
                  localValue: .resolved(Timestamp(seconds: 0, nanoseconds: 0)))

IncrementableInt & IncrementableDouble

Property for handling FieldValue.increment()

struct Model: Codable, Equatable, Modelable {
    var num: IncrementableInt = 0
let document: Document<Model> = Document() = .increment(1)


Property for handling FieldValue.arrayRemove(), FieldValue.arrayUnion()

struct Model: Codable, Equatable, Modelable {
    var array: OperableArray<Int> = [0, 0]
let document: Document<Model> = Document() = .arrayUnion([1]) = .arrayRemove([1])


File is a class for accessing Firestorage.
You can save Data in the same path as Document by the follow:

let document: Document<Model> = Document(id: "DOCUMENT_ID")
let file: File = File(document.storageReference)

File supports multiple MIMETypes. Although File infers MIMEType from the name, it is better to input MIMEType explicitly.

  • [x] plain
  • [x] csv
  • [x] html
  • [x] css
  • [x] javascript
  • [x] octetStream(String?)
  • [x] pdf
  • [x] zip
  • [x] tar
  • [x] lzh
  • [x] jpeg
  • [x] pjpeg
  • [x] png
  • [x] gif
  • [x] mp4
  • [x] custom(String, String)

Upload & Download

Upload and Download each return a task. You can manage your progress by accessing tasks.

// upload
let ref: StorageReference ="/a")
let data: Data = "test".data(using: .utf8)!
let file: File = File(ref, data: data, name: "n", mimeType: .plain)
let task = { (metadata, error) in


// download
let task = file.getData(completion: { (data, error) in
    let text: String = String(data: data!, encoding: .utf8)!


StorageBatch is used when uploading multiple files to Cloud Storage.

let textData: Data = "test".data(using: .utf8)!
let textFile: File = File( "c"), data: textData, mimeType: .plain)

let jpgData: Data = image.jpegData(compressionQuality: 1)!
let jpgFile: File = File( "d"), jpgData: textData, mimeType: .jpeg)
batch.commit { error in


Migrate from Pring


The difference from Pring is that ReferenceCollection and NestedCollection have been abolished.
In Pring, adding a child Object to the ReferenceCollection and NestedCollection of the parent Object saved the parent Object at the same time when it was saved.
Ballcap requires the developer to save SubCollection using Batch.
In addition, Pring also saved the File at the same time as the Object with the File was saved.
Ballcap requires that developers save files using StorageBatch.


Ballcap can handle Object class by inheriting Object class like Pring.
If you inherit Object class, you must conform to DataRepresentable.

class Room: Object, DataRepresentable {

    var data: Model?

    struct Model: Modelable & Codable {
        var members: [String] = []


Ballcap has discontinued NestedCollection and ReferenceCollection Class. Instead, it represents SubCollection by defining CollectionKeys.

Class must match HierarchicalStructurable to use CollectionKeys.

class Room: Object, DataRepresentable & HierarchicalStructurable {

    var data: Model?

    var transcripts: [Transcript] = []

    struct Model: Modelable & Codable {
        var members: [String] = []

    enum CollectionKeys: String {
        case transcripts

Use the collection function to access the SubCollection.

let collectionReference: CollectionReference = obj.collection(path: .transcripts)

SubCollection’s Document save

let batch: Batch = Batch()
let room: Room = Room(), to: room.collection(path: .transcripts))

Latest podspec

    "name": "Ballcap",
    "version": "0.7.3",
    "summary": "Firestore design framework",
    "description": "s.swift_version = "5"nBallcap is a framework for operating Cloud Firestore Document.",
    "homepage": "",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    "authors": {
        "1amageek": "[email protected]"
    "social_media_url": "",
    "platforms": {
        "ios": "11.0"
    "source": {
        "git": "",
        "tag": "0.7.3"
    "source_files": "Ballcap/**/*.swift",
    "requires_arc": true,
    "static_framework": true,
    "dependencies": {
        "Firebase": [],
        "Firebase/Firestore": [],
        "Firebase/Storage": []

Pin It on Pinterest

Share This