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

Introduce ASCollectionGalleryLayoutDelegate #76

Merged
merged 25 commits into from
Jul 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
169641e
Implement ASCollectionGalleryLayoutDelegate
nguyenhuy Apr 19, 2017
ff06ad5
Handle items that span multiple pages and other improvements in galle…
nguyenhuy May 4, 2017
7b0c9f8
Minor fixes
nguyenhuy May 4, 2017
a82a0be
Fix failing tests
nguyenhuy Jul 8, 2017
abec0f4
Fix custom collection example
nguyenhuy Jul 10, 2017
600f145
Implement missing method in gallery layout delegate
nguyenhuy Jul 10, 2017
a2d789e
Fix warnings
nguyenhuy Jul 10, 2017
fbfcf76
Some improvements
nguyenhuy Jul 10, 2017
ab98d55
Fix file licenses
nguyenhuy Jul 10, 2017
01eaba9
Move measure range logic to ASCollectionLayout
nguyenhuy Jul 12, 2017
a2b269f
Track unmeasured elements
nguyenhuy Jul 12, 2017
f7ae618
Remove pending layout in ASCollectionLayout
nguyenhuy Jul 12, 2017
8d55066
Get back pending layout because the timing to latch new data is not i…
nguyenhuy Jul 12, 2017
a4a0ae5
Add ASCollectionLayoutCache
nguyenhuy Jul 12, 2017
305a641
Fix file licenses
nguyenhuy Jul 12, 2017
2c99128
Fix xcodeproj
nguyenhuy Jul 12, 2017
6b92d9a
Add async collection layout to examples/ASCollectionView
nguyenhuy Jul 13, 2017
25afc72
Measure method in ASCollectionLayout to be a class method
nguyenhuy Jul 13, 2017
e997d30
Encourage more immutable states
nguyenhuy Jul 13, 2017
7461c13
Remove additionalInfo property in ASCollectionLayoutState
nguyenhuy Jul 13, 2017
b4df3c8
ASCollectionLayoutState to correctly filter unmeasured elements
nguyenhuy Jul 13, 2017
e7b1482
Add ASHashing to umbrella header
nguyenhuy Jul 13, 2017
1a643cb
Fix file licenses
nguyenhuy Jul 13, 2017
5950a9f
Add ASDispatchAsync and use it in ASCollectionLayout
nguyenhuy Jul 13, 2017
bbca3f2
Improve code comment in ASCollectionLayoutState
nguyenhuy Jul 14, 2017
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
90 changes: 69 additions & 21 deletions AsyncDisplayKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Add your own contributions to the next release on the line below this with your name.
- [ASTextNode2] Add initial implementation for link handling. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/396)
- [ASTextNode2] Provide compile flag to globally enable new implementation of ASTextNode: ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/410)
- Add ASCollectionGalleryLayoutDelegate - an async collection layout that makes same-size collections (e.g photo galleries, pagers, etc) fast and lightweight! [Huy Nguyen](https://github.com/nguyenhuy/) [#76](https://github.com/TextureGroup/Texture/pull/76)

##2.3.5
- Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler)
Expand Down
1 change: 1 addition & 0 deletions Source/ASCollectionNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ - (void)didEnterPreloadState
[super didEnterPreloadState];
// Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load.
// We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view.
// TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view
[[self view] layoutIfNeeded];
}

Expand Down
6 changes: 4 additions & 2 deletions Source/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionFlowLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>

#import <AsyncDisplayKit/ASSectionController.h>
#import <AsyncDisplayKit/ASSupplementaryNodeSource.h>
Expand Down Expand Up @@ -98,17 +99,18 @@
#import <AsyncDisplayKit/ASControlNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASEventLog.h>
#import <AsyncDisplayKit/ASHashing.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASMutableAttributedStringBuilder.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
#import <AsyncDisplayKit/ASTextKitComponents.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASTraitCollection.h>
#import <AsyncDisplayKit/ASVisibilityProtocols.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/ASEventLog.h>

#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>
Expand Down
1 change: 0 additions & 1 deletion Source/Details/ASCollectionFlowLayoutDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
//

#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down
27 changes: 12 additions & 15 deletions Source/Details/ASCollectionFlowLayoutDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

#import <AsyncDisplayKit/ASCollectionFlowLayoutDelegate.h>

#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
Expand All @@ -43,26 +44,17 @@ - (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirect
return self;
}

- (ASSizeRange)sizeRangeThatFits:(CGSize)viewportSize
- (ASScrollDirection)scrollableDirections
{
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO) {
sizeRange.min.height = viewportSize.height;
sizeRange.max.height = viewportSize.height;
}
if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) {
sizeRange.min.width = viewportSize.width;
sizeRange.max.width = viewportSize.width;
}
return sizeRange;
return _scrollableDirections;
}

- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
return nil;
}

- (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
NSMutableArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node);
Expand All @@ -80,8 +72,13 @@ - (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutConte
alignContent:ASStackLayoutAlignContentStart
children:children];
stackSpec.concurrent = YES;
ASLayout *layout = [stackSpec layoutThatFits:[self sizeRangeThatFits:context.viewportSize]];
return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout];

ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections);
ASLayout *layout = [stackSpec layoutThatFits:sizeRange];

return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nonnull(ASLayout * _Nonnull sublayout) {
return ((ASCellNode *)sublayout.layoutElement).collectionElement;
}];
}

@end
32 changes: 32 additions & 0 deletions Source/Details/ASCollectionGalleryLayoutDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// ASCollectionGalleryLayoutDelegate.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASScrollDirection.h>

