Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support tappable headers #193

Merged
merged 5 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions BlueprintLists/Sources/HeaderFooter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,82 @@ public typealias BlueprintHeaderContent = BlueprintHeaderFooterContent
public typealias BlueprintFooterContent = BlueprintHeaderFooterContent


public protocol BlueprintHeaderFooterContent : HeaderFooterContent where ContentView == BlueprintView
public protocol BlueprintHeaderFooterContent : HeaderFooterContent
where
ContentView == BlueprintView,
BackgroundView == BlueprintView,
PressedBackgroundView == BlueprintView
{
//
// MARK: Creating Blueprint Element Representations
//

/// Required. Create and return the Blueprint element used to represent the content.
var elementRepresentation : Element { get }

/// Optional. Create and return the Blueprint element used to represent the background of the content.
/// You usually provide this method alongside `pressedBackground`, if your content
/// supports an `onTap` handler.
///
/// Note
/// ----
/// The default implementation of this method returns nil, and provides no background.
///
var background : Element? { get }

/// Optional. Create and return the Blueprint element used to represent the background of the content when it is pressed.
/// You usually provide this method alongside `background`, if your content supports an `onTap` handler.
///
/// Note
/// ----
/// The default implementation of this method returns nil, and provides no selected background.
///
var pressedBackground : Element? { get }
}


public extension BlueprintHeaderFooterContent
{
//
// MARK: BlueprintHeaderFooterContent
//

var background : Element? {
nil
}

var pressedBackground : Element? {
nil
}

//
// MARK: HeaderFooterContent
//

func apply(to view: ContentView, reason: ApplyReason)
func apply(to views: HeaderFooterContentViews<Self>, reason: ApplyReason)
{
views.content.element = self.elementRepresentation
views.background.element = self.background
views.pressed.element = self.pressedBackground
}

static func createReusableContentView(frame: CGRect) -> ContentView
{
view.element = self.elementRepresentation
let view = BlueprintView(frame: frame)
view.backgroundColor = .clear

return view
}

static func createReusableBackgroundView(frame: CGRect) -> BackgroundView
{
let view = BlueprintView(frame: frame)
view.backgroundColor = .clear

return view
}

static func createReusableHeaderFooterView(frame: CGRect) -> ContentView
static func createReusablePressedBackgroundView(frame: CGRect) -> PressedBackgroundView
{
let view = BlueprintView(frame: frame)
view.backgroundColor = .clear
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

- [Simplify `Sizing` now that enums support default associated values.](https://github.com/kyleve/Listable/pull/189). Now instead of separate `.thatFits` and `.thatFitsWith(Constraint)` enums, there is a single `.thatFits(Constraint = .noConstraint)` case (the same applies for `autolayout`).

- Changed [how `zIndexes` are assigned to header and items, and support tapping headers / footers](https://github.com/kyleve/Listable/pull/193). This allows registering an `onTap` handler for any HeaderFooter, and providing a background to display while the tap's press is active.

### Misc

# Past Releases
Expand Down
4 changes: 4 additions & 0 deletions Demo/Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
0A07119324BA798400CDF65D /* ListStateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A07119224BA798400CDF65D /* ListStateViewController.swift */; };
0A0E070423870A5700DDD27D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 0A0E070323870A5700DDD27D /* README.md */; };
0A49210424E5E11300D17038 /* AccordionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A49210324E5E11300D17038 /* AccordionViewController.swift */; };
0A793B5824E4B53500850139 /* ManualSelectionManagementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A793B5724E4B53500850139 /* ManualSelectionManagementViewController.swift */; };
0A87BA652463567B0047C3B5 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 0A87BA642463567B0047C3B5 /* CHANGELOG.md */; };
0AA4D9B9248064A300CF95A5 /* CustomLayoutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA4D9A8248064A200CF95A5 /* CustomLayoutsViewController.swift */; };
Expand Down Expand Up @@ -45,6 +46,7 @@
06908B38E59ACD7502B5332F /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = "<group>"; };
0A07119224BA798400CDF65D /* ListStateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStateViewController.swift; sourceTree = "<group>"; };
0A0E070323870A5700DDD27D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
0A49210324E5E11300D17038 /* AccordionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccordionViewController.swift; sourceTree = "<group>"; };
0A793B5724E4B53500850139 /* ManualSelectionManagementViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSelectionManagementViewController.swift; sourceTree = "<group>"; };
0A87BA642463567B0047C3B5 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
0AA4D9A8248064A200CF95A5 /* CustomLayoutsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLayoutsViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -117,6 +119,7 @@
0AA4D9CA248064AE00CF95A5 /* Demo Screens */ = {
isa = PBXGroup;
children = (
0A49210324E5E11300D17038 /* AccordionViewController.swift */,
0AA4D9B4248064A300CF95A5 /* AutoScrollingViewController.swift */,
0AA4D9AC248064A300CF95A5 /* BlueprintListDemoViewController.swift */,
0AA4D9B2248064A300CF95A5 /* CollectionViewAppearance.swift */,
Expand Down Expand Up @@ -429,6 +432,7 @@
0AA4D9C9248064A300CF95A5 /* CollectionViewBasicDemoViewController.swift in Sources */,
0AA4D9BC248064A300CF95A5 /* KeyboardTestingViewController.swift in Sources */,
0AA4D9C1248064A300CF95A5 /* CoordinatorViewController.swift in Sources */,
0A49210424E5E11300D17038 /* AccordionViewController.swift in Sources */,
0AA4D9B9248064A300CF95A5 /* CustomLayoutsViewController.swift in Sources */,
0AA4D9C3248064A300CF95A5 /* CollectionViewAppearance.swift in Sources */,
2B8804652490844A003BB351 /* SpacingCustomizationViewController.swift in Sources */,
Expand Down
106 changes: 106 additions & 0 deletions Demo/Sources/Demos/Demo Screens/AccordionViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// AccordionViewController.swift
// Demo
//
// Created by Kyle Van Essen on 8/10/20.
// Copyright © 2020 Kyle Van Essen. All rights reserved.
//


import BlueprintLists
import BlueprintUICommonControls


final class AccordionViewController : ListViewController
{
var expandedSectionIndex : Int = 1

override func configure(list: inout ListProperties) {

list += (1...10).map { sectionIndex in

Section(sectionIndex) { section in

section.header = HeaderFooter(
AccordionHeader(text: "Section Header #\(sectionIndex)"),
onTap: { _ in
self.expandedSectionIndex = sectionIndex
self.reload(animated: true)
}
)

if expandedSectionIndex == sectionIndex {
section += (1...sectionIndex).map { itemIndex in
Item(AccordionRow(text: "Row #\(sectionIndex), \(itemIndex)")) {
$0.insertAndRemoveAnimations = .fade
}
}
}
}
}
}
}

fileprivate struct AccordionHeader : BlueprintHeaderFooterContent, Equatable
{
var text : String

var elementRepresentation: Element {
Label(text: self.text) {
$0.alignment = .left
$0.font = .systemFont(ofSize: 18.0, weight: .semibold)
}
.inset(horizontal: 20.0, vertical: 10.0)
.constrainedTo(height: .atLeast(80.0))
}

var background: Element? {
self.background(with: .white)
}

var pressedBackground: Element? {
self.background(with: .white(0.9))
}

private func background(with color : UIColor) -> Element {
Overlay(
elements: [
Box(backgroundColor: color),

Box(backgroundColor: .init(white: 0.85, alpha: 1.0))
.constrainedTo(height: .absolute(1.0))
.aligned(vertically: .bottom, horizontally: .fill)
]
)
}
}


fileprivate struct AccordionRow : BlueprintItemContent, Equatable
{
var text : String

var identifier: Identifier<AccordionRow> {
.init(text)
}

func element(with info: ApplyItemContentInfo) -> Element {
Label(text: self.text) {
$0.alignment = .left
$0.font = .systemFont(ofSize: 16.0, weight: .regular)
}
.inset(horizontal: 20.0, vertical: 10.0)
.constrainedTo(height: .atLeast(60.0))
}

func backgroundElement(with info: ApplyItemContentInfo) -> Element? {
Overlay(
elements: [
Box(backgroundColor: .white),
Box(backgroundColor: .init(white: 0.90, alpha: 1.0))
.constrainedTo(height: .absolute(1.0))
.aligned(vertically: .bottom, horizontally: .fill)
]
)
}
}
9 changes: 8 additions & 1 deletion Demo/Sources/Demos/DemosRootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,21 @@ public final class DemosRootViewController : ListViewController
onSelect: { _ in
self.push(ItemInsertAndRemoveAnimationsViewController())
})

section += Item(
DemoItem(text: "Manual Selection Management"),
selectionStyle: .selectable(),
onSelect: { _ in
self.push(ManualSelectionManagementViewController())
}
)

section += Item(
DemoItem(text: "Accordion View"),
selectionStyle: .tappable,
onSelect: { _ in
self.push(AccordionViewController())
})
}

