Skip to content

Commit

Permalink
Polish Demos view controller
Browse files Browse the repository at this point in the history
Summary:
Adds additional polish and refinement to the initial Demos view controller.

* Adds SF Symbols to each section.
* Correctly selects and deselects cells when in and out split view mode.
* Fixes demo table view cell to adapt to safe area insets correctly.

Reviewed By: fabiomassimo

Differential Revision: D45686668

fbshipit-source-id: 5e24120a0aab5cb3fb9c33b0b0d7c087a56d6304
  • Loading branch information
TimOliver authored and facebook-github-bot committed Jul 20, 2023
1 parent 1e48bf9 commit 6ab6109
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = IGListKitExamples/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1067,6 +1068,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = IGListKitExamples/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1090,6 +1092,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "IGListKitExamples-UITests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1114,6 +1117,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "IGListKitExamples-UITests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1138,6 +1142,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/IGListKitMessageExample/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1164,6 +1169,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "$(SRCROOT)/IGListKitMessageExample/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1188,6 +1194,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = IGListKitTodayExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1213,6 +1220,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = IGListKitTodayExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
1 change: 0 additions & 1 deletion Examples/Examples-iOS/IGListKitExamples/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

return true
}

}

extension AppDelegate: UISplitViewControllerDelegate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import UIKit
final class DemoItem: NSObject {

let name: String
let imageName: String
let controllerClass: UIViewController.Type
let controllerIdentifier: String?

init(
name: String,
imageName: String,
controllerClass: UIViewController.Type,
controllerIdentifier: String? = nil
) {
self.name = name
self.imageName = imageName
self.controllerClass = controllerClass
self.controllerIdentifier = controllerIdentifier
}
Expand All @@ -41,7 +44,6 @@ extension DemoItem: ListDiffable {
}

final class DemoSectionController: ListSectionController {

private var object: DemoItem?

override func sizeForItem(at index: Int) -> CGSize {
Expand All @@ -51,6 +53,10 @@ final class DemoSectionController: ListSectionController {
override func cellForItem(at index: Int) -> UICollectionViewCell {
let cell: LabelCell = collectionContext.dequeueReusableCell(for: self, at: index)
cell.text = object?.name
cell.imageName = object?.imageName
if let splitViewController = viewController?.splitViewController {
cell.disclosureImageView.isHidden = splitViewController.viewControllers.count > 1
}
return cell
}

Expand All @@ -59,6 +65,8 @@ final class DemoSectionController: ListSectionController {
}

override func didSelectItem(at index: Int) {
setSeparatorsHidden(true)

let navigationController = UINavigationController()
navigationController.navigationBar.prefersLargeTitles = true

Expand All @@ -75,4 +83,20 @@ final class DemoSectionController: ListSectionController {
}
}

override func didDeselectItem(at index: Int) {
setSeparatorsHidden(false)
}

private func setSeparatorsHidden(_ hidden: Bool) {
if let cell = collectionContext.cellForItem(at: 0, sectionController: self) as? LabelCell {
cell.separator.isHidden = hidden
}

if section > 0,
let listAdapter = collectionContext as? ListAdapter,
let previousSectionController = listAdapter.sectionController(forSection: section - 1),
let previousCell = collectionContext.cellForItem(at: 0, sectionController: previousSectionController) as? LabelCell {
previousCell.separator.isHidden = hidden
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,51 @@ final class DemosViewController: UIViewController, ListAdapterDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())

let demos: [DemoItem] = [
DemoItem(name: "Tail Loading",
DemoItem(name: "Tail Loading", imageName: "arrow.down.circle",
controllerClass: LoadMoreViewController.self),
DemoItem(name: "Search Autocomplete",
DemoItem(name: "Search Autocomplete", imageName: "magnifyingglass",
controllerClass: SearchViewController.self),
DemoItem(name: "Mixed Data",
DemoItem(name: "Mixed Data", imageName: "square.fill.text.grid.1x2",
controllerClass: MixedDataViewController.self),
DemoItem(name: "Nested Adapter",
DemoItem(name: "Nested Adapter", imageName: "curlybraces",
controllerClass: NestedAdapterViewController.self),
DemoItem(name: "Empty View",
DemoItem(name: "Empty View", imageName: "exclamationmark.triangle",
controllerClass: EmptyViewController.self),
DemoItem(name: "Single Section Controller",
DemoItem(name: "Single Section Controller", imageName: "1.square",
controllerClass: SingleSectionViewController.self),
DemoItem(name: "Storyboard",
DemoItem(name: "Storyboard", imageName: "rectangle.on.rectangle",
controllerClass: SingleSectionViewController.self,
controllerIdentifier: "demo"),
DemoItem(name: "Single Section Storyboard",
DemoItem(name: "Single Section Storyboard", imageName: "rectangle",
controllerClass: SingleSectionStoryboardViewController.self,
controllerIdentifier: "singleSectionDemo"),
DemoItem(name: "Working Range",
DemoItem(name: "Working Range", imageName: "arrow.left.and.right",
controllerClass: WorkingRangeViewController.self),
DemoItem(name: "Diff Algorithm",
DemoItem(name: "Diff Algorithm", imageName: "function",
controllerClass: DiffTableViewController.self),
DemoItem(name: "Supplementary Views",
DemoItem(name: "Supplementary Views", imageName: "square.stack.3d.up",
controllerClass: SupplementaryViewController.self),
DemoItem(name: "Self-sizing cells",
DemoItem(name: "Self-sizing cells", imageName: "brain",
controllerClass: SelfSizingCellsViewController.self),
DemoItem(name: "Display delegate",
DemoItem(name: "Display delegate", imageName: "megaphone",
controllerClass: DisplayViewController.self),
DemoItem(name: "Objc Demo",
DemoItem(name: "Objc Demo", imageName: "c.square",
controllerClass: ObjcDemoViewController.self),
DemoItem(name: "Objc Generated Model Demo",
DemoItem(name: "Objc Generated Model Demo", imageName: "c.circle",
controllerClass: ObjcGeneratedModelDemoViewController.self),
DemoItem(name: "Calendar (auto diffing)",
DemoItem(name: "Calendar (auto diffing)", imageName: "calendar",
controllerClass: CalendarViewController.self),
DemoItem(name: "Dependency Injection",
DemoItem(name: "Dependency Injection", imageName: "syringe",
controllerClass: AnnouncingDepsViewController.self),
DemoItem(name: "Reorder Cells",
DemoItem(name: "Reorder Cells", imageName: "arrow.up.and.down.and.arrow.left.and.right",
controllerClass: ReorderableViewController.self)
]

override func viewDidLoad() {
super.viewDidLoad()
title = "IGListKit"
navigationController?.navigationBar.prefersLargeTitles = true
collectionView.alwaysBounceVertical = true
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
Expand All @@ -74,9 +75,32 @@ final class DemosViewController: UIViewController, ListAdapterDataSource {

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.frame = view.bounds
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

guard let splitViewController = splitViewController else {
return
}

if splitViewController.viewControllers.count > 1 {
// When on iPad, this view controller is visible all the time, so on initial launch, select the first section
if let firstSection = adapter.sectionController(forSection: 0) {
firstSection.collectionContext.selectItem(at: 0, sectionController: firstSection, animated: false, scrollPosition: .top)
}
} else {
// On iPhone, deselect all cells when returning to this view controller (since we'll be coming back from a navigation pop)
for sectionController in adapter.visibleSectionControllers() {
sectionController.collectionContext.deselectItem(at: 0, sectionController: sectionController, animated: animated)
// UIColletionView doesn't call the deselection delegate by design when manually deselected, so manually deselect here
sectionController.didDeselectItem(at: 0)
}
}
}

// MARK: ListAdapterDataSource

func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
Expand Down
72 changes: 64 additions & 8 deletions Examples/Examples-iOS/IGListKitExamples/Views/LabelCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import UIKit

final class LabelCell: UICollectionViewCell {

fileprivate static let insets = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15)
fileprivate static let font = UIFont.systemFont(ofSize: 17)
fileprivate static var insets = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15)
fileprivate static let font = UIFont.systemFont(ofSize: 18)
fileprivate static let symbolFont = UIFont.boldSystemFont(ofSize: 21)

static var singleLineHeight: CGFloat {
return font.lineHeight + insets.top + insets.bottom
Expand Down Expand Up @@ -39,6 +40,21 @@ final class LabelCell: UICollectionViewCell {
return layer
}()

let imageView: UIImageView = {
let imageView = UIImageView()
imageView.tintColor = .titleLabel
imageView.contentMode = .scaleAspectFit
return imageView
}()

let disclosureImageView: UIImageView = {
let configuration = UIImage.SymbolConfiguration(pointSize: 17, weight: .bold, scale: .small)
let chevronImage = UIImage(systemName: "chevron.right", withConfiguration: configuration)
let imageView = UIImageView(image: chevronImage)
imageView.tintColor = .defaultSeparator
return imageView
}()

var text: String? {
get {
return label.text
Expand All @@ -48,11 +64,25 @@ final class LabelCell: UICollectionViewCell {
}
}

var imageName: String? {
didSet {
guard let imageName else {
return
}
let configuration = UIImage.SymbolConfiguration(font: LabelCell.symbolFont, scale: .default)
let image = UIImage(systemName: imageName, withConfiguration: configuration)
imageView.image = image
setNeedsLayout()
}
}

override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = UIColor.background
contentView.addSubview(label)
contentView.layer.addSublayer(separator)
contentView.addSubview(disclosureImageView)
contentView.addSubview(imageView)
}

required init?(coder aDecoder: NSCoder) {
Expand All @@ -62,16 +92,38 @@ final class LabelCell: UICollectionViewCell {
override func layoutSubviews() {
super.layoutSubviews()
let bounds = contentView.bounds
label.frame = bounds.inset(by: LabelCell.insets)
let height: CGFloat = 0.5
let left = LabelCell.insets.left
separator.frame = CGRect(x: left, y: bounds.height - height, width: bounds.width - left, height: height)

let hasImage = imageName?.count ?? 0 > 0
let imageCenterX = (hasImage ? LabelCell.insets.left + 15 : 0) + safeAreaInsets.left
if hasImage {
imageView.sizeToFit()
imageView.frame.origin = CGPoint(x: imageCenterX - imageView.bounds.midX,
y: bounds.midY - imageView.bounds.midY)
}

disclosureImageView.frame.origin = CGPoint(x: bounds.width - (LabelCell.insets.right + disclosureImageView.bounds.width + safeAreaInsets.right),
y: bounds.midY - disclosureImageView.bounds.midY)

let labelX = hasImage ? imageCenterX + 25 : (LabelCell.insets.left + safeAreaInsets.left)
label.frame = CGRect(x: labelX,
y: LabelCell.insets.top,
width: bounds.width - (labelX - disclosureImageView.frame.minX),
height: bounds.height - (LabelCell.insets.top + LabelCell.insets.bottom))

let separatorHeight: CGFloat = 1.0 / (window?.screen.nativeScale ?? 2.0)
let left = label.frame.minX
separator.frame = CGRect(x: left, y: bounds.height - separatorHeight, width: bounds.width - left, height: separatorHeight)
}

override var isHighlighted: Bool {
didSet {
let color = isHighlighted ? UIColor.gray.withAlphaComponent(0.3) : UIColor.clear
contentView.backgroundColor = color
setHighlighted(isHighlighted)
}
}

override var isSelected: Bool {
didSet {
setHighlighted(isSelected)
}
}

Expand All @@ -80,6 +132,10 @@ final class LabelCell: UICollectionViewCell {
separator.backgroundColor = UIColor.defaultSeparator.cgColor
}

private func setHighlighted(_ highlighted: Bool) {
let color = highlighted ? UIColor.gray.withAlphaComponent(0.3) : UIColor.clear
contentView.backgroundColor = color
}
}

extension LabelCell: ListBindable {
Expand Down

0 comments on commit 6ab6109

Please sign in to comment.