NS_ASSUME_NONNULL_BEGIN

/**
* A thread-safe layout delegate that arranges items with the same size into a flow layout.
*
* @note Supplemenraty elements are not supported.
*/
AS_SUBCLASSING_RESTRICTED
@interface ASCollectionGalleryLayoutDelegate : NSObject <ASCollectionLayoutDelegate>

- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize NS_DESIGNATED_INITIALIZER;

- (instancetype)init __unavailable;

@end

NS_ASSUME_NONNULL_END
86 changes: 86 additions & 0 deletions Source/Details/ASCollectionGalleryLayoutDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// ASCollectionGalleryLayoutDelegate.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>

#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>

#pragma mark - ASCollectionGalleryLayoutDelegate

@implementation ASCollectionGalleryLayoutDelegate {
ASScrollDirection _scrollableDirections;
CGSize _itemSize;
}

- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections itemSize:(CGSize)itemSize
{
self = [super init];
if (self) {
ASDisplayNodeAssertFalse(CGSizeEqualToSize(CGSizeZero, itemSize));
_scrollableDirections = scrollableDirections;
_itemSize = itemSize;
}
return self;
}

- (ASScrollDirection)scrollableDirections
{
return _scrollableDirections;
}

- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements
{
return [NSValue valueWithCGSize:_itemSize];
}

+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
CGSize pageSize = context.viewportSize;
CGSize itemSize = ((NSValue *)context.additionalInfo).CGSizeValue;
ASScrollDirection scrollableDirections = context.scrollableDirections;
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context
contentSize:CGSizeZero
elementToLayoutAttributesTable:[NSMapTable weakToStrongObjectsMapTable]];
}

// Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element
ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
flexWrap:ASStackLayoutFlexWrapWrap
alignContent:ASStackLayoutAlignContentStart
children:children];
stackSpec.concurrent = YES;
ASLayout *layout = [stackSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)];

return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement *(ASLayout *sublayout) {
return ((_ASGalleryLayoutItem *)sublayout.layoutElement).collectionElement;
}];
}

@end
6 changes: 3 additions & 3 deletions Source/Details/ASCollectionLayoutContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASScrollDirection.h>

@class ASElementMap;

NS_ASSUME_NONNULL_BEGIN

AS_SUBCLASSING_RESTRICTED

@interface ASCollectionLayoutContext : NSObject

@property (nonatomic, assign, readonly) CGSize viewportSize;
@property (nonatomic, strong, readonly) ASElementMap *elements;
@property (nonatomic, assign, readonly) ASScrollDirection scrollableDirections;
@property (nonatomic, weak, readonly) ASElementMap *elements;
@property (nonatomic, strong, readonly, nullable) id additionalInfo;

- (instancetype)init __unavailable;
Expand Down
107 changes: 107 additions & 0 deletions Source/Details/ASCollectionLayoutContext.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// ASCollectionLayoutContext.m
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>

#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHashing.h>

@implementation ASCollectionLayoutContext {
Class<ASCollectionLayoutDelegate> _layoutDelegateClass;

// This ivar doesn't directly involve in the layout calculation process, i.e contexts can be equal regardless of the layout caches.
// As a result, this ivar is ignored in -isEqualToContext: and -hash.
__weak ASCollectionLayoutCache *_layoutCache;
}

- (instancetype)initWithViewportSize:(CGSize)viewportSize
scrollableDirections:(ASScrollDirection)scrollableDirections
elements:(ASElementMap *)elements
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
layoutCache:(ASCollectionLayoutCache *)layoutCache
additionalInfo:(id)additionalInfo
{
self = [super init];
if (self) {
ASDisplayNodeAssertTrue([layoutDelegateClass conformsToProtocol:@protocol(ASCollectionLayoutDelegate)]);
_viewportSize = viewportSize;
_scrollableDirections = scrollableDirections;
_elements = elements;
_layoutDelegateClass = layoutDelegateClass;
_layoutCache = layoutCache;
_additionalInfo = additionalInfo;
}
return self;
}

- (Class<ASCollectionLayoutDelegate>)layoutDelegateClass
{
return _layoutDelegateClass;
}

- (ASCollectionLayoutCache *)layoutCache
{
return _layoutCache;
}

- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context
{
if (context == nil) {
return NO;
}

// NOTE: ASObjectIsEqual returns YES when both objects are nil.
// So don't use ASObjectIsEqual on _elements.
// It is a weak property and 2 layouts generated from different sets of elements
// should never be considered the same even if they are nil now.
return CGSizeEqualToSize(_viewportSize, context.viewportSize)
&& _scrollableDirections == context.scrollableDirections
&& [_elements isEqual:context.elements]
&& _layoutDelegateClass == context.layoutDelegateClass
&& ASObjectIsEqual(_additionalInfo, context.additionalInfo);
}

- (BOOL)isEqual:(id)other
{
if (self == other) {
return YES;
}
if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) {
return NO;
}
return [self isEqualToContext:other];
}

- (NSUInteger)hash
{
struct {
CGSize viewportSize;
ASScrollDirection scrollableDirections;
NSUInteger elementsHash;
NSUInteger layoutDelegateClassHash;
NSUInteger additionalInfoHash;
} data = {
_viewportSize,
_scrollableDirections,
_elements.hash,
_layoutDelegateClass.hash,
[_additionalInfo hash]
};
return ASHashBytes(&data, sizeof(data));
}

@end
Loading