Skip to content

Hierarchy View

FUIHierarchyView

open class FUIHierarchyView: UIView, FUIContentCopyable

A view that manages a collection of items in a multi-column layout.

Initial Loading Process

  1. Hierarchy view asks data source for a root item and put it in the first column. Hierarchy view will be empty if nil is returned for root. If so, hierarchy view finishes loading.
  2. Hierarchy view asks data source for number of children of the root and get each child item iteratively.
  3. Hierarchy view asks data source for the parent of root item. If that parent exists, insert it into preview column before the root.

Image

Interaction Behavior

Expand Children

Tap on the hierarchy accessory view will drill down one level in the hierarchy view to show the children of tapped item.

Image

Image

Swipe

Quick swipe left/right to view the previous or next level in the hierarchy.

Image

Image

Pan

Pan left/right to navigate further in hierarchy view.

Image

Usage

Have your data source object conform to FUIHierarchyViewDataSource protocol and set it to hierarchy view's dataSource property.

class YourHierarchyViewDataSource: FUIHierarchyViewDataSource {}

hierarchyView.dataSource = YourHierarchyViewDataSource()

Register your custom cell if needed. Otherwise hierarchy view will use FUIHierarchyCollectionItem by default.

hierarchyView.register(YourCustomCollectionViewCell.self, forCellWithReuseIdentifier: "ReuseIdentifierForYourCustomCell")

Implement dataSource methods.

// Define your data model.
var model = HierarchyViewModel()

func rootUUID(in hierarchyView: FUIHierarchyView) -> String? {
    return model.rootObject?.uuid
}

func hierarchyView(_ hierarchyView: FUIHierarchyView, numberOfChildrenForItemWith uuid: String) -> Int {
    guard let object = model.getObject(for: uuid) else {
        preconditionFailure("Object with String: \(uuid) does not exsit in model")
    }

    if let children = object.children {
        return children.count
    }

    // Get children asynchroniously if they haven't been loaded yet. When loading completes, cache those children for future access and insert children into hierarchy view by calling `insertChildren(of:at:)`.
    object.asyncFetchChildren { children in
        self.model.updateCache(objects: children)
        self.hierarchyView.insertChildren(of: uuid, at: IndexSet(integersIn: 0..<children.count))
    }

    // Return zero children before children is loaded.
    return 0
}

func hierarchyView(_ hierarchyView: FUIHierarchyView, uuidForChildItemAt index: Int, with parentUUID: String) -> String {
    guard let parent = model.getObject(for: parentUUID) else {
        preconditionFailure("Parent object with String: \(parentUUID) does not exsit")
    }

    guard let children = parent.children else {
        preconditionFailure("Children of parent: \(parent) is not loaded yet")
    }

    return children[index].uuid
}

func hierarchyView(_ hierarchyView: FUIHierarchyView, parentForItemWith uuid: String) -> String? {
    guard let object = model.getObject(for: uuid) else {
        preconditionFailure("Object with String: \(uuid) does not exsit in model")
    }

    if let parent = object.parent {
        return parent.uuid
    }

    // Get parent asynchronously if they haven't been loaded yet. When loading completes, cache the parent for future access and insert it into hierarchy view by calling `invalidateParent(of:)`.
    object.asyncFetchParent { parent in
        guard let parent = parent else {
            return
        }

        self.model.updateCache(objects: [parent])
        self.model.updateRootObject(newRootObject: parent)
        self.hierarchyView.invalidateParent(of: uuid)
    }

    // Return nil before parent is loaded.
    return nil
}

func hierarchyView(_ hierarchyView: FUIHierarchyView, cellForItemWith uuid: String) -> FUIHierarchyCollectionItem {
    let cell = hierarchyView.dequeueReusableCell(withReuseIdentifier: ReuseIdentifierForYourCustomCell, with: uuid) as! YourCustomCollectionViewCell
    guard let object = model.getObject(for: uuid) else {
        return cell
    }

    cell.title.text = object.name
    cell.subtitle.text = object.type
    cell.body.text = object.location
    cell.accessoryType = .disclosureIndicator
    if object.numberOfChildren > 0 {
        cell.isHierarchyButtonHidden = false
        cell.hierarchyAttributeText = String(object.numberOfChildren)
    }
    else {
        cell.isHierarchyButtonHidden = true
    }

    return cell
}

func hierarchyView(_ hierarchyView: FUIHierarchyView, titleForItemWith uuid: String) -> String? {
    return model.getObject(for: uuid)?.name
}

Make your delegate object conform to FUIHierarchyViewDelegate protocol and assign it to hierarchy view's delegate property if needed.

class YourHierarchyViewDelegate: FUIHierarchyViewDelegate {}

hierarchyView.delegate = YourHierarchyViewDelegate()

Theming

Supported class paths:

1
2
fdlFUIHierarchyView {}
fdlFUIHierarchyView_header {}

Last update: April 14, 2021