From 79b39f3b79d3d62ebd7b7dc99400224583788e5e Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Thu, 11 Jun 2020 15:16:07 -0700 Subject: [PATCH] Support additional layout options in ListAppearance. Update tests to utilize snapshot tests. --- Internal Pods/Snapshot/Sources/Snapshot.swift | 17 +- .../Sources/ViewHierarchySnapshot.swift | 8 +- .../Snapshot/Sources/ViewImageSnapshot.swift | 8 +- .../Snapshot/Sources/ViewIterations.swift | 12 +- .../Layout/List/DefaultListLayout.swift | 190 +++++++++++++++--- .../Layout/List/ListAppearance.monopic | Bin 0 -> 3431 bytes .../ListLayout/ListLayoutAttributes.swift | 10 + .../Layout/LayoutAttributesSnapshot.swift | 35 ++++ .../Layout/List/DefaultListLayoutTests.swift | 83 ++++---- Podfile.lock | 2 +- 10 files changed, 280 insertions(+), 85 deletions(-) create mode 100644 Listable/Sources/Layout/List/ListAppearance.monopic create mode 100644 Listable/Tests/Layout/LayoutAttributesSnapshot.swift diff --git a/Internal Pods/Snapshot/Sources/Snapshot.swift b/Internal Pods/Snapshot/Sources/Snapshot.swift index 0819bba4f..0255788a0 100644 --- a/Internal Pods/Snapshot/Sources/Snapshot.swift +++ b/Internal Pods/Snapshot/Sources/Snapshot.swift @@ -17,6 +17,11 @@ public struct Snapshot internal typealias OnFail = (_ message : String, _ file : StaticString, _ line : UInt) -> () internal var onFail : OnFail = XCTFail + public init(iterations: [Iteration] , input : Iteration.RenderingFormat) + { + self.init(iterations: iterations) { _ in input } + } + public init(iterations : [Iteration], test : @escaping Test) { let hasIterations = iterations.isEmpty == false @@ -133,8 +138,16 @@ public protocol SnapshotOutputFormat public struct SnapshotOutputInfo : Equatable { - var directoryName : String - var fileExtension : String + public var directoryName : String + public var fileExtension : String + + public init( + directoryName : String, + fileExtension : String + ) { + self.directoryName = directoryName + self.fileExtension = fileExtension + } } diff --git a/Internal Pods/Snapshot/Sources/ViewHierarchySnapshot.swift b/Internal Pods/Snapshot/Sources/ViewHierarchySnapshot.swift index f3a21bbcb..4225ce3e0 100644 --- a/Internal Pods/Snapshot/Sources/ViewHierarchySnapshot.swift +++ b/Internal Pods/Snapshot/Sources/ViewHierarchySnapshot.swift @@ -8,13 +8,13 @@ import UIKit -public struct ViewHierarchySnapshot : SnapshotOutputFormat +public struct ViewHierarchySnapshot : SnapshotOutputFormat { // MARK: SnapshotOutputFormat - public typealias RenderingFormat = UIView + public typealias RenderingFormat = ViewType - public static func snapshotData(with renderingFormat : UIView) throws -> Data + public static func snapshotData(with renderingFormat : ViewType) throws -> Data { let hierarchy = renderingFormat.textHierarchy let string = hierarchy.stringValue @@ -29,7 +29,7 @@ public struct ViewHierarchySnapshot : SnapshotOutputFormat ) } - public static func validate(render view: UIView, existingData : Data) throws + public static func validate(render view: ViewType, existingData : Data) throws { let textHierarchy = try ViewHierarchySnapshot.snapshotData(with: view) diff --git a/Internal Pods/Snapshot/Sources/ViewImageSnapshot.swift b/Internal Pods/Snapshot/Sources/ViewImageSnapshot.swift index 1e349b009..28449145f 100644 --- a/Internal Pods/Snapshot/Sources/ViewImageSnapshot.swift +++ b/Internal Pods/Snapshot/Sources/ViewImageSnapshot.swift @@ -8,13 +8,13 @@ import UIKit -public struct ViewImageSnapshot : SnapshotOutputFormat +public struct ViewImageSnapshot : SnapshotOutputFormat { // MARK: SnapshotOutputFormat - public typealias RenderingFormat = UIView + public typealias RenderingFormat = ViewType - public static func snapshotData(with renderingFormat : UIView) throws -> Data + public static func snapshotData(with renderingFormat : ViewType) throws -> Data { return renderingFormat.toImage.pngData()! } @@ -26,7 +26,7 @@ public struct ViewImageSnapshot : SnapshotOutputFormat ) } - public static func validate(render newView : UIView, existingData: Data) throws + public static func validate(render newView : ViewType, existingData: Data) throws { let existing = try ViewImageSnapshot.image(with: existingData) let new = newView.toImage diff --git a/Internal Pods/Snapshot/Sources/ViewIterations.swift b/Internal Pods/Snapshot/Sources/ViewIterations.swift index bcd7135cd..a7f47edb5 100644 --- a/Internal Pods/Snapshot/Sources/ViewIterations.swift +++ b/Internal Pods/Snapshot/Sources/ViewIterations.swift @@ -8,7 +8,7 @@ import Foundation -public struct ViewIteration : SnapshotIteration +public struct ViewIteration : SnapshotIteration { public init(name: String) { @@ -17,18 +17,18 @@ public struct ViewIteration : SnapshotIteration // MARK: SnapshotIteration - public typealias RenderingFormat = UIView + public typealias RenderingFormat = ViewType public var name : String - public func prepare(render : UIView) -> UIView + public func prepare(render : ViewType) -> ViewType { return render } } -public struct SizedViewIteration : SnapshotIteration +public struct SizedViewIteration : SnapshotIteration { public let size : CGSize @@ -39,13 +39,13 @@ public struct SizedViewIteration : SnapshotIteration // MARK: SnapshotIteration - public typealias RenderingFormat = UIView + public typealias RenderingFormat = ViewType public var name : String { return "\(self.size.width) x \(self.size.height)" } - public func prepare(render : UIView) -> UIView + public func prepare(render : ViewType) -> ViewType { render.frame.origin = .zero render.frame.size = self.size diff --git a/Listable/Sources/Layout/List/DefaultListLayout.swift b/Listable/Sources/Layout/List/DefaultListLayout.swift index f3507806b..5aecb0936 100644 --- a/Listable/Sources/Layout/List/DefaultListLayout.swift +++ b/Listable/Sources/Layout/List/DefaultListLayout.swift @@ -11,39 +11,132 @@ import Foundation public extension Appearance { var list : ListAppearance { - get { - self[ListAppearance.self, default: ListAppearance()] - } - - set { - self[ListAppearance.self] = newValue - } + get { self[ListAppearance.self, default: ListAppearance()] } + set { self[ListAppearance.self] = newValue } } } - +/// +/// `ListAppearance` defines the appearance and layout attribute for list layouts within a Listable list. +/// +/// The below diagram shows where each of the properties on the `ListAppearance.Layout` values are +/// applied when laying out the list. +/// +/// Note +/// ---- +/// Do not edit this ASCII diagram directly. +/// Edit the `ListAppearance.monopic` file in this directory using Monodraw. +/// ``` +/// ┌─────────────────────────────────────────────────────────────────┐ +/// │ padding.top │ +/// │ ┌─────────────────────────────────────────────────────────┐ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ ││ │ +/// │ ││ List Header ││ │ +/// │ ││ ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ │ │ +/// │ │ headerToFirstSectionSpacing │ │ +/// │ │ │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ ││ │ +/// │ ││ Section Header ││ │ +/// │ ││ ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ sectionHeaderBottomSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ itemSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ itemToSectionFooterSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ ││ │ +/// │ p ││ Section Footer ││ p │ +/// │ a ││ ││ a │ +/// │ d │└───────────────────────────────────────────────────────┘│ d │ +/// │ d │ │ d │ +/// │ i │ interSectionSpacingWithFooter │ i │ +/// │ n │ │ n │ +/// │ g │┌───────────────────────────────────────────────────────┐│ g │ +/// │ . ││ ││ . │ +/// │ l ││ Section Header ││ r │ +/// │ e ││ ││ i │ +/// │ f │└───────────────────────────────────────────────────────┘│ g │ +/// │ t │ sectionHeaderBottomSpacing │ h │ +/// │ │┌───────────────────────────────────────────────────────┐│ t │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ itemSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ │ │ +/// │ │ interSectionSpacingWithNoFooter │ │ +/// │ │ │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ ││ │ +/// │ ││ Section Header ││ │ +/// │ ││ ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ sectionHeaderBottomSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ itemSpacing │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ Item ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ │ │ │ +/// │ │ lastSectionToFooterSpacing │ │ +/// │ │ │ │ +/// │ │┌───────────────────────────────────────────────────────┐│ │ +/// │ ││ ││ │ +/// │ ││ List Footer ││ │ +/// │ ││ ││ │ +/// │ │└───────────────────────────────────────────────────────┘│ │ +/// │ └─────────────────────────────────────────────────────────┘ │ +/// │ padding.bottom │ +/// └─────────────────────────────────────────────────────────────────┘ +/// ``` public struct ListAppearance : Equatable { + /// Default sizing attributes for content in the list. public var sizing : Sizing + + /// Layout attributes for content in the list. public var layout : Layout + /// Creates a new `ListAppearance` object. public init(sizing : Sizing = Sizing(), layout : Layout = Layout()) { self.sizing = sizing self.layout = layout } + /// Sizing options for the list. public struct Sizing : Equatable { + /// The default height for items in a list. public var itemHeight : CGFloat + /// The default height for section headers in a list. public var sectionHeaderHeight : CGFloat + /// The default height for section footer in a list. public var sectionFooterHeight : CGFloat + /// The default height for the list's header. public var listHeaderHeight : CGFloat + /// The default height for the list's footer. public var listFooterHeight : CGFloat + /// The default height for the list's overscroll footer. public var overscrollFooterHeight : CGFloat + /// When providing the `ItemPosition` for items in a list, specifies the max spacing + /// for items to be considered in the same group. For example, if this value is 1, and + /// items are spaced 2pts apart, the items will be in a new group. public var itemPositionGroupingHeight : CGFloat public init( @@ -73,40 +166,66 @@ public struct ListAppearance : Equatable } } - + + /// Layout options for the list. public struct Layout : Equatable { + /// The padding to place around the outside of the content of the list. public var padding : UIEdgeInsets + /// The width of the content of the list, which can be optionally constrained. public var width : WidthConstraint + + /// The spacing between the list header and the first section. + /// Not applied if there is no list header. + public var headerToFirstSectionSpacing : CGFloat + /// The spacing to apply between sections, if the previous section has no footer. public var interSectionSpacingWithNoFooter : CGFloat + /// The spacing to apply between sections, if the previous section has a footer. public var interSectionSpacingWithFooter : CGFloat + /// The spacing to apply below a section header, before its items. + /// Not applied if there is no section header. public var sectionHeaderBottomSpacing : CGFloat + /// The spacing between individual items within a section in a list. public var itemSpacing : CGFloat + /// The spacing between the last item in the section and the footer. + /// Not applied if there is no section footer. public var itemToSectionFooterSpacing : CGFloat + + /// The spacing between the last section and the footer of the list. + /// Not applied if there is no list footer. + public var lastSectionToFooterSpacing : CGFloat + /// Creates a new `Layout` with the provided options. public init( padding : UIEdgeInsets = .zero, width : WidthConstraint = .noConstraint, + headerToFirstSectionSpacing : CGFloat = 0.0, interSectionSpacingWithNoFooter : CGFloat = 0.0, interSectionSpacingWithFooter : CGFloat = 0.0, sectionHeaderBottomSpacing : CGFloat = 0.0, itemSpacing : CGFloat = 0.0, - itemToSectionFooterSpacing : CGFloat = 0.0 + itemToSectionFooterSpacing : CGFloat = 0.0, + lastSectionToFooterSpacing : CGFloat = 0.0 ) { self.padding = padding self.width = width + self.headerToFirstSectionSpacing = headerToFirstSectionSpacing + self.interSectionSpacingWithNoFooter = interSectionSpacingWithNoFooter self.interSectionSpacingWithFooter = interSectionSpacingWithFooter self.sectionHeaderBottomSpacing = sectionHeaderBottomSpacing self.itemSpacing = itemSpacing self.itemToSectionFooterSpacing = itemToSectionFooterSpacing + + self.lastSectionToFooterSpacing = lastSectionToFooterSpacing } + /// Easily mutate the `Layout` in place. public mutating func set(with block : (inout Layout) -> ()) { var edited = self @@ -114,6 +233,7 @@ public struct ListAppearance : Equatable self = edited } + /// Provides a width for layout. internal static func width( with width : CGFloat, padding : HorizontalPadding, @@ -242,6 +362,16 @@ final class DefaultListLayout : ListLayout // Header // + switch direction { + case .vertical: + lastSectionMaxY += layout.padding.top + lastContentMaxY += layout.padding.top + + case .horizontal: + lastSectionMaxY += layout.padding.left + lastContentMaxY += layout.padding.left + } + performLayout(for: self.content.header) { header in let hasListHeader = self.content.header.isPopulated @@ -265,19 +395,14 @@ final class DefaultListLayout : ListLayout header.y = lastContentMaxY if hasListHeader { - lastSectionMaxY = direction.maxY(for: header.defaultFrame) - lastContentMaxY = direction.maxY(for: header.defaultFrame) + lastSectionMaxY += direction.maxY(for: header.defaultFrame) + lastContentMaxY += direction.maxY(for: header.defaultFrame) } - } - - switch direction { - case .vertical: - lastSectionMaxY += layout.padding.top - lastContentMaxY += layout.padding.top - case .horizontal: - lastSectionMaxY += layout.padding.left - lastContentMaxY += layout.padding.left + if self.content.sections.isEmpty == false { + lastSectionMaxY += layout.headerToFirstSectionSpacing + lastContentMaxY += layout.headerToFirstSectionSpacing + } } // @@ -320,7 +445,10 @@ final class DefaultListLayout : ListLayout if hasSectionHeader { lastContentMaxY = direction.maxY(for: section.header.defaultFrame) - lastContentMaxY += layout.sectionHeaderBottomSpacing + + if section.items.isEmpty == false { + lastContentMaxY += layout.sectionHeaderBottomSpacing + } } } @@ -448,7 +576,12 @@ final class DefaultListLayout : ListLayout // Add additional padding from config. - if isLast == false { + if isLast { + if self.content.footer.isPopulated { + lastSectionMaxY += layout.lastSectionToFooterSpacing + lastContentMaxY += layout.lastSectionToFooterSpacing + } + } else { let additionalSectionSpacing: CGFloat if let customInterSectionSpacing = section.layout.customInterSectionSpacing { additionalSectionSpacing = customInterSectionSpacing @@ -463,11 +596,6 @@ final class DefaultListLayout : ListLayout } } - switch direction { - case .vertical: lastContentMaxY += layout.padding.bottom - case .horizontal: lastContentMaxY += layout.padding.right - } - // // Footer // @@ -496,10 +624,14 @@ final class DefaultListLayout : ListLayout if hasFooter { lastContentMaxY = direction.maxY(for: footer.defaultFrame) - lastContentMaxY += layout.sectionHeaderBottomSpacing } } + switch direction { + case .vertical: lastContentMaxY += layout.padding.bottom + case .horizontal: lastContentMaxY += layout.padding.right + } + // // Overscroll Footer // diff --git a/Listable/Sources/Layout/List/ListAppearance.monopic b/Listable/Sources/Layout/List/ListAppearance.monopic new file mode 100644 index 0000000000000000000000000000000000000000..1d5273ccdd1fbba442407165968bbe9156796115 GIT binary patch literal 3431 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 Data { + renderingFormat.layout.layout.content.layoutAttributes.stringRepresentation.data(using: .utf8)! + } + + static var outputInfo: SnapshotOutputInfo { + SnapshotOutputInfo( + directoryName: "ListAttributes", + fileExtension: "txt" + ) + } + + static func validate(render: ListView, existingData: Data) throws { + let new = try Self.snapshotData(with: render) + + if new != existingData { + throw SnapshotValidationError.notMatching + } + } +} diff --git a/Listable/Tests/Layout/List/DefaultListLayoutTests.swift b/Listable/Tests/Layout/List/DefaultListLayoutTests.swift index bd9618f56..c74da5ed3 100644 --- a/Listable/Tests/Layout/List/DefaultListLayoutTests.swift +++ b/Listable/Tests/Layout/List/DefaultListLayoutTests.swift @@ -6,6 +6,7 @@ // import XCTest +import Snapshot @testable import Listable @@ -70,8 +71,6 @@ class ListAppearance_LayoutTests : XCTestCase class DefaultListLayoutTests : XCTestCase { - // Note: This test is temporary to allow for further refactoring of the layout system. Will be replaced. - func test_layout_vertical() { let listView = ListView(frame: CGRect(origin: .zero, size: CGSize(width: 200.0, height: 700.0))) @@ -80,59 +79,65 @@ class DefaultListLayoutTests : XCTestCase listView.setContent { list in list.appearance.direction = .vertical + list.layoutType = .list + + list.appearance.list.layout = .init( + padding: UIEdgeInsets(top: 5.0, left: 10.0, bottom: 20.0, right: 15.0), + width: .noConstraint, + headerToFirstSectionSpacing: 10.0, + interSectionSpacingWithNoFooter: 15.0, + interSectionSpacingWithFooter: 20.0, + sectionHeaderBottomSpacing: 10.0, + itemSpacing: 5.0, + itemToSectionFooterSpacing: 10.0, + lastSectionToFooterSpacing: 20.0 + ) list.content.header = HeaderFooter(TestingHeaderFooterContent(color: .blue), sizing: .fixed(height: 50.0)) list.content.footer = HeaderFooter(TestingHeaderFooterContent(color: .blue), sizing: .fixed(height: 70.0)) list += Section(identifier: "first") { section in - section.layout = Section.Layout(customInterSectionSpacing: 88) + section.layout = Section.Layout(customInterSectionSpacing: 30) - section.header = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 55.0)) - section.footer = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 45.0)) + section.header = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 30.0)) + section.footer = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 40.0)) section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.1)), sizing: .fixed(height: 20.0)) + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.2)), sizing: .fixed(height: 20.0)) + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.3)), sizing: .fixed(height: 20.0)) } list += Section(identifier: "second") { section in - section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.1)), sizing: .fixed(height: 40.0)) - section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.2)), sizing: .fixed(height: 60.0)) + section.header = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 30.0)) + section.footer = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 40.0)) + + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.1)), sizing: .fixed(height: 30.0)) + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.2)), sizing: .fixed(height: 40.0)) + } + + list += Section(identifier: "third") { section in + section.header = HeaderFooter(TestingHeaderFooterContent(color: .green), sizing: .fixed(height: 30.0)) + + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.1)), sizing: .fixed(height: 10.0)) + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.2)), sizing: .fixed(height: 20.0)) + } + + list += Section(identifier: "fourth") { section in + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.1)), sizing: .fixed(height: 10.0)) + section += Item(TestingItemContent(color: .init(white: 0.0, alpha: 0.2)), sizing: .fixed(height: 20.0)) } } - let layout = DefaultListLayout(delegate: listView.delegate, appearance: listView.appearance, behavior: listView.behavior, in: listView.collectionView) - _ = layout.layout(delegate: listView.delegate, in: listView.collectionView) + listView.collectionView.layoutIfNeeded() - let attributes = layout.content.layoutAttributes + let contentSize = listView.layout.layout.content.contentSize - let expectedAttributes = ListLayoutAttributes( - contentSize: CGSize(width: 200.0, height: 428.0), - header: .init(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 50.0)), - footer: .init(frame: CGRect(x: 0.0, y: 358.0, width: 200.0, height: 70.0)), - overscrollFooter: nil, - sections: [ - .init( - frame: CGRect(x: 0.0, y: 50.0, width: 200.0, height: 120.0), - header: .init(frame: CGRect(x: 0.0, y: 50.0, width: 200.0, height: 55.0)), - footer: .init(frame: CGRect(x: 0.0, y: 125.0, width: 200.0, height: 45.0)), - items: [ - .init(frame: CGRect(x: 0.0, y: 105.0, width: 200.0, height: 20.0)) - ] - ), - - .init( - frame: CGRect(x: 0.0, y: 258.0, width: 200.0, height: 100.0), - header: nil, - footer: nil, - items: [ - .init(frame: CGRect(x: 0.0, y: 258.0, width: 200.0, height: 40.0)), - .init(frame: CGRect(x: 0.0, y: 298.0, width: 200.0, height: 60.0)), - ] - ), - - ] - ) - - XCTAssertEqual(attributes, expectedAttributes) + listView.frame.size.height = contentSize.height + + let snapshot = Snapshot(iterations: [SizedViewIteration(size: contentSize)], input: listView) + + snapshot.test(output: ViewImageSnapshot.self) + snapshot.test(output: LayoutAttributesSnapshot.self) } // Note: This test is temporary to allow for further refactoring of the layout system. Will be replaced. diff --git a/Podfile.lock b/Podfile.lock index 8fa87455e..f14a7d6eb 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -52,4 +52,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ede5b0b1b0dfe04b49195609ea6bccb6b577e01f -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.3