From 14e601e2bfbf822cd4e844f74bbd8ebc2464b8dd Mon Sep 17 00:00:00 2001 From: StephaneMagne Date: Fri, 1 Sep 2023 10:41:20 -0700 Subject: [PATCH] Update CollectionDataAnimationDelegate to give more timing options. Update minimum version to iOS 11. --- LiveCollections.xcodeproj/project.pbxproj | 6 +- .../ScenarioSeven/CarouselController.swift | 2 +- .../ScenarioSevenViewController.swift | 2 +- .../ScenarioThreeViewController.swift | 2 +- .../AnyDeltaUpdatableViewDelegate.swift | 8 +- .../Delegate/CollectionDataDelegate.swift | 16 ++- .../UIKit+DeltaUpdates.swift | 132 +++++++++--------- .../UIKit+SectionDeltaUpdates.swift | 118 +++++----------- 8 files changed, 122 insertions(+), 164 deletions(-) diff --git a/LiveCollections.xcodeproj/project.pbxproj b/LiveCollections.xcodeproj/project.pbxproj index d83f191..39fa3b5 100644 --- a/LiveCollections.xcodeproj/project.pbxproj +++ b/LiveCollections.xcodeproj/project.pbxproj @@ -541,7 +541,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -598,7 +598,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -622,7 +622,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -650,7 +649,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/CarouselController.swift b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/CarouselController.swift index c9078d1..5435b59 100644 --- a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/CarouselController.swift +++ b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/CarouselController.swift @@ -112,7 +112,7 @@ extension CarouselController: CollectionDataAnimationDelegate { return .preciseAnimations } - func animateAlongsideUpdate(with duration: TimeInterval) { + func animateAlongsideUpdate(for state: CollectionDataAnimationState) { // animate alongside the collection view animation here } } diff --git a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/ScenarioSevenViewController.swift b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/ScenarioSevenViewController.swift index e4ddb68..0189ca5 100644 --- a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/ScenarioSevenViewController.swift +++ b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioSeven/ScenarioSevenViewController.swift @@ -100,7 +100,7 @@ extension ScenarioSevenViewController: CollectionDataAnimationDelegate { return .preciseAnimations } - func animateAlongsideUpdate(with duration: TimeInterval) { + func animateAlongsideUpdate(for state: CollectionDataAnimationState) { // animate alongside the table view animation here } } diff --git a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioThreeViewController.swift b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioThreeViewController.swift index 67cdb27..aa94d1a 100644 --- a/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioThreeViewController.swift +++ b/LiveCollectionsSample/LiveCollectionsSample/Source/Scenarios/Scenario ViewControllers/ScenarioThreeViewController.swift @@ -131,7 +131,7 @@ extension ScenarioThreeViewController: CollectionDataAnimationDelegate { return .preciseAnimations } - func animateAlongsideUpdate(with duration: TimeInterval) { + func animateAlongsideUpdate(for state: CollectionDataAnimationState) { // animate alongside the collection view animation here // use a CollectionDataSynchronizer object if you would like // all animations to map to a single call diff --git a/Sources/LiveCollections/Classes/Internal/AnyProtocols/AnyDeltaUpdatableViewDelegate.swift b/Sources/LiveCollections/Classes/Internal/AnyProtocols/AnyDeltaUpdatableViewDelegate.swift index 302ff07..3f8ff37 100644 --- a/Sources/LiveCollections/Classes/Internal/AnyProtocols/AnyDeltaUpdatableViewDelegate.swift +++ b/Sources/LiveCollections/Classes/Internal/AnyProtocols/AnyDeltaUpdatableViewDelegate.swift @@ -42,8 +42,8 @@ extension AnyDeltaUpdatableViewDelegate: DeltaUpdatableViewDelegate { return animationDelegate?.preferredItemAnimationStyle(for: itemDelta) ?? .preciseAnimations } - func animateAlongsideUpdate(with duration: TimeInterval) { - animationDelegate?.animateAlongsideUpdate(with: duration) + func animateAlongsideUpdate(for state: CollectionDataAnimationState) { + animationDelegate?.animateAlongsideUpdate(for: state) } var view: DeltaUpdatableView? { @@ -79,8 +79,8 @@ extension AnySectionDeltaUpdatableViewDelegate: SectionDeltaUpdatableViewDelegat return animationDelegate?.preferredItemAnimationStyle(for: itemDelta) ?? .preciseAnimations } - func animateAlongsideUpdate(with duration: TimeInterval) { - animationDelegate?.animateAlongsideUpdate(with: duration) + func animateAlongsideUpdate(for state: CollectionDataAnimationState) { + animationDelegate?.animateAlongsideUpdate(for: state) } func preferredSectionAnimationStyle(for sectionDelta: IndexDelta) -> AnimationStyle { diff --git a/Sources/LiveCollections/Classes/Public/Delegate/CollectionDataDelegate.swift b/Sources/LiveCollections/Classes/Public/Delegate/CollectionDataDelegate.swift index 50726a5..464d666 100644 --- a/Sources/LiveCollections/Classes/Public/Delegate/CollectionDataDelegate.swift +++ b/Sources/LiveCollections/Classes/Public/Delegate/CollectionDataDelegate.swift @@ -67,14 +67,26 @@ public protocol CollectionDataCalculationNotificationDelegate: AnyObject { func collectionDataDidEndCalculating() } -// MARK: CollectionDataCalculationNotificationDelegate +// MARK: CollectionDataAnimationDelegate + +public enum CollectionDataAnimationGroup { + case insertDeleteMove + case reload +} + +public enum CollectionDataAnimationState { + case immediatelyBefore(group: CollectionDataAnimationGroup, duration: TimeInterval) + case immediatelyAfter(group: CollectionDataAnimationGroup, duration: TimeInterval) + case during(group: CollectionDataAnimationGroup, duration: TimeInterval) + case completed(group: CollectionDataAnimationGroup) +} /** Use to control the style of animations or to animate alongside a UITableView or UICollectionView animation */ public protocol CollectionDataAnimationDelegate: AnyObject { func preferredItemAnimationStyle(for itemDelta: IndexDelta) -> AnimationStyle - func animateAlongsideUpdate(with duration: TimeInterval) + func animateAlongsideUpdate(for state: CollectionDataAnimationState) } // MARK: CollectionSectionDataAnimationDelegate diff --git a/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+DeltaUpdates.swift b/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+DeltaUpdates.swift index 4d771e2..c5a03b5 100644 --- a/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+DeltaUpdates.swift +++ b/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+DeltaUpdates.swift @@ -66,14 +66,25 @@ extension UITableView: DeltaUpdatableView { } } - if #available(iOS 11.0, *) { - performBatchUpdates({ [weak self] in - tableViewUpdates.forEach { $0.sectionUpdate.update() } - guard self != nil else { return } - deleteMoveInsert() - tableViewUpdates.uniqueAnimationDelegates.forEach { $0.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) } - }, completion: { [weak self] animationsCompletedSuccessfully in - guard let strongSelf = self else { + performBatchUpdates(.insertDeleteMove, delegates: tableViewUpdates.uniqueAnimationDelegates, { [weak self] in + tableViewUpdates.forEach { $0.sectionUpdate.update() } + guard self != nil else { return } + deleteMoveInsert() + }, completion: { [weak self] animationsCompletedSuccessfully in + guard let strongSelf = self else { + tableViewUpdates.forEach { $0.sectionUpdate.completion?() } + return + } + guard animationsCompletedSuccessfully else { + strongSelf.reloadData() + tableViewUpdates.forEach { $0.sectionUpdate.completion?() } + return + } + strongSelf.performBatchUpdates(.reload, delegates: tableViewUpdates.uniqueAnimationDelegates, { [weak weakSelf = strongSelf] in + guard weakSelf != nil else { return } + reload() + }, completion: { [weak weakSelf = strongSelf] animationsCompletedSuccessfully in + guard let strongSelf = weakSelf else { tableViewUpdates.forEach { $0.sectionUpdate.completion?() } return } @@ -82,73 +93,27 @@ extension UITableView: DeltaUpdatableView { tableViewUpdates.forEach { $0.sectionUpdate.completion?() } return } - strongSelf.performBatchUpdates({ [weak weakSelf = strongSelf] in - guard weakSelf != nil else { return } - reload() - }, completion: { [weak weakSelf = strongSelf] animationsCompletedSuccessfully in - guard let strongSelf = weakSelf else { - tableViewUpdates.forEach { $0.sectionUpdate.completion?() } - return - } - guard animationsCompletedSuccessfully else { - strongSelf.reloadData() - tableViewUpdates.forEach { $0.sectionUpdate.completion?() } - return - } - - tableViewUpdates.manualReload(view: strongSelf) { - tableViewUpdates.forEach { $0.sectionUpdate.completion?() } - } - }) - }) - } else { - beginUpdates() - tableViewUpdates.forEach { $0.sectionUpdate.update() } - deleteMoveInsert() - tableViewUpdates.uniqueAnimationDelegates.forEach { $0.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) } - endUpdates() - - guard isVisibleOnScreen else { - reloadData() - tableViewUpdates.forEach { $0.sectionUpdate.completion?() } - return - } - beginUpdates() - reload() - endUpdates() - - tableViewUpdates.manualReload(view: self) { - tableViewUpdates.forEach { $0.sectionUpdate.completion?() } - } - } + tableViewUpdates.manualReload(view: strongSelf) { + tableViewUpdates.forEach { $0.sectionUpdate.completion?() } + } + }) + }) } public func reloadSections(for sectionUpdates: [SectionUpdate]) { - if #available(iOS 11.0, *) { - performBatchUpdates({ [weak self] in - sectionUpdates.forEach { sectionUpdate in - sectionUpdate.update() - guard self != nil else { return } - let indexSet = IndexSet([sectionUpdate.section]) - reloadSections(indexSet, with: preferredReloadSectionAnimation(for: sectionUpdate.section)) - } - sectionUpdates.uniqueAnimationDelegates.forEach { $0.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) } - }, completion: { _ in - sectionUpdates.forEach { $0.completion?() } - }) - } else { - beginUpdates() + performBatchUpdates({ [weak self] in sectionUpdates.forEach { sectionUpdate in sectionUpdate.update() + guard self != nil else { return } let indexSet = IndexSet([sectionUpdate.section]) reloadSections(indexSet, with: preferredReloadSectionAnimation(for: sectionUpdate.section)) } sectionUpdates.uniqueAnimationDelegates.forEach { $0.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) } - endUpdates() + }, completion: { _ in sectionUpdates.forEach { $0.completion?() } - } + }) } } @@ -189,7 +154,7 @@ extension UICollectionView: DeltaUpdatableView { return } - performBatchUpdates({ [weak self] in + performBatchUpdates(.insertDeleteMove, delegates: collectionViewUpdates.uniqueAnimationDelegates, { [weak self] in collectionViewUpdates.forEach { $0.sectionUpdate.update() } guard let strongSelf = self else { return } for update in collectionViewUpdates { @@ -201,7 +166,6 @@ extension UICollectionView: DeltaUpdatableView { } strongSelf.insertItems(at: delta.insertedIndexPaths) } - collectionViewUpdates.uniqueAnimationDelegates.forEach { $0.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) } }, completion: { [weak self] animationsCompletedSuccessfully in guard let strongSelf = self else { collectionViewUpdates.forEach { $0.sectionUpdate.completion?() } @@ -219,7 +183,7 @@ extension UICollectionView: DeltaUpdatableView { return } - strongSelf.performBatchUpdates({ [weak weakSelf = strongSelf] in + strongSelf.performBatchUpdates(.reload, delegates: collectionViewUpdates.uniqueAnimationDelegates, { [weak weakSelf = strongSelf] in guard let strongSelf = weakSelf else { return } for update in filteredUpdates { let delta = update.indexPathsToAnimate @@ -497,3 +461,39 @@ private extension Sequence where Element == SectionUpdate { }) } } + +// MARK: AnimationDelegate Helper + +private extension UITableView { + + func performBatchUpdates(_ group: CollectionDataAnimationGroup, delegates: [CollectionDataAnimationDelegate], _ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { + delegates.forEach { $0.animateAlongsideUpdate(for: .immediatelyBefore(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + + performBatchUpdates { + updates?() + delegates.forEach { $0.animateAlongsideUpdate(for: .during(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + } completion: { value in + completion?(value) + delegates.forEach { $0.animateAlongsideUpdate(for: .completed(group: group)) } + } + + delegates.forEach { $0.animateAlongsideUpdate(for: .immediatelyAfter(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + } +} + +private extension UICollectionView { + + func performBatchUpdates(_ group: CollectionDataAnimationGroup, delegates: [CollectionDataAnimationDelegate], _ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) { + delegates.forEach { $0.animateAlongsideUpdate(for: .immediatelyBefore(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + + performBatchUpdates { + updates?() + delegates.forEach { $0.animateAlongsideUpdate(for: .during(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + } completion: { value in + completion?(value) + delegates.forEach { $0.animateAlongsideUpdate(for: .completed(group: group)) } + } + + delegates.forEach { $0.animateAlongsideUpdate(for: .immediatelyAfter(group: group, duration: TimeInterval.standardCollectionAnimationDuration)) } + } +} diff --git a/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+SectionDeltaUpdates.swift b/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+SectionDeltaUpdates.swift index 9d212c5..41c4211 100644 --- a/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+SectionDeltaUpdates.swift +++ b/Sources/LiveCollections/Classes/Public/UIKit & Foundatin Extensions/UIKit+SectionDeltaUpdates.swift @@ -45,27 +45,18 @@ extension UITableView: SectionDeltaUpdatableView { self.insertSections(sectionInsertedIndexSet as IndexSet, with: self.preferredInsertSectionAnimation) } - if #available(iOS 11.0, *) { - performBatchUpdates({ [weak self] in - updateData() - guard let strongSelf = self else { return } - guard strongSelf.isVisibleOnScreen else { - strongSelf.reloadData() - return - } - deleteMoveInsert() - delegate?.animateAlongsideSectionUpdate(with: TimeInterval.standardCollectionAnimationDuration) - }, completion: { _ in - completion?() - }) - } else { - beginUpdates() + performBatchUpdates({ [weak self] in updateData() + guard let strongSelf = self else { return } + guard strongSelf.isVisibleOnScreen else { + strongSelf.reloadData() + return + } deleteMoveInsert() delegate?.animateAlongsideSectionUpdate(with: TimeInterval.standardCollectionAnimationDuration) - endUpdates() + }, completion: { _ in completion?() - } + }) } public func performAnimations(sectionItemDelta itemIndexPathDelta: IndexPathDelta, delegate: SectionDeltaUpdatableViewDelegate?, updateData: (() -> Void), completion: (() -> Void)?) { @@ -109,55 +100,31 @@ extension UITableView: SectionDeltaUpdatableView { self.reloadRows(at: automaticReloadIndexPaths, with: self.preferredReloadRowAnimation) } - if #available(iOS 11.0, *) { - performBatchUpdates({ [weak self] in - updateData() - guard let strongSelf = self else { return } - guard strongSelf.isVisibleOnScreen else { - strongSelf.reloadData() - return - } - deleteMoveInsert() - delegate?.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) - }, completion: { [weak self] _ in - guard let strongSelf = self else { - completion?() - return - } - strongSelf.performBatchUpdates({ [weak weakSelf = strongSelf] in - guard let strongSelf = weakSelf else { return } - guard strongSelf.isVisibleOnScreen else { - strongSelf.reloadData() - return - } - reload() - }, completion: { _ in - strongSelf.manualReload(indexPaths: manualReloadIndexPaths, delegate: delegate, viewCompletion: completion) - }) - }) - } else { - beginUpdates() + performBatchUpdates({ [weak self] in updateData() - guard isVisibleOnScreen else { - reloadData() - completion?() + guard let strongSelf = self else { return } + guard strongSelf.isVisibleOnScreen else { + strongSelf.reloadData() return } deleteMoveInsert() delegate?.animateAlongsideUpdate(with: TimeInterval.standardCollectionAnimationDuration) - endUpdates() - - beginUpdates() - guard isVisibleOnScreen else { - reloadData() + }, completion: { [weak self] _ in + guard let strongSelf = self else { completion?() return } - reload() - endUpdates() - - manualReload(indexPaths: manualReloadIndexPaths, delegate: delegate, viewCompletion: completion) - } + strongSelf.performBatchUpdates({ [weak weakSelf = strongSelf] in + guard let strongSelf = weakSelf else { return } + guard strongSelf.isVisibleOnScreen else { + strongSelf.reloadData() + return + } + reload() + }, completion: { _ in + strongSelf.manualReload(indexPaths: manualReloadIndexPaths, delegate: delegate, viewCompletion: completion) + }) + }) } // NOTE: You can only call this method safely if you are only performing reloads @@ -172,41 +139,22 @@ extension UITableView: SectionDeltaUpdatableView { return } - if #available(iOS 11.0, *) { - performBatchUpdates({ [weak self] in - updateData() - guard let strongSelf = self else { return } - guard strongSelf.isVisibleOnScreen else { - strongSelf.reloadData() - return - } - guard let numberOfSections = strongSelf.dataSource?.numberOfSections?(in: strongSelf) else { - return - } - let sections = IndexSet(integersIn: 0..