list("layouts") { section in
Expand Down
28 changes: 20 additions & 8 deletions Listable/Sources/HeaderFooter/HeaderFooter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public struct HeaderFooter<Content:HeaderFooterContent> : AnyHeaderFooter
public var sizing : Sizing
public var layout : HeaderFooterLayout

public typealias OnTap = (Content) -> ()
public var onTap : OnTap?

public var debuggingIdentifier : String? = nil

internal let reuseIdentifier : ReuseIdentifier<Content>
Expand All @@ -47,8 +50,7 @@ public struct HeaderFooter<Content:HeaderFooterContent> : AnyHeaderFooter
public init(
_ content : Content,
build : Build
)
{
) {
self.init(content)

build(&self)
Expand All @@ -57,23 +59,32 @@ public struct HeaderFooter<Content:HeaderFooterContent> : AnyHeaderFooter
public init(
_ content : Content,
sizing : Sizing = .thatFits(.init(.atLeast(.default))),
layout : HeaderFooterLayout = HeaderFooterLayout()
layout : HeaderFooterLayout = HeaderFooterLayout(),
onTap : OnTap? = nil
) {
self.content = content

self.sizing = sizing
self.layout = layout

self.onTap = onTap

self.reuseIdentifier = ReuseIdentifier.identifier(for: Content.self)
}

// MARK: AnyHeaderFooter_Internal

public func apply(to anyView : UIView, reason: ApplyReason)
{
let view = anyView as! Content.ContentView
let view = anyView as! HeaderFooterContentView<Content>

let views = HeaderFooterContentViews<Content>(
content: view.content,
background: view.background,
pressed: view.pressedBackground
)

self.content.apply(to: view, reason: reason)
self.content.apply(to: views, reason: reason)
}

public func anyIsEquivalent(to other : AnyHeaderFooter) -> Bool
Expand Down Expand Up @@ -106,9 +117,10 @@ extension HeaderFooter : SignpostLoggable
public struct HeaderFooterLayout : Equatable
{
public var width : CustomWidth

public init(width : CustomWidth = .default)
{

public init(
width : CustomWidth = .default
) {
self.width = width
}
}
Loading