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
subtitle
Subtitle for the side bar.
footer
Footer view for the side bar.
data
The data for constructing the expandable list within the side bar.
children
The key path to the optional property of a data element whose value indicates the children of that element.
rowContent
The data-binding closure which returns the view of each row in an expandable list.
selectedItem
Initially selected data element.
selectionDidChange
The 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
footer
Footer view for the side bar.
data
The array of ‘FUISideBarItemModel’ for constructing the expandable list within the side bar which supports editing.
rowContent
The data-bound closure which returns the view of each row in an expandable list.
selectedItem
Initially selected data element.
selectionDidChange
The closure that defines behaviors once a data element in the expandable list has been selected.