Skip to content

Commit

Permalink
refactor(SegmentSelector) Rename SegmentSelector to Filter (#136)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: SegmentSelector as been renamed to Filter.
  • Loading branch information
jmpg93 committed Sep 14, 2021
1 parent 94624e2 commit 6830ea9
Show file tree
Hide file tree
Showing 34 changed files with 152 additions and 148 deletions.
128 changes: 66 additions & 62 deletions Mistica.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// SegmentSelector.swift
// Filter.swift
//
// Made with ❤️ by Novum
//
Expand All @@ -9,31 +9,31 @@
import Foundation
import UIKit

/// The different segment rendering modes supported by `SegmentSelector`.
/// The different segment rendering modes supported by `Filter`.
private enum SegmentsContentMode {
case leading
case center
}

/// The SegmentSelectorDelegate protocol defines methods that allow you to manage the selection and deselection of
/// segments in a `SegmentSelector`. The methods of this protocol are all optional.
public protocol SegmentSelectorDelegate: AnyObject {
func segmentSelector(_ segmentSelector: SegmentSelector, didProgramaticallySelectSegment segment: Segment)
func segmentSelector(_ segmentSelector: SegmentSelector, didManuallySelectSegment segment: Segment)
func segmentSelector(_ segmentSelector: SegmentSelector, didDeselectSegment segment: Segment)
/// The FilterDelegate protocol defines methods that allow you to manage the selection and deselection of
/// segments in a `Filter`. The methods of this protocol are all optional.
public protocol FilterDelegate: AnyObject {
func filter(_ filter: Filter, didProgramaticallySelectSegment segment: Segment)
func filter(_ filter: Filter, didManuallySelectSegment segment: Segment)
func filter(_ filter: Filter, didDeselectSegment segment: Segment)
}

/// Provide default empty implementations for all methods.
public extension SegmentSelectorDelegate {
func segmentSelector(_: SegmentSelector, didProgramaticallySelectSegment _: Segment) {}
func segmentSelector(_: SegmentSelector, didManuallySelectSegment _: Segment) {}
func segmentSelector(_: SegmentSelector, didDeselectSegment _: Segment) {}
public extension FilterDelegate {
func filter(_: Filter, didProgramaticallySelectSegment _: Segment) {}
func filter(_: Filter, didManuallySelectSegment _: Segment) {}
func filter(_: Filter, didDeselectSegment _: Segment) {}
}

/// `SegmentSelector` is used to render a scrollable selector that will typically be placed below the navigation bar,
/// `Filter` is used to render a scrollable selector that will typically be placed below the navigation bar,
/// and which is used to let the user select one segment among a list of them. You will typically use the selected
/// segment to filter or categorize the content below.
public class SegmentSelector: UIView {
public class Filter: UIView {
private enum Constants {
/// This component has a fixed height.
static let componentHeight: CGFloat = 49
Expand All @@ -48,7 +48,7 @@ public class SegmentSelector: UIView {
private let bottomSeparator: UIImageView

/// Set this delegate to receive notifications about selection/deselection of segments
public weak var delegate: SegmentSelectorDelegate?
public weak var delegate: FilterDelegate?

/// The list of segments shown by the component. Setting this property causes the component to reload.
public var segments: [Segment] = [] {
Expand All @@ -71,7 +71,7 @@ public class SegmentSelector: UIView {

/// The mode in which the segments will be rendered.
///
/// There are two modes of displaying the segments inside a `SegmentSelector`:
/// There are two modes of displaying the segments inside a `Filter`:
/// - `leading`: Segments are always aligned to the left of the screen.
/// - `center`: When the bounds of the component are bigger than the `contentSize`
/// plus the separators and insets, segments will be rendered in the center of the
Expand Down Expand Up @@ -125,7 +125,7 @@ public class SegmentSelector: UIView {

// MARK: Public API

public extension SegmentSelector {
public extension Filter {
/// Use this method to programatically select a Segment.
///
/// Calling this method causes `segment` to be selected if it exists. Otherwise, nothing will happen.
Expand All @@ -150,7 +150,7 @@ public extension SegmentSelector {

// MARK: UICollectionViewDataSource

extension SegmentSelector: UICollectionViewDataSource {
extension Filter: UICollectionViewDataSource {
public func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
segments.count
}
Expand All @@ -169,7 +169,7 @@ extension SegmentSelector: UICollectionViewDataSource {

// MARK: UICollectionViewDelegate

extension SegmentSelector: UICollectionViewDelegate {
extension Filter: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
// We allow selection only when the cell is not already selected.
indexPath.item != selectedSegmentIndex
Expand All @@ -187,7 +187,7 @@ extension SegmentSelector: UICollectionViewDelegate {
// MARK: UILargeContentViewerInteractionDelegate

@available(iOS 13, *)
extension SegmentSelector: UILargeContentViewerInteractionDelegate {
extension Filter: UILargeContentViewerInteractionDelegate {
public func largeContentViewerInteraction(_: UILargeContentViewerInteraction, didEndOn item: UILargeContentViewerItem?, at _: CGPoint) {
guard let cell = item as? SegmentCell,
let indexPath = collectionView.indexPath(for: cell),
Expand All @@ -199,7 +199,7 @@ extension SegmentSelector: UILargeContentViewerInteractionDelegate {

// MARK: Private

private extension SegmentSelector {
private extension Filter {
func setUpSegmentsContentMode(for traitCollection: UITraitCollection) {
switch traitCollection.horizontalSizeClass {
case .regular:
Expand All @@ -217,7 +217,7 @@ private extension SegmentSelector {
case .leading:
layout = UICollectionViewFlowLayout()
case .center:
layout = SegmentSelectorCenteredContentLayout()
layout = FilterCenteredContentLayout()
}
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = Constants.intersegmentSpacing
Expand Down Expand Up @@ -289,25 +289,25 @@ private extension SegmentSelector {

func notifyProgrammaticSelection(at indexPath: IndexPath) {
guard let segment = segment(atIndex: indexPath.item) else { return }
delegate?.segmentSelector(self, didProgramaticallySelectSegment: segment)
delegate?.filter(self, didProgramaticallySelectSegment: segment)
}

func notifyUserSelection(at indexPath: IndexPath) {
guard let segment = segment(atIndex: indexPath.item) else { return }
delegate?.segmentSelector(self, didManuallySelectSegment: segment)
delegate?.filter(self, didManuallySelectSegment: segment)
}

func notifySegmentDeselection(at indexPath: IndexPath) {
guard let segment = segment(atIndex: indexPath.item) else { return }
delegate?.segmentSelector(self, didDeselectSegment: segment)
delegate?.filter(self, didDeselectSegment: segment)
}

/// Adjusts the content offset so if the selected cell is slightly out of the screen, the collectionView will
/// scroll to make it totally visible.
func adjustContentOffsetIfNeeded(in collectionView: UICollectionView, forCellSelectedAt indexPath: IndexPath) {
// Force a layout pass immediately, since we depend on the attributes to perform contentOffset adjustments, and
// this method might be called right after a layout invalidation.
// For example, the first time SegmentSelector appear on screen, we want to set the initial list of segments (this
// For example, the first time Filter appear on screen, we want to set the initial list of segments (this
// calls reloadData and invalidateLayout), and then we might also want to preselect a segment (i.e. deeplink with
// categoryId parameter). In that case, we need to force layout or the attributes of the item won't be calculated yet.
collectionView.layoutIfNeeded()
Expand Down Expand Up @@ -360,7 +360,7 @@ private extension SegmentSelector {

// MARK: Theme Variant did change notification

@objc extension SegmentSelector {
@objc extension Filter {
func themeDidChange() {
collectionView.backgroundColor = .navigationBarBackground
bottomSeparator.image = UIImage(color: .navigationBarDivider)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// SegmentSelectorCenteredContentLayout.swift
// FilterCenteredContentLayout.swift
//
// Made with ❤️ by Novum
//
Expand All @@ -20,7 +20,7 @@ import UIKit
///
/// This layout inherits from `BoundsChangeInvalidatingFlowLayout`, which will invalidate layout only when `bounds.size`
/// changes.
class SegmentSelectorCenteredContentLayout: BoundsChangeInvalidatingFlowLayout {
class FilterCenteredContentLayout: BoundsChangeInvalidatingFlowLayout {
// Override the contentSize because we are also overriding layoutAttributesForElements, and there we are adding a
// padding to the left of every attribute, which we need to take in account when calculating the contentSize.
// Otherwise the rightmost cells might even get deallocated, since the UICollectionView might consider them not
Expand Down Expand Up @@ -62,7 +62,7 @@ class SegmentSelectorCenteredContentLayout: BoundsChangeInvalidatingFlowLayout {
}
}

private extension SegmentSelectorCenteredContentLayout {
private extension FilterCenteredContentLayout {
var totalHorizontalInsets: CGFloat {
guard let collectionView = collectionView else { return 0 }
return collectionView.adjustedContentInset.left + collectionView.adjustedContentInset.right
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SegmentSelector
# Filter

`SegmentSelector` is used to render a scrollable selector that will typically be placed below the navigation bar, and which is used to let the user select one segment among a list of them. You will typically use the selected segment to filter or categorize the content below.
`Filter` is used to render a scrollable selector that will typically be placed below the navigation bar, and which is used to let the user select one segment among a list of them. You will typically use the selected segment to filter or categorize the content below.

![demo static](./docs/images/demo.png)

Expand All @@ -22,7 +22,7 @@ Also, there are two modes of displaying the segments:

## Accessibility

Given that this component is designed to be placed below a navigation bar (or in substitution of the NavBar), the component opts out of Dynamic Type, and makes use of Large Content Viewer. This means that when the user selects an accessibility text size, the user can long press in the `SegmentSelector` to show a HUD with the name of the categories, similarly to what happens in native toolbars.
Given that this component is designed to be placed below a navigation bar (or in substitution of the NavBar), the component opts out of Dynamic Type, and makes use of Large Content Viewer. This means that when the user selects an accessibility text size, the user can long press in the `Filter` to show a HUD with the name of the categories, similarly to what happens in native toolbars.

![demo-accessibility](./docs/images/demo-accessibility.gif)

Expand All @@ -33,14 +33,14 @@ Finally, the component is optimized for VoiceOver.

## Usage

Create an instance of `SegmentSelector` by providing a list of `Segment`'s to be displayed, and optionally a `SegmentsContentMode`.
Create an instance of `Filter` by providing a list of `Segment`'s to be displayed, and optionally a `SegmentsContentMode`.

```swift
let segments = [
Segment(id: "0", title: "segment 0"),
Segment(id: "1", title: "segment 1")
]
let segmentsSelector = SegmentSelector(segments: segments,
let segmentsSelector = Filter(segments: segments,
scrollableContentMode: .center)
```

Expand All @@ -51,16 +51,16 @@ Set a delegate for the component if you want to be notified when a segment is se

### Interface Builder

You can also embed `SegmentSelector` in a xib. Just put a plain UIView in your canvas and then in the Identity Editor tab, set `SegmentSelector` as the class name. Then you can create an outlet and add segments in `viewDidLoad()` or wherever fits better your problem.
You can also embed `Filter` in a xib. Just put a plain UIView in your canvas and then in the Identity Editor tab, set `Filter` as the class name. Then you can create an outlet and add segments in `viewDidLoad()` or wherever fits better your problem.

```swift
class ViewController: UIViewController {

@IBOutlet var segmentSelector: SegmentSelector!
@IBOutlet var filter: Filter!

override func viewDidLoad() {
super.viewDidLoad()
segmentSelector.segments = [
filter.segments = [
Segment(id: "0", title: "TV"),
Segment(id: "1", title: "Sports")
]
Expand All @@ -71,4 +71,4 @@ class ViewController: UIViewController {

## Sizing

`SegmentSelector` has a **fixed height** of 48 points. So keep this in mind when laying out this component.
`Filter` has a **fixed height** of 48 points. So keep this in mind when laying out this component.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// SegmentSelectorTests.swift
// FilterTests.swift
//
// Made with ❤️ by Novum
//
Expand All @@ -10,34 +10,34 @@
import SnapshotTesting
import XCTest

final class SegmentSelectorTests: XCTestCase {
final class FilterTests: XCTestCase {
override func setUp() {
super.setUp()
UIView.setAnimationsEnabled(false)

isRecording = false
}

func testSegmentsInSegmentSelector() {
func testSegmentsInFilter() {
assertSnapshotForAllBrandsAndStyles(
as: .image(size: CGSize(width: 320, height: 49)),
viewBuilder: makeSegmentSelector(segments: 5, hasSelectedItem: false)
viewBuilder: makeFilter(segments: 5, hasSelectedItem: false)
)
}

func testSelectedSegmentsInSegmentSelector() {
func testSelectedSegmentsInFilter() {
assertSnapshotForAllBrandsAndStyles(
as: .image(size: CGSize(width: 320, height: 49)),
viewBuilder: makeSegmentSelector(segments: 5, hasSelectedItem: true)
viewBuilder: makeFilter(segments: 5, hasSelectedItem: true)
)
}

func makeSegmentSelector(segments: Int, hasSelectedItem: Bool) -> SegmentSelector {
func makeFilter(segments: Int, hasSelectedItem: Bool) -> Filter {
let segments = (0 ..< segments).map { Segment(id: "\($0)", title: "Segment") }
let segmentSelector = SegmentSelector(segments: segments)
let filter = Filter(segments: segments)
if hasSelectedItem, let segment = segments.first {
segmentSelector.select(segment)
filter.select(segment)
}
return segmentSelector
return filter
}
}
8 changes: 4 additions & 4 deletions MisticaCatalog/MisticaCatalog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
B8F990462546C98700DFBFE9 /* UICatalogInputFieldsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F9901E2546C98600DFBFE9 /* UICatalogInputFieldsViewController.swift */; };
B8F990472546C98700DFBFE9 /* UICatalogViewStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F9901F2546C98600DFBFE9 /* UICatalogViewStatesViewController.swift */; };
B8F990482546C98700DFBFE9 /* UICatalogControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990202546C98600DFBFE9 /* UICatalogControlsViewController.swift */; };
B8F990492546C98700DFBFE9 /* UICatalogSegmentSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990212546C98600DFBFE9 /* UICatalogSegmentSelectorViewController.swift */; };
B8F990492546C98700DFBFE9 /* UICatalogFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990212546C98600DFBFE9 /* UICatalogFilterViewController.swift */; };
B8F9904B2546C98700DFBFE9 /* UICatalogTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990232546C98600DFBFE9 /* UICatalogTagsViewController.swift */; };
B8F9904C2546C98700DFBFE9 /* UICatalogPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990242546C98600DFBFE9 /* UICatalogPopoverViewController.swift */; };
B8F9904D2546C98700DFBFE9 /* UICatalogListsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F990252546C98600DFBFE9 /* UICatalogListsViewController.swift */; };
Expand Down Expand Up @@ -92,7 +92,7 @@
B8F9901E2546C98600DFBFE9 /* UICatalogInputFieldsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogInputFieldsViewController.swift; sourceTree = "<group>"; };
B8F9901F2546C98600DFBFE9 /* UICatalogViewStatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogViewStatesViewController.swift; sourceTree = "<group>"; };
B8F990202546C98600DFBFE9 /* UICatalogControlsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogControlsViewController.swift; sourceTree = "<group>"; };
B8F990212546C98600DFBFE9 /* UICatalogSegmentSelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogSegmentSelectorViewController.swift; sourceTree = "<group>"; };
B8F990212546C98600DFBFE9 /* UICatalogFilterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogFilterViewController.swift; sourceTree = "<group>"; };
B8F990232546C98600DFBFE9 /* UICatalogTagsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogTagsViewController.swift; sourceTree = "<group>"; };
B8F990242546C98600DFBFE9 /* UICatalogPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogPopoverViewController.swift; sourceTree = "<group>"; };
B8F990252546C98600DFBFE9 /* UICatalogListsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICatalogListsViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -228,7 +228,7 @@
B8F9901E2546C98600DFBFE9 /* UICatalogInputFieldsViewController.swift */,
B8F9901F2546C98600DFBFE9 /* UICatalogViewStatesViewController.swift */,
B8F990202546C98600DFBFE9 /* UICatalogControlsViewController.swift */,
B8F990212546C98600DFBFE9 /* UICatalogSegmentSelectorViewController.swift */,
B8F990212546C98600DFBFE9 /* UICatalogFilterViewController.swift */,
B8F990232546C98600DFBFE9 /* UICatalogTagsViewController.swift */,
B8F990242546C98600DFBFE9 /* UICatalogPopoverViewController.swift */,
B8F990252546C98600DFBFE9 /* UICatalogListsViewController.swift */,
Expand Down Expand Up @@ -362,7 +362,7 @@
B8F990502546C98700DFBFE9 /* UICatalogBadgeViewController.swift in Sources */,
B8F990392546C98700DFBFE9 /* Bundle+MisticaCatalog.swift in Sources */,
B8F990512546C98700DFBFE9 /* UICatalogHeaderViewController.swift in Sources */,
B8F990492546C98700DFBFE9 /* UICatalogSegmentSelectorViewController.swift in Sources */,
B8F990492546C98700DFBFE9 /* UICatalogFilterViewController.swift in Sources */,
1C37E32626BBF83B00062775 /* UICatalogEmptyStateViewController.swift in Sources */,
B8F990312546C98700DFBFE9 /* NavigationBarStyles.swift in Sources */,
18726451259A27CF00B9CD0D /* UICatalogStepperViewController.swift in Sources */,
Expand Down
Loading

0 comments on commit 6830ea9

Please sign in to comment.