From 8da03356e416acad252fa685ced0e920ddfcff8c Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 11:15:55 -0400 Subject: [PATCH] Fixing reuse and sizing issues. --- MVMCoreUI/Atomic/Organisms/Carousel.swift | 70 +++++++++++++------ .../Atomic/Organisms/CarouselModel.swift | 22 +++--- .../ThreeLayerTableViewController.swift | 2 +- .../BaseControllers/ViewController.swift | 3 +- 4 files changed, 63 insertions(+), 34 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 54dc37e7..1ed2ecd2 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -54,6 +54,32 @@ open class Carousel: View { private var size: CGFloat? + // Updates the model and index. + public func updateModelIndex() { + (model as? CarouselModel)?.index = pageIndex + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.layoutCollection() + } + } + + /// Invalidates the layout and ensures we are paged to the correct cell. + open func layoutCollection() { + collectionView.collectionViewLayout.invalidateLayout() + showPeaking(false) + + // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. + DispatchQueue.main.async { + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.collectionView.layoutIfNeeded() + self.showPeaking(true) + } + } + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() @@ -73,15 +99,12 @@ open class Carousel: View { open override func updateView(_ size: CGFloat) { super.updateView(size) self.size = size - collectionView.collectionViewLayout.invalidateLayout() - showPeaking(false) - - // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. - DispatchQueue.main.async { - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) - self.collectionView.layoutIfNeeded() - self.showPeaking(true) + + // Update cells and re-layout. + for cell in collectionView.visibleCells { + (cell as? MVMCoreViewProtocol)?.updateView(size) } + layoutCollection() } // MARK: - MoleculeViewProtocol @@ -108,6 +131,9 @@ open class Carousel: View { } setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) + + pageIndex = carouselModel.index + pagingView?.setPage(carouselModel.index) collectionView.reloadData() } @@ -197,6 +223,7 @@ open class Carousel: View { } let currentPage = pager.currentPage() localSelf.pageIndex = currentPage + localSelf.updateModelIndex() localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning) }) } @@ -246,7 +273,7 @@ open class Carousel: View { extension Carousel: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = (size ?? collectionView.bounds.width) * CGFloat(itemWidthPercent) + let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) return CGSize(width: itemWidth, height: collectionView.bounds.height) } @@ -280,19 +307,18 @@ extension Carousel: UIScrollViewDelegate { func goTo(_ index: Int, animated: Bool) { showPeaking(false) - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) - self.currentIndex = index - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated) - if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) { - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) + currentIndex = index + updateModelIndex() + collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: animated) + if let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) UIAccessibility.post(notification: .layoutChanged, argument: cell) } } func handleUserOnBufferCell() { - guard loop else { - return - } + guard loop else { return } let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in @@ -320,9 +346,11 @@ extension Carousel: UIScrollViewDelegate { let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 if index < 1 { - self.currentIndex = 0 + currentIndex = 0 + updateModelIndex() } else if index > CGFloat(lastCellIndex - 1) { - self.currentIndex = lastCellIndex + currentIndex = lastCellIndex + updateModelIndex() } } @@ -348,9 +376,7 @@ extension Carousel: UIScrollViewDelegate { targetContentOffset.pointee = scrollView.contentOffset // This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). - guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { - return - } + guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return } // We switch cards if we pass the velocity threshold or position threshold (currently 50%). let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift index 9fb4fe3b..c5f73a01 100644 --- a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift @@ -12,7 +12,7 @@ import UIKit public static var identifier: String = "carousel" public var backgroundColor: Color? public var molecules: [CarouselItemModel] - + public var index: Int = 0 public var spacing: Float? public var border: Bool? public var loop: Bool? @@ -29,6 +29,7 @@ import UIKit case moleculeName case backgroundColor case molecules + case index case spacing case border case loop @@ -40,15 +41,16 @@ import UIKit required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) - self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - self.spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) - self.border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) - self.loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) - self.height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) - self.itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) - self.itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) - self.pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) + molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) + index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) + border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) + loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) + height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) + itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) + itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) + pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index c0f267fc..000f2e7f 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -33,7 +33,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { bottomView.updateView(width) showFooter(width) } - self.tableView?.reloadData() + tableView?.reloadData() } open override func handleNewData() { diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index d19ebdaf..eb4be908 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -278,7 +278,8 @@ import UIKit return } - if needsUpdateUI || screenSizeChanged() { + // First update should be explicit (hence the zero check) + if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) { updateViews() needsUpdateUI = false }