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

Properly insert/delete items #159

Merged
merged 1 commit into from
Jul 17, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions Example/Source/Bricks/Custom/SegmentHeaderBrick.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class SegmentHeaderBrickCell: BrickCell, Bricklike {
return
}

dataSource.configure(cell: self)

self.segmentControl.removeAllSegments()
for (index, title) in dataSource.titles.enumerated() {
self.segmentControl.insertSegment(withTitle: title, at: index, animated: false)
Expand All @@ -40,6 +42,12 @@ class SegmentHeaderBrickCell: BrickCell, Bricklike {
protocol SegmentHeaderBrickDataSource: class {
var titles: [String] { get }
var selectedSegmentIndex: Int { get }

func configure(cell: SegmentHeaderBrickCell)
}

extension SegmentHeaderBrickDataSource {
func configure(cell: SegmentHeaderBrickCell) {/*Optional*/}
}

protocol SegmentHeaderBrickDelegate: class {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class BrickCollectionInteractiveViewController: BrickViewController, HasTitle {

extension BrickCollectionInteractiveViewController {

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let brickInfo = brickCollectionView.brickInfo(at:indexPath)
if brickInfo.brick.identifier == BrickIdentifiers.titleLabel {
even = !even
Expand Down Expand Up @@ -192,7 +192,7 @@ extension BrickCollectionInteractiveViewController: CollectionBrickCellDataSourc
layout.hideBehaviorDataSource = self
}

func dataSourceForCollectionBrickCell(cell: CollectionBrickCell) -> BrickCollectionViewDataSource {
func dataSourceForCollectionBrickCell(_ cell: CollectionBrickCell) -> BrickCollectionViewDataSource {
return dataSources[cell.index]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class DynamicContentViewController: BrickViewController, HasTitle {
var hidden: Bool = false
var reload: Bool = false

var imageURLs: [NSURL]?
var imageURLs: [URL]?
let placeholderCount = 5
let overrideContentSource = ActivityIndicatorOverrideSource()

Expand Down Expand Up @@ -59,7 +59,7 @@ class DynamicContentViewController: BrickViewController, HasTitle {
self.overrideContentSource.shouldOverride = false
imageURLs = []
for _ in 1...5 {
self.imageURLs?.append(NSURL(string:"https://secure.img2.wfrcdn.com/lf/8/hash/2664/10628031/1/custom_image.jpg")!)
self.imageURLs?.append(URL(string:"https://secure.img2.wfrcdn.com/lf/8/hash/2664/10628031/1/custom_image.jpg")!)
}
self.brickCollectionView.reloadBricksWithIdentifiers([DynamicContentViewController.Identifiers.HideableSectionContentImage])
}
Expand Down Expand Up @@ -116,11 +116,11 @@ extension DynamicContentViewController: BrickRepeatCountDataSource {

extension DynamicContentViewController: ImageBrickDataSource {

func imageURLForImageBrickCell(imageBrickCell: ImageBrickCell) -> NSURL? {
func imageURLForImageBrickCell(_ imageBrickCell: ImageBrickCell) -> URL? {
return imageURLs?[imageBrickCell.index]
}

func contentModeForImageBrickCell(imageBrickCell: ImageBrickCell) -> UIViewContentMode {
func contentModeForImageBrickCell(_ imageBrickCell: ImageBrickCell) -> UIViewContentMode {
return .scaleAspectFit
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ class InteractiveAlignViewController: BrickViewController, HasTitle {

self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(InteractiveAlignViewController.add))

self.registerBrickClass(LabelBrick.self)
self.brickCollectionView.layout.appearBehavior = ScaleAppearBehavior(scale: 0.5)

let labelBrick = LabelBrick("Label", width: .ratio(ratio: 1/3), height: .fixed(size: 100), backgroundColor: UIColor.lightGray.withAlphaComponent(0.3), dataSource: self)
let labelBrick = GenericBrick<UILabel>("Label", width: .ratio(ratio: 1), /*height: .fixed(size: 100),*/ backgroundColor: .brickGray1) { label, cell in
label.text = "BRICK \(cell.index)"
label.configure(textColor: UIColor.brickGray1.complemetaryColor)
cell.edgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
}

let section = BrickSection(bricks: [
labelBrick,
Expand Down
12 changes: 10 additions & 2 deletions Source/Cells/BrickCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,16 @@ open class BrickCell: BaseBrickCell {

let preferred = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

let size = CGSize(width: layoutAttributes.frame.width, height: self.heightForBrickView(withWidth: layoutAttributes.frame.width))
preferred.frame.size = size
// We're inverting the frame because the given frame is already transformed
var invertedFrame = layoutAttributes.frame.applying(layoutAttributes.transform.inverted())
let size = CGSize(width: layoutAttributes.frame.width, height: self.heightForBrickView(withWidth: invertedFrame.width))

// Setting the size of the frame will return the "transformed" size
invertedFrame.size = size

// We need to invert the frame again, because UILayoutAttributes will transform the frame again
preferred.frame = invertedFrame.applying(layoutAttributes.transform.inverted())

return preferred
}

Expand Down
71 changes: 27 additions & 44 deletions Source/Layout/BrickFlowLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,6 @@ open class BrickFlowLayout: UICollectionViewLayout, BrickLayout {
/// Sections
internal fileprivate(set) var sections: [Int: BrickLayoutSection]?

/// Flag to indicate that an update cycle is happening
var isUpdating: Bool = false

/// IndexPaths being added
var insertedIndexPaths: [IndexPath] = []

Expand Down Expand Up @@ -140,7 +137,7 @@ open class BrickFlowLayout: UICollectionViewLayout, BrickLayout {
if let sections = sections {

//Only continue calculating if the new frame of interest is further than the old frame
let shouldContinueCalculating = scrollDirection == .vertical ? oldRect.maxY <= frameOfInterest.maxY : oldRect.maxX <= frameOfInterest.maxX
let shouldContinueCalculating = scrollDirection == .vertical ? oldRect.maxY < frameOfInterest.maxY : oldRect.maxX < frameOfInterest.maxX

if shouldContinueCalculating {
let currentSections = sections.values
Expand Down Expand Up @@ -222,12 +219,6 @@ open class BrickFlowLayout: UICollectionViewLayout, BrickLayout {
sections?[sectionIndex] = section
}

internal func updateNumberOfItems(_ brickSection: BrickLayoutSection, numberOfItems: Int? = nil) {
brickSection.setNumberOfItems(numberOfItems ?? _collectionView.numberOfItems(inSection: brickSection.sectionIndex), addedAttributes: { (attributes, oldFrame) in
}, removedAttributes: { (attributes, oldFrame) in
})
}

internal func updateNumberOfItemsInSection(_ section: Int, numberOfItems: Int, updatedAttributes: @escaping OnAttributesUpdatedHandler) {
guard let brickSection = sections?[section] else {
return
Expand All @@ -238,7 +229,6 @@ open class BrickFlowLayout: UICollectionViewLayout, BrickLayout {
}

let height = brickSection.frame.height
self.updateNumberOfItems(brickSection, numberOfItems: numberOfItems)

guard let indexPath = dataSource?.brickLayout(self, indexPathFor: section) else {
return
Expand Down Expand Up @@ -309,33 +299,13 @@ extension BrickFlowLayout {
case .updateHeight(let indexPath, _): delegate?.brickLayout(self, didUpdateHeightForItemAtIndexPath: indexPath)
default: break
}
} else if context.invalidateDataSourceCounts {
invalidateDataCounts(context)
} else {
return
} else if !context.invalidateDataSourceCounts {
invalidateLayout(with: BrickLayoutInvalidationContext(type: .invalidate))
}

super.invalidateLayout(with: context)
}

func invalidateDataCounts(_ context: UICollectionViewLayoutInvalidationContext) {
zIndexer.reset(for: self)

var changedSections = [Int: Int]()
for section in 0..<_collectionView.numberOfSections {
if let brickSection = sections?[section] {
let numberOfItems = _collectionView.numberOfItems(inSection: section)
if brickSection.numberOfItems != numberOfItems {
changedSections[section] = numberOfItems
}
}
}
if !changedSections.isEmpty {
_ = BrickLayoutInvalidationContext(type: .invalidateDataSourceCounts(sections: changedSections)).invalidateWithLayout(self, context: context)
}

}

open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
if !isCalculating {
_ = calculateSectionsIfNeeded(rect)
Expand All @@ -346,8 +316,8 @@ extension BrickFlowLayout {
}

var attributes: [UICollectionViewLayoutAttributes] = []
for (_, section) in sections {
attributes += section.layoutAttributesForElementsInRect(rect, with: zIndexer)
for (sectionIndex, section) in sections {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use sections.forEach { ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic has a reference to to self (_collectionView & zIndexer)

attributes += section.layoutAttributesForElementsInRect(rect, with: zIndexer, maxIndex: _collectionView.numberOfItems(inSection: sectionIndex) - 1)
}

return attributes
Expand Down Expand Up @@ -447,9 +417,10 @@ extension BrickFlowLayout: BrickLayoutSectionDataSource {
let type = _dataSource.brickLayout(self, brickLayoutTypeForItemAt: indexPath)
switch type {
case .brick: break
case .section(let section):
if let brickSection = sections?[section] {
updateNumberOfItems(brickSection)
case .section(let sectionIndex):
if let brickSection = sections?[sectionIndex] {
brickSection.sectionAttributes = attributes

if brickSection.sectionWidth != width {
brickSection.setSectionWidth(width, updatedAttributes: updatedAttributes)
} else if brickSection.origin.x != origin.x {
Expand All @@ -460,7 +431,7 @@ extension BrickFlowLayout: BrickLayoutSectionDataSource {
brickSection.invalidateAttributes(updatedAttributes)
}
} else {
calculateSection(for: section, with: attributes, containedInWidth: width, at: origin)
calculateSection(for: sectionIndex, with: attributes, containedInWidth: width, at: origin)
}
}
}
Expand Down Expand Up @@ -599,7 +570,6 @@ extension BrickFlowLayout: BrickLayoutInvalidationProvider {
switch type {
case .section(let section):
if let brickSection = self.sections?[section] {
updateNumberOfItems(brickSection)
brickSection.setOrigin(attributes.frame.origin, fromBehaviors: fromBehaviors, updatedAttributes: { attributes, oldFrame in
updatedAttributes(attributes, oldFrame)
self.attributesWereUpdated(attributes, oldFrame: oldFrame, fromBehaviors: fromBehaviors, updatedAttributes: updatedAttributes)
Expand Down Expand Up @@ -693,9 +663,23 @@ extension BrickFlowLayout {
}
}

isUpdating = true
if (insertedIndexPaths.count + deletedIndexPaths.count) > 0 {
zIndexer.reset(for: self)
for (sectionIndex, section) in sections! { // OK to force unwrap sections. Without them being initialized, there wouldn't be any insert/delete possible
let inserted = insertedIndexPaths.filter({ (indexPath) -> Bool in
return indexPath.section == sectionIndex
})

let deleted = deletedIndexPaths.filter({ (indexPath) -> Bool in
return indexPath.section == sectionIndex
})

section.updateNumberOfItems(inserted: inserted.map({$0.item}), deleted: deleted.map({$0.item}))
}
}

reloadItems(at: reloadIndexPaths)
invalidateLayout(with: BrickLayoutInvalidationContext(type: .invalidate))
}

fileprivate func reloadItems(at indexPaths: [IndexPath]) {
Expand All @@ -706,7 +690,7 @@ extension BrickFlowLayout {

switch _dataSource.brickLayout(self, brickLayoutTypeForItemAt: indexPath) {
case .brick:
invalidateLayout(with: BrickLayoutInvalidationContext(type: .invalidateHeight(indexPath: indexPath)))
BrickLayoutInvalidationContext(type: .invalidateHeight(indexPath: indexPath)).invalidateWithLayout(self)
default: break
}
}
Expand All @@ -717,14 +701,13 @@ extension BrickFlowLayout {
insertedIndexPaths = []
deletedIndexPaths = []
reloadIndexPaths = []
isUpdating = false
}


override open func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

var attributes: BrickLayoutAttributes?

if insertedIndexPaths.contains(itemIndexPath) {
if let copy = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)?.copy() as? BrickLayoutAttributes {
appearBehavior?.configureAttributesForAppearing(copy, in: _collectionView)
Expand Down
14 changes: 3 additions & 11 deletions Source/Layout/BrickLayoutInvalidationContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ enum BrickLayoutInvalidationContextType {
case scrolling
case rotation
case behaviorsChanged
case invalidateDataSourceCounts(sections: [Int: Int]) // Key: section Value: numberOfItems
case invalidate
case updateVisibility

Expand All @@ -26,7 +25,7 @@ enum BrickLayoutInvalidationContextType {
*/
var shouldInvalidateAllAttributes: Bool {
switch self {
case .rotation, .invalidate, .creation, .updateVisibility, .invalidateDataSourceCounts(_), .updateHeight(_): return true
case .rotation, .invalidate, .creation, .updateVisibility, .updateHeight(_): return true
default: return false
}
}
Expand All @@ -43,7 +42,6 @@ protocol BrickLayoutInvalidationProvider: class {
func recalculateContentSize() -> CGSize
func invalidateContent(_ updatedAttributes: @escaping OnAttributesUpdatedHandler)
func registerUpdatedAttributes(_ attributes: BrickLayoutAttributes, oldFrame: CGRect?, fromBehaviors: Bool, updatedAttributes: @escaping OnAttributesUpdatedHandler)
func updateNumberOfItemsInSection(_ section: Int, numberOfItems: Int, updatedAttributes: @escaping OnAttributesUpdatedHandler)
func applyHideBehavior(updatedAttributes: @escaping OnAttributesUpdatedHandler)
func updateContentSize(_ contentSize: CGSize)
}
Expand All @@ -64,10 +62,12 @@ class BrickLayoutInvalidationContext: UICollectionViewLayoutInvalidationContext
self.type = type
}

@discardableResult
func invalidateWithLayout(_ layout: UICollectionViewLayout) -> Bool {
return self.invalidateWithLayout(layout, context: self)
}

@discardableResult
func invalidateWithLayout(_ layout: UICollectionViewLayout, context: UICollectionViewLayoutInvalidationContext) -> Bool {
guard
let provider = layout as? BrickLayoutInvalidationProvider
Expand Down Expand Up @@ -105,14 +105,6 @@ class BrickLayoutInvalidationContext: UICollectionViewLayoutInvalidationContext
self.invalidateSections(provider, layout: layout)
case .updateVisibility:
self.applyHideBehaviors(provider, updatedAttributes: updateAttributes)
case .invalidateDataSourceCounts(let sections):
let keys = Array(sections.keys).sorted(by: <) // We need to sort the keys first so the updates are done in the correct order
for section in keys {
let numberOfItems = sections[section]!
provider.updateNumberOfItemsInSection(section, numberOfItems: numberOfItems, updatedAttributes: { attributes, olfFrame in
})
}
applyHideBehaviors(provider, updatedAttributes: updateAttributes)
default: break
}

Expand Down
Loading