FUISideBar
@available(iOS 14, *)
@MainActor
open class FUISideBar<Data>: UIView where Data: RandomAccessCollection,
Data.Element: Identifiable & Hashable
A FUISideBar<Data> is a generic view that displaying data in an expandable list within a side bar.
Usage
Initialization:
Implement a data model for the expandable list that will be displayed in side bar.
struct BarItem: Identifiable, Hashable {
let id = UUID()
let title: String
var icon: UIImage?
var filledIcon: UIImage?
var subtitle: String?
var status: UIImage?
let children: [BarItem]?
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
hasher.combine(self.title)
}
static func == (lhs: BarItem, rhs: BarItem) -> Bool {
lhs.id == rhs.id
}
init(title: String, icon: UIImage? = nil, filledIcon: UIImage? = nil, subtitle: String? = nil, status: UIImage? = nil, children: [BarItem]? = nil) {
self.title = title
self.icon = icon
self.filledIcon = filledIcon
self.subtitle = subtitle
self.status = status
self.children = children
}
}
let items: [BarItem] = [
BarItem(title: "Root Item 0.1", icon: UIImage(systemName: "cloud.snow"), subtitle: "9,999+", status: UIImage(systemName: "clock"), children: nil),
BarItem(title: "Root Item 0.4", icon: UIImage(systemName: "cloud.snow"), children: nil),
BarItem(title: "Group 1", children: [
BarItem(title: "Child Item 1.1", icon: UIImage(systemName: "square.and.pencil"), subtitle: "66", status: UIImage(systemName: "circle"), children: nil),
BarItem(title: "Child Item 1.2", icon: UIImage(systemName: "square.and.pencil"), status: UIImage(systemName: "circle"), children: nil)
]),
BarItem(title: "Group 2", children: [
BarItem(title: "Child Item 2.1", icon: UIImage(systemName: "folder"), subtitle: "5", status: UIImage(systemName: "mail"), children: nil)
])
]
Initialize an FUISideBar with the data model, a binding of row content view, and the initially selected item. Please note, with this initializer, the sidebar does not support editing.
let sidebar = FUISideBar(data: items,
children: \.children,
rowContent: { item in
FUISideBarListItem(icon: item.icon, filledIcon: item.filledIcon, title: item.title, subtitle: item.subtitle, accessoryIcon: item.status)
},
selectedItem: items.first)
Or, Initialize an FUISideBar with the data model, you will need to provide an array of FUISideBarItemModel, a binding of row content view, and the initially selected item. With this initializer, the sidebar support editing.
struct BarItem: FUISideBarItemModel {
var id: UUID = UUID()
var title: String
var icon: UIImage?
var filledIcon: UIImage?
var subtitle: String?
var status: UIImage?
var isInvisible: Bool
var children: [any FUISideBarItemModel]?
static func == (lhs: EditableBarItem, rhs: EditableBarItem) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
hasher.combine(self.title)
}
init(title: String, icon: UIImage? = nil, filledIcon: UIImage? = nil, subtitle: String? = nil, status: UIImage? = nil, children: [EditableBarItem]? = nil, isInvisible: Bool = false) {
self.title = title
self.icon = icon
self.filledIcon = filledIcon
self.subtitle = subtitle
self.status = status
self.children = children
self.isInvisible = isInvisible
}
}
let items: [BarItem] = [
BarItem(title: "Root Item 0.1", icon: UIImage(systemName: "square.dashed), filledIcon: UIImage(systemName: "square.dashed.inset.filled"), subtitle: "9,999+", status: UIImage(systemName: "clock"), children: nil),
BarItem(title: "Root Item 0.4", icon: UIImage(systemName: "cloud.snow"), children: nil),
BarItem(title: "Group 1", children: [
BarItem(title: "Child Item 1.1", icon: UIImage(systemName: "square.and.pencil"), subtitle: "66", status: UIImage(systemName: "circle"), children: nil),
BarItem(title: "Child Item 1.2", icon: UIImage(systemName: "square.and.pencil"), status: UIImage(systemName: "circle"), children: nil)
]),
BarItem(title: "Group 2", children: [
BarItem(title: "Child Item 2.1", icon: UIImage(systemName: "folder"), subtitle: "5", status: UIImage(systemName: "mail"), children: nil)
])
]
let sidebar = FUISideBar(data: items,
rowContent: { item in
FUISideBarListItem(icon: item.icon, filledIcon: item.filledIcon, title: item.title, subtitle: item.subtitle, accessoryIcon: item.status)
},
selectedItem: items.first)
Selection Handler:
A selectionDidChange closure may be used to custom the handling of an selected data element.
sidebar.selectionDidChange = { [weak self] item in
self?.presentationDidChange?(item)
}
Handle Editing:
An ‘isEditing’ property of FUISideBar<Data> indicates whether the sidebar is in editing mode or not. Please note, it is essential to use the corresponding Initialize method which permits the editing of the sidebar. Additionally, an ‘Edit’ button can be added to the navigation bar to allow the sidebar to transition from editing mode to the view model.
self.navigationItem.rightBarButtonItem = self.editButtonItem
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.sideBar?.isEditing = editing
configNavBarStyle()
configureSearchBarOnNavBar(true)
}
Monitor changed data:
A dataChange closure may be used to receive notifications when the sidebar transitions from its editing mode to the view model if the sidebar item was reordered or visible was changed. This arrangement facilitates effective monitoring and management of changes. This should be used exclusively when the sidebar supports editing and you are particularly concerned about the modified data.
sideBar?.dataChange = { (items: [BarItem]) in
// Check data changes
}
Handle Search:
A ‘queryString’ property of FUISideBar<Data> is used to trigger the searching on SideBar. You can add the FUISearchController as navigation item for searching and get the query string from UISearchResultsUpdating.
let fuiSearchController = addResultsController ? FUISearchController(searchResultsController: SearchResultsViewController()) : FUISearchController(searchResultsController: nil)
fuiSearchController.searchResultsUpdater = self
fuiSearchController.hidesNavigationBarDuringPresentation = hidesNavBar
fuiSearchController.setsNavigationBarColor = true
fuiSearchController.setsStatusBarStyle = true
fuiSearchController.searchBar.placeholderText = "Search"
fuiSearchController.searchBar.delegate = self
self.searchController = fuiSearchController
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = true // false
func updateSearchResults(for searchController: UISearchController) {
self.sideBar?.queryString = searchController.searchBar.text
}
-
String for displaying subtitle in the side bar.
Declaration
Swift
@MainActor public private(set) var subtitle: String? { get } -
Footer view of the side bar.
Declaration
Swift
@MainActor public private(set) var footer: UIView? { get } -
Bool to indicate whether the side bar is in editing mode or not
Declaration
Swift
@MainActor public var isEditing: Bool { get set } -
String for searching in the side bar
Declaration
Swift
@MainActor public var queryString: String? { get set } -
Closure that defining behaviors once selection has been changed.
Declaration
Swift
@MainActor public var selectionDidChange: ((Data.Element?) -> Void)? { get set } -
Closure that defines behaviors once the sidebar transitions from editing mode to the view mode, and the data of the sidebar has been changed, such as reordering, visibility changes, etc. The data passed into the closure includes all the changed data. This facilitates effective monitoring and management of changes.
Declaration
Swift
@MainActor public var dataChange: (([any FUISideBarItemModel]) -> Void)? -
Public initializer for FUISideBar. with this initializer, the sidebar does not support editing
Declaration
Swift
@MainActor public init(subtitle: String? = nil, footer: UIView? = nil, data: Data, children: KeyPath<Data.Element, Data?>, rowContent: @escaping (Data.Element) -> UIView, selectedItem: Data.Element? = nil, selectionDidChange: ((Data.Element?) -> Void)? = nil)Parameters
subtitleSubtitle for the side bar.
footerFooter view for the side bar.
dataThe data for constructing the expandable list within the side bar.
childrenThe key path to the optional property of a data element whose value indicates the children of that element.
rowContentThe data-binding closure which returns the view of each row in an expandable list.
selectedItemInitially selected data element.
selectionDidChangeThe closure that defines behaviors once a data element in the expandable list has been selected.
-
Public initializer for FUISideBar. With this initializer, the sidebar supports editing by setting the ‘isEditing’ property of sidebar
Declaration
Swift
@MainActor public init(footer: UIView? = nil, data: [any FUISideBarItemModel], rowContent: @escaping (any FUISideBarItemModel) -> UIView, selectedItem: (any FUISideBarItemModel)? = nil, selectionDidChange: (((any FUISideBarItemModel)?) -> Void)? = nil)Parameters
footerFooter view for the side bar.
dataThe array of ‘FUISideBarItemModel’ for constructing the expandable list within the side bar which supports editing.
rowContentThe data-bound closure which returns the view of each row in an expandable list.
selectedItemInitially selected data element.
selectionDidChangeThe closure that defines behaviors once a data element in the expandable list has been selected.