Skip to content

Instantly share code, notes, and snippets.

@bnickel
Created June 21, 2018 19:04
Show Gist options
  • Save bnickel/6cf4e1bf92049d060687f665c4b6253a to your computer and use it in GitHub Desktop.
Save bnickel/6cf4e1bf92049d060687f665c4b6253a to your computer and use it in GitHub Desktop.
A class that lets you schedule a bunch of batch updates to UITableView and then perform them all at once or not, with a callback on completion.
func setSelectedSite(_ site:SEAPISite, selectedTab:SESiteTab, inSection:Int, tableView:UITableView, animated:Bool, completion:((Void) -> Void)?) {
let batchUpdate = SETableViewBatchUpdate(tableView: tableView)
if site != selectedSite {
if let indexPaths = selectedSiteTransientItemRange?.map({ IndexPath(row: $0, section: inSection) }) {
batchUpdate.deleteRows(atIndexPaths: indexPaths, with: .fade)
}
selectedSite = site
rebuildMenu()
if let indexPaths = selectedSiteTransientItemRange?.map({ IndexPath(row: $0, section: inSection) }) {
batchUpdate.insertRows(atIndexPaths: indexPaths, with: .fade)
}
}
let indexPathToSelect:IndexPath?
if let offset = tabIdToChildItemIndexMap[selectedTab],
let row = selectedSiteIndex != NSNotFound ? selectedSiteIndex + 1 + offset : Optional<Int>.none {
indexPathToSelect = IndexPath(row: row, section: inSection)
} else {
indexPathToSelect = nil
}
// The following selection procedure is way uglier than I would hope. If you set a selected cell during a batch update, it can end up with no background during the animation.
// As a result, if we're doing a batch update, our best bet is to scroll to the selected cell during the update and then select when the animation completes.
if batchUpdate.hasUpdates {
// If we're going to select a new row, deselect the old row before animating.
if indexPathToSelect != nil {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: selectedIndexPath, animated: false)
}
}
batchUpdate.run(animated: animated, completion: {
if let indexPathToSelect = indexPathToSelect {
tableView.selectRow(at: indexPathToSelect, animated: false, scrollPosition: .none)
}
completion?()
})
if let indexPathToSelect = indexPathToSelect {
tableView.scrollToRow(at: indexPathToSelect, at: .middle, animated: animated)
}
} else {
if let indexPathToSelect = indexPathToSelect , indexPathToSelect != tableView.indexPathForSelectedRow {
tableView.selectRow(at: indexPathToSelect, animated: animated, scrollPosition: .none)
}
completion?()
}
}
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SETableViewBatchUpdate : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithTableView:(UITableView *)tableView NS_DESIGNATED_INITIALIZER;
- (void)runAnimated:(BOOL)animated completion:(void (^ _Nullable)())completion;
@property (nonatomic, assign, readonly) BOOL hasUpdates;
@end
@interface SETableViewBatchUpdate (Convenience)
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection NS_AVAILABLE_IOS(5_0);
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath NS_AVAILABLE_IOS(5_0);
@end
NS_ASSUME_NONNULL_END
#import "SETableViewBatchUpdate.h"
@interface SETableViewBatchUpdate ()
@property (nonatomic, strong, readonly) UITableView *tableView;
@property (nonatomic, strong, readonly) NSMutableArray *invocations;
@property (nonatomic, assign) BOOL hasRun;
@property (nonatomic, assign, readwrite) BOOL hasUpdates;
@end
@implementation SETableViewBatchUpdate
- (instancetype)initWithTableView:(UITableView *)tableView
{
NSParameterAssert(tableView != nil);
self = [super init];
if (self) {
_tableView = tableView;
_invocations = [NSMutableArray array];
}
return self;
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [super respondsToSelector:selector] || [self.tableView respondsToSelector:selector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [super methodSignatureForSelector:selector] ?: [self.tableView methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
if ([self.tableView respondsToSelector:selector]) {
[invocation retainArguments];
[self.invocations addObject:invocation];
self.hasUpdates = YES;
} else {
[self doesNotRecognizeSelector:selector];
}
}
- (void)runAnimated:(BOOL)animated completion:(void (^)())completion
{
NSAssert(!self.hasRun, @"Batch updates should only run once.");
self.hasRun = YES;
if (animated && self.invocations.count > 0) {
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.tableView.userInteractionEnabled = YES;
if (completion) { completion(); }
}];
[self.tableView beginUpdates];
self.tableView.userInteractionEnabled = NO;
for (NSInvocation *invocation in self.invocations) {
[invocation invokeWithTarget:self.tableView];
}
[self.tableView endUpdates];
[self.tableView correctHeaderViewsAfterUpdates];
[CATransaction commit];
} else {
[self.tableView reloadData];
if (completion) { completion(); }
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment