4949#import " HUBActionPerformer.h"
5050#import " HUBViewModelDiff.h"
5151#import " HUBComponentGestureRecognizer.h"
52+ #import " HUBViewModelRenderer.h"
5253
5354static NSTimeInterval const HUBImageDownloadTimeThreshold = 0.07 ;
5455
@@ -74,6 +75,7 @@ @interface HUBViewControllerImplementation () <
7475@property (nonatomic , strong , nullable , readonly ) id <HUBContentReloadPolicy> contentReloadPolicy;
7576@property (nonatomic , strong , nullable , readonly ) id <HUBImageLoader> imageLoader;
7677@property (nonatomic , strong , nullable ) UICollectionView *collectionView;
78+ @property (nonatomic , strong , nullable ) HUBViewModelRenderer *viewModelRenderer;
7779@property (nonatomic , assign ) BOOL collectionViewIsScrolling;
7880@property (nonatomic , strong , readonly ) NSMutableSet <NSString *> *registeredCollectionViewCellReuseIdentifiers;
7981@property (nonatomic , strong , readonly ) NSMutableDictionary <NSURL *, NSMutableArray<HUBComponentImageLoadingContext *> *> *componentImageLoadingContexts;
@@ -87,10 +89,8 @@ @interface HUBViewControllerImplementation () <
8789@property (nonatomic , strong , readonly ) HUBComponentReusePool *childComponentReusePool;
8890@property (nonatomic , strong , nullable ) HUBComponentWrapper *highlightedComponentWrapper;
8991@property (nonatomic , strong , nullable ) id <HUBViewModel> viewModel;
90- @property (nonatomic , strong , nullable ) HUBViewModelDiff *lastViewModelDiff;
9192@property (nonatomic , assign ) BOOL viewHasAppeared;
9293@property (nonatomic , assign ) BOOL viewHasBeenLaidOut;
93- @property (nonatomic ) BOOL viewModelIsInitial;
9494@property (nonatomic ) BOOL viewModelHasChangedSinceLastLayoutUpdate;
9595@property (nonatomic ) CGFloat visibleKeyboardHeight;
9696
@@ -137,7 +137,6 @@ - (instancetype)initWithViewURI:(NSURL *)viewURI
137137 _actionHandler = actionHandler;
138138 _scrollHandler = scrollHandler;
139139 _imageLoader = imageLoader;
140- _viewModelIsInitial = YES ;
141140 _registeredCollectionViewCellReuseIdentifiers = [NSMutableSet new ];
142141 _componentImageLoadingContexts = [NSMutableDictionary new ];
143142 _contentOffsetObservingComponentWrappers = [NSHashTable hashTableWithOptions: NSPointerFunctionsWeakMemory ];
@@ -228,8 +227,11 @@ - (void)viewDidLayoutSubviews
228227 self.viewHasBeenLaidOut = YES ;
229228
230229 if (self.viewModel != nil ) {
231- id <HUBViewModel> const viewModel = self.viewModel ;
232- [self reloadCollectionViewWithViewModel: viewModel animated: NO ];
230+ if (self.viewModelHasChangedSinceLastLayoutUpdate || !CGRectEqualToRect (self.collectionView .frame , self.view .bounds )) {
231+ self.collectionView .frame = self.view .bounds ;
232+ id <HUBViewModel> const viewModel = self.viewModel ;
233+ [self reloadCollectionViewWithViewModel: viewModel animated: NO ];
234+ }
233235 }
234236}
235237
@@ -351,16 +353,10 @@ - (void)viewModelLoader:(id<HUBViewModelLoader>)viewModelLoader didLoadViewModel
351353
352354 id <HUBViewControllerDelegate> const delegate = self.delegate ;
353355 [delegate viewController: self willUpdateWithViewModel: viewModel];
354-
355- if (self.viewModel != nil && !self.viewModelIsInitial ) {
356- id <HUBViewModel> const currentModel = self.viewModel ;
357- self.lastViewModelDiff = [HUBViewModelDiff diffFromViewModel: currentModel toViewModel: viewModel];
358- }
359356
360357 HUBCopyNavigationItemProperties (self.navigationItem , viewModel.navigationItem );
361358
362359 self.viewModel = viewModel;
363- self.viewModelIsInitial = NO ;
364360 self.viewModelHasChangedSinceLastLayoutUpdate = YES ;
365361 [self .view setNeedsLayout ];
366362
@@ -761,67 +757,30 @@ - (void)createCollectionViewIfNeeded
761757
762758- (void )reloadCollectionViewWithViewModel : (id <HUBViewModel>)viewModel animated : (BOOL )animated
763759{
764- if (!self.viewModelHasChangedSinceLastLayoutUpdate ) {
765- if (CGRectEqualToRect (self.collectionView .frame , self.view .bounds )) {
766- return ;
767- }
768- }
769-
770- self.collectionView .frame = self.view .bounds ;
771-
772- [self saveStatesForVisibleComponents ];
773-
774760 if (![self .collectionView.collectionViewLayout isKindOfClass: [HUBCollectionViewLayout class ]]) {
775761 self.collectionView .collectionViewLayout = [[HUBCollectionViewLayout alloc ] initWithComponentRegistry: self .componentRegistry
776762 componentLayoutManager: self .componentLayoutManager];
777763 }
778-
779- HUBCollectionViewLayout * const layout = (HUBCollectionViewLayout *)self.collectionView .collectionViewLayout ;
780-
781- /* Performing batch updates inbetween viewDidLoad and viewDidAppear is seemingly not allowed, as it
782- causes an assertion inside a private UICollectionView method. If no diff exists, fall back to
783- a complete reload. */
784- if (!self.viewHasAppeared || self.lastViewModelDiff == nil ) {
785- [self .collectionView reloadData ];
786-
787- [layout computeForCollectionViewSize: self .collectionView.frame.size viewModel: viewModel diff: self .lastViewModelDiff];
788764
789- if (self.viewHasAppeared ) {
790- /* Forcing a re-layout as the reloadData-call doesn't trigger the numberOfItemsInSection:-calls
791- by itself, and batch update calls don't play well without having an initial item count. */
792- [self .collectionView setNeedsLayout ];
793- [self .collectionView layoutIfNeeded ];
794- }
795-
796- self.lastViewModelDiff = nil ;
797- } else {
798- void (^updateBlock)() = ^{
799- [self .collectionView performBatchUpdates: ^{
800- HUBViewModelDiff * const lastDiff = self.lastViewModelDiff ;
801-
802- [self .collectionView insertItemsAtIndexPaths: lastDiff.insertedBodyComponentIndexPaths];
803- [self .collectionView deleteItemsAtIndexPaths: lastDiff.deletedBodyComponentIndexPaths];
804- [self .collectionView reloadItemsAtIndexPaths: lastDiff.reloadedBodyComponentIndexPaths];
805-
806- [layout computeForCollectionViewSize: self .collectionView.frame.size viewModel: viewModel diff: self .lastViewModelDiff];
807- } completion: ^(BOOL finished) {
808- self.lastViewModelDiff = nil ;
809- }];
810- };
811-
812- if (animated) {
813- updateBlock ();
814- } else {
815- [UIView performWithoutAnimation: updateBlock];
816- }
765+ if (self.viewModelRenderer == nil ) {
766+ UICollectionView * const nonnullCollectionView = self.collectionView ;
767+ self.viewModelRenderer = [[HUBViewModelRenderer alloc ] initWithCollectionView: nonnullCollectionView];
817768 }
769+
770+ [self saveStatesForVisibleComponents ];
771+
772+ [self .viewModelRenderer renderViewModel: viewModel
773+ usingBatchUpdates: self .viewHasAppeared
774+ animated: animated
775+ completion: ^{
776+ [self .delegate viewControllerDidFinishRendering: self ];
777+ }];
818778
819779 [self configureHeaderComponent ];
820780 [self configureOverlayComponents ];
821781 [self headerAndOverlayComponentViewsWillAppear ];
822782
823783 self.viewModelHasChangedSinceLastLayoutUpdate = NO ;
824- [self .delegate viewControllerDidFinishRendering: self ];
825784}
826785
827786- (void )saveStatesForVisibleComponents
0 commit comments