mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Merge commit '3ccc2f0f15d2e4ed7105f0ef6aea575e05cfd7ad'
# Conflicts: # AsyncDisplayKit.xcodeproj/project.pbxproj # Source/ASDisplayNode.mm # Source/ASMapNode.mm # Source/Details/ASDataController.mm # Source/Details/ASPINRemoteImageDownloader.h # Source/Details/ASPINRemoteImageDownloader.m
This commit is contained in:
62
.github/GITHUB_RULES.md
vendored
62
.github/GITHUB_RULES.md
vendored
@@ -1,62 +0,0 @@
|
||||
### Contribute to ASDK's Friendly Reputation
|
||||
|
||||
ASDK has earned its reputation as an exceptionally welcoming place for newbie & experienced developers alike through the extra time Scott takes to thank _everyone_ who posts a question, bug, feature request or PR, for their time and contribution to the project, no matter how large the contribution (or silly the question).
|
||||
|
||||
###PR Reviewing
|
||||
|
||||
Merge permissions granted to Scott Goodson (@appleguy), Michael Schneider (@maicki), Adlai Holler (@Adlai-Holler)
|
||||
|
||||
**PR Type** | **Required Reviewers**
|
||||
--- | ---
|
||||
Documentation | Anyone
|
||||
Bug Fix | 2 (external PR) or 1 (internal PR) of the following (Scott, Michael, Adlai, Levi)
|
||||
Refactoring | 1-3 depending on size / author familiarity with feature
|
||||
New API | Scott + component owner + 1 additional
|
||||
Breaking API | Scott + component owner + 1 additional
|
||||
|
||||
**Component** | **Experts For Reviewing**
|
||||
--- | ---
|
||||
ASTextNode + subclasses | Ricky / Oliver
|
||||
ASImageNode + subclasses | Garrett / Scott / Michael
|
||||
ASDataController / Table / Collection | Michael
|
||||
ASRangeController | Scott
|
||||
ASLayout | Huy
|
||||
ASDisplayNode | Garret / Michael / Levi
|
||||
ASVideoNode | #asvideonode channel
|
||||
|
||||
###PR Merging
|
||||
|
||||
BE CAUTIOUS, DON'T CAUSE A REGRESSION
|
||||
|
||||
Try to include as much as possible:
|
||||
- Description / Screenshots
|
||||
- Motivation & Context
|
||||
- Methods of testing / Sample app
|
||||
- What type of change it is (bug fix, new feature, breaking change)
|
||||
- Tag @hannahmbanana on any documentation needs*
|
||||
- Title the PR with the component in brackets - e.g. "[ASTextNode] fix threading issues..."
|
||||
- New files need to include the required Facebook licensing header info.
|
||||
- For future viewers / potential contributors, try to describe why this PR is helpful / useful / awesome / makes an impact on the current or future community
|
||||
|
||||
###What stays on GitHub vs goes to Ship?
|
||||
|
||||
GitHub:
|
||||
- active bugs
|
||||
- active community discussions
|
||||
- unresolved community questions
|
||||
- open issue about slack channel
|
||||
- open issue with list of “up-for-grabs” tasks to get involved
|
||||
|
||||
Ship:
|
||||
- feature requests
|
||||
- documentation requests
|
||||
- performance optimizations / refactoring
|
||||
|
||||
Comment for moving to Ship:
|
||||
|
||||
@\<FEATURE_REQUESTOR\> The community is planning an exciting long term road map for the project and getting organized around how to deliver these feature requests.
|
||||
|
||||
If you are interested in helping contribute to this component or any other, don’t hesitate to send us an email at AsyncDisplayKit@gmail.com or ping us on <a href="https://github.com/facebook/AsyncDisplayKit/issues/1582">
|
||||
ASDK's Slack</a> channel. If you would like to contribute for a few weeks, we can also add you to our Ship bug tracker so that you can see what everyone is working on and actively coordinate with us.
|
||||
|
||||
As always, keep filing issues and submitting pull requests here on Github and we will only move things to the new tracker if they require long term coordination.
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,7 @@
|
||||
// If you're looking for help, please consider joining our slack channel:
|
||||
// http://asyncdisplaykit.org/slack
|
||||
// http://asyncdisplaykit.org/slack (we'll be updating the name to Texture soon)
|
||||
|
||||
// The more information you include, the faster we can help you out!
|
||||
// Please include: a sample project or screenshots, code snippets
|
||||
// AsyncDisplayKit version, and/or backtraces for any crashes (> bt all).
|
||||
// Texture version, and/or backtraces for any crashes (> bt all).
|
||||
// Please delete these lines before posting. Thanks!
|
||||
30
ASDK-Licenses/LICENSE
Normal file
30
ASDK-Licenses/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
||||
BSD License
|
||||
|
||||
For AsyncDisplayKit software
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// ASListKitTestAdapterDataSource.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <IGListKit/IGListKit.h>
|
||||
|
||||
@interface ASListKitTestAdapterDataSource : NSObject <IGListAdapterDataSource>
|
||||
|
||||
// array of numbers which is then passed to -[IGListTestSection setItems:]
|
||||
@property (nonatomic, strong) NSArray <NSNumber *> *objects;
|
||||
|
||||
@end
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// ASListKitTestAdapterDataSource.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASListKitTestAdapterDataSource.h"
|
||||
#import "ASListTestSection.h"
|
||||
|
||||
@implementation ASListKitTestAdapterDataSource
|
||||
|
||||
- (NSArray *)objectsForListAdapter:(IGListAdapter *)listAdapter
|
||||
{
|
||||
return self.objects;
|
||||
}
|
||||
|
||||
- (IGListSectionController <IGListSectionType> *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object
|
||||
{
|
||||
ASListTestSection *section = [[ASListTestSection alloc] init];
|
||||
return section;
|
||||
}
|
||||
|
||||
- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ASListTestCellNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASListTestCellNode : ASCellNode
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ASListTestCellNode.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASListTestCellNode.h"
|
||||
|
||||
@implementation ASListTestCellNode
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// ASListTestObject.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <IGListKit/IGListKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASListTestObject : NSObject <IGListDiffable, NSCopying>
|
||||
|
||||
- (instancetype)initWithKey:(id <NSCopying>)key value:(id)value;
|
||||
|
||||
@property (nonatomic, strong, readonly) id key;
|
||||
@property (nonatomic, strong) id value;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// ASListTestSection.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <IGListKit/IGListKit.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASListTestSection : IGListSectionController <IGListSectionType, ASSectionController>
|
||||
|
||||
@property (nonatomic) NSInteger itemCount;
|
||||
|
||||
@property (nonatomic) NSInteger selectedItemIndex;
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ASListTestSupplementaryNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASListTestSupplementaryNode : ASCellNode
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ASListTestSupplementaryNode.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASListTestSupplementaryNode.h"
|
||||
|
||||
@implementation ASListTestSupplementaryNode
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// ASListTestSupplementarySource.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 12/25/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <IGListKit/IGListKit.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASListTestSupplementarySource : NSObject <IGListSupplementaryViewSource, ASSupplementaryNodeSource>
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSArray<NSString *> *supportedElementKinds;
|
||||
|
||||
@property (nonatomic, weak) id<IGListCollectionContext> collectionContext;
|
||||
|
||||
@property (nonatomic, weak) IGListSectionController<IGListSectionType> *sectionController;
|
||||
|
||||
@end
|
||||
@@ -1,9 +0,0 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
|
||||
platform :ios, '8.0'
|
||||
target 'ASDKListKitTests' do
|
||||
pod 'AsyncDisplayKit/IGListKit', :path => '..'
|
||||
pod 'IGListKit', :git => 'https://github.com/Instagram/IGListKit', :commit => 'e9e09d7'
|
||||
pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master'
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
BUCK
1
BUCK
@@ -88,7 +88,6 @@ asyncdisplaykit_library(
|
||||
for name in ['AsyncDisplayKit', 'AsyncDisplayKit-PINRemoteImage']:
|
||||
asyncdisplaykit_library(
|
||||
name = name,
|
||||
additional_preprocessor_flags = ['-DPIN_REMOTE_IMAGE=1'],
|
||||
deps = [
|
||||
'//Pods/PINRemoteImage:PINRemoteImage-PINCache',
|
||||
],
|
||||
|
||||
55
CHANGELOG.md
Normal file
55
CHANGELOG.md
Normal file
@@ -0,0 +1,55 @@
|
||||
## master
|
||||
|
||||
* 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)
|
||||
- Overhaul logging and add activity tracing support. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
- Fix a crash where scrolling a table view after entering editing mode could lead to bad internal states in the table. [Huy Nguyen](https://github.com/nguyenhuy) [#416](https://github.com/TextureGroup/Texture/pull/416/)
|
||||
- Fix a crash in collection view that occurs if batch updates are performed while scrolling [Huy Nguyen](https://github.com/nguyenhuy) [#378](https://github.com/TextureGroup/Texture/issues/378)
|
||||
- Some improvements in ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#407](https://github.com/TextureGroup/Texture/pull/407)
|
||||
|
||||
##2.3.4
|
||||
- [Yoga] Rewrite YOGA_TREE_CONTIGUOUS mode with improved behavior and cleaner integration [Scott Goodson](https://github.com/appleguy)
|
||||
- [ASTraitCollection] Convert ASPrimitiveTraitCollection from lock to atomic. [Scott Goodson](https://github.com/appleguy)
|
||||
- Add a synchronous mode to ASCollectionNode, for colletion view data source debugging. [Hannah Troisi](https://github.com/hannahmbanana)
|
||||
- [ASDisplayNode+Layout] Add check for orphaned nodes after layout transition to clean up. #336. [Scott Goodson](https://github.com/appleguy)
|
||||
- Fixed an issue where GIFs with placeholders never had their placeholders uncover the GIF. [Garrett Moon](https://github.com/garrettmoon)
|
||||
- [Yoga] Implement ASYogaLayoutSpec, a simplified integration strategy for Yoga-powered layout calculation. [Scott Goodson](https://github.com/appleguy)
|
||||
- Fixed an issue where calls to setNeedsDisplay and setNeedsLayout would stop working on loaded nodes. [Garrett Moon](https://github.com/garrettmoon)
|
||||
- Migrated unit tests to OCMock 3.4 (from 2.2) and improved the multiplex image node tests. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
- Fix CollectionNode double-load issue. This should significantly improve performance in cases where a collection node has content immediately available on first layout i.e. not fetched from the network. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
- Overhaul layout flattening algorithm [Huy Nguyen](https://github.com/nguyenhuy) [#395](https://github.com/TextureGroup/Texture/pull/395).
|
||||
|
||||
## 2.3.3
|
||||
- [ASTextKitFontSizeAdjuster] Replace use of NSAttributedString's boundingRectWithSize:options:context: with NSLayoutManager's boundingRectForGlyphRange:inTextContainer: [Ricky Cancro](https://github.com/rcancro)
|
||||
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. [Adlai Holler](https://github.com/Adlai-Holler) [#49](https://github.com/TextureGroup/Texture/pull/49)
|
||||
- Fix `__has_include` check in ASLog.h [Philipp Smorygo](Philipp.Smorygo@jetbrains.com)
|
||||
- Fix potential deadlock in ASControlNode [Garrett Moon](https://github.com/garrettmoon)
|
||||
- [Yoga Beta] Improvements to the experimental support for Yoga layout [Scott Goodson](appleguy)
|
||||
- Make cell node `indexPath` and `supplementaryElementKind` atomic so you can read from any thread. [Adlai-Holler](https://github.com/Adlai-Holler) [#49](https://github.com/TextureGroup/Texture/pull/74)
|
||||
- Update the rasterization API and un-deprecate it. [Adlai Holler](https://github.com/Adlai-Holler)[#82](https://github.com/TextureGroup/Texture/pull/49)
|
||||
- Simplified & optimized hashing code. [Adlai Holler](https://github.com/Adlai-Holler) [#86](https://github.com/TextureGroup/Texture/pull/86)
|
||||
- Improve the performance & safety of ASDisplayNode subnodes. [Adlai Holler](https://github.com/Adlai-Holler) [#223](https://github.com/TextureGroup/Texture/pull/223)
|
||||
- Move more properties from ASTableView, ASCollectionView to their respective node classes. [Adlai Holler](https://github.com/Adlai-Holler)
|
||||
- Remove finalLayoutElement [Michael Schneider](https://github.com/maicki)[#96](https://github.com/TextureGroup/Texture/pull/96)
|
||||
- Add ASPageTable - A map table for fast retrieval of objects within a certain page [Huy Nguyen](https://github.com/nguyenhuy)
|
||||
- Add new public `-supernodes`, `-supernodesIncludingSelf`, and `-supernodeOfClass:includingSelf:` methods. [Adlai Holler](https://github.com/Adlai-Holler)[#246](https://github.com/TextureGroup/Texture/pull/246)
|
||||
- Improve our handling supernode traversal to avoid loading layers and fix assertion failures you might hit in debug. [Adlai Holler](https://github.com/Adlai-Holler)[#246](https://github.com/TextureGroup/Texture/pull/246)
|
||||
- [ASDisplayNode] Pass drawParameter in rendering context callbacks [Michael Schneider](https://github.com/maicki)[#248](https://github.com/TextureGroup/Texture/pull/248)
|
||||
- [ASTextNode] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [Michael Schneider](https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
|
||||
- [ASDisplayNode] Remove instance:-drawRect:withParameters:isCancelled:isRasterizing: (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
|
||||
- [ASTextNode] Add an experimental new implementation. See `+[ASTextNode setExperimentOptions:]`. [Adlai Holler](https://github.com/Adlai-Holler)[#259](https://github.com/TextureGroup/Texture/pull/259)
|
||||
- [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260)
|
||||
- [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262)
|
||||
- [Layout] Extract layout implementation code into it's own subcategories [Michael Schneider](https://github.com/maicki)[#272](https://github.com/TextureGroup/Texture/pull/272)
|
||||
- [Fix] Fix a potential crash when cell nodes that need layout are deleted during the same runloop. [Adlai Holler](https://github.com/Adlai-Holler) [#279](https://github.com/TextureGroup/Texture/pull/279)
|
||||
- [Batch fetching] Add ASBatchFetchingDelegate that takes scroll velocity and remaining time into account [Huy Nguyen](https://github.com/nguyenhuy) [#281](https://github.com/TextureGroup/Texture/pull/281)
|
||||
- [Fix] Fix a major regression in our image node contents caching. [Adlai Holler](https://github.com/Adlai-Holler) [#287](https://github.com/TextureGroup/Texture/pull/287)
|
||||
- [Fix] Fixed a bug where ASVideoNodeDelegate error reporting callback would crash an app because of not responding to selector. [Sergey Petrachkov](https://github.com/Petrachkov) [#291](https://github.com/TextureGroup/Texture/issues/291)
|
||||
- [IGListKit] Add IGListKit headers to public section of Xcode project [Michael Schneider](https://github.com/maicki)[#286](https://github.com/TextureGroup/Texture/pull/286)
|
||||
- [Layout] Ensure -layout and -layoutDidFinish are called only if a node is loaded. [Huy Nguyen](https://github.com/nguyenhuy) [#285](https://github.com/TextureGroup/Texture/pull/285)
|
||||
- [Layout Debugger] Small changes needed for the coming layout debugger [Huy Nguyen](https://github.com/nguyenhuy) [#337](https://github.com/TextureGroup/Texture/pull/337)
|
||||
4
CI/build.sh
Executable file
4
CI/build.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
./build.sh all
|
||||
5
CI/exclude-from-build.json
Normal file
5
CI/exclude-from-build.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
"^plans/",
|
||||
"^docs/",
|
||||
"^CI/exclude-from-build.json$"
|
||||
]
|
||||
306
CONTRIBUTING.md
306
CONTRIBUTING.md
@@ -1,76 +1,270 @@
|
||||
# Contributing to AsyncDisplayKit
|
||||
We want to make contributing to this project as easy and transparent as
|
||||
possible, and actively welcome your pull requests. If you run into problems,
|
||||
please open an issue on GitHub.
|
||||
# Contribution Guidelines
|
||||
Texture is the most actively developed open source project at [Pinterest](https://www.pinterest.com) and is used extensively to deliver a world-class Pinner experience. This document is setup to ensure contributing to the project is easy and transparent.
|
||||
|
||||
# Questions
|
||||
|
||||
If you are having difficulties using Texture or have a question about usage, please ask a
|
||||
question in our [Slack channel](http://texturegroup.org/slack.html). **Please do not ask for help by filing Github issues.**
|
||||
|
||||
# Core Team
|
||||
|
||||
The Core Team reviews and helps iterate on the RFC Issues from the community at large and acting as the approver of these RFCs. Team members help drive Texture forward in a coherent direction consistent with the goal of creating the best possible general purpose UI framework for iOS. Team members will have merge permissions on the repository.
|
||||
|
||||
Members of the core team are appointed based on their technical expertise and proven contribution to the community. The current core team members are:
|
||||
|
||||
- Adlai Holler ([@](http://github.com/adlai-holler)[adlai-holler](http://github.com/adlai-holler))
|
||||
- Garrett Moon [(](https://github.com/garrettmoon)[@](https://github.com/garrettmoon)[garrett](https://github.com/garrettmoon)[moon](https://github.com/garrettmoon))
|
||||
- Huy Nguyen ([@](https://github.com/nguyenhuy)[nguyenhuy](https://github.com/nguyenhuy))
|
||||
- Michael Schneider ([@maicki](https://github.com/maicki))
|
||||
- Scott Goodson ([@appleguy](https://github.com/appleguy))
|
||||
|
||||
Over time, exceptional community members from a much more diverse background will be appointed based on their record of community involvement and contributions.
|
||||
|
||||
# Issues
|
||||
|
||||
Think you've found a bug or have a new feature to suggest? [Let us know](https://github.com/TextureGroup/Texture/issues/new)!
|
||||
|
||||
## Where to Find Known Issues
|
||||
|
||||
We use [GitHub Issues](https://github.com/texturegroup/texture/issues) for all bug tracking. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist.
|
||||
|
||||
## Reporting New Issues
|
||||
1. Update to the most recent master release if possible. We may have already fixed your bug.
|
||||
2. Search for similar [issues](https://github.com/TextureGroup/Texture/issues). It's possible somebody has encountered this bug already.
|
||||
3. Provide a reduced test case that specifically shows the problem. This demo should be fully operational with the exception of the bug you want to demonstrate. The more pared down, the better. If it is not possible to produce a test case, please make sure you provide very specific steps to reproduce the error. If we cannot reproduce it, and there is no other evidence to help us figure out a fix we will close the issue.
|
||||
4. Your issue will be verified. The provided example will be tested for correctness. The Texture team will work with you until your issue can be verified.
|
||||
5. Keep up to date with feedback from the Texture team on your issue. Your issue may be closed if it becomes stale.
|
||||
6. If possible, submit a Pull Request with a failing test. Better yet, take a stab at fixing the bug yourself if you can!
|
||||
|
||||
The more information you provide, the easier it is for us to validate that there is a bug and the faster we'll be able to take action.
|
||||
|
||||
## Issues Triaging
|
||||
- You might be requested to provide a reproduction or extra information. In that case, the issue will be labeled as **N****eeds More Info**. If we did not get any response after fourteen days, we will ping you to remind you about it. We might close the issue if we do not hear from you after two weeks since the original notice.
|
||||
- If you submit a feature request as a GitHub issue, you will be invited to follow the instructions in this section otherwise the issue will be closed.
|
||||
- Issues that become inactive will be labelled accordingly to inform the original poster and Texture contributors that the issue should be closed since the issue is no longer actionable. The issue can be reopened at a later time if needed, e.g. becomes actionable again.
|
||||
- If possible, issues will be labeled to indicate the status or priority. For example, labels may have a prefix for Status: X, or Priority: X. Statuses may include: In Progress, On Hold. Priorities may include: P1, P2 or P3 (high to low priority).
|
||||
# Requesting a Feature
|
||||
|
||||
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend filing an RFC [issue](https://github.com/TextureGroup/Texture/issues/new) outlined below. This lets us reach an agreement on your proposal before you put significant effort into implementing it.
|
||||
|
||||
If you're only fixing a bug, it's fine to submit a pull request right away, but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
|
||||
|
||||
## RFC Issue process
|
||||
1. Texture has an RFC process for feature requests. To begin the discussion either gather feedback on the Texture Slack channel or draft an Texture RFC as a Github Issue.
|
||||
2. The title of the GitHub RFC Issue should have `[RFC]` as prefix: `[RFC] Add new cool feature`
|
||||
3. Provide a clear and detailed explanation of the feature you want and why it's important to add. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on library for Texture.
|
||||
4. If the feature is complex, consider writing an Texture RFC issue. Even if you can’t implement the feature yourself, consider writing an RFC issue. If we do end up accepting the feature, the issue provides the needed documentation for contributors to develop the feature according the specification accepted by the core team. We will tag accepted RFC issues with **Needs Volunteer**.
|
||||
5. After discussing the feature you may choose to attempt a Pull Request. If you're at all able, start writing some code. We always have more work to do than time to do it. If you can write some code then that will speed the process along.
|
||||
|
||||
In short, if you have an idea that would be nice to have, create an issue on the [TextureGroup](https://github.com/TextureGroup/Texture)[/](https://github.com/TextureGroup/Texture)[Texture](https://github.com/TextureGroup/Texture) repo. If you have a question about requesting a feature, start a discussion in our [Slack channel](http://texturegroup.org/slack.html).
|
||||
|
||||
# Our Development Process
|
||||
|
||||
All work on Texture happens directly on [GitHub](https://github.com/TextureGroup/Texture). Both core team members and external contributors send pull requests which go through the same review process.
|
||||
|
||||
## `master` is under active development
|
||||
|
||||
We will do our best to keep master in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to communicate these changes and version appropriately so you can lock into a specific version if need be.
|
||||
|
||||
## Pull Requests
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. Make sure your code lints.
|
||||
6. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
7. Make sure that any new files conform to the correct file header style below
|
||||
|
||||
#### ASDK files header style
|
||||
If you send a pull request, please do it against the master branch. We maintain stable branches for major versions separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable major version.
|
||||
|
||||
Before submitting a pull request, please make sure the following is done…
|
||||
|
||||
1. Search GitHub for an open or closed [pull request](https://github.com/TextureGroup/Texture/pulls?utf8=✓&q=is%3Apr) that relates to your submission. You don't want to duplicate effort.
|
||||
2. Fork the [repo](https://github.com/TextureGroup/Texture) and create your branch from master:
|
||||
git checkout -b my-fix-branch master
|
||||
3. Create your patch, including appropriate test cases. Please follow our Coding Guidelines.
|
||||
4. Please make sure every commit message are meaningful so it that makes it clearer for people to review and easier to understand your intention
|
||||
5. Ensure tests pass CI on GitHub for your Pull Request.
|
||||
6. If you haven't already, sign the CLA.
|
||||
|
||||
**Copyright Notice for files**
|
||||
Copy and paste this to the top of your new file(s):
|
||||
```objc
|
||||
//
|
||||
// ASDisplayNode.mm
|
||||
// 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
|
||||
//
|
||||
```
|
||||
|
||||
If you’ve modified an existing file, change the header to:
|
||||
```objc
|
||||
//
|
||||
// ASPagerFlowLayout.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 2/12/16.
|
||||
// ASDisplayNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
```
|
||||
|
||||
#### Example project files header style
|
||||
# Semantic Versioning
|
||||
|
||||
Texture follows semantic versioning. We release patch versions for bug fixes, minor versions for new features (and rarely, clear and easy to fix breaking changes), and major versions for any major breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance.
|
||||
|
||||
We tag every pull request with a label marking whether the change should go in the next patch, minor, or a major version. We release new versions pretty regularly usually every few weeks. The version will be a patch or minor version if it does not contain major new features or breaking API changes and a major version if it does.
|
||||
|
||||
# Coding Guidelines
|
||||
- Indent using 2 spaces (this conserves space in print and makes line wrapping less likely). Never indent with tabs. Be sure to set this preference in Xcode.
|
||||
- Do your best to keep it around 120 characters line length
|
||||
- End files with a newline.
|
||||
- Don’t leave trailing whitespace.
|
||||
- Space after `@property` declarations and conform to ordering of attributes
|
||||
```objc
|
||||
@property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking;
|
||||
@property (nonatomic, readwrite, strong, nullable) NSAttributedString *attributedText;
|
||||
```
|
||||
//
|
||||
// PhotoCellNode.m
|
||||
// Sample
|
||||
//
|
||||
// Created by Levi McCallum on 2/12/16.
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
- In method signatures, there should be a space after the method type (-/+ symbol). There should be a space between the method segments (matching Apple's style). Always include a keyword and be descriptive with the word before the argument which describes the argument.
|
||||
```objc
|
||||
@interface SomeClass
|
||||
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
|
||||
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
|
||||
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
|
||||
- (id)viewWithTag:(NSInteger)tag;
|
||||
@end
|
||||
```
|
||||
- Internal methods should be prefixed with a `_`
|
||||
```objc
|
||||
- (void)_internalMethodWithParameter:(id)param;
|
||||
```
|
||||
- Method braces and other braces (if/else/switch/while etc.) always open on the same line as the statement but close on a new line.
|
||||
```objc
|
||||
if (foo == bar) {
|
||||
//..
|
||||
} else {
|
||||
//..
|
||||
}
|
||||
```
|
||||
- Method, `@interface` , and `@implementation` brackets on the following line
|
||||
```objc
|
||||
@implementation SomeClass
|
||||
- (void)someMethod
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
@end
|
||||
```
|
||||
- Function brackets on the same line
|
||||
```objc
|
||||
static void someFunction() {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
- Operator goes with the variable name
|
||||
```objc
|
||||
NSAttributedString *attributedText = self.textNode.attributedText;
|
||||
```
|
||||
- Locking
|
||||
- Add a `_locked_` in front of the method name that needs to be called with a lock held
|
||||
```objc
|
||||
- (void)_locked_needsToBeCalledWithLock {}
|
||||
```
|
||||
- Locking safety:
|
||||
- It is fine for a `_locked_` method to call other `_locked_` methods.
|
||||
- On the other hand, the following should not be done:
|
||||
- Calling normal, unlocked methods inside a `_locked_` method
|
||||
- Calling subclass hooks that are meant to be overridden by developers inside a `_locked_` method.
|
||||
- Subclass hooks
|
||||
- that are meant to be overwritten by users should not be called with a lock held.
|
||||
- that are used internally the same conventions as above apply.
|
||||
- There are multiple ways to acquire a lock:
|
||||
1. Explicitly call `.lock()` and `.unlock()` :
|
||||
```objc
|
||||
- (void)setContentSpacing:(CGFloat)contentSpacing
|
||||
{
|
||||
__instanceLock__.lock();
|
||||
BOOL needsUpdate = (contentSpacing != _contentSpacing);
|
||||
if (needsUpdate) {
|
||||
_contentSpacing = contentSpacing;
|
||||
}
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (needsUpdate) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)contentSpacing
|
||||
{
|
||||
CGFloat contentSpacing = 0.0;
|
||||
__instanceLock__.lock();
|
||||
contentSpacing = _contentSpacing;
|
||||
__instanceLock__.unlock();
|
||||
return contentSpacing;
|
||||
}
|
||||
```
|
||||
2. Create an `ASDN::MutexLocker` :
|
||||
```objc
|
||||
- (void)setContentSpacing:(CGFloat)contentSpacing
|
||||
{
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (contentSpacing == _contentSpacing) {
|
||||
return;
|
||||
}
|
||||
|
||||
_contentSpacing = contentSpacing;
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CGFloat)contentSpacing
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _contentSpacing;
|
||||
}
|
||||
```
|
||||
- Nullability
|
||||
- The adoption of annotations is straightforward. The standard we adopt is using the `NS_ASSUME_NONNULL_BEGIN` and `NS_ASSUME_NONNULL_END` on all headers. Then indicate nullability for the pointers that can be so.
|
||||
- There is mostly no sense using nullability annotations outside of interface declarations.
|
||||
```objc
|
||||
// Properties
|
||||
@property(nonatomic, strong, nullable) NSNumber *status
|
||||
|
||||
// Methods
|
||||
- (nullable NSNumber *)doSomethingWithString:(nullable NSString *)str;
|
||||
|
||||
// Functions
|
||||
NSString * _Nullable ASStringWithQuotesIfMultiword(NSString * _Nullable string);
|
||||
|
||||
// Typedefs
|
||||
typedef void (^RemoteCallback)(id _Nullable result, NSError * _Nullable error);
|
||||
|
||||
// Block as parameter
|
||||
- (void)reloadDataWithCompletion:(void (^ _Nullable)())completion;
|
||||
|
||||
// Block as parameter with parameter and return value
|
||||
- (void)convertObject:(id _Nonnull (^ _Nullable)(id _Nullable input))handler;
|
||||
|
||||
// More complex pointer types
|
||||
- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map;
|
||||
```
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
to do this once to work on any of Facebook's open source projects.
|
||||
# Contributor License Agreement (CLA)
|
||||
|
||||
Complete your CLA here: <https://code.facebook.com/cla>
|
||||
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be accepted, the CLA must be signed.
|
||||
|
||||
## Issues
|
||||
We use GitHub issues to track public bugs. Please ensure your description is
|
||||
clear and has sufficient instructions to be able to reproduce the issue.
|
||||
Complete your CLA [here](https://cla-assistant.io/TextureGroup/Texture)
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
disclosure of security bugs. In those cases, please go through the process
|
||||
outlined on that page and do not file a public issue.
|
||||
# License
|
||||
|
||||
## Getting Help
|
||||
We use Slack for real-time debugging, community updates, and general talk about ASDK. Signup at http://asdk-slack-auto-invite.herokuapp.com or email AsyncDisplayKit(at)gmail.com to get an invite.
|
||||
|
||||
## Coding Style
|
||||
* 2 spaces for indentation rather than tabs
|
||||
|
||||
## License
|
||||
By contributing to AsyncDisplayKit, you agree that your contributions will be
|
||||
licensed under its BSD license.
|
||||
By contributing to Texture, you agree that your contributions will be licensed under its Apache 2 license.
|
||||
|
||||
4
Cartfile
4
Cartfile
@@ -1,2 +1,2 @@
|
||||
github "pinterest/PINRemoteImage" "3.0.0-beta.6"
|
||||
github "pinterest/PINCache" "3.0.1-beta.2"
|
||||
github "pinterest/PINRemoteImage" "3.0.0-beta.11"
|
||||
github "pinterest/PINCache" "3.0.1-beta.5"
|
||||
|
||||
101
Dangerfile
Normal file
101
Dangerfile
Normal file
@@ -0,0 +1,101 @@
|
||||
source_pattern = /(\.m|\.mm|\.h)$/
|
||||
|
||||
# Sometimes it's a README fix, or something like that - which isn't relevant for
|
||||
# including in a project's CHANGELOG for example
|
||||
declared_trivial = github.pr_title.include? "#trivial"
|
||||
has_changes_in_source_directory = !git.modified_files.grep(/Source/).empty?
|
||||
|
||||
modified_source_files = git.modified_files.grep(source_pattern)
|
||||
has_modified_source_files = !modified_source_files.empty?
|
||||
added_source_files = git.added_files.grep(source_pattern)
|
||||
has_added_source_files = !added_source_files.empty?
|
||||
|
||||
# Make it more obvious that a PR is a work in progress and shouldn't be merged yet
|
||||
warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]"
|
||||
|
||||
# Warn when there is a big PR
|
||||
warn("This is a big PR, please consider splitting it up to ease code review.") if git.lines_of_code > 500
|
||||
|
||||
# Changelog entries are required for changes to source files.
|
||||
no_changelog_entry = !git.modified_files.include?("CHANGELOG.md")
|
||||
if has_changes_in_source_directory && no_changelog_entry && !declared_trivial
|
||||
warn("Any source code changes should have an entry in CHANGELOG.md or have #trivial in their title.")
|
||||
end
|
||||
|
||||
def full_license(partial_license, filename)
|
||||
license_header = <<-HEREDOC
|
||||
//
|
||||
HEREDOC
|
||||
license_header += "// " + filename + "\n"
|
||||
license_header += <<-HEREDOC
|
||||
// Texture
|
||||
//
|
||||
HEREDOC
|
||||
license_header += partial_license
|
||||
return license_header
|
||||
end
|
||||
|
||||
def check_file_header(files_to_check, licenses)
|
||||
repo_name = github.pr_json["base"]["repo"]["full_name"]
|
||||
pr_number = github.pr_json["number"]
|
||||
files = github.api.pull_request_files(repo_name, pr_number)
|
||||
files.each do |file|
|
||||
if files_to_check.include?(file["filename"])
|
||||
filename = File.basename(file["filename"])
|
||||
|
||||
data = ""
|
||||
contents = github.api.get file["contents_url"]
|
||||
open(contents["download_url"]) { |io|
|
||||
data += io.read
|
||||
}
|
||||
|
||||
correct_license = false
|
||||
licenses.each do |license|
|
||||
license_header = full_license(license, filename)
|
||||
if data.start_with?(license_header)
|
||||
correct_license = true
|
||||
end
|
||||
end
|
||||
|
||||
if correct_license == false
|
||||
warn ("Please ensure license is correct for #{filename}: \n```\n" + full_license(licenses[0], filename) + "```")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure new files have proper header
|
||||
new_source_license_header = <<-HEREDOC
|
||||
// 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
|
||||
//
|
||||
HEREDOC
|
||||
|
||||
if has_added_source_files
|
||||
check_file_header(added_source_files, [new_source_license_header])
|
||||
end
|
||||
|
||||
# Ensure modified files have proper header
|
||||
modified_source_license_header = <<-HEREDOC
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
HEREDOC
|
||||
|
||||
if has_modified_source_files
|
||||
check_file_header(modified_source_files, [modified_source_license_header, new_source_license_header])
|
||||
end
|
||||
3
Gemfile
3
Gemfile
@@ -1,3 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'slather'
|
||||
gem 'danger'
|
||||
gem 'danger-slack'
|
||||
BIN
InstrumentsTemplates/SystemTrace.tracetemplate
Normal file
BIN
InstrumentsTemplates/SystemTrace.tracetemplate
Normal file
Binary file not shown.
203
LICENSE
203
LICENSE
@@ -1,30 +1,187 @@
|
||||
BSD License
|
||||
The Texture project was created by Pinterest as a continuation, under a
|
||||
different name and license, of the AsyncDisplayKit codebase originally developed
|
||||
by Facebook. AsyncDisplayKit was originally released by Facebook under a BSD
|
||||
license and additional patent grant. Those BSD and patent licenses govern use
|
||||
of code in Texture contributed prior to 4/13/2017 (the original AsyncDisplayKit
|
||||
code), and copies of the licenses are included in the /ASDK-Licenses directory
|
||||
of this source tree for reference.
|
||||
|
||||
For AsyncDisplayKit software
|
||||
All code contributed to Texture after 4/13/2017 is released by Pinterest under
|
||||
the Apache License, Version 2.0.
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
1. Definitions.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
6
Podfile
6
Podfile
@@ -3,12 +3,13 @@ source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '8.0'
|
||||
|
||||
target :'AsyncDisplayKitTests' do
|
||||
pod 'OCMock', '~> 2.2'
|
||||
pod 'OCMock', '~> 3.4'
|
||||
pod 'FBSnapshotTestCase/Core', '~> 2.1'
|
||||
pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master'
|
||||
|
||||
# Only for buck build
|
||||
pod 'PINRemoteImage', '3.0.0-beta.7'
|
||||
pod 'PINRemoteImage', '3.0.0-beta.10'
|
||||
end
|
||||
|
||||
#TODO CocoaPods plugin instead?
|
||||
post_install do |installer|
|
||||
@@ -28,4 +29,3 @@ target :'AsyncDisplayKitTests' do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
36
README.md
36
README.md
@@ -1,45 +1,47 @@
|
||||

|
||||
## Coming from AsyncDisplayKit? Learn more [here](https://medium.com/@Pinterest_Engineering/introducing-texture-a-new-home-for-asyncdisplaykit-e7c003308f50)
|
||||
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||

|
||||
|
||||
[](http://AsyncDisplayKit.org)
|
||||
[](http://AsyncDisplayKit.org)
|
||||
[](http://cocoapods.org/pods/Texture)
|
||||
[](http://cocoapods.org/pods/Texture)
|
||||
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
[](http://texturegroup.org)
|
||||
[](http://texturegroup.org)
|
||||
|
||||
[](http://cocoapods.org/pods/Texture)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE)
|
||||
[](https://github.com/texturegroup/texture/blob/master/LICENSE)
|
||||
|
||||
## Installation
|
||||
|
||||
ASDK is available via CocoaPods or Carthage. See our [Installation](http://asyncdisplaykit.org/docs/installation.html) guide for instructions.
|
||||
Texture is available via CocoaPods or Carthage. See our [Installation](http://texturegroup.org/docs/installation.html) guide for instructions.
|
||||
|
||||
## Performance Gains
|
||||
|
||||
AsyncDisplayKit's basic unit is the `node`. An ASDisplayNode is an abstraction over `UIView`, which in turn is an abstraction over `CALayer`. Unlike views, which can only be used on the main thread, nodes are thread-safe: you can instantiate and configure entire hierarchies of them in parallel on background threads.
|
||||
Texture's basic unit is the `node`. An ASDisplayNode is an abstraction over `UIView`, which in turn is an abstraction over `CALayer`. Unlike views, which can only be used on the main thread, nodes are thread-safe: you can instantiate and configure entire hierarchies of them in parallel on background threads.
|
||||
|
||||
To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That's 16 milliseconds to execute all layout and drawing code! And because of system overhead, your code usually has less than ten milliseconds to run before it causes a frame drop.
|
||||
|
||||
AsyncDisplayKit lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction.
|
||||
Texture lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction.
|
||||
|
||||
## Advanced Developer Features
|
||||
|
||||
As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating ASDK.
|
||||
As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating Texture.
|
||||
|
||||
## Learn More
|
||||
|
||||
* Read the our [Getting Started](http://asyncdisplaykit.org/docs/getting-started.html) guide
|
||||
* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples)
|
||||
* Browse the [API reference](http://asyncdisplaykit.org/appledocs.html)
|
||||
* Read the our [Getting Started](http://texturegroup.org/docs/getting-started.html) guide
|
||||
* Get the [sample projects](https://github.com/texturegroup/texture/tree/master/examples)
|
||||
* Browse the [API reference](http://texturegroup.org/appledocs.html)
|
||||
|
||||
## Getting Help
|
||||
|
||||
We use Slack for real-time debugging, community updates, and general talk about ASDK. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email AsyncDisplayKit(at)gmail.com to get an invite.
|
||||
We use Slack for real-time debugging, community updates, and general talk about Texture. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email textureframework@gmail.com to get an invite.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome any contributions. See the [CONTRIBUTING](https://github.com/facebook/AsyncDisplayKit/blob/master/CONTRIBUTING.md) file for how to get involved.
|
||||
We welcome any contributions. See the [CONTRIBUTING](https://github.com/texturegroup/texture/blob/master/CONTRIBUTING.md) file for how to get involved.
|
||||
|
||||
## License
|
||||
|
||||
AsyncDisplayKit is BSD-licensed. We also provide an additional patent grant. The files in the `/examples` directory are licensed under a separate license as specified in each file; documentation is licensed CC-BY-4.0.
|
||||
The Texture project was created by Pinterest as a continuation, under a different name and license, of the AsyncDisplayKit codebase originally developed by Facebook. AsyncDisplayKit was originally released by Facebook under a BSD license and additional patent grant. Those BSD and patent licenses govern use of code in Texture contributed prior to 4/13/2017 (the original AsyncDisplayKit code), and copies of the licenses are included in the root directory of this source tree for reference. All code contributed to Texture after 4/13/2017 is released by Pinterest under an Apache 2.0 license.
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//
|
||||
// ASBlockTypes.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Adlai Holler on 1/25/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <Foundation/Foundation.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASButtonNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASControlNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASButtonNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASButtonNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASCellNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -15,6 +22,7 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASCellNode, ASTextNode;
|
||||
@protocol ASRangeManagingNode;
|
||||
|
||||
typedef NSUInteger ASCellNodeAnimation;
|
||||
|
||||
@@ -82,7 +90,7 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
||||
* @return The supplementary element kind, or @c nil if this node does not represent a supplementary element.
|
||||
*/
|
||||
//TODO change this to be a generic "kind" or "elementKind" that exposes `nil` for row kind
|
||||
@property (nonatomic, copy, readonly, nullable) NSString *supplementaryElementKind;
|
||||
@property (atomic, copy, readonly, nullable) NSString *supplementaryElementKind;
|
||||
|
||||
/*
|
||||
* The layout attributes currently assigned to this node, if any.
|
||||
@@ -108,10 +116,24 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
||||
/**
|
||||
* The current index path of this cell node, or @c nil if this node is
|
||||
* not a valid item inside a table node or collection node.
|
||||
*
|
||||
* @note This property must be accessed on the main thread.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSIndexPath *indexPath;
|
||||
@property (atomic, readonly, nullable) NSIndexPath *indexPath;
|
||||
|
||||
/**
|
||||
* BETA: API is under development. We will attempt to provide an easy migration pathway for any changes.
|
||||
*
|
||||
* The view-model currently assigned to this node, if any.
|
||||
*
|
||||
* This property may be set off the main thread, but this method will never be invoked concurrently on the
|
||||
*/
|
||||
@property (atomic, nullable) id viewModel;
|
||||
|
||||
/**
|
||||
* Asks the node whether it can be updated to the given view model.
|
||||
*
|
||||
* The default implementation returns YES if the class matches that of the current view-model.
|
||||
*/
|
||||
- (BOOL)canUpdateToViewModel:(id)viewModel;
|
||||
|
||||
/**
|
||||
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller
|
||||
@@ -121,10 +143,9 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
||||
|
||||
|
||||
/**
|
||||
* The owning node (ASCollectionNode/ASTableNode) of this cell node, or @c nil if this node is
|
||||
* not a valid item inside a table node or collection node or if those nodes are nil.
|
||||
* The table- or collection-node that this cell is a member of, if any.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly, nullable) ASDisplayNode *owningNode;
|
||||
@property (atomic, weak, readonly, nullable) id<ASRangeManagingNode> owningNode;
|
||||
|
||||
/*
|
||||
* ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASCellNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -39,12 +46,6 @@
|
||||
ASDisplayNode *_viewControllerNode;
|
||||
UIViewController *_viewController;
|
||||
BOOL _suspendInteractionDelegate;
|
||||
|
||||
struct {
|
||||
unsigned int isTableNode:1;
|
||||
unsigned int isCollectionNode:1;
|
||||
} _owningNodeType;
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -115,13 +116,6 @@
|
||||
_viewControllerNode.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)layoutDidFinish
|
||||
{
|
||||
[super layoutDidFinish];
|
||||
|
||||
_viewControllerNode.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)_rootNodeDidInvalidateSize
|
||||
{
|
||||
if (_interactionDelegate != nil) {
|
||||
@@ -160,19 +154,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOwningNode:(ASDisplayNode *)owningNode
|
||||
{
|
||||
_owningNode = owningNode;
|
||||
|
||||
memset(&_owningNodeType, 0, sizeof(_owningNodeType));
|
||||
|
||||
if ([owningNode isKindOfClass:[ASTableNode class]]) {
|
||||
_owningNodeType.isTableNode = 1;
|
||||
} else if ([owningNode isKindOfClass:[ASCollectionNode class]]) {
|
||||
_owningNodeType.isCollectionNode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)__setSelectedFromUIKit:(BOOL)selected;
|
||||
{
|
||||
if (selected != _selected) {
|
||||
@@ -191,17 +172,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
- (BOOL)canUpdateToViewModel:(id)viewModel
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_owningNodeType.isTableNode) {
|
||||
return [(ASTableNode *)self.owningNode indexPathForNode:self];
|
||||
} else if (_owningNodeType.isCollectionNode) {
|
||||
return [(ASCollectionNode *)self.owningNode indexPathForNode:self];
|
||||
return [self.viewModel class] == [viewModel class];
|
||||
}
|
||||
|
||||
return nil;
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.owningNode indexPathForNode:self];
|
||||
}
|
||||
|
||||
- (UIViewController *)viewController
|
||||
@@ -339,13 +317,13 @@
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"collectionNode" : ASObjectDescriptionMakeTiny(owningNode) }];
|
||||
[result addObject:@{ @"collectionNode" : owningNode }];
|
||||
} else if ([owningNode isKindOfClass:[ASTableNode class]]) {
|
||||
NSIndexPath *ip = [(ASTableNode *)owningNode indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"tableNode" : ASObjectDescriptionMakeTiny(owningNode) }];
|
||||
[result addObject:@{ @"tableNode" : owningNode }];
|
||||
|
||||
} else if ([scrollView isKindOfClass:[ASCollectionView class]]) {
|
||||
NSIndexPath *ip = [(ASCollectionView *)scrollView indexPathForNode:self];
|
||||
@@ -370,6 +348,11 @@
|
||||
return self.collectionElement.supplementaryElementKind;
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
//
|
||||
// ASCollectionNode+Beta.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate, ASBatchFetchingDelegate;
|
||||
@class ASElementMap;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -24,8 +32,36 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (strong, nonatomic, nullable) Class collectionViewClass;
|
||||
|
||||
/**
|
||||
* The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread.
|
||||
*/
|
||||
@property (strong, nonatomic, readonly) ASElementMap *visibleElements;
|
||||
|
||||
@property (strong, readonly, nullable) id<ASCollectionLayoutDelegate> layoutDelegate;
|
||||
|
||||
@property (nonatomic, weak) id<ASBatchFetchingDelegate> batchFetchingDelegate;
|
||||
|
||||
/**
|
||||
* When this mode is enabled, ASCollectionView matches the timing of UICollectionView as closely as possible,
|
||||
* ensuring that all reload and edit operations are performed on the main thread as blocking calls.
|
||||
*
|
||||
* This mode is useful for applications that are debugging issues with their collection view implementation.
|
||||
* In particular, some applications do not properly conform to the API requirement of UICollectionView, and these
|
||||
* applications may experience difficulties with ASCollectionView. Providing this mode allows for developers to
|
||||
* work towards resolving technical debt in their collection view data source, while ramping up asynchronous
|
||||
* collection layout.
|
||||
*
|
||||
* NOTE: Because this mode results in expensive operations like cell layout being performed on the main thread,
|
||||
* it should be used as a tool to resolve data source conformance issues with Apple collection view API.
|
||||
*
|
||||
* @default defaults to NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
|
||||
|
||||
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
|
||||
|
||||
- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASCollectionNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Scott Goodson on 9/5/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -17,6 +22,7 @@
|
||||
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
#import <AsyncDisplayKit/ASRangeManagingNode.h>
|
||||
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
||||
@protocol ASCollectionDelegate;
|
||||
@@ -29,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used
|
||||
* as a subnode of another node, and provide room for many (great) features and improvements later on.
|
||||
*/
|
||||
@interface ASCollectionNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol>
|
||||
@interface ASCollectionNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol, ASRangeManagingNode>
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@@ -79,6 +85,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -collectionNode:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the collection node will be flipped.
|
||||
* If the value of this property is YES, the first cell node will be at the bottom of the collection node (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO.
|
||||
@@ -98,6 +111,24 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelection;
|
||||
|
||||
/**
|
||||
* The layout used to organize the node's items.
|
||||
*
|
||||
* @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the node’s items.
|
||||
*/
|
||||
@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout;
|
||||
|
||||
/**
|
||||
* Optional introspection object for the collection node's layout.
|
||||
*
|
||||
* @discussion Since supplementary and decoration nodes are controlled by the layout, this object
|
||||
* is used as a bridge to provide information to the internal data controller about the existence of these views and
|
||||
* their associated index paths. For collections using `UICollectionViewFlowLayout`, a default inspector
|
||||
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
|
||||
* collection layout subclasses will need to provide their own implementation of an inspector object for their
|
||||
* supplementary elements to be compatible with `ASCollectionNode`'s supplementary node support.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in full mode.
|
||||
@@ -158,6 +189,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
* Determines collection node's current scroll direction. Supports 2-axis collection nodes.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollDirection;
|
||||
|
||||
/**
|
||||
* Determines collection node's scrollable directions.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollableDirections;
|
||||
|
||||
#pragma mark - Editing
|
||||
|
||||
/**
|
||||
@@ -375,6 +420,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieves the view-model for the item at the given index path, if any.
|
||||
*
|
||||
* @param indexPath The index path of the requested item.
|
||||
*
|
||||
* @return The view-model for the given item, or @c nil if no item exists at the specified path or no view-model was provided.
|
||||
*
|
||||
* @warning This API is beta and subject to change. We'll try to provide an easy migration path.
|
||||
*/
|
||||
- (nullable id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieve the index path for the item with the given node.
|
||||
*
|
||||
@@ -460,6 +516,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode;
|
||||
|
||||
/**
|
||||
* --BETA--
|
||||
* Asks the data source for a view-model for the item at the given index path.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param indexPath The index path of the item.
|
||||
*
|
||||
* @return An object that contains all the data for this item.
|
||||
*/
|
||||
- (nullable id)collectionNode:(ASCollectionNode *)collectionNode viewModelForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Similar to -collectionNode:nodeForItemAtIndexPath:
|
||||
* This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented.
|
||||
@@ -735,7 +802,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done:
|
||||
* 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement
|
||||
collectionNode:constrainedSizeForItemAtIndexPath:.
|
||||
* 4b. Custom collection layouts. Set .view.layoutInspector and have it implement
|
||||
* 4b. Custom collection layouts. Set .layoutInspector and have it implement
|
||||
collectionView:constrainedSizeForNodeAtIndexPath:.
|
||||
*
|
||||
* For an example of using this method with all steps above (including a custom layout, 4b.),
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
//
|
||||
// ASCollectionNode.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Scott Goodson on 9/5/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASCollectionInternal.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayout.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
|
||||
#import <AsyncDisplayKit/ASSectionContext.h>
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
@@ -34,10 +42,14 @@
|
||||
@interface _ASCollectionPendingState : NSObject
|
||||
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
|
||||
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
|
||||
@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout;
|
||||
@property (nonatomic, assign) ASLayoutRangeMode rangeMode;
|
||||
@property (nonatomic, assign) BOOL allowsSelection; // default is YES
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelection; // default is NO
|
||||
@property (nonatomic, assign) BOOL inverted; //default is NO
|
||||
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
@property (weak, nonatomic) id <ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
@end
|
||||
|
||||
@implementation _ASCollectionPendingState
|
||||
@@ -102,6 +114,7 @@
|
||||
{
|
||||
ASDN::RecursiveMutex _environmentStateLock;
|
||||
Class _collectionViewClass;
|
||||
id<ASBatchFetchingDelegate> _batchFetchingDelegate;
|
||||
}
|
||||
@property (nonatomic) _ASCollectionPendingState *pendingState;
|
||||
@end
|
||||
@@ -134,13 +147,21 @@
|
||||
return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
|
||||
{
|
||||
return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
|
||||
{
|
||||
if (self = [super init]) {
|
||||
// Must call the setter here to make sure pendingState is created and the layout is configured.
|
||||
[self setCollectionViewLayout:layout];
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[self setViewBlock:^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(strongSelf)];
|
||||
return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
@@ -157,16 +178,20 @@
|
||||
|
||||
if (_pendingState) {
|
||||
_ASCollectionPendingState *pendingState = _pendingState;
|
||||
self.pendingState = nil;
|
||||
view.asyncDelegate = pendingState.delegate;
|
||||
view.asyncDataSource = pendingState.dataSource;
|
||||
view.inverted = pendingState.inverted;
|
||||
view.allowsSelection = pendingState.allowsSelection;
|
||||
view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
|
||||
view.usesSynchronousDataLoading = pendingState.usesSynchronousDataLoading;
|
||||
view.layoutInspector = pendingState.layoutInspector;
|
||||
self.pendingState = nil;
|
||||
|
||||
if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
|
||||
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
|
||||
}
|
||||
|
||||
// Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,18 +206,21 @@
|
||||
[self.rangeController clearContents];
|
||||
}
|
||||
|
||||
- (void)didExitPreloadState
|
||||
{
|
||||
[super didExitPreloadState];
|
||||
[self.rangeController clearPreloadedData];
|
||||
}
|
||||
|
||||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
|
||||
{
|
||||
[super interfaceStateDidChange:newState fromState:oldState];
|
||||
[ASRangeController layoutDebugOverlayIfNeeded];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
#if ASRangeControllerLoggingEnabled
|
||||
- (void)didEnterVisibleState
|
||||
{
|
||||
@@ -207,6 +235,12 @@
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)didExitPreloadState
|
||||
{
|
||||
[super didExitPreloadState];
|
||||
[self.rangeController clearPreloadedData];
|
||||
}
|
||||
|
||||
#pragma mark Setter / Getter
|
||||
|
||||
// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection
|
||||
@@ -250,6 +284,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLayoutInspector:(id<ASCollectionViewLayoutInspecting>)layoutInspector
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
_pendingState.layoutInspector = layoutInspector;
|
||||
} else {
|
||||
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
|
||||
self.view.layoutInspector = layoutInspector;
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASCollectionViewLayoutInspecting>)layoutInspector
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
return _pendingState.layoutInspector;
|
||||
} else {
|
||||
return self.view.layoutInspector;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
_pendingState.leadingScreensForBatching = leadingScreensForBatching;
|
||||
} else {
|
||||
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
|
||||
self.view.leadingScreensForBatching = leadingScreensForBatching;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)leadingScreensForBatching
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
return _pendingState.leadingScreensForBatching;
|
||||
} else {
|
||||
return self.view.leadingScreensForBatching;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id <ASCollectionDelegate>)delegate
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
@@ -341,6 +413,80 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
[self _configureCollectionViewLayout:layout];
|
||||
_pendingState.collectionViewLayout = layout;
|
||||
} else {
|
||||
[self _configureCollectionViewLayout:layout];
|
||||
self.view.collectionViewLayout = layout;
|
||||
}
|
||||
}
|
||||
|
||||
- (UICollectionViewLayout *)collectionViewLayout
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
return _pendingState.collectionViewLayout;
|
||||
} else {
|
||||
return self.view.collectionViewLayout;
|
||||
}
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
{
|
||||
return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone;
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollableDirections
|
||||
{
|
||||
return [self isNodeLoaded] ? self.view.scrollableDirections : ASScrollDirectionNone;
|
||||
}
|
||||
|
||||
- (ASElementMap *)visibleElements
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
// TODO Own the data controller when view is not yet loaded
|
||||
return self.dataController.visibleMap;
|
||||
}
|
||||
|
||||
- (id<ASCollectionLayoutDelegate>)layoutDelegate
|
||||
{
|
||||
UICollectionViewLayout *layout = self.collectionViewLayout;
|
||||
if ([layout isKindOfClass:[ASCollectionLayout class]]) {
|
||||
return ((ASCollectionLayout *)layout).layoutDelegate;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setBatchFetchingDelegate:(id<ASBatchFetchingDelegate>)batchFetchingDelegate
|
||||
{
|
||||
_batchFetchingDelegate = batchFetchingDelegate;
|
||||
}
|
||||
|
||||
- (id<ASBatchFetchingDelegate>)batchFetchingDelegate
|
||||
{
|
||||
return _batchFetchingDelegate;
|
||||
}
|
||||
|
||||
- (BOOL)usesSynchronousDataLoading
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
return _pendingState.usesSynchronousDataLoading;
|
||||
} else {
|
||||
return self.view.usesSynchronousDataLoading;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUsesSynchronousDataLoading:(BOOL)usesSynchronousDataLoading
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
_pendingState.usesSynchronousDataLoading = usesSynchronousDataLoading;
|
||||
} else {
|
||||
self.view.usesSynchronousDataLoading = usesSynchronousDataLoading;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Range Tuning
|
||||
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
||||
@@ -447,6 +593,12 @@
|
||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node;
|
||||
}
|
||||
|
||||
- (id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
[self reloadDataInitiallyIfNeeded];
|
||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].viewModel;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
||||
{
|
||||
return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement];
|
||||
@@ -533,9 +685,17 @@
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (self.nodeLoaded) {
|
||||
[self.view reloadDataWithCompletion:completion];
|
||||
if (!self.nodeLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self performBatchUpdates:^{
|
||||
[self.view.changeSet reloadData];
|
||||
} completion:^(BOOL finished){
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reloadData
|
||||
@@ -543,14 +703,19 @@
|
||||
[self reloadDataWithCompletion:nil];
|
||||
}
|
||||
|
||||
- (void)relayoutItems
|
||||
{
|
||||
[self.view relayoutItems];
|
||||
}
|
||||
|
||||
- (void)reloadDataImmediately
|
||||
{
|
||||
[self.view reloadDataImmediately];
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self reloadData];
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
}
|
||||
|
||||
- (void)relayoutItems
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (self.nodeLoaded) {
|
||||
[self.view relayoutItems];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)beginUpdates
|
||||
@@ -663,6 +828,16 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock)
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
if ([layout isKindOfClass:[ASCollectionLayout class]]) {
|
||||
ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout;
|
||||
collectionLayout.collectionNode = self;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASCollectionView.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -31,13 +38,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
* Asynchronous UICollectionView with Intelligent Preloading capabilities.
|
||||
*
|
||||
* @discussion ASCollectionView is a true subclass of UICollectionView, meaning it is pointer-compatible
|
||||
* with code that currently uses UICollectionView.
|
||||
*
|
||||
* The main difference is that asyncDataSource expects -nodeForItemAtIndexPath, an ASCellNode, and
|
||||
* the sizeForItemAtIndexPath: method is eliminated (as are the performance problems caused by it).
|
||||
* This is made possible because ASCellNodes can calculate their own size, and preload ahead of time.
|
||||
*
|
||||
* @note ASCollectionNode is strongly recommended over ASCollectionView. This class exists for adoption convenience.
|
||||
*/
|
||||
@interface ASCollectionView : UICollectionView
|
||||
@@ -49,25 +49,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
|
||||
/**
|
||||
* Optional introspection object for the collection view's layout.
|
||||
*
|
||||
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
|
||||
* is used as a bridge to provide information to the internal data controller about the existence of these views and
|
||||
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
|
||||
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
|
||||
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
|
||||
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
|
||||
/**
|
||||
* Retrieves the node for the item at the given index path.
|
||||
*
|
||||
@@ -110,28 +91,47 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionView (Deprecated)
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Optional introspection object for the collection view's layout.
|
||||
*
|
||||
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
|
||||
* is used as a bridge to provide information to the internal data controller about the existence of these views and
|
||||
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
|
||||
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
|
||||
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
|
||||
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Determines collection view's current scroll direction. Supports 2-axis collection views.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollDirection;
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollDirection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Determines collection view's scrollable directions.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollableDirections;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionView (Deprecated)
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollableDirections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Forces the .contentInset to be UIEdgeInsetsZero.
|
||||
@@ -268,14 +268,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* the main thread.
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadDataWithCompletion:(nullable void (^)())completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
- (void)reloadDataWithCompletion:(nullable void (^)())completion AS_UNAVAILABLE("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadData ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
- (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes.
|
||||
@@ -283,7 +283,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @warning This method is substantially more expensive than UICollectionView's version and will block the main thread
|
||||
* while all the cells load.
|
||||
*/
|
||||
- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreCommitted instead.");
|
||||
- (void)reloadDataImmediately AS_UNAVAILABLE("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Triggers a relayout of all nodes.
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
//
|
||||
// ASCollectionView.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASBatchFetching.h>
|
||||
#import <AsyncDisplayKit/ASDelegateProxy.h>
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionInternal.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayout.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewLayoutController.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
@@ -24,16 +33,16 @@
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASRangeController.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
#import <AsyncDisplayKit/_ASCollectionViewCell.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewLayoutFacilitatorProtocol.h>
|
||||
#import <AsyncDisplayKit/_ASCollectionReusableView.h>
|
||||
#import <AsyncDisplayKit/ASPagerNode.h>
|
||||
#import <AsyncDisplayKit/ASSectionContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
/**
|
||||
* A macro to get self.collectionNode and assign it to a local variable, or return
|
||||
@@ -72,7 +81,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
#pragma mark -
|
||||
#pragma mark ASCollectionView.
|
||||
|
||||
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate, ASCALayerExtendedDelegate, UICollectionViewDelegateFlowLayout> {
|
||||
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASCALayerExtendedDelegate, UICollectionViewDelegateFlowLayout> {
|
||||
ASCollectionViewProxy *_proxyDataSource;
|
||||
ASCollectionViewProxy *_proxyDelegate;
|
||||
|
||||
@@ -81,9 +90,11 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
ASCollectionViewLayoutController *_layoutController;
|
||||
id<ASCollectionViewLayoutInspecting> _defaultLayoutInspector;
|
||||
__weak id<ASCollectionViewLayoutInspecting> _layoutInspector;
|
||||
NSMutableSet *_cellsForVisibilityUpdates;
|
||||
NSMutableSet *_cellsForLayoutUpdates;
|
||||
NSHashTable<_ASCollectionViewCell *> *_cellsForVisibilityUpdates;
|
||||
NSHashTable<ASCellNode *> *_cellsForLayoutUpdates;
|
||||
id<ASCollectionViewLayoutFacilitatorProtocol> _layoutFacilitator;
|
||||
CGFloat _leadingScreensForBatching;
|
||||
BOOL _inverted;
|
||||
|
||||
NSUInteger _superBatchUpdateCount;
|
||||
BOOL _isDeallocating;
|
||||
@@ -94,6 +105,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
NSMutableSet *_registeredSupplementaryKinds;
|
||||
|
||||
// CountedSet because UIKit may display the same element in multiple cells e.g. during animations.
|
||||
NSCountedSet<ASCollectionElement *> *_visibleElements;
|
||||
|
||||
CGPoint _deceleratingVelocity;
|
||||
|
||||
BOOL _zeroContentInsets;
|
||||
@@ -131,11 +145,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
*/
|
||||
BOOL _hasEverCheckedForBatchFetchingDueToUpdate;
|
||||
|
||||
/**
|
||||
* The change set that we're currently building, if any.
|
||||
*/
|
||||
_ASHierarchyChangeSet *_changeSet;
|
||||
|
||||
/**
|
||||
* Counter used to keep track of nested batch updates.
|
||||
*/
|
||||
@@ -146,6 +155,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
unsigned int scrollViewWillBeginDragging:1;
|
||||
unsigned int scrollViewDidEndDragging:1;
|
||||
unsigned int scrollViewWillEndDragging:1;
|
||||
unsigned int scrollViewDidEndDecelerating:1;
|
||||
unsigned int collectionViewWillDisplayNodeForItem:1;
|
||||
unsigned int collectionViewWillDisplayNodeForItemDeprecated:1;
|
||||
unsigned int collectionViewDidEndDisplayingNodeForItem:1;
|
||||
@@ -192,6 +202,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
unsigned int collectionViewNumberOfItemsInSection:1;
|
||||
unsigned int collectionNodeNodeForItem:1;
|
||||
unsigned int collectionNodeNodeBlockForItem:1;
|
||||
unsigned int viewModelForItem:1;
|
||||
unsigned int collectionNodeNodeForSupplementaryElement:1;
|
||||
unsigned int collectionNodeNodeBlockForSupplementaryElement:1;
|
||||
unsigned int collectionNodeSupplementaryElementKindsInSection:1;
|
||||
@@ -213,6 +224,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
unsigned int didChangeCollectionViewDataSource:1;
|
||||
unsigned int didChangeCollectionViewDelegate:1;
|
||||
} _layoutInspectorFlags;
|
||||
|
||||
BOOL _hasDataControllerLayoutDelegate;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -239,20 +252,20 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
||||
{
|
||||
return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil eventLog:nil];
|
||||
return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil owningNode:nil eventLog:nil];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator eventLog:(ASEventLog *)eventLog
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator owningNode:(ASCollectionNode *)owningNode eventLog:(ASEventLog *)eventLog
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
|
||||
return nil;
|
||||
|
||||
// Disable UICollectionView prefetching.
|
||||
// Disable UICollectionView prefetching. Use super, because self disables this method.
|
||||
// Experiments done by Instagram show that this option being YES (default)
|
||||
// when unused causes a significant hit to scroll performance.
|
||||
// https://github.com/Instagram/IGListKit/issues/318
|
||||
if (AS_AT_LEAST_IOS10) {
|
||||
self.prefetchingEnabled = NO;
|
||||
super.prefetchingEnabled = NO;
|
||||
}
|
||||
|
||||
_layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self];
|
||||
@@ -262,9 +275,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
_rangeController.delegate = self;
|
||||
_rangeController.layoutController = _layoutController;
|
||||
|
||||
_dataController = [[ASDataController alloc] initWithDataSource:self eventLog:eventLog];
|
||||
_dataController = [[ASDataController alloc] initWithDataSource:self node:owningNode eventLog:eventLog];
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.environmentDelegate = self;
|
||||
|
||||
_batchContext = [[ASBatchContext alloc] init];
|
||||
|
||||
@@ -281,9 +293,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
|
||||
|
||||
_registeredSupplementaryKinds = [NSMutableSet set];
|
||||
_visibleElements = [[NSCountedSet alloc] init];
|
||||
|
||||
_cellsForVisibilityUpdates = [NSMutableSet set];
|
||||
_cellsForLayoutUpdates = [NSMutableSet set];
|
||||
_cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
_cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
[self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier];
|
||||
@@ -292,6 +305,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
_retainedLayer = self.layer;
|
||||
}
|
||||
|
||||
[self _configureCollectionViewLayout:layout];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -313,31 +328,23 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
#pragma mark -
|
||||
#pragma mark Overrides.
|
||||
|
||||
- (void)reloadDataWithCompletion:(void (^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (! _dataController.initialReloadDataHasBeenCalled) {
|
||||
// If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working.
|
||||
_superIsPendingDataLoad = YES;
|
||||
[super reloadData];
|
||||
}
|
||||
|
||||
void (^batchUpdatesCompletion)(BOOL);
|
||||
if (completion) {
|
||||
batchUpdatesCompletion = ^(BOOL) {
|
||||
completion();
|
||||
};
|
||||
}
|
||||
|
||||
[self performBatchUpdates:^{
|
||||
[_changeSet reloadData];
|
||||
} completion:batchUpdatesCompletion];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is not available to be called by the public i.e.
|
||||
* it should only be called by UICollectionView itself. UICollectionView
|
||||
* does this e.g. during the first layout pass, or if you call -numberOfSections
|
||||
* before its content is loaded.
|
||||
*/
|
||||
- (void)reloadData
|
||||
{
|
||||
[self reloadDataWithCompletion:nil];
|
||||
[super reloadData];
|
||||
|
||||
// UICollectionView calls -reloadData during first layoutSubviews and when the data source changes.
|
||||
// This fires off the first load of cell nodes.
|
||||
if (_asyncDataSource != nil && !self.dataController.initialReloadDataHasBeenCalled) {
|
||||
[self performBatchUpdates:^{
|
||||
[_changeSet reloadData];
|
||||
} completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated
|
||||
@@ -347,13 +354,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadDataImmediately
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self reloadData];
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
}
|
||||
|
||||
- (void)relayoutItems
|
||||
{
|
||||
[_dataController relayoutAllNodes];
|
||||
@@ -431,6 +431,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
|
||||
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
|
||||
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
|
||||
_asyncDataSourceFlags.viewModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:viewModelForItemAtIndexPath:)];
|
||||
|
||||
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
|
||||
if (_asyncDataSourceFlags.interop) {
|
||||
@@ -482,6 +483,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
_asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)];
|
||||
_asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
|
||||
_asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)];
|
||||
_asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)];
|
||||
_asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)];
|
||||
_asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)];
|
||||
@@ -532,10 +534,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout
|
||||
- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super setCollectionViewLayout:collectionViewLayout];
|
||||
|
||||
[self _configureCollectionViewLayout:collectionViewLayout];
|
||||
|
||||
// Trigger recreation of layout inspector with new collection view layout
|
||||
if (_layoutInspector != nil) {
|
||||
_layoutInspector = nil;
|
||||
@@ -697,7 +702,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
if (indexPath.item == NSNotFound) {
|
||||
return indexPath;
|
||||
} else {
|
||||
return [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
|
||||
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,8 +749,26 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return visibleNodes;
|
||||
}
|
||||
|
||||
- (BOOL)usesSynchronousDataLoading
|
||||
{
|
||||
return self.dataController.usesSynchronousDataLoading;
|
||||
}
|
||||
|
||||
- (void)setUsesSynchronousDataLoading:(BOOL)usesSynchronousDataLoading
|
||||
{
|
||||
self.dataController.usesSynchronousDataLoading = usesSynchronousDataLoading;
|
||||
}
|
||||
|
||||
#pragma mark Internal
|
||||
|
||||
- (void)_configureCollectionViewLayout:(nonnull UICollectionViewLayout *)layout
|
||||
{
|
||||
_hasDataControllerLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)];
|
||||
if (_hasDataControllerLayoutDelegate) {
|
||||
_dataController.layoutDelegate = (id<ASDataControllerLayoutDelegate>)layout;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Performing nested batch updates with super (e.g. resizing a cell node & updating collection view during same frame)
|
||||
can cause super to throw data integrity exceptions because it checks the data source counts before
|
||||
@@ -777,6 +800,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
if (_batchUpdateCount == 0) {
|
||||
_changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]];
|
||||
_changeSet.rootActivity = as_activity_create("Perform async collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
_changeSet.submitActivity = as_activity_create("Submit changes for collection update", _changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
}
|
||||
_batchUpdateCount++;
|
||||
}
|
||||
@@ -794,6 +819,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
if (_batchUpdateCount == 0) {
|
||||
_ASHierarchyChangeSet *changeSet = _changeSet;
|
||||
|
||||
// Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop
|
||||
_changeSet = nil;
|
||||
changeSet.animated = animated;
|
||||
@@ -805,9 +831,14 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self beginUpdates];
|
||||
as_activity_scope(_changeSet.rootActivity);
|
||||
{
|
||||
// Only include client code in the submit activity, the rest just lives in the root activity.
|
||||
as_activity_scope(_changeSet.submitActivity);
|
||||
if (updates) {
|
||||
updates();
|
||||
}
|
||||
}
|
||||
[self endUpdatesAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
@@ -821,7 +852,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
{
|
||||
ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration");
|
||||
[_registeredSupplementaryKinds addObject:elementKind];
|
||||
[self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier];
|
||||
[self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier];
|
||||
}
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections
|
||||
@@ -921,7 +952,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASCellNode *cell = [self nodeForItemAtIndexPath:indexPath];
|
||||
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
|
||||
if (element == nil) {
|
||||
ASDisplayNodeAssert(NO, @"Unexpected nil element for collectionView:layout:sizeForItemAtIndexPath: %@, %@, %@", self, collectionViewLayout, indexPath);
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
ASCellNode *cell = element.node;
|
||||
if (cell.shouldUseUIKitCell) {
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
|
||||
CGSize size = [(id)_asyncDelegate collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath];
|
||||
@@ -929,38 +966,46 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return size;
|
||||
}
|
||||
}
|
||||
ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
|
||||
return [self sizeForElement:e];
|
||||
|
||||
return [self sizeForElement:element];
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
|
||||
ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader
|
||||
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionHeader
|
||||
atIndexPath:indexPath];
|
||||
if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
|
||||
if (element == nil) {
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
|
||||
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForHeaderInSection:section];
|
||||
}
|
||||
}
|
||||
ASCollectionElement *e = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
|
||||
return [self sizeForElement:e];
|
||||
|
||||
return [self sizeForElement:element];
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout referenceSizeForFooterInSection:(NSInteger)section
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
|
||||
ASCellNode *cell = [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter
|
||||
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionFooter
|
||||
atIndexPath:indexPath];
|
||||
if (cell.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
|
||||
if (element == nil) {
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
if (element.node.shouldUseUIKitCell && _asyncDelegateFlags.interop) {
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) {
|
||||
return [(id)_asyncDelegate collectionView:collectionView layout:layout referenceSizeForFooterInSection:section];
|
||||
}
|
||||
}
|
||||
ASCollectionElement *e = [_dataController.visibleMap supplementaryElementOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath];
|
||||
return [self sizeForElement:e];
|
||||
|
||||
return [self sizeForElement:element];
|
||||
}
|
||||
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
@@ -970,7 +1015,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
|
||||
UICollectionReusableView *view = nil;
|
||||
ASCellNode *node = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath].node;
|
||||
ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath];
|
||||
ASCellNode *node = element.node;
|
||||
|
||||
BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopViewForSupplementaryElement && (_asyncDataSourceFlags.interopAlwaysDequeue || node.shouldUseUIKitCell);
|
||||
if (shouldDequeueExternally) {
|
||||
@@ -981,6 +1027,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
if (_ASCollectionReusableView *reusableView = ASDynamicCastStrict(view, _ASCollectionReusableView)) {
|
||||
reusableView.element = element;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
[_rangeController configureContentView:view forCellNode:node];
|
||||
}
|
||||
@@ -991,7 +1041,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UICollectionViewCell *cell = nil;
|
||||
ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
|
||||
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
|
||||
ASCellNode *node = element.node;
|
||||
|
||||
BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && node.shouldUseUIKitCell);
|
||||
if (shouldDequeueExternally) {
|
||||
@@ -1000,30 +1051,38 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(node != nil, @"Cell node should exist. indexPath = %@, collectionDataSource = %@", indexPath, self);
|
||||
ASDisplayNodeAssert(element != nil, @"Element should exist. indexPath = %@, collectionDataSource = %@", indexPath, self);
|
||||
|
||||
if (_ASCollectionViewCell *asCell = ASDynamicCast(cell, _ASCollectionViewCell)) {
|
||||
asCell.node = node;
|
||||
if (_ASCollectionViewCell *asCell = ASDynamicCastStrict(cell, _ASCollectionViewCell)) {
|
||||
asCell.element = element;
|
||||
[_rangeController configureContentView:cell.contentView forCellNode:node];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (_asyncDelegateFlags.interopWillDisplayCell) {
|
||||
[(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
|
||||
[(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView willDisplayCell:rawCell forItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
// Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass:
|
||||
// We must exit early here, because only _ASCollectionViewCell implements the -node accessor method.
|
||||
if ([cell class] != [_ASCollectionViewCell class]) {
|
||||
_ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell);
|
||||
if (cell == nil) {
|
||||
[_rangeController setNeedsUpdate];
|
||||
return;
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = [cell node];
|
||||
ASCollectionElement *element = cell.element;
|
||||
if (element) {
|
||||
ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element);
|
||||
[_visibleElements addObject:element];
|
||||
} else {
|
||||
ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", rawCell, self, indexPath);
|
||||
return;
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = element.node;
|
||||
cellNode.scrollView = collectionView;
|
||||
|
||||
// Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be to
|
||||
@@ -1054,26 +1113,33 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
[_rangeController setNeedsUpdate];
|
||||
|
||||
if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) {
|
||||
if ([cell consumesCellNodeVisibilityEvents]) {
|
||||
[_cellsForVisibilityUpdates addObject:cell];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(_ASCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (_asyncDelegateFlags.interopDidEndDisplayingCell) {
|
||||
[(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
|
||||
[(id <ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView didEndDisplayingCell:rawCell forItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
// Since _ASCollectionViewCell is not available for subclassing, this is faster than isKindOfClass:
|
||||
// We must exit early here, because only _ASCollectionViewCell implements the -node accessor method.
|
||||
if ([cell class] != [_ASCollectionViewCell class]) {
|
||||
_ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell);
|
||||
if (cell == nil) {
|
||||
[_rangeController setNeedsUpdate];
|
||||
return;
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = [cell node];
|
||||
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
|
||||
// Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element.
|
||||
ASCollectionElement *element = cell.element;
|
||||
if (element) {
|
||||
[_visibleElements removeObject:element];
|
||||
} else {
|
||||
ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", rawCell, self, indexPath);
|
||||
return;
|
||||
}
|
||||
|
||||
ASCellNode *cellNode = element.node;
|
||||
|
||||
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
|
||||
if (ASCollectionNode *collectionNode = self.collectionNode) {
|
||||
@@ -1094,21 +1160,58 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
cell.layoutAttributes = nil;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)rawView forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
_ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView);
|
||||
if (view == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASCollectionElement *element = view.element;
|
||||
if (element) {
|
||||
ASDisplayNodeAssertTrue([_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath] == view.element);
|
||||
[_visibleElements addObject:element];
|
||||
} else {
|
||||
ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplaySupplementaryView: %@, %@, %@", rawView, self, indexPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Under iOS 10+, cells may be removed/re-added to the collection view without
|
||||
// receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g.
|
||||
// if the user is scrolling back and forth across a small set of items.
|
||||
// In this case, we have to fetch the layout attributes manually.
|
||||
// This may be possible under iOS < 10 but it has not been observed yet.
|
||||
if (view.layoutAttributes == nil) {
|
||||
view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath];
|
||||
}
|
||||
|
||||
if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) {
|
||||
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
|
||||
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
|
||||
ASCellNode *node = element.node;
|
||||
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
|
||||
[_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)rawView forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
_ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView);
|
||||
if (view == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element.
|
||||
ASCollectionElement *element = view.element;
|
||||
if (element) {
|
||||
[_visibleElements removeObject:element];
|
||||
} else {
|
||||
ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingSupplementaryView: %@, %@, %@", rawView, self, indexPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) {
|
||||
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
|
||||
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
|
||||
ASCellNode *node = element.node;
|
||||
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
|
||||
[_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node];
|
||||
}
|
||||
@@ -1290,11 +1393,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
[self _checkForBatchFetching];
|
||||
}
|
||||
|
||||
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
|
||||
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
|
||||
// Only nodes that respond to the selector are added to _cellsForVisibilityUpdates
|
||||
[[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView];
|
||||
}
|
||||
if (_asyncDelegateFlags.scrollViewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
@@ -1311,7 +1412,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
if (targetContentOffset != NULL) {
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset];
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity];
|
||||
}
|
||||
|
||||
if (_asyncDelegateFlags.scrollViewWillEndDragging) {
|
||||
@@ -1319,12 +1420,19 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
||||
{
|
||||
_deceleratingVelocity = CGPointZero;
|
||||
|
||||
if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
|
||||
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
||||
{
|
||||
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
|
||||
[[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
|
||||
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView];
|
||||
}
|
||||
if (_asyncDelegateFlags.scrollViewWillBeginDragging) {
|
||||
[_asyncDelegate scrollViewWillBeginDragging:scrollView];
|
||||
@@ -1333,10 +1441,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
||||
{
|
||||
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
|
||||
[[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
|
||||
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging inScrollView:scrollView];
|
||||
}
|
||||
if (_asyncDelegateFlags.scrollViewDidEndDragging) {
|
||||
[_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
|
||||
@@ -1345,6 +1451,26 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
#pragma mark - Scroll Direction.
|
||||
|
||||
- (BOOL)inverted
|
||||
{
|
||||
return _inverted;
|
||||
}
|
||||
|
||||
- (void)setInverted:(BOOL)inverted
|
||||
{
|
||||
_inverted = inverted;
|
||||
}
|
||||
|
||||
- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching
|
||||
{
|
||||
_leadingScreensForBatching = leadingScreensForBatching;
|
||||
}
|
||||
|
||||
- (CGFloat)leadingScreensForBatching
|
||||
{
|
||||
return _leadingScreensForBatching;
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
{
|
||||
CGPoint scrollVelocity;
|
||||
@@ -1456,6 +1582,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASBatchFetchingDelegate>)batchFetchingDelegate{
|
||||
return self.collectionNode.batchFetchingDelegate;
|
||||
}
|
||||
|
||||
- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes
|
||||
{
|
||||
// Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided
|
||||
@@ -1477,22 +1607,24 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return;
|
||||
}
|
||||
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset];
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero];
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset
|
||||
- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity
|
||||
{
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset)) {
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset, velocity)) {
|
||||
[self _beginBatchFetching];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
as_activity_create_for_scope("Batch fetch for collection node");
|
||||
[_batchContext beginBatchFetching];
|
||||
if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
|
||||
as_log_debug(ASCollectionLog(), "Beginning batch fetch for %@ with context %@", collectionNode, _batchContext);
|
||||
[_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
} else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) {
|
||||
@@ -1505,9 +1637,18 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - ASDataControllerSource
|
||||
|
||||
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (!_asyncDataSourceFlags.viewModelForItem) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
|
||||
return [_asyncDataSource collectionNode:collectionNode viewModelForItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASCellNodeBlock block = nil;
|
||||
@@ -1544,7 +1685,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return cell;
|
||||
};
|
||||
} else {
|
||||
ASDisplayNodeFailAssert(@"ASCollection could not get a node block for row at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the <ASCollectionDataSourceInterop> protocol!", indexPath, cell, block);
|
||||
ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the <ASCollectionDataSourceInterop> protocol!", indexPath, cell, block);
|
||||
block = ^{
|
||||
return [[ASCellNode alloc] init];
|
||||
};
|
||||
@@ -1560,7 +1701,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
if (node.interactionDelegate == nil) {
|
||||
node.interactionDelegate = strongSelf;
|
||||
}
|
||||
if (_inverted) {
|
||||
if (strongSelf.inverted) {
|
||||
node.transform = CATransform3DMakeScale(1, -1, 1) ;
|
||||
}
|
||||
return node;
|
||||
@@ -1568,11 +1709,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return block;
|
||||
}
|
||||
|
||||
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
|
||||
{
|
||||
if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) {
|
||||
@@ -1605,15 +1741,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size
|
||||
{
|
||||
NSIndexPath *indexPath = [self indexPathForNode:element.node];
|
||||
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
|
||||
CGRect rect = attributes.frame;
|
||||
return CGSizeEqualToSizeWithIn(rect.size, size, FLT_EPSILON);
|
||||
|
||||
if (indexPath == nil) {
|
||||
ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented.");
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id<ASTraitEnvironment>)dataControllerEnvironment
|
||||
{
|
||||
return self.collectionNode;
|
||||
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
|
||||
return CGSizeEqualToSizeWithIn(attributes.size, size, FLT_EPSILON);
|
||||
}
|
||||
|
||||
#pragma mark - ASDataControllerSource optional methods
|
||||
@@ -1667,6 +1800,11 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) {
|
||||
@@ -1736,37 +1874,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
|
||||
- (NSHashTable<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
if (CGRectIsEmpty(self.bounds)) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
ASElementMap *map = _dataController.visibleMap;
|
||||
NSMutableArray<ASCollectionElement *> *result = [NSMutableArray array];
|
||||
|
||||
// Visible items
|
||||
for (NSIndexPath *indexPath in self.indexPathsForVisibleItems) {
|
||||
ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath];
|
||||
if (element != nil) {
|
||||
[result addObject:element];
|
||||
} else {
|
||||
ASDisplayNodeFailAssert(@"Couldn't find 'visible' item at index path %@ in map %@", indexPath, map);
|
||||
}
|
||||
}
|
||||
|
||||
// Visible supplementary elements
|
||||
for (NSString *kind in map.supplementaryElementKinds) {
|
||||
for (NSIndexPath *indexPath in [self asdk_indexPathsForVisibleSupplementaryElementsOfKind:kind]) {
|
||||
ASCollectionElement *element = [map supplementaryElementOfKind:kind atIndexPath:indexPath];
|
||||
if (element != nil) {
|
||||
[result addObject:element];
|
||||
} else {
|
||||
ASDisplayNodeFailAssert(@"Couldn't find 'visible' supplementary element of kind %@ at index path %@ in map %@", kind, indexPath, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return ASPointerTableByFlatMapping(_visibleElements, id element, element);
|
||||
}
|
||||
|
||||
- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController
|
||||
@@ -1821,24 +1931,30 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
|
||||
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (!self.asyncDataSource || _superIsPendingDataLoad) {
|
||||
updates();
|
||||
[changeSet executeCompletionHandlerWithFinished:NO];
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
}
|
||||
|
||||
ASPerformBlockWithoutAnimation(!changeSet.animated, ^{
|
||||
as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
if (changeSet.includesReloadData) {
|
||||
_superIsPendingDataLoad = YES;
|
||||
updates();
|
||||
[super reloadData];
|
||||
as_log_debug(ASCollectionLog(), "Did reloadData %@", self.collectionNode);
|
||||
[changeSet executeCompletionHandlerWithFinished:YES];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillPerformBatchUpdates];
|
||||
|
||||
__block NSUInteger numberOfUpdates = 0;
|
||||
[self _superPerformBatchUpdates:^{
|
||||
updates();
|
||||
|
||||
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
[super reloadItemsAtIndexPaths:change.indexPaths];
|
||||
numberOfUpdates++;
|
||||
@@ -1869,19 +1985,24 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
numberOfUpdates++;
|
||||
}
|
||||
} completion:^(BOOL finished){
|
||||
as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode);
|
||||
// Flush any range changes that happened as part of the update animations ending.
|
||||
[_rangeController updateIfNeeded];
|
||||
[self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates];
|
||||
[changeSet executeCompletionHandlerWithFinished:finished];
|
||||
}];
|
||||
as_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode);
|
||||
|
||||
// Flush any range changes that happened as part of submitting the update.
|
||||
as_activity_scope(changeSet.rootActivity);
|
||||
[_rangeController updateIfNeeded];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - ASCellNodeDelegate
|
||||
|
||||
- (void)nodeSelectedStateDidChange:(ASCellNode *)node
|
||||
{
|
||||
NSIndexPath *indexPath = [self indexPathForNode:node];
|
||||
@@ -1908,16 +2029,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (!sizeChanged) {
|
||||
return;
|
||||
}
|
||||
[self nodesDidRelayout:@[node]];
|
||||
}
|
||||
|
||||
- (void)nodesDidRelayout:(NSArray<ASCellNode *> *)nodes
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@@ -2020,6 +2131,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
*/
|
||||
- (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds
|
||||
{
|
||||
if (_hasDataControllerLayoutDelegate) {
|
||||
// Let the layout delegate handle bounds changes if it's available.
|
||||
return;
|
||||
}
|
||||
if (self.collectionViewLayout == nil) {
|
||||
return;
|
||||
}
|
||||
@@ -2050,6 +2165,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
#pragma mark - UICollectionView dead-end intercepts
|
||||
|
||||
- (void)setPrefetchDataSource:(id<UICollectionViewDataSourcePrefetching>)prefetchDataSource
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting.
|
||||
|
||||
// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASCollectionViewLayoutFacilitatorProtocol.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASCollectionViewProtocols.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASContextTransitioning.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 2/4/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDimension.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASControlNode+Subclasses.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASControlNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASControlNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASControlNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASControlNode.h>
|
||||
@@ -273,6 +280,11 @@ CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return super.supportsLayerBacking && !self.userInteractionEnabled;
|
||||
}
|
||||
|
||||
#pragma mark - Action Messages
|
||||
|
||||
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
|
||||
@@ -415,34 +427,44 @@ CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode);
|
||||
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread(); //We access self.view below, it's not safe to call this off of main.
|
||||
NSParameterAssert(controlEvents != 0);
|
||||
|
||||
ASDN::MutexLocker l(_controlLock);
|
||||
NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray<ASControlTargetAction *> alloc] init];
|
||||
|
||||
_controlLock.lock();
|
||||
|
||||
// Enumerate the events in the mask, invoking the target-action pairs for each.
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Use a copy to itereate, the action perform could call remove causing a mutation crash.
|
||||
NSMutableArray *eventTargetActionArray = [_controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)] copy];
|
||||
|
||||
// Iterate on each target action pair
|
||||
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
|
||||
SEL action = targetAction.action;
|
||||
id responder = targetAction.target;
|
||||
for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) {
|
||||
ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init];
|
||||
resolvedTargetAction.action = targetAction.action;
|
||||
resolvedTargetAction.target = targetAction.target;
|
||||
|
||||
// NSNull means that a nil target was set, so start at self and travel the responder chain
|
||||
if (!responder && targetAction.createdWithNoTarget) {
|
||||
if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) {
|
||||
// if the target cannot perform the action, travel the responder chain to try to find something that does
|
||||
responder = [self.view targetForAction:action withSender:self];
|
||||
resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[responder performSelector:action withObject:self withObject:event];
|
||||
#pragma clang diagnostic pop
|
||||
if (resolvedTargetAction.target) {
|
||||
[resolvedEventTargetActionArray addObject:resolvedTargetAction];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_controlLock.unlock();
|
||||
|
||||
//We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
for (ASControlTargetAction *targetAction in resolvedEventTargetActionArray) {
|
||||
[targetAction.target performSelector:targetAction.action withObject:self withObject:event];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
#pragma mark - Convenience
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode+Beta.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASAvailability.h>
|
||||
@@ -15,6 +22,7 @@
|
||||
|
||||
#if YOGA
|
||||
#import YOGA_HEADER_PATH
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -129,11 +137,11 @@ typedef struct {
|
||||
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;
|
||||
|
||||
/**
|
||||
* @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store.
|
||||
* @abstract Whether to draw all descendent nodes' contents into this node's layer's backing store.
|
||||
*
|
||||
* @discussion
|
||||
* When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing
|
||||
* store. Defaults to NO.
|
||||
* When called, causes all descendent nodes' contents to be drawn directly into this node's layer's backing
|
||||
* store.
|
||||
*
|
||||
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
|
||||
* good candidate for rasterization. Rasterizing descendants has two main benefits:
|
||||
@@ -147,8 +155,11 @@ typedef struct {
|
||||
*
|
||||
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
|
||||
* rendering model.
|
||||
*
|
||||
* Note: You cannot add subnodes whose layers/views are already loaded to a rasterized node.
|
||||
* Note: You cannot call this method after the receiver's layer/view is loaded.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shouldRasterizeDescendants ASDISPLAYNODE_DEPRECATED_MSG("Deprecated in version 2.2");
|
||||
- (void)enableSubtreeRasterization;
|
||||
|
||||
@end
|
||||
|
||||
@@ -160,12 +171,15 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable
|
||||
|
||||
@interface ASDisplayNode (Yoga)
|
||||
|
||||
@property (nonatomic, strong) NSArray *yogaChildren;
|
||||
@property (nonatomic, strong) ASLayout *yogaCalculatedLayout;
|
||||
@property (nonatomic, strong, nullable) NSArray *yogaChildren;
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child;
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child;
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
|
||||
|
||||
@property (nonatomic, assign) BOOL yogaLayoutInProgress;
|
||||
@property (nonatomic, strong, nullable) ASLayout *yogaCalculatedLayout;
|
||||
// These methods should not normally be called directly.
|
||||
- (void)invalidateCalculatedYogaLayout;
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
|
||||
@@ -174,8 +188,11 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable
|
||||
|
||||
@interface ASLayoutElementStyle (Yoga)
|
||||
|
||||
@property (nonatomic, assign, readwrite) ASStackLayoutDirection direction;
|
||||
@property (nonatomic, assign, readwrite) CGFloat spacing;
|
||||
- (YGNodeRef)yogaNodeCreateIfNeeded;
|
||||
@property (nonatomic, assign, readonly) YGNodeRef yogaNode;
|
||||
|
||||
@property (nonatomic, assign, readwrite) ASStackLayoutDirection flexDirection;
|
||||
@property (nonatomic, assign, readwrite) YGDirection direction;
|
||||
@property (nonatomic, assign, readwrite) ASStackLayoutJustifyContent justifyContent;
|
||||
@property (nonatomic, assign, readwrite) ASStackLayoutAlignItems alignItems;
|
||||
@property (nonatomic, assign, readwrite) YGPositionType positionType;
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode+Convenience.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Adlai Holler on 2/24/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNode.h>
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode+Convenience.m
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Adlai Holler on 2/24/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 "ASDisplayNode+Convenience.h"
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode+Deprecated.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
963
Source/ASDisplayNode+Layout.mm
Normal file
963
Source/ASDisplayNode+Layout.mm
Normal file
@@ -0,0 +1,963 @@
|
||||
//
|
||||
// ASDisplayNode+Layout.mm
|
||||
// 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/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASLayoutElement)
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutElement)
|
||||
|
||||
#pragma mark <ASLayoutElement>
|
||||
|
||||
- (BOOL)implementsLayoutMethod
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return (_methodOverrides & (ASDisplayNodeMethodOverrideLayoutSpecThatFits |
|
||||
ASDisplayNodeMethodOverrideCalcLayoutThatFits |
|
||||
ASDisplayNodeMethodOverrideCalcSizeThatFits)) != 0 || _layoutSpecBlock != nil;
|
||||
}
|
||||
|
||||
|
||||
- (ASLayoutElementStyle *)style
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_style == nil) {
|
||||
_style = [[ASLayoutElementStyle alloc] init];
|
||||
}
|
||||
return _style;
|
||||
}
|
||||
|
||||
- (ASLayoutElementType)layoutElementType
|
||||
{
|
||||
return ASLayoutElementTypeDisplayNode;
|
||||
}
|
||||
|
||||
- (NSArray<id<ASLayoutElement>> *)sublayoutElements
|
||||
{
|
||||
return self.subnodes;
|
||||
}
|
||||
|
||||
#pragma mark Measurement Pass
|
||||
|
||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
// For now we just call the deprecated measureWithSizeRange: method to not break old API
|
||||
return [self measureWithSizeRange:constrainedSize];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];
|
||||
}
|
||||
|
||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// If one or multiple layout transitions are in flight it still can happen that layout information is requested
|
||||
// on other threads. As the pending and calculated layout to be updated in the layout transition in here just a
|
||||
// layout calculation wil be performed without side effect
|
||||
if ([self _isLayoutTransitionInvalid]) {
|
||||
return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize];
|
||||
}
|
||||
|
||||
ASLayout *layout = nil;
|
||||
NSUInteger version = _layoutVersion;
|
||||
if (_calculatedDisplayNodeLayout->isValid(constrainedSize, parentSize, version)) {
|
||||
ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self);
|
||||
layout = _calculatedDisplayNodeLayout->layout;
|
||||
} else if (_pendingDisplayNodeLayout != nullptr && _pendingDisplayNodeLayout->isValid(constrainedSize, parentSize, version)) {
|
||||
ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self);
|
||||
layout = _pendingDisplayNodeLayout->layout;
|
||||
} else {
|
||||
// Create a pending display node layout for the layout pass
|
||||
layout = [self calculateLayoutThatFits:constrainedSize
|
||||
restrictedToSize:self.style.size
|
||||
relativeToParentSize:parentSize];
|
||||
_pendingDisplayNodeLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, parentSize, version);
|
||||
ASDisplayNodeAssertNotNil(layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newly calculated layout should not be nil! %@", self);
|
||||
}
|
||||
|
||||
return layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
|
||||
}
|
||||
|
||||
#pragma mark ASLayoutElementStyleExtensibility
|
||||
|
||||
ASLayoutElementStyleExtensibilityForwarding
|
||||
|
||||
#pragma mark ASPrimitiveTraitCollection
|
||||
|
||||
- (ASPrimitiveTraitCollection)primitiveTraitCollection
|
||||
{
|
||||
return _primitiveTraitCollection.load();
|
||||
}
|
||||
|
||||
- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection
|
||||
{
|
||||
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection.load()) == NO) {
|
||||
_primitiveTraitCollection = traitCollection;
|
||||
ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection));
|
||||
|
||||
[self asyncTraitCollectionDidChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASTraitCollection *)asyncTraitCollection
|
||||
{
|
||||
return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection];
|
||||
}
|
||||
|
||||
ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||
|
||||
- (NSString *)asciiArtString
|
||||
{
|
||||
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
|
||||
}
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding];
|
||||
if (_debugName) {
|
||||
[result appendFormat:@" (%@)", _debugName];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASLayout)
|
||||
|
||||
@implementation ASDisplayNode (ASLayout)
|
||||
|
||||
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
|
||||
{
|
||||
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together.
|
||||
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits),
|
||||
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:");
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_layoutSpecBlock = layoutSpecBlock;
|
||||
}
|
||||
|
||||
- (ASLayoutSpecBlock)layoutSpecBlock
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _layoutSpecBlock;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculatedLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _calculatedDisplayNodeLayout->layout;
|
||||
}
|
||||
|
||||
- (CGSize)calculatedSize
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_pendingDisplayNodeLayout != nullptr) {
|
||||
return _pendingDisplayNodeLayout->layout.size;
|
||||
}
|
||||
return _calculatedDisplayNodeLayout->layout.size;
|
||||
}
|
||||
|
||||
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_pendingDisplayNodeLayout != nullptr) {
|
||||
return _pendingDisplayNodeLayout->constrainedSize;
|
||||
}
|
||||
return _calculatedDisplayNodeLayout->constrainedSize;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASLayoutElementStylability)
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutElementStylability)
|
||||
|
||||
- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock
|
||||
{
|
||||
styleBlock(self.style);
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASLayoutInternal)
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutInternal)
|
||||
|
||||
/**
|
||||
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
|
||||
*
|
||||
* @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know
|
||||
* that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen.
|
||||
*/
|
||||
- (void)_setNeedsLayoutFromAbove
|
||||
{
|
||||
as_activity_create_for_scope("Set needs layout from above");
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
|
||||
// Mark the node for layout in the next layout pass
|
||||
[self setNeedsLayout];
|
||||
|
||||
__instanceLock__.lock();
|
||||
// Escalate to the root; entire tree must allow adjustments so the layout fits the new child.
|
||||
// Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack)
|
||||
ASDisplayNode *supernode = _supernode;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (supernode) {
|
||||
// Threading model requires that we unlock before calling a method on our parent.
|
||||
[supernode _setNeedsLayoutFromAbove];
|
||||
} else {
|
||||
// Let the root node method know that the size was invalidated
|
||||
[self _rootNodeDidInvalidateSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_rootNodeDidInvalidateSize
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
|
||||
__instanceLock__.lock();
|
||||
|
||||
// We are the root node and need to re-flow the layout; at least one child needs a new size.
|
||||
CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size);
|
||||
|
||||
// Figure out constrainedSize to use
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout);
|
||||
if (_pendingDisplayNodeLayout != nullptr) {
|
||||
constrainedSize = _pendingDisplayNodeLayout->constrainedSize;
|
||||
} else if (_calculatedDisplayNodeLayout->layout != nil) {
|
||||
constrainedSize = _calculatedDisplayNodeLayout->constrainedSize;
|
||||
}
|
||||
|
||||
__instanceLock__.unlock();
|
||||
|
||||
// Perform a measurement pass to get the full tree layout, adapting to the child's new size.
|
||||
ASLayout *layout = [self layoutThatFits:constrainedSize];
|
||||
|
||||
// Check if the returned layout has a different size than our current bounds.
|
||||
if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) {
|
||||
// If so, inform our container we need an update (e.g Table, Collection, ViewController, etc).
|
||||
[self displayNodeDidInvalidateSizeNewSize:layout.size];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
|
||||
// The default implementation of display node changes the size of itself to the new size
|
||||
CGRect oldBounds = self.bounds;
|
||||
CGSize oldSize = oldBounds.size;
|
||||
CGSize newSize = size;
|
||||
|
||||
if (! CGSizeEqualToSize(oldSize, newSize)) {
|
||||
self.bounds = (CGRect){ oldBounds.origin, newSize };
|
||||
|
||||
// Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint
|
||||
// and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted.
|
||||
CGPoint anchorPoint = self.anchorPoint;
|
||||
CGPoint oldPosition = self.position;
|
||||
CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x;
|
||||
CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y;
|
||||
self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds
|
||||
{
|
||||
// Check if we are a subnode in a layout transition.
|
||||
// In this case no measurement is needed as it's part of the layout transition
|
||||
if ([self _isLayoutTransitionInvalid]) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size);
|
||||
|
||||
// Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest)
|
||||
// If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below).
|
||||
if (_pendingDisplayNodeLayout == nullptr) {
|
||||
if (_calculatedDisplayNodeLayout->version >= _layoutVersion
|
||||
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES
|
||||
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
as_activity_create_for_scope("Update node layout for current bounds");
|
||||
as_log_verbose(ASLayoutLog(), "Node %@, bounds size %@, calculatedSize %@, calculatedIsDirty %d", self, NSStringFromCGSize(boundsSizeForLayout), NSStringFromCGSize(_calculatedDisplayNodeLayout->layout.size), _calculatedDisplayNodeLayout->isDirty());
|
||||
// _calculatedDisplayNodeLayout is not reusable we need to transition to a new one
|
||||
[self cancelLayoutTransition];
|
||||
|
||||
BOOL didCreateNewContext = NO;
|
||||
ASLayoutElementContext *context = ASLayoutElementGetCurrentContext();
|
||||
if (context == nil) {
|
||||
context = [[ASLayoutElementContext alloc] init];
|
||||
ASLayoutElementPushContext(context);
|
||||
didCreateNewContext = YES;
|
||||
}
|
||||
|
||||
// Figure out previous and pending layouts for layout transition
|
||||
std::shared_ptr<ASDisplayNodeLayout> nextLayout = _pendingDisplayNodeLayout;
|
||||
#define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout)
|
||||
|
||||
// nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied.
|
||||
// If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr->
|
||||
BOOL pendingLayoutApplicable = NO;
|
||||
if (nextLayout == nullptr) {
|
||||
as_log_verbose(ASLayoutLog(), "No pending layout.");
|
||||
} else if (nextLayout->version < _layoutVersion) {
|
||||
as_log_verbose(ASLayoutLog(), "Pending layout is stale.");
|
||||
} else if (layoutSizeDifferentFromBounds) {
|
||||
as_log_verbose(ASLayoutLog(), "Pending layout size %@ doesn't match bounds size.", NSStringFromCGSize(nextLayout->layout.size));
|
||||
} else {
|
||||
as_log_verbose(ASLayoutLog(), "Using pending layout %@.", nextLayout->layout);
|
||||
pendingLayoutApplicable = YES;
|
||||
}
|
||||
|
||||
if (!pendingLayoutApplicable) {
|
||||
as_log_verbose(ASLayoutLog(), "Measuring with previous constrained size.");
|
||||
// Use the last known constrainedSize passed from a parent during layout (if never, use bounds).
|
||||
NSUInteger version = _layoutVersion;
|
||||
ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
|
||||
restrictedToSize:self.style.size
|
||||
relativeToParentSize:boundsSizeForLayout];
|
||||
|
||||
nextLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, boundsSizeForLayout, version);
|
||||
}
|
||||
|
||||
if (didCreateNewContext) {
|
||||
ASLayoutElementPopContext();
|
||||
}
|
||||
|
||||
// If our new layout's desired size for self doesn't match current size, ask our parent to update it.
|
||||
// This can occur for either pre-calculated or newly-calculated layouts.
|
||||
if (nextLayout->requestedLayoutFromAbove == NO
|
||||
&& CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) {
|
||||
as_log_verbose(ASLayoutLog(), "Layout size doesn't match bounds size. Requesting layout from above.");
|
||||
// The layout that we have specifies that this node (self) would like to be a different size
|
||||
// than it currently is. Because that size has been computed within the constrainedSize, we
|
||||
// expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this.
|
||||
// However, in some cases apps may manually interfere with this (setting a different bounds).
|
||||
// In this case, we need to detect that we've already asked to be resized to match this
|
||||
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout.
|
||||
nextLayout->requestedLayoutFromAbove = YES;
|
||||
[self _setNeedsLayoutFromAbove];
|
||||
}
|
||||
|
||||
// Prepare to transition to nextLayout
|
||||
ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self);
|
||||
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:nextLayout
|
||||
previousLayout:_calculatedDisplayNodeLayout];
|
||||
|
||||
// If a parent is currently executing a layout transition, perform our layout application after it.
|
||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) {
|
||||
// If no transition, apply our new layout immediately (common case).
|
||||
[self _completePendingLayoutTransition];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASSizeRange)_locked_constrainedSizeForLayoutPass
|
||||
{
|
||||
// TODO: The logic in -_setNeedsLayoutFromAbove seems correct and doesn't use this method.
|
||||
// logic seems correct. For what case does -this method need to do the CGSizeEqual checks?
|
||||
// IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK
|
||||
|
||||
CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size);
|
||||
|
||||
// Checkout if constrained size of pending or calculated display node layout can be used
|
||||
if (_pendingDisplayNodeLayout != nullptr
|
||||
&& (_pendingDisplayNodeLayout->requestedLayoutFromAbove
|
||||
|| CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||
// We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node
|
||||
// layout constrained size
|
||||
return _pendingDisplayNodeLayout->constrainedSize;
|
||||
} else if (_calculatedDisplayNodeLayout->layout != nil
|
||||
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove
|
||||
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||
// We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different
|
||||
return _calculatedDisplayNodeLayout->constrainedSize;
|
||||
} else {
|
||||
// In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can
|
||||
// be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to
|
||||
// the one returned from layoutThatFits: or layoutThatFits: was never called
|
||||
return ASSizeRangeMake(boundsSizeForLayout);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_layoutSublayouts
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
|
||||
ASLayout *layout;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_calculatedDisplayNodeLayout->version < _layoutVersion) {
|
||||
return;
|
||||
}
|
||||
layout = _calculatedDisplayNodeLayout->layout;
|
||||
}
|
||||
|
||||
for (ASDisplayNode *node in self.subnodes) {
|
||||
CGRect frame = [layout frameForElement:node];
|
||||
if (CGRectIsNull(frame)) {
|
||||
// There is no frame for this node in our layout.
|
||||
// This currently can happen if we get a CA layout pass
|
||||
// while waiting for the client to run animateLayoutTransition:
|
||||
} else {
|
||||
node.frame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASAutomatic Subnode Management)
|
||||
|
||||
@implementation ASDisplayNode (ASAutomaticSubnodeManagement)
|
||||
|
||||
#pragma mark Automatically Manages Subnodes
|
||||
|
||||
- (BOOL)automaticallyManagesSubnodes
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _automaticallyManagesSubnodes;
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_automaticallyManagesSubnodes = automaticallyManagesSubnodes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASDisplayNode (ASLayoutTransition)
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutTransition)
|
||||
|
||||
- (BOOL)_isLayoutTransitionInvalid
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
|
||||
ASLayoutElementContext *context = ASLayoutElementGetCurrentContext();
|
||||
if (context == nil || _pendingTransitionID != context.transitionID) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/// Starts a new transition and returns the transition id
|
||||
- (int32_t)_startNewTransition
|
||||
{
|
||||
static std::atomic<int32_t> gNextTransitionID;
|
||||
int32_t newTransitionID = gNextTransitionID.fetch_add(1) + 1;
|
||||
_transitionID = newTransitionID;
|
||||
return newTransitionID;
|
||||
}
|
||||
|
||||
/// Returns NO if there was no transition to cancel/finish.
|
||||
- (BOOL)_finishOrCancelTransition
|
||||
{
|
||||
int32_t oldValue = _transitionID.exchange(ASLayoutElementContextInvalidTransitionID);
|
||||
return oldValue != ASLayoutElementContextInvalidTransitionID;
|
||||
}
|
||||
|
||||
#pragma mark Layout Transition
|
||||
|
||||
- (void)transitionLayoutWithAnimation:(BOOL)animated
|
||||
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
||||
measurementCompletion:(void(^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
[self setNeedsLayout];
|
||||
|
||||
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
|
||||
animated:animated
|
||||
shouldMeasureAsync:shouldMeasureAsync
|
||||
measurementCompletion:completion];
|
||||
|
||||
}
|
||||
|
||||
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
|
||||
animated:(BOOL)animated
|
||||
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
||||
measurementCompletion:(void(^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
as_activity_create_for_scope("Transition node layout");
|
||||
as_log_debug(ASLayoutLog(), "Transition layout for %@ sizeRange %@ anim %d asyncMeasure %d", self, NSStringFromASSizeRange(constrainedSize), animated, shouldMeasureAsync);
|
||||
|
||||
if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) {
|
||||
// Using CGSizeZero for the sizeRange can cause negative values in client layout code.
|
||||
// Most likely called transitionLayout: without providing a size, before first layout pass.
|
||||
as_log_verbose(ASLayoutLog(), "Ignoring transition due to bad size range.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are a subnode in a layout transition.
|
||||
// In this case no measurement is needed as we're part of the layout transition.
|
||||
if ([self _isLayoutTransitionInvalid]) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one.");
|
||||
}
|
||||
|
||||
// Every new layout transition has a transition id associated to check in subsequent transitions for cancelling
|
||||
int32_t transitionID = [self _startNewTransition];
|
||||
as_log_verbose(ASLayoutLog(), "Transition ID is %d", transitionID);
|
||||
// NOTE: This block captures self. It's cheaper than hitting the weak table.
|
||||
asdisplaynode_iscancelled_block_t isCancelled = ^{
|
||||
BOOL result = (_transitionID != transitionID);
|
||||
if (result) {
|
||||
as_log_verbose(ASLayoutLog(), "Transition %d canceled, superseded by %d", transitionID, _transitionID.load());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Move all subnodes in layout pending state for this transition
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
ASDisplayNodeAssert(node->_transitionID == ASLayoutElementContextInvalidTransitionID, @"Can't start a transition when one of the subnodes is performing one.");
|
||||
node.hierarchyState |= ASHierarchyStateLayoutPending;
|
||||
node->_pendingTransitionID = transitionID;
|
||||
});
|
||||
|
||||
// Transition block that executes the layout transition
|
||||
void (^transitionBlock)(void) = ^{
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a full layout creation pass with passed in constrained size to create the new layout for the transition
|
||||
NSUInteger newLayoutVersion = _layoutVersion;
|
||||
ASLayout *newLayout;
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASLayoutElementContext *ctx = [[ASLayoutElementContext alloc] init];
|
||||
ctx.transitionID = transitionID;
|
||||
ASLayoutElementPushContext(ctx);
|
||||
|
||||
BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO);
|
||||
self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x
|
||||
newLayout = [self calculateLayoutThatFits:constrainedSize
|
||||
restrictedToSize:self.style.size
|
||||
relativeToParentSize:constrainedSize.max];
|
||||
if (automaticallyManagesSubnodesDisabled) {
|
||||
self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x
|
||||
}
|
||||
|
||||
ASLayoutElementPopContext();
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASPerformBlockOnMainThread(^{
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
as_activity_create_for_scope("Commit layout transition");
|
||||
ASLayoutTransition *pendingLayoutTransition;
|
||||
_ASTransitionContext *pendingLayoutTransitionContext;
|
||||
{
|
||||
// Grab __instanceLock__ here to make sure this transition isn't invalidated
|
||||
// right after it passed the validation test and before it proceeds
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// Update calculated layout
|
||||
auto previousLayout = _calculatedDisplayNodeLayout;
|
||||
auto pendingLayout = std::make_shared<ASDisplayNodeLayout>(newLayout,
|
||||
constrainedSize,
|
||||
constrainedSize.max,
|
||||
newLayoutVersion);
|
||||
[self _locked_setCalculatedDisplayNodeLayout:pendingLayout];
|
||||
|
||||
// Setup pending layout transition for animation
|
||||
_pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||
pendingLayout:pendingLayout
|
||||
previousLayout:previousLayout];
|
||||
// Setup context for pending layout transition. we need to hold a strong reference to the context
|
||||
_pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
|
||||
layoutDelegate:_pendingLayoutTransition
|
||||
completionDelegate:self];
|
||||
}
|
||||
|
||||
// Apply complete layout transitions for all subnodes
|
||||
{
|
||||
as_activity_create_for_scope("Complete pending layout transitions for subtree");
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node _completePendingLayoutTransition];
|
||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||
});
|
||||
}
|
||||
|
||||
// Measurement pass completion
|
||||
// Give the subclass a change to hook into before calling the completion block
|
||||
[self _layoutTransitionMeasurementDidFinish];
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
|
||||
// Apply the subnode insertion immediately to be able to animate the nodes
|
||||
[pendingLayoutTransition applySubnodeInsertions];
|
||||
|
||||
// Kick off animating the layout transition
|
||||
{
|
||||
as_activity_create_for_scope("Animate layout transition");
|
||||
[self animateLayoutTransition:pendingLayoutTransitionContext];
|
||||
}
|
||||
|
||||
// Mark transaction as finished
|
||||
[self _finishOrCancelTransition];
|
||||
});
|
||||
};
|
||||
|
||||
// Start transition based on flag on current or background thread
|
||||
if (shouldMeasureAsync) {
|
||||
ASPerformBlockOnBackgroundThread(transitionBlock);
|
||||
} else {
|
||||
transitionBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelLayoutTransition
|
||||
{
|
||||
if ([self _finishOrCancelTransition]) {
|
||||
// Tell subnodes to exit layout pending state and clear related properties
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)defaultLayoutTransitionDuration
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionDuration;
|
||||
}
|
||||
|
||||
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)defaultLayoutTransitionDelay
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionDelay;
|
||||
}
|
||||
|
||||
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
|
||||
}
|
||||
|
||||
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return _defaultLayoutTransitionOptions;
|
||||
}
|
||||
|
||||
#pragma mark <LayoutTransitioning>
|
||||
|
||||
/*
|
||||
* Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out
|
||||
* animation is provided.
|
||||
*/
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
if ([context isAnimated] == NO) {
|
||||
[self _layoutSublayouts];
|
||||
[context completeTransition:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
ASDisplayNode *node = self;
|
||||
|
||||
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
|
||||
|
||||
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
|
||||
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
|
||||
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
|
||||
|
||||
NSMutableArray<_ASAnimatedTransitionContext *> *insertedSubnodeContexts = [NSMutableArray array];
|
||||
NSMutableArray<_ASAnimatedTransitionContext *> *removedSubnodeContexts = [NSMutableArray array];
|
||||
|
||||
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
|
||||
if ([insertedSubnodes containsObject:subnode] == NO) {
|
||||
// This is an existing subnode, check if it is resized, moved or both
|
||||
CGRect fromFrame = [context initialFrameForNode:subnode];
|
||||
CGRect toFrame = [context finalFrameForNode:subnode];
|
||||
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
|
||||
[insertedSubnodes addObject:subnode];
|
||||
}
|
||||
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
|
||||
[movedSubnodes addObject:subnode];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create contexts for inserted and removed subnodes
|
||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||
[insertedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:insertedSubnode alpha:insertedSubnode.alpha]];
|
||||
}
|
||||
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
||||
[removedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:removedSubnode alpha:removedSubnode.alpha]];
|
||||
}
|
||||
|
||||
// Fade out inserted subnodes
|
||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
|
||||
insertedSubnode.alpha = 0;
|
||||
}
|
||||
|
||||
// Adjust groupOpacity for animation
|
||||
BOOL originAllowsGroupOpacity = node.allowsGroupOpacity;
|
||||
node.allowsGroupOpacity = YES;
|
||||
|
||||
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
|
||||
// Fade removed subnodes and views out
|
||||
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
||||
removedSubnode.alpha = 0;
|
||||
}
|
||||
|
||||
// Fade inserted subnodes in
|
||||
for (_ASAnimatedTransitionContext *insertedSubnodeContext in insertedSubnodeContexts) {
|
||||
insertedSubnodeContext.node.alpha = insertedSubnodeContext.alpha;
|
||||
}
|
||||
|
||||
// Update frame of self and moved subnodes
|
||||
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
|
||||
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
|
||||
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
|
||||
if (isResized == YES) {
|
||||
CGPoint position = node.frame.origin;
|
||||
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
|
||||
}
|
||||
for (ASDisplayNode *movedSubnode in movedSubnodes) {
|
||||
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
|
||||
}
|
||||
} completion:^(BOOL finished) {
|
||||
// Restore all removed subnode alpha values
|
||||
for (_ASAnimatedTransitionContext *removedSubnodeContext in removedSubnodeContexts) {
|
||||
removedSubnodeContext.node.alpha = removedSubnodeContext.alpha;
|
||||
}
|
||||
|
||||
// Restore group opacity
|
||||
node.allowsGroupOpacity = originAllowsGroupOpacity;
|
||||
|
||||
// Subnode removals are automatically performed
|
||||
[context completeTransition:finished];
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses
|
||||
* to manually perform deletions.
|
||||
*/
|
||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
__instanceLock__.lock();
|
||||
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
[pendingLayoutTransition applySubnodeRemovals];
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the pending layout transition immediately without going through the the Layout Transition Animation API
|
||||
*/
|
||||
- (void)_completePendingLayoutTransition
|
||||
{
|
||||
__instanceLock__.lock();
|
||||
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (pendingLayoutTransition != nil) {
|
||||
[self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout];
|
||||
[self _completeLayoutTransition:pendingLayoutTransition];
|
||||
}
|
||||
[self _pendingLayoutTransitionDidComplete];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be directly called to commit the given layout transition immediately to complete without calling through to the
|
||||
* Layout Transition Animation API
|
||||
*/
|
||||
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
|
||||
{
|
||||
// Layout transition is not supported for nodes that do not have automatic subnode management enabled
|
||||
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trampoline to the main thread if necessary
|
||||
if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) {
|
||||
[layoutTransition commitTransition];
|
||||
} else {
|
||||
// Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[layoutTransition commitTransition];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_assertSubnodeState
|
||||
{
|
||||
// Verify that any orphaned nodes are removed.
|
||||
// This can occur in rare cases if main thread layout is flushed while a background layout is calculating.
|
||||
|
||||
if (self.automaticallyManagesSubnodes == NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *subnodes = [self subnodes];
|
||||
NSArray *sublayouts = _calculatedDisplayNodeLayout->layout.sublayouts;
|
||||
|
||||
auto currentSubnodes = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality
|
||||
capacity:subnodes.count];
|
||||
auto layoutSubnodes = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality
|
||||
capacity:sublayouts.count];;
|
||||
for (ASDisplayNode *subnode in subnodes) {
|
||||
[currentSubnodes addObject:subnode];
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
id <ASLayoutElement> layoutElement = sublayout.layoutElement;
|
||||
ASDisplayNodeAssert([layoutElement isKindOfClass:[ASDisplayNode class]],
|
||||
@"All calculatedLayouts should be flattened and only contain nodes!");
|
||||
[layoutSubnodes addObject:(ASDisplayNode *)layoutElement];
|
||||
}
|
||||
|
||||
// Verify that all subnodes that occur in the current ASLayout tree are present in .subnodes array.
|
||||
if ([layoutSubnodes isSubsetOfHashTable:currentSubnodes] == NO) {
|
||||
// Note: This should be converted to an assertion after confirming it is rare.
|
||||
NSLog(@"Warning: node's layout includes subnodes that have not been added: node = %@, subnodes = %@, subnodes in layout = %@", self, currentSubnodes, layoutSubnodes);
|
||||
}
|
||||
|
||||
// Verify that everything in the .subnodes array is present in the ASLayout tree (and correct it if not).
|
||||
[currentSubnodes minusHashTable:layoutSubnodes];
|
||||
for (ASDisplayNode *orphanedSubnode in currentSubnodes) {
|
||||
NSLog(@"Automatically removing orphaned subnode %@, from parent %@", orphanedSubnode, self);
|
||||
[orphanedSubnode removeFromSupernode];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_pendingLayoutTransitionDidComplete
|
||||
{
|
||||
// This assertion introduces a breaking behavior for nodes that has ASM enabled but also manually manage some subnodes.
|
||||
// Let's gate it behind YOGA flag and remove it right after a branch cut.
|
||||
#if YOGA
|
||||
[self _assertSubnodeState];
|
||||
#endif
|
||||
|
||||
// Subclass hook
|
||||
[self calculatedLayoutDidChange];
|
||||
|
||||
// Grab lock after calling out to subclass
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
|
||||
// This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
|
||||
// First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
|
||||
if (_placeholderEnabled && !_placeholderImage && [self _locked_displaysAsynchronously]) {
|
||||
|
||||
// Zero-sized nodes do not require a placeholder.
|
||||
ASLayout *layout = _calculatedDisplayNodeLayout->layout;
|
||||
CGSize layoutSize = (layout ? layout.size : CGSizeZero);
|
||||
if (layoutSize.width * layoutSize.height <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've displayed our contents, we don't need a placeholder.
|
||||
// Contents is a thread-affined property and can't be read off main after loading.
|
||||
if (self.isNodeLoaded) {
|
||||
ASPerformBlockOnMainThread(^{
|
||||
if (self.contents == nil) {
|
||||
_placeholderImage = [self placeholderImage];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (self.contents == nil) {
|
||||
_placeholderImage = [self placeholderImage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup pending layout transition
|
||||
_pendingLayoutTransition = nil;
|
||||
}
|
||||
|
||||
- (void)_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
[self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout];
|
||||
}
|
||||
|
||||
- (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
||||
{
|
||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self);
|
||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0);
|
||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0);
|
||||
|
||||
// Flatten the layout if it wasn't done before (@see -calculateLayoutThatFits:).
|
||||
if ([ASDisplayNode shouldStoreUnflattenedLayouts]) {
|
||||
_unflattenedLayout = displayNodeLayout->layout;
|
||||
displayNodeLayout->layout = [_unflattenedLayout filteredNodeLayoutTree];
|
||||
}
|
||||
|
||||
_calculatedDisplayNodeLayout = displayNodeLayout;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode+Subclasses.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <pthread.h>
|
||||
@@ -115,7 +122,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @warning Subclasses must not override this; it returns the last cached layout and is never expensive.
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly, assign) ASLayout *calculatedLayout;
|
||||
@property (nullable, nonatomic, readonly, strong) ASLayout *calculatedLayout;
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
/** @name View Lifecycle */
|
||||
@@ -272,9 +279,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
|
||||
*/
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(nullable id <NSObject>)parameters
|
||||
/*+ (void)drawRect:(CGRect)bounds withParameters:(nullable id)parameters
|
||||
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock
|
||||
isRasterizing:(BOOL)isRasterizing;
|
||||
isRasterizing:(BOOL)isRasterizing;*/
|
||||
|
||||
/**
|
||||
* @summary Delegate override to provide new layer contents as a UIImage.
|
||||
@@ -289,7 +296,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
|
||||
*/
|
||||
+ (nullable UIImage *)displayWithParameters:(nullable id<NSObject>)parameters
|
||||
+ (nullable UIImage *)displayWithParameters:(nullable id)parameters
|
||||
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;
|
||||
|
||||
/**
|
||||
@@ -507,7 +514,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,221 +1,46 @@
|
||||
//
|
||||
// ASDisplayNode+Yoga.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Scott Goodson on 2/8/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASAvailability.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASYogaLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
#define YOGA_LAYOUT_LOGGING 0
|
||||
|
||||
extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
if (node == nil) {
|
||||
return;
|
||||
}
|
||||
block(node);
|
||||
for (ASDisplayNode *child in [node yogaChildren]) {
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(child, block);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Yoga Type Conversion Helpers
|
||||
|
||||
YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems);
|
||||
YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent);
|
||||
YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf);
|
||||
YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction);
|
||||
float yogaFloatForCGFloat(CGFloat value);
|
||||
float yogaDimensionToPoints(ASDimension dimension);
|
||||
float yogaDimensionToPercent(ASDimension dimension);
|
||||
ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets);
|
||||
YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode,
|
||||
float width, YGMeasureMode widthMode,
|
||||
float height, YGMeasureMode heightMode);
|
||||
|
||||
#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \
|
||||
if (dimension.unit == ASDimensionUnitPoints) { \
|
||||
YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \
|
||||
} else if (dimension.unit == ASDimensionUnitFraction) { \
|
||||
YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \
|
||||
} else { \
|
||||
YGNodeStyleSet##property(yogaNode, YGUndefined); \
|
||||
}\
|
||||
|
||||
#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \
|
||||
if (dimension.unit == ASDimensionUnitPoints) { \
|
||||
YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \
|
||||
} else if (dimension.unit == ASDimensionUnitFraction) { \
|
||||
YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \
|
||||
} else { \
|
||||
YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \
|
||||
} \
|
||||
|
||||
#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \
|
||||
if (dimension.unit == ASDimensionUnitPoints) { \
|
||||
YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \
|
||||
} else if (dimension.unit == ASDimensionUnitFraction) { \
|
||||
ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \
|
||||
} else { \
|
||||
YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \
|
||||
} \
|
||||
|
||||
YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems)
|
||||
{
|
||||
switch (alignItems) {
|
||||
case ASStackLayoutAlignItemsNotSet: return YGAlignAuto;
|
||||
case ASStackLayoutAlignItemsStart: return YGAlignFlexStart;
|
||||
case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd;
|
||||
case ASStackLayoutAlignItemsCenter: return YGAlignCenter;
|
||||
case ASStackLayoutAlignItemsStretch: return YGAlignStretch;
|
||||
case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline;
|
||||
// FIXME: WARNING, Yoga does not currently support last-baseline item alignment.
|
||||
case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline;
|
||||
}
|
||||
}
|
||||
|
||||
YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent)
|
||||
{
|
||||
switch (justifyContent) {
|
||||
case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart;
|
||||
case ASStackLayoutJustifyContentCenter: return YGJustifyCenter;
|
||||
case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd;
|
||||
case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween;
|
||||
case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround;
|
||||
}
|
||||
}
|
||||
|
||||
YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf)
|
||||
{
|
||||
switch (alignSelf) {
|
||||
case ASStackLayoutAlignSelfStart: return YGAlignFlexStart;
|
||||
case ASStackLayoutAlignSelfCenter: return YGAlignCenter;
|
||||
case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd;
|
||||
case ASStackLayoutAlignSelfStretch: return YGAlignStretch;
|
||||
case ASStackLayoutAlignSelfAuto: return YGAlignAuto;
|
||||
}
|
||||
}
|
||||
|
||||
YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction)
|
||||
{
|
||||
return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow;
|
||||
}
|
||||
|
||||
float yogaFloatForCGFloat(CGFloat value)
|
||||
{
|
||||
if (value < CGFLOAT_MAX / 2) {
|
||||
return value;
|
||||
} else {
|
||||
return YGUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
float yogaDimensionToPoints(ASDimension dimension)
|
||||
{
|
||||
ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints,
|
||||
@"Dimensions should not be type Fraction for this method: %f", dimension.value);
|
||||
return yogaFloatForCGFloat(dimension.value);
|
||||
}
|
||||
|
||||
float yogaDimensionToPercent(ASDimension dimension)
|
||||
{
|
||||
ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction,
|
||||
@"Dimensions should not be type Points for this method: %f", dimension.value);
|
||||
return 100.0 * yogaFloatForCGFloat(dimension.value);
|
||||
|
||||
}
|
||||
|
||||
ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets)
|
||||
{
|
||||
switch (edge) {
|
||||
case YGEdgeLeft: return insets.left;
|
||||
case YGEdgeTop: return insets.top;
|
||||
case YGEdgeRight: return insets.right;
|
||||
case YGEdgeBottom: return insets.bottom;
|
||||
default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported.");
|
||||
return ASDimensionAuto;
|
||||
}
|
||||
}
|
||||
|
||||
YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode,
|
||||
float height, YGMeasureMode heightMode)
|
||||
{
|
||||
id <ASLayoutElement> layoutElement = (__bridge id <ASLayoutElement>)YGNodeGetContext(yogaNode);
|
||||
ASSizeRange sizeRange;
|
||||
sizeRange.max = CGSizeMake(width, height);
|
||||
sizeRange.min = sizeRange.max;
|
||||
if (widthMode == YGMeasureModeAtMost) {
|
||||
sizeRange.min.width = 0.0;
|
||||
}
|
||||
if (heightMode == YGMeasureModeAtMost) {
|
||||
sizeRange.min.height = 0.0;
|
||||
}
|
||||
CGSize size = [[layoutElement layoutThatFits:sizeRange] size];
|
||||
return (YGSize){ .width = (float)size.width, .height = (float)size.height };
|
||||
}
|
||||
|
||||
#pragma mark - ASDisplayNode+Yoga
|
||||
|
||||
@interface ASDisplayNode (YogaInternal)
|
||||
@property (nonatomic, weak) ASDisplayNode *yogaParent;
|
||||
@property (nonatomic, assign) YGNodeRef yogaNode;
|
||||
- (ASSizeRange)_locked_constrainedSizeForLayoutPass;
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (Yoga)
|
||||
|
||||
- (void)setYogaNode:(YGNodeRef)yogaNode
|
||||
{
|
||||
_yogaNode = yogaNode;
|
||||
}
|
||||
|
||||
- (YGNodeRef)yogaNode
|
||||
{
|
||||
if (_yogaNode == NULL) {
|
||||
_yogaNode = YGNodeNew();
|
||||
}
|
||||
return _yogaNode;
|
||||
}
|
||||
|
||||
- (void)setYogaParent:(ASDisplayNode *)yogaParent
|
||||
{
|
||||
if (_yogaParent == yogaParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
|
||||
YGNodeRef oldParentRef = YGNodeGetParent(yogaNode);
|
||||
if (oldParentRef != NULL) {
|
||||
YGNodeRemoveChild(oldParentRef, yogaNode);
|
||||
}
|
||||
|
||||
_yogaParent = yogaParent;
|
||||
if (yogaParent) {
|
||||
self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled;
|
||||
YGNodeRef newParentRef = yogaParent.yogaNode;
|
||||
YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef));
|
||||
} else {
|
||||
self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)yogaParent
|
||||
{
|
||||
return _yogaParent;
|
||||
}
|
||||
|
||||
- (void)setYogaChildren:(NSArray *)yogaChildren
|
||||
{
|
||||
for (ASDisplayNode *child in _yogaChildren) {
|
||||
for (ASDisplayNode *child in [_yogaChildren copy]) {
|
||||
// Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren
|
||||
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
|
||||
[self removeYogaChild:child];
|
||||
@@ -243,11 +68,10 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
// Clean up state in case this child had another parent.
|
||||
[self removeYogaChild:child];
|
||||
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
[_yogaChildren addObject:child];
|
||||
|
||||
self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled;
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
}
|
||||
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child
|
||||
@@ -255,13 +79,45 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
// YGNodeRef removal is done in setParent:
|
||||
child.yogaParent = nil;
|
||||
|
||||
[_yogaChildren removeObjectIdenticalTo:child];
|
||||
|
||||
if (_yogaChildren.count == 0 && self.yogaParent == nil) {
|
||||
self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled;
|
||||
// YGNodeRef removal is done in setParent:
|
||||
child.yogaParent = nil;
|
||||
}
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
|
||||
{
|
||||
if (AS_AT_LEAST_IOS9) {
|
||||
UIUserInterfaceLayoutDirection layoutDirection =
|
||||
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
|
||||
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
|
||||
? YGDirectionLTR : YGDirectionRTL);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setYogaParent:(ASDisplayNode *)yogaParent
|
||||
{
|
||||
if (_yogaParent == yogaParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
YGNodeRef yogaNode = [self.style yogaNodeCreateIfNeeded];
|
||||
YGNodeRef oldParentRef = YGNodeGetParent(yogaNode);
|
||||
if (oldParentRef != NULL) {
|
||||
YGNodeRemoveChild(oldParentRef, yogaNode);
|
||||
}
|
||||
|
||||
_yogaParent = yogaParent;
|
||||
if (yogaParent) {
|
||||
YGNodeRef newParentRef = [yogaParent.style yogaNodeCreateIfNeeded];
|
||||
YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef));
|
||||
}
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)yogaParent
|
||||
{
|
||||
return _yogaParent;
|
||||
}
|
||||
|
||||
- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout
|
||||
@@ -274,20 +130,30 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
return _yogaCalculatedLayout;
|
||||
}
|
||||
|
||||
- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress
|
||||
{
|
||||
setFlag(YogaLayoutInProgress, yogaLayoutInProgress);
|
||||
[self updateYogaMeasureFuncIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL)yogaLayoutInProgress
|
||||
{
|
||||
return checkFlag(YogaLayoutInProgress);
|
||||
}
|
||||
|
||||
- (ASLayout *)layoutForYogaNode
|
||||
{
|
||||
YGNodeRef yogaNode = self.yogaNode;
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode));
|
||||
|
||||
// TODO: If it were possible to set .flattened = YES, it would be valid to do so here.
|
||||
return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil];
|
||||
}
|
||||
|
||||
- (void)setupYogaCalculatedLayout
|
||||
{
|
||||
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
uint32_t childCount = YGNodeGetChildCount(yogaNode);
|
||||
ASDisplayNodeAssert(childCount == self.yogaChildren.count,
|
||||
@"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren);
|
||||
@@ -300,109 +166,69 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
// The layout for self should have position CGPointNull, but include the calculated size.
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
|
||||
|
||||
self.yogaCalculatedLayout = layout;
|
||||
}
|
||||
|
||||
- (void)setYogaMeasureFuncIfNeeded
|
||||
- (void)updateYogaMeasureFuncIfNeeded
|
||||
{
|
||||
// Manual size calculation via calculateSizeThatFits:
|
||||
// This will be used for ASTextNode, as well as any other leaf node that has no layout spec.
|
||||
if ((self.methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == NO
|
||||
&& self.layoutSpecBlock == NULL && self.yogaChildren.count == 0) {
|
||||
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
|
||||
YGNodeSetContext(yogaNode, (__bridge void *)self);
|
||||
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
|
||||
}
|
||||
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
|
||||
// This will be used for ASTextNode, as well as any other node that has no Yoga children
|
||||
BOOL isLeafNode = (self.yogaChildren.count == 0);
|
||||
BOOL definesCustomLayout = [self implementsLayoutMethod];
|
||||
|
||||
// We set the measure func only during layout. Otherwise, a cycle is created:
|
||||
// The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef.
|
||||
BOOL shouldHaveMeasureFunc = (isLeafNode && definesCustomLayout && checkFlag(YogaLayoutInProgress));
|
||||
|
||||
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil);
|
||||
}
|
||||
|
||||
- (void)invalidateCalculatedYogaLayout
|
||||
{
|
||||
// Yoga internally asserts that this method may only be called on nodes with a measurement function.
|
||||
YGNodeRef yogaNode = self.yogaNode;
|
||||
if (YGNodeGetMeasureFunc(yogaNode)) {
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
if (yogaNode && YGNodeGetMeasureFunc(yogaNode)) {
|
||||
YGNodeMarkDirty(yogaNode);
|
||||
}
|
||||
self.yogaCalculatedLayout = nil;
|
||||
}
|
||||
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
|
||||
{
|
||||
if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState)) {
|
||||
ASDisplayNodeAssert(NO, @"A Yoga layout is being performed by a parent; children must not perform their own until it is done! %@", [self displayNodeRecursiveDescription]);
|
||||
ASDisplayNode *yogaParent = self.yogaParent;
|
||||
|
||||
if (yogaParent) {
|
||||
ASYogaLog(@"ESCALATING to Yoga root: %@", self);
|
||||
// TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually.
|
||||
[yogaParent calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained];
|
||||
return;
|
||||
}
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// Prepare all children for the layout pass with the current Yoga tree configuration.
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
node.hierarchyState |= ASHierarchyStateYogaLayoutMeasuring;
|
||||
node.yogaLayoutInProgress = YES;
|
||||
});
|
||||
|
||||
YGNodeRef rootYogaNode = self.yogaNode;
|
||||
if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {
|
||||
rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||
}
|
||||
|
||||
ASYogaLog(@"CALCULATING at Yoga root with constraint = {%@, %@}: %@",
|
||||
NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self);
|
||||
|
||||
YGNodeRef rootYogaNode = self.style.yogaNode;
|
||||
|
||||
// Apply the constrainedSize as a base, known frame of reference.
|
||||
// If the root node also has style.*Size set, these will be overridden below.
|
||||
// YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there).
|
||||
|
||||
// TODO(appleguy): Reconcile the self.style.*Size properties with rootConstrainedSize
|
||||
YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width));
|
||||
YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height));
|
||||
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
ASLayoutElementStyle *style = node.style;
|
||||
YGNodeRef yogaNode = node.yogaNode;
|
||||
|
||||
YGNodeStyleSetDirection (yogaNode, YGDirectionInherit);
|
||||
|
||||
YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap);
|
||||
YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow);
|
||||
YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink);
|
||||
YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis);
|
||||
|
||||
YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.direction));
|
||||
YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent));
|
||||
YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf));
|
||||
ASStackLayoutAlignItems alignItems = style.alignItems;
|
||||
if (alignItems != ASStackLayoutAlignItemsNotSet) {
|
||||
YGNodeStyleSetAlignItems(yogaNode, yogaAlignItems(alignItems));
|
||||
}
|
||||
|
||||
YGNodeStyleSetPositionType (yogaNode, style.positionType);
|
||||
ASEdgeInsets position = style.position;
|
||||
ASEdgeInsets margin = style.margin;
|
||||
ASEdgeInsets padding = style.padding;
|
||||
ASEdgeInsets border = style.border;
|
||||
|
||||
YGEdge edge = YGEdgeLeft;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge);
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge);
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge);
|
||||
YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge);
|
||||
edge = (edge == YGEdgeLeft ? YGEdgeTop : (edge == YGEdgeTop ? YGEdgeRight : YGEdgeBottom));
|
||||
}
|
||||
|
||||
CGFloat aspectRatio = style.aspectRatio;
|
||||
if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) {
|
||||
YGNodeStyleSetAspectRatio(yogaNode, aspectRatio);
|
||||
}
|
||||
|
||||
// For the root node, we use rootConstrainedSize above. For children, consult the style for their size.
|
||||
if (node != self) {
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, Width, style.width);
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, Height, style.height);
|
||||
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, MinWidth, style.minWidth);
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, MinHeight, style.minHeight);
|
||||
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxWidth, style.maxWidth);
|
||||
YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxHeight, style.maxHeight);
|
||||
}
|
||||
|
||||
[node setYogaMeasureFuncIfNeeded];
|
||||
|
||||
/* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT
|
||||
void YGNodeStyleSetFlexDirection(YGNodeRef node, YGFlexDirection flexDirection);
|
||||
void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow);
|
||||
void YGNodeStyleSetFlex(YGNodeRef node, float flex);
|
||||
*/
|
||||
});
|
||||
|
||||
// It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here.
|
||||
YGNodeCalculateLayout(rootYogaNode,
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.width),
|
||||
@@ -411,10 +237,10 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node setupYogaCalculatedLayout];
|
||||
node.hierarchyState &= ~ASHierarchyStateYogaLayoutMeasuring;
|
||||
node.yogaLayoutInProgress = NO;
|
||||
});
|
||||
|
||||
#if YOGA_LAYOUT_LOGGING
|
||||
#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */
|
||||
// Concurrent layouts will interleave the NSLog messages unless we serialize.
|
||||
// Use @synchornize rather than trampolining to the main thread so the tree state isn't changed.
|
||||
@synchronized ([ASDisplayNode class]) {
|
||||
@@ -429,7 +255,7 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
YGNodePrint(node.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
#endif /* YOGA_LAYOUT_LOGGING */
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASDisplayNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
@@ -18,6 +25,7 @@
|
||||
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -49,7 +57,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(__kindof ASDisplayNode * node);
|
||||
/**
|
||||
* ASDisplayNode will / did render node content in context.
|
||||
*/
|
||||
typedef void (^ASDisplayNodeContextModifier)(CGContextRef context);
|
||||
typedef void (^ASDisplayNodeContextModifier)(CGContextRef context, id _Nullable drawParameters);
|
||||
|
||||
/**
|
||||
* ASDisplayNode layout spec block. This block can be used instead of implementing layoutSpecThatFits: in subclass
|
||||
@@ -114,7 +122,11 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
*
|
||||
*/
|
||||
|
||||
@interface ASDisplayNode : NSObject <ASLayoutElement, ASLayoutElementStylability, NSFastEnumeration>
|
||||
@interface ASDisplayNode : NSObject
|
||||
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(nullable id)parameters
|
||||
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock
|
||||
isRasterizing:(BOOL)isRasterizing;
|
||||
|
||||
/** @name Initializing a node object */
|
||||
|
||||
@@ -178,8 +190,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
* @param body The work to be performed when the node is loaded.
|
||||
*
|
||||
* @precondition The node is not already loaded.
|
||||
* @note This will only be called the next time the node is loaded. If the node is later added to a subtree of a node
|
||||
* that has `shouldRasterizeDescendants=YES`, and is unloaded, this block will not be called if it is loaded again.
|
||||
*/
|
||||
- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body;
|
||||
|
||||
@@ -197,7 +207,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
/**
|
||||
* Set the block that should be used to load this node's layer.
|
||||
*
|
||||
* @param viewBlock The block that creates a layer for this node.
|
||||
* @param layerBlock The block that creates a layer for this node.
|
||||
*
|
||||
* @precondition The node is not yet loaded.
|
||||
*
|
||||
@@ -210,8 +220,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
*
|
||||
* @return NO if the node wraps a _ASDisplayView, YES otherwise.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign, getter=isSynchronous) BOOL synchronous;
|
||||
|
||||
@property (atomic, readonly, assign, getter=isSynchronous) BOOL synchronous;
|
||||
|
||||
/** @name Getting view and layer */
|
||||
|
||||
@@ -291,61 +300,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
*/
|
||||
@property (nonatomic, class, copy) ASDisplayNodeNonFatalErrorBlock nonFatalErrorBlock;
|
||||
|
||||
|
||||
/** @name Managing dimensions */
|
||||
|
||||
/**
|
||||
* @abstract Asks the node to return a layout based on given size range.
|
||||
*
|
||||
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
|
||||
*
|
||||
* @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used).
|
||||
*
|
||||
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
|
||||
* constraint and the result.
|
||||
*
|
||||
* @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may
|
||||
* be expensive if result is not cached.
|
||||
*
|
||||
* @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:]
|
||||
*/
|
||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize;
|
||||
|
||||
/**
|
||||
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
|
||||
* implement layoutSpecThatFits:
|
||||
*
|
||||
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
|
||||
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
|
||||
* method -layoutSpecThatFits:
|
||||
*
|
||||
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
|
||||
* an exception. A future version of the framework may support using both, calling them serially, with the
|
||||
* .layoutSpecBlock superseding any values set by the method override.
|
||||
*
|
||||
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
|
||||
*/
|
||||
@property (nonatomic, readwrite, copy, nullable) ASLayoutSpecBlock layoutSpecBlock;
|
||||
|
||||
/**
|
||||
* @abstract Return the calculated size.
|
||||
*
|
||||
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
||||
* calling -measure: on them in -calculateLayoutThatFits.
|
||||
*
|
||||
* @return Size already calculated by -calculateLayoutThatFits:.
|
||||
*
|
||||
* @warning Subclasses must not override this; it returns the last cached measurement and is never expensive.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGSize calculatedSize;
|
||||
|
||||
/**
|
||||
* @abstract Return the constrained size range used for calculating layout.
|
||||
*
|
||||
* @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout;
|
||||
|
||||
/** @name Managing the nodes hierarchy */
|
||||
|
||||
|
||||
@@ -518,7 +472,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
* @discussion Defaults to ASDefaultDrawingPriority. There may be multiple drawing threads, and some of them may
|
||||
* decide to perform operations in queued order (regardless of drawingPriority)
|
||||
*/
|
||||
@property (nonatomic, assign) NSInteger drawingPriority;
|
||||
@property (atomic, assign) NSInteger drawingPriority;
|
||||
|
||||
/** @name Hit Testing */
|
||||
|
||||
@@ -592,12 +546,41 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
*/
|
||||
- (CGRect)convertRect:(CGRect)rect fromNode:(nullable ASDisplayNode *)node AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Whether or not the node would support having .layerBacked = YES.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL supportsLayerBacking;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Convenience methods for debugging.
|
||||
*/
|
||||
@interface ASDisplayNode (Debugging) <ASLayoutElementAsciiArtProtocol, ASDebugNameProvider>
|
||||
@interface ASDisplayNode (Debugging) <ASDebugNameProvider>
|
||||
|
||||
/**
|
||||
* Set to YES to tell all ASDisplayNode instances to store their unflattened layouts.
|
||||
*
|
||||
* The layout can be accessed via `-unflattenedCalculatedLayout`.
|
||||
*
|
||||
* Flattened layouts use less memory and are faster to lookup. On the other hand, unflattened layouts are useful for debugging
|
||||
* because they preserve original information.
|
||||
*/
|
||||
+ (void)setShouldStoreUnflattenedLayouts:(BOOL)shouldStore;
|
||||
|
||||
/**
|
||||
* Whether or not ASDisplayNode instances should store their unflattened layouts.
|
||||
*
|
||||
* The layout can be accessed via `-unflattenedCalculatedLayout`.
|
||||
*
|
||||
* Flattened layouts use less memory and are faster to lookup. On the other hand, unflattened layouts are useful for debugging
|
||||
* because they preserve original information.
|
||||
*
|
||||
* Defaults to NO.
|
||||
*/
|
||||
+ (BOOL)shouldStoreUnflattenedLayouts;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) ASLayout *unflattenedCalculatedLayout;
|
||||
|
||||
/**
|
||||
* @abstract Return a description of the node hierarchy.
|
||||
@@ -608,7 +591,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* ## UIView bridge
|
||||
*
|
||||
@@ -639,29 +621,37 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
*/
|
||||
- (void)setNeedsLayout;
|
||||
|
||||
@property (nonatomic, strong, nullable) id contents; // default=nil
|
||||
/**
|
||||
* Performs a layout pass on the node. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.
|
||||
*/
|
||||
- (void)layoutIfNeeded;
|
||||
|
||||
@property (nonatomic, assign) CGRect frame; // default=CGRectZero
|
||||
@property (nonatomic, assign) CGRect bounds; // default=CGRectZero
|
||||
@property (nonatomic, assign) CGPoint position; // default=CGPointZero
|
||||
@property (nonatomic, assign) CGFloat alpha; // default=1.0f
|
||||
|
||||
@property (nonatomic, assign) BOOL clipsToBounds; // default==NO
|
||||
@property (nonatomic, getter=isHidden) BOOL hidden; // default==NO
|
||||
@property (nonatomic, getter=isOpaque) BOOL opaque; // default==YES
|
||||
|
||||
@property (nonatomic, assign) BOOL allowsGroupOpacity;
|
||||
@property (nonatomic, assign) BOOL allowsEdgeAntialiasing;
|
||||
@property (nonatomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask
|
||||
@property (nonatomic, strong, nullable) id contents; // default=nil
|
||||
@property (nonatomic, assign) CGRect contentsRect; // default={0,0,1,1}. @see CALayer.h for details.
|
||||
@property (nonatomic, assign) CGRect contentsCenter; // default={0,0,1,1}. @see CALayer.h for details.
|
||||
@property (nonatomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for details.
|
||||
@property (nonatomic, assign) CGFloat rasterizationScale; // default=1.0f.
|
||||
|
||||
@property (nonatomic, getter=isHidden) BOOL hidden; // default==NO
|
||||
@property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO
|
||||
@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes)
|
||||
@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes)
|
||||
@property (nonatomic, assign) CGFloat alpha; // default=1.0f
|
||||
@property (nonatomic, assign) CGRect bounds; // default=CGRectZero
|
||||
@property (nonatomic, assign) CGRect frame; // default=CGRectZero
|
||||
@property (nonatomic, assign) CGPoint anchorPoint; // default={0.5, 0.5}
|
||||
@property (nonatomic, assign) CGFloat zPosition; // default=0.0
|
||||
@property (nonatomic, assign) CGPoint position; // default=CGPointZero
|
||||
@property (nonatomic, assign) CGFloat cornerRadius; // default=0.0
|
||||
@property (nonatomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info
|
||||
@property (nonatomic, assign) CATransform3D transform; // default=CATransform3DIdentity
|
||||
@property (nonatomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity
|
||||
|
||||
@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
|
||||
#if TARGET_OS_IOS
|
||||
@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @abstract The node view's background color.
|
||||
*
|
||||
@@ -682,17 +672,23 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
* contentMode for your content while it's being re-rendered.
|
||||
*/
|
||||
@property (nonatomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
|
||||
@property (nonatomic, copy) NSString *contentsGravity; // Use .contentMode in preference when possible.
|
||||
@property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute; // default=Unspecified
|
||||
|
||||
@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
|
||||
#if TARGET_OS_IOS
|
||||
@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
|
||||
#endif
|
||||
@property (nonatomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black
|
||||
@property (nonatomic, nullable) CGColorRef shadowColor; // default=opaque rgb black
|
||||
@property (nonatomic, assign) CGFloat shadowOpacity; // default=0.0
|
||||
@property (nonatomic, assign) CGSize shadowOffset; // default=(0, -3)
|
||||
@property (nonatomic, assign) CGFloat shadowRadius; // default=3
|
||||
@property (nonatomic, assign) CGFloat borderWidth; // default=0
|
||||
@property (nonatomic, assign, nullable) CGColorRef borderColor; // default=opaque rgb black
|
||||
@property (nonatomic, nullable) CGColorRef borderColor; // default=opaque rgb black
|
||||
|
||||
@property (nonatomic, assign) BOOL allowsGroupOpacity;
|
||||
@property (nonatomic, assign) BOOL allowsEdgeAntialiasing;
|
||||
@property (nonatomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask
|
||||
|
||||
@property (nonatomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO
|
||||
@property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes)
|
||||
@property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes)
|
||||
|
||||
// UIResponder methods
|
||||
// By default these fall through to the underlying view, but can be overridden.
|
||||
@@ -740,7 +736,74 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (LayoutTransitioning)
|
||||
@interface ASDisplayNode (ASLayoutElement) <ASLayoutElement>
|
||||
|
||||
/**
|
||||
* @abstract Asks the node to return a layout based on given size range.
|
||||
*
|
||||
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
|
||||
*
|
||||
* @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used).
|
||||
*
|
||||
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
|
||||
* constraint and the result.
|
||||
*
|
||||
* @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may
|
||||
* be expensive if result is not cached.
|
||||
*
|
||||
* @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:]
|
||||
*/
|
||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayoutElementStylability) <ASLayoutElementStylability>
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayout)
|
||||
|
||||
/** @name Managing dimensions */
|
||||
|
||||
/**
|
||||
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
|
||||
* implement layoutSpecThatFits:
|
||||
*
|
||||
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
|
||||
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
|
||||
* method -layoutSpecThatFits:
|
||||
*
|
||||
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
|
||||
* an exception. A future version of the framework may support using both, calling them serially, with the
|
||||
* .layoutSpecBlock superseding any values set by the method override.
|
||||
*
|
||||
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
|
||||
*/
|
||||
@property (nonatomic, readwrite, copy, nullable) ASLayoutSpecBlock layoutSpecBlock;
|
||||
|
||||
/**
|
||||
* @abstract Return the calculated size.
|
||||
*
|
||||
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
||||
* calling -measure: on them in -calculateLayoutThatFits.
|
||||
*
|
||||
* @return Size already calculated by -calculateLayoutThatFits:.
|
||||
*
|
||||
* @warning Subclasses must not override this; it returns the last cached measurement and is never expensive.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGSize calculatedSize;
|
||||
|
||||
/**
|
||||
* @abstract Return the constrained size range used for calculating layout.
|
||||
*
|
||||
* @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayoutTransitioning)
|
||||
|
||||
/**
|
||||
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
|
||||
@@ -814,7 +877,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
/*
|
||||
* ASDisplayNode support for automatic subnode management.
|
||||
*/
|
||||
@interface ASDisplayNode (AutomaticSubnodeManagement)
|
||||
@interface ASDisplayNode (ASAutomaticSubnodeManagement)
|
||||
|
||||
/**
|
||||
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASDisplayNodeExtras.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <QuartzCore/QuartzCore.h>
|
||||
@@ -20,13 +27,21 @@
|
||||
* for the nodes will be set to `MYButtonNode.titleNode` and `MYButtonNode.countNode`.
|
||||
*/
|
||||
#if DEBUG
|
||||
#define ASSetDebugName(node, format, ...) node.debugName = [NSString stringWithFormat:format, __VA_ARGS__]
|
||||
#define ASSetDebugNames(...) _ASSetDebugNames(self.class, @"" # __VA_ARGS__, __VA_ARGS__, nil)
|
||||
#else
|
||||
#define ASSetDebugName(node, name)
|
||||
#define ASSetDebugNames(...)
|
||||
#endif
|
||||
|
||||
/// For deallocation of objects on the main thread across multiple run loops.
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern void ASPerformMainThreadDeallocation(_Nullable id object);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// Because inline methods can't be extern'd and need to be part of the translation unit of code
|
||||
// that compiles with them to actually inline, we both declare and define these in the header.
|
||||
@@ -71,6 +86,30 @@ __unused static NSString * _Nonnull NSStringFromASInterfaceState(ASInterfaceStat
|
||||
return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]];
|
||||
}
|
||||
|
||||
#define INTERFACE_STATE_DELTA(Name) ({ \
|
||||
if ((oldState & ASInterfaceState##Name) != (newState & ASInterfaceState##Name)) { \
|
||||
[changes appendFormat:@"%c%s ", (newState & ASInterfaceState##Name ? '+' : '-'), #Name]; \
|
||||
} \
|
||||
})
|
||||
|
||||
/// e.g. { +Visible, -Preload } (although that should never actually happen.)
|
||||
/// NOTE: Changes to MeasureLayout state don't really mean anything so we omit them for now.
|
||||
__unused static NSString * _Nonnull NSStringFromASInterfaceStateChange(ASInterfaceState oldState, ASInterfaceState newState)
|
||||
{
|
||||
if (oldState == newState) {
|
||||
return @"{ }";
|
||||
}
|
||||
|
||||
NSMutableString *changes = [NSMutableString stringWithString:@"{ "];
|
||||
INTERFACE_STATE_DELTA(Preload);
|
||||
INTERFACE_STATE_DELTA(Display);
|
||||
INTERFACE_STATE_DELTA(Visible);
|
||||
[changes appendString:@"}"];
|
||||
return changes;
|
||||
}
|
||||
|
||||
#undef INTERFACE_STATE_DELTA
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
@@ -91,7 +130,7 @@ extern ASDisplayNode * _Nullable ASLayerToDisplayNode(CALayer * _Nullable layer)
|
||||
extern ASDisplayNode * _Nullable ASViewToDisplayNode(UIView * _Nullable view) AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
Given a node, returns the root of the node heirarchy (where supernode == nil)
|
||||
Given a node, returns the root of the node hierarchy (where supernode == nil)
|
||||
*/
|
||||
extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node) AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@@ -118,12 +157,12 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL tr
|
||||
/**
|
||||
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
|
||||
*/
|
||||
extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT;
|
||||
extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodes` property instead.");
|
||||
|
||||
/**
|
||||
Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class.
|
||||
*/
|
||||
extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT;
|
||||
extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodeOfClass:includingSelf:` method instead.");
|
||||
|
||||
/**
|
||||
* Given a layer, find the window it lives in, if any.
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
//
|
||||
// ASDisplayNodeExtras.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
|
||||
|
||||
#import <queue>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
@@ -90,7 +98,7 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDi
|
||||
layer = node.layer;
|
||||
}
|
||||
|
||||
if (traverseSublayers && layer && node.shouldRasterizeDescendants == NO) {
|
||||
if (traverseSublayers && layer && node.rasterizesSubtree == NO) {
|
||||
/// NOTE: The docs say `sublayers` returns a copy, but it does not.
|
||||
/// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated
|
||||
for (CALayer *sublayer in [[layer sublayers] copy]) {
|
||||
@@ -131,24 +139,21 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL tr
|
||||
|
||||
ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
CALayer *layer = node.layer;
|
||||
|
||||
while (layer) {
|
||||
node = ASLayerToDisplayNode(layer);
|
||||
if (block(node)) {
|
||||
return node;
|
||||
// This function has historically started with `self` but the name suggests
|
||||
// that it wouldn't. Perhaps we should change the behavior.
|
||||
for (ASDisplayNode *ancestor in node.supernodesIncludingSelf) {
|
||||
if (block(ancestor)) {
|
||||
return ancestor;
|
||||
}
|
||||
layer = layer.superlayer;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
|
||||
{
|
||||
return ASDisplayNodeFindFirstSupernode(start, ^(ASDisplayNode *n) {
|
||||
return [n isKindOfClass:c];
|
||||
});
|
||||
// This function has historically started with `self` but the name suggests
|
||||
// that it wouldn't. Perhaps we should change the behavior.
|
||||
return [start supernodeOfClass:c includingSelf:YES];
|
||||
}
|
||||
|
||||
static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASEditableTextNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASEditableTextNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASEditableTextNode.h>
|
||||
@@ -306,6 +313,11 @@
|
||||
[super setLayerBacked:layerBacked];
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Configuration
|
||||
@synthesize delegate = _delegate;
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASImageNode+AnimatedImage.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Garrett Moon on 3/22/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASImageNode.h>
|
||||
@@ -24,6 +29,7 @@
|
||||
#import <AsyncDisplayKit/ASNetworkImageNode.h>
|
||||
#import <AsyncDisplayKit/ASWeakProxy.h>
|
||||
|
||||
#define ASAnimatedImageDebug 0
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@interface ASNetworkImageNode (Private)
|
||||
@@ -49,6 +55,7 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
return;
|
||||
}
|
||||
|
||||
id <ASAnimatedImageProtocol> previousAnimatedImage = _animatedImage;
|
||||
_animatedImage = animatedImage;
|
||||
|
||||
if (animatedImage != nil) {
|
||||
@@ -69,6 +76,13 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[self animatedImageSet:_animatedImage previousAnimatedImage:previousAnimatedImage];
|
||||
}
|
||||
|
||||
- (void)animatedImageSet:(id <ASAnimatedImageProtocol>)newAnimatedImage previousAnimatedImage:(id <ASAnimatedImageProtocol>)previousAnimatedImage
|
||||
{
|
||||
//Subclasses may override
|
||||
}
|
||||
|
||||
- (id <ASAnimatedImageProtocol>)animatedImage
|
||||
@@ -301,6 +315,7 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
self.lastDisplayLinkFire = 0;
|
||||
} else {
|
||||
self.contents = (__bridge id)frameImage;
|
||||
[self displayDidFinish];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASImageNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <UIKit/UIKit.h>
|
||||
@@ -143,6 +150,9 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
|
||||
*
|
||||
* @discussion Set this to an object which conforms to ASAnimatedImageProtocol
|
||||
* to have the ASImageNode playback an animated image.
|
||||
* @warning this method should not be overridden, it may not always be called as
|
||||
* another method is used internally. If you need to know when the animatedImage
|
||||
* is set, override @c animatedImageSet:previousAnimatedImage:
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) id <ASAnimatedImageProtocol> animatedImage;
|
||||
|
||||
@@ -163,6 +173,15 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
|
||||
*/
|
||||
@property (nonatomic, strong) NSString *animatedImageRunLoopMode;
|
||||
|
||||
/**
|
||||
* @abstract Method called when animated image has been set
|
||||
*
|
||||
* @discussion This method is for subclasses to override so they can know if an animated image
|
||||
* has been set on the node.
|
||||
* @warning this method is called with the node's lock held.
|
||||
*/
|
||||
- (void)animatedImageSet:(id <ASAnimatedImageProtocol>)newAnimatedImage previousAnimatedImage:(id <ASAnimatedImageProtocol>)previousAnimatedImage;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASImageNode (Unavailable)
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASImageNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASImageNode.h>
|
||||
@@ -25,7 +32,7 @@
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHashHelpers.h>
|
||||
#import <AsyncDisplayKit/ASHashing.h>
|
||||
#import <AsyncDisplayKit/ASWeakMap.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
|
||||
@@ -35,32 +42,45 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct ASImageNodeDrawParameters {
|
||||
BOOL opaque;
|
||||
CGRect bounds;
|
||||
CGFloat contentsScale;
|
||||
UIColor *backgroundColor;
|
||||
UIViewContentMode contentMode;
|
||||
BOOL cropEnabled;
|
||||
BOOL forceUpscaling;
|
||||
CGSize forcedSize;
|
||||
CGRect cropRect;
|
||||
CGRect cropDisplayBounds;
|
||||
asimagenode_modification_block_t imageModificationBlock;
|
||||
};
|
||||
typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
|
||||
|
||||
@interface ASImageNodeDrawParameters : NSObject {
|
||||
@package
|
||||
UIImage *_image;
|
||||
BOOL _opaque;
|
||||
CGRect _bounds;
|
||||
CGFloat _contentsScale;
|
||||
UIColor *_backgroundColor;
|
||||
UIViewContentMode _contentMode;
|
||||
BOOL _cropEnabled;
|
||||
BOOL _forceUpscaling;
|
||||
CGSize _forcedSize;
|
||||
CGRect _cropRect;
|
||||
CGRect _cropDisplayBounds;
|
||||
asimagenode_modification_block_t _imageModificationBlock;
|
||||
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
|
||||
ASImageNodeDrawParametersBlock _didDrawBlock;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASImageNodeDrawParameters
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Contains all data that is needed to generate the content bitmap.
|
||||
*/
|
||||
@interface ASImageNodeContentsKey : NSObject {}
|
||||
@interface ASImageNodeContentsKey : NSObject
|
||||
|
||||
@property (nonatomic, strong) UIImage *image;
|
||||
@property CGSize backingSize;
|
||||
@property CGRect imageDrawRect;
|
||||
@property BOOL isOpaque;
|
||||
@property (nonatomic, strong) UIColor *backgroundColor;
|
||||
@property (nonatomic, copy) ASDisplayNodeContextModifier preContextBlock;
|
||||
@property (nonatomic, copy) ASDisplayNodeContextModifier postContextBlock;
|
||||
@property (nonatomic, copy) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
|
||||
@property (nonatomic, copy) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
|
||||
@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock;
|
||||
|
||||
@end
|
||||
@@ -84,8 +104,8 @@ struct ASImageNodeDrawParameters {
|
||||
&& CGRectEqualToRect(_imageDrawRect, other.imageDrawRect)
|
||||
&& _isOpaque == other.isOpaque
|
||||
&& [_backgroundColor isEqual:other.backgroundColor]
|
||||
&& _preContextBlock == other.preContextBlock
|
||||
&& _postContextBlock == other.postContextBlock
|
||||
&& _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext
|
||||
&& _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext
|
||||
&& _imageModificationBlock == other.imageModificationBlock;
|
||||
} else {
|
||||
return NO;
|
||||
@@ -94,25 +114,29 @@ struct ASImageNodeDrawParameters {
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
NSUInteger subhashes[] = {
|
||||
// Profiling shows that the work done in UIImage's `hash` is on the order of 0.005ms on an A5 processor
|
||||
// and isn't proportional to the size of the image.
|
||||
[_image hash],
|
||||
|
||||
// TODO: Hashing the floats in a CGRect or CGSize is tricky. Equality of floats is
|
||||
// fuzzy, but it's a 100% requirement that two equal values must produce an identical hash value.
|
||||
// Until there's a robust solution for hashing floats, leave all float values out of the hash.
|
||||
// This may lead to a greater number of isEqual comparisons but does not comprimise correctness.
|
||||
//AS::hash<CGRect>()(_backingSize),
|
||||
//AS::hash<CGRect>()(_imageDrawRect),
|
||||
|
||||
AS::hash<BOOL>()(_isOpaque),
|
||||
[_backgroundColor hash],
|
||||
AS::hash<void *>()((void*)_preContextBlock),
|
||||
AS::hash<void *>()((void*)_postContextBlock),
|
||||
AS::hash<void *>()((void*)_imageModificationBlock),
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic warning "-Wpadded"
|
||||
struct {
|
||||
NSUInteger imageHash;
|
||||
CGSize backingSize;
|
||||
CGRect imageDrawRect;
|
||||
NSInteger isOpaque;
|
||||
NSUInteger backgroundColorHash;
|
||||
void *willDisplayNodeContentWithRenderingContext;
|
||||
void *didDisplayNodeContentWithRenderingContext;
|
||||
void *imageModificationBlock;
|
||||
#pragma clang diagnostic pop
|
||||
} data = {
|
||||
_image.hash,
|
||||
_backingSize,
|
||||
_imageDrawRect,
|
||||
_isOpaque,
|
||||
_backgroundColor.hash,
|
||||
(void *)_willDisplayNodeContentWithRenderingContext,
|
||||
(void *)_didDisplayNodeContentWithRenderingContext,
|
||||
(void *)_imageModificationBlock
|
||||
};
|
||||
return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));
|
||||
return ASHashBytes(&data, sizeof(data));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -128,7 +152,6 @@ struct ASImageNodeDrawParameters {
|
||||
void (^_displayCompletionBlock)(BOOL canceled);
|
||||
|
||||
// Drawing
|
||||
ASImageNodeDrawParameters _drawParameter;
|
||||
ASTextNode *_debugLabelNode;
|
||||
|
||||
// Cropping.
|
||||
@@ -142,17 +165,7 @@ struct ASImageNodeDrawParameters {
|
||||
@synthesize image = _image;
|
||||
@synthesize imageModificationBlock = _imageModificationBlock;
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[super initialize];
|
||||
|
||||
if (self != [ASImageNode class]) {
|
||||
// Prevent custom drawing in subclasses
|
||||
ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
|
||||
}
|
||||
}
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
@@ -185,6 +198,8 @@ struct ASImageNodeDrawParameters {
|
||||
[self invalidateAnimatedImage];
|
||||
}
|
||||
|
||||
#pragma mark - Placeholder
|
||||
|
||||
- (UIImage *)placeholderImage
|
||||
{
|
||||
// FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set.
|
||||
@@ -282,63 +297,61 @@ struct ASImageNodeDrawParameters {
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
_drawParameter = {
|
||||
.bounds = self.bounds,
|
||||
.opaque = self.opaque,
|
||||
.contentsScale = self.contentsScaleForDisplay,
|
||||
.backgroundColor = self.backgroundColor,
|
||||
.contentMode = self.contentMode,
|
||||
.cropEnabled = _cropEnabled,
|
||||
.forceUpscaling = _forceUpscaling,
|
||||
.forcedSize = _forcedSize,
|
||||
.cropRect = _cropRect,
|
||||
.cropDisplayBounds = _cropDisplayBounds,
|
||||
.imageModificationBlock = _imageModificationBlock
|
||||
ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init];
|
||||
drawParameters->_image = [self _locked_Image];
|
||||
drawParameters->_bounds = [self threadSafeBounds];
|
||||
drawParameters->_opaque = self.opaque;
|
||||
drawParameters->_contentsScale = _contentsScaleForDisplay;
|
||||
drawParameters->_backgroundColor = self.backgroundColor;
|
||||
drawParameters->_contentMode = self.contentMode;
|
||||
drawParameters->_cropEnabled = _cropEnabled;
|
||||
drawParameters->_forceUpscaling = _forceUpscaling;
|
||||
drawParameters->_forcedSize = _forcedSize;
|
||||
drawParameters->_cropRect = _cropRect;
|
||||
drawParameters->_cropDisplayBounds = _cropDisplayBounds;
|
||||
drawParameters->_imageModificationBlock = _imageModificationBlock;
|
||||
drawParameters->_willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext;
|
||||
drawParameters->_didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
// Hack for now to retain the weak entry that was created while this drawing happened
|
||||
drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_weakCacheEntry = entry;
|
||||
};
|
||||
|
||||
return nil;
|
||||
return drawParameters;
|
||||
}
|
||||
|
||||
- (NSDictionary *)debugLabelAttributes
|
||||
+ (UIImage *)displayWithParameters:(id<NSObject>)parameter isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
return @{
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:15.0],
|
||||
NSForegroundColorAttributeName: [UIColor redColor]
|
||||
};
|
||||
}
|
||||
ASImageNodeDrawParameters *drawParameter = (ASImageNodeDrawParameters *)parameter;
|
||||
|
||||
- (UIImage *)displayWithParameters:(id<NSObject> *)parameter isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
UIImage *image = self.image;
|
||||
UIImage *image = drawParameter->_image;
|
||||
if (image == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (_displayWithoutProcessing) {
|
||||
if (true) {
|
||||
return image;
|
||||
}
|
||||
|
||||
__instanceLock__.lock();
|
||||
ASImageNodeDrawParameters drawParameter = _drawParameter;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
CGRect drawParameterBounds = drawParameter.bounds;
|
||||
BOOL forceUpscaling = drawParameter.forceUpscaling;
|
||||
CGSize forcedSize = drawParameter.forcedSize;
|
||||
BOOL cropEnabled = drawParameter.cropEnabled;
|
||||
BOOL isOpaque = drawParameter.opaque;
|
||||
UIColor *backgroundColor = drawParameter.backgroundColor;
|
||||
UIViewContentMode contentMode = drawParameter.contentMode;
|
||||
CGFloat contentsScale = drawParameter.contentsScale;
|
||||
CGRect cropDisplayBounds = drawParameter.cropDisplayBounds;
|
||||
CGRect cropRect = drawParameter.cropRect;
|
||||
asimagenode_modification_block_t imageModificationBlock = drawParameter.imageModificationBlock;
|
||||
CGRect drawParameterBounds = drawParameter->_bounds;
|
||||
BOOL forceUpscaling = drawParameter->_forceUpscaling;
|
||||
CGSize forcedSize = drawParameter->_forcedSize;
|
||||
BOOL cropEnabled = drawParameter->_cropEnabled;
|
||||
BOOL isOpaque = drawParameter->_opaque;
|
||||
UIColor *backgroundColor = drawParameter->_backgroundColor;
|
||||
UIViewContentMode contentMode = drawParameter->_contentMode;
|
||||
CGFloat contentsScale = drawParameter->_contentsScale;
|
||||
CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds;
|
||||
CGRect cropRect = drawParameter->_cropRect;
|
||||
asimagenode_modification_block_t imageModificationBlock = drawParameter->_imageModificationBlock;
|
||||
ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds);
|
||||
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds);
|
||||
|
||||
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
|
||||
|
||||
@@ -355,19 +368,6 @@ struct ASImageNodeDrawParameters {
|
||||
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
|
||||
CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale));
|
||||
|
||||
if (_debugLabelNode) {
|
||||
CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);
|
||||
if (pixelCountRatio != 1.0) {
|
||||
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio];
|
||||
_debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]];
|
||||
_debugLabelNode.hidden = NO;
|
||||
[self setNeedsLayout];
|
||||
} else {
|
||||
_debugLabelNode.hidden = YES;
|
||||
_debugLabelNode.attributedText = nil;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill ||
|
||||
contentMode == UIViewContentModeScaleAspectFit ||
|
||||
contentMode == UIViewContentModeCenter;
|
||||
@@ -412,22 +412,25 @@ struct ASImageNodeDrawParameters {
|
||||
contentsKey.imageDrawRect = imageDrawRect;
|
||||
contentsKey.isOpaque = isOpaque;
|
||||
contentsKey.backgroundColor = backgroundColor;
|
||||
contentsKey.preContextBlock = preContextBlock;
|
||||
contentsKey.postContextBlock = postContextBlock;
|
||||
contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext;
|
||||
contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext;
|
||||
contentsKey.imageModificationBlock = imageModificationBlock;
|
||||
|
||||
if (isCancelled()) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
ASWeakMapEntry<UIImage *> *entry = [self.class contentsForkey:contentsKey isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled];
|
||||
if (entry == nil) { // If nil, we were cancelled.
|
||||
ASWeakMapEntry<UIImage *> *entry = [self.class contentsForkey:contentsKey
|
||||
drawParameters:parameter
|
||||
isCancelled:isCancelled];
|
||||
// If nil, we were cancelled.
|
||||
if (entry == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
__instanceLock__.lock();
|
||||
_weakCacheEntry = entry; // Retain so that the entry remains in the weak cache
|
||||
__instanceLock__.unlock();
|
||||
if (drawParameter->_didDrawBlock) {
|
||||
drawParameter->_didDrawBlock(entry);
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
@@ -435,7 +438,7 @@ struct ASImageNodeDrawParameters {
|
||||
static ASWeakMap<ASImageNodeContentsKey *, UIImage *> *cache = nil;
|
||||
static ASDN::Mutex cacheLock;
|
||||
|
||||
+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
{
|
||||
ASDN::MutexLocker l(cacheLock);
|
||||
@@ -444,13 +447,12 @@ static ASDN::Mutex cacheLock;
|
||||
}
|
||||
ASWeakMapEntry *entry = [cache entryForKey:key];
|
||||
if (entry != nil) {
|
||||
// cache hit
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
UIImage *contents = [self createContentsForkey:key isCancelled:isCancelled];
|
||||
UIImage *contents = [self createContentsForkey:key drawParameters:drawParameters isCancelled:isCancelled];
|
||||
if (contents == nil) { // If nil, we were cancelled
|
||||
return nil;
|
||||
}
|
||||
@@ -461,7 +463,7 @@ static ASDN::Mutex cacheLock;
|
||||
}
|
||||
}
|
||||
|
||||
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
// The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
|
||||
// A5 processor for a 400x800 backingSize.
|
||||
@@ -477,8 +479,8 @@ static ASDN::Mutex cacheLock;
|
||||
BOOL contextIsClean = YES;
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (context && key.preContextBlock) {
|
||||
key.preContextBlock(context);
|
||||
if (context && key.willDisplayNodeContentWithRenderingContext) {
|
||||
key.willDisplayNodeContentWithRenderingContext(context, drawParameters);
|
||||
contextIsClean = NO;
|
||||
}
|
||||
|
||||
@@ -509,8 +511,8 @@ static ASDN::Mutex cacheLock;
|
||||
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
|
||||
}
|
||||
|
||||
if (context && key.postContextBlock) {
|
||||
key.postContextBlock(context);
|
||||
if (context && key.didDisplayNodeContentWithRenderingContext) {
|
||||
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
|
||||
}
|
||||
|
||||
// The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an
|
||||
@@ -524,7 +526,7 @@ static ASDN::Mutex cacheLock;
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (key.imageModificationBlock != NULL) {
|
||||
if (key.imageModificationBlock) {
|
||||
result = key.imageModificationBlock(result);
|
||||
}
|
||||
|
||||
@@ -538,8 +540,26 @@ static ASDN::Mutex cacheLock;
|
||||
__instanceLock__.lock();
|
||||
void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock;
|
||||
UIImage *image = _image;
|
||||
BOOL hasDebugLabel = (_debugLabelNode != nil);
|
||||
__instanceLock__.unlock();
|
||||
|
||||
// Update the debug label if necessary
|
||||
if (hasDebugLabel) {
|
||||
// For debugging purposes we don't care about locking for now
|
||||
CGSize imageSize = image.size;
|
||||
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
|
||||
CGSize boundsSizeInPixels = CGSizeMake(std::floor(self.bounds.size.width * self.contentsScale), std::floor(self.bounds.size.height * self.contentsScale));
|
||||
CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);
|
||||
if (pixelCountRatio != 1.0) {
|
||||
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio];
|
||||
_debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]];
|
||||
_debugLabelNode.hidden = NO;
|
||||
} else {
|
||||
_debugLabelNode.hidden = YES;
|
||||
_debugLabelNode.attributedText = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've got a block to perform after displaying, do it.
|
||||
if (image && displayCompletionBlock) {
|
||||
|
||||
@@ -699,6 +719,15 @@ static ASDN::Mutex cacheLock;
|
||||
_debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize};
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)debugLabelAttributes
|
||||
{
|
||||
return @{
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:15.0],
|
||||
NSForegroundColorAttributeName: [UIColor redColor]
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Extras
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASMapNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
//
|
||||
// ASMapNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <AsyncDisplayKit/ASMapNode.h>
|
||||
|
||||
@@ -427,6 +436,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASMultiplexImageNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASMultiplexImageNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
@@ -17,8 +24,9 @@
|
||||
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#if PIN_REMOTE_IMAGE
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
|
||||
#else
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
@@ -172,7 +180,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
#if PIN_REMOTE_IMAGE
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
|
||||
#else
|
||||
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
|
||||
@@ -250,7 +258,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
- (BOOL)placeholderShouldPersist
|
||||
{
|
||||
return (self.image == nil && self.imageIdentifiers.count > 0);
|
||||
return (self.image == nil && self.animatedImage == nil && self.imageIdentifiers.count > 0);
|
||||
}
|
||||
|
||||
/* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary
|
||||
@@ -443,7 +451,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
// Grab the best possible image we can load right now.
|
||||
id bestImmediatelyAvailableImageIdentifier = nil;
|
||||
UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier];
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "%@ Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier);
|
||||
|
||||
// Load it. This kicks off cache fetching/downloading, as appropriate.
|
||||
[self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil];
|
||||
@@ -573,6 +581,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
return;
|
||||
}
|
||||
|
||||
as_activity_create_for_scope("Load next image for multiplex image node");
|
||||
as_log_verbose(ASImageLoadingLog(), "Loading image for %@ ident: %@", self, nextImageIdentifier);
|
||||
self.loadingImageIdentifier = nextImageIdentifier;
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
@@ -588,13 +598,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
[strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error];
|
||||
};
|
||||
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Loading next image, ident: %@", self, nextImageIdentifier);
|
||||
|
||||
// Ask our data-source if it's got this image.
|
||||
if (_dataSourceFlags.image) {
|
||||
UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier];
|
||||
if (image) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Acquired next image (%@) from data-source", self, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from data source for %@ ident: %@", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
@@ -603,7 +611,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil;
|
||||
// If we fail to get a URL for the image, we have no source and can't proceed.
|
||||
if (!nextImageURL) {
|
||||
ASMultiplexImageNodeLogError(@"[%p] Could not acquire URL for next image (%@). Bailing.", self, nextImageIdentifier);
|
||||
as_log_error(ASImageLoadingLog(), "Could not acquire URL %@ ident: (%@)", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]);
|
||||
return;
|
||||
}
|
||||
@@ -613,14 +621,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
|
||||
// Load the asset.
|
||||
[self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from asset library", weakSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
|
||||
else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) {
|
||||
[self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
@@ -635,7 +643,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
// If we had a cache-hit, we're done.
|
||||
if (imageFromCache) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from cache", strongSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache);
|
||||
finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
@@ -648,7 +656,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
// Otherwise, we've got to download it.
|
||||
[strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from download", strongSelf, nextImageIdentifier);
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (downloadedImage) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage);
|
||||
} else {
|
||||
as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error);
|
||||
}
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}];
|
||||
@@ -844,7 +857,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
// We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already.
|
||||
// Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image.
|
||||
if (image || imageIdentifierCount == 0) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image);
|
||||
as_log_verbose(ASImageLoadingLog(), "[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image);
|
||||
id previousIdentifier = self.loadedImageIdentifier;
|
||||
UIImage *previousImage = self.image;
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASNavigationController.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Garrett Moon on 4/27/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
//
|
||||
// ASNavigationController.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Garrett Moon on 4/27/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASNavigationController.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
@implementation ASNavigationController
|
||||
{
|
||||
@@ -60,39 +67,53 @@ ASVisibilityDepthImplementation;
|
||||
|
||||
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("Pop multiple from ASNavigationController");
|
||||
NSArray *viewControllers = [super popToViewController:viewController animated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped %@ to %@, removing %@", self, viewController, ASGetDescriptionValueString(viewControllers));
|
||||
|
||||
[self visibilityDepthDidChange];
|
||||
return viewControllers;
|
||||
}
|
||||
|
||||
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("Pop to root of ASNavigationController");
|
||||
NSArray *viewControllers = [super popToRootViewControllerAnimated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped view controllers %@ from %@", ASGetDescriptionValueString(viewControllers), self);
|
||||
|
||||
[self visibilityDepthDidChange];
|
||||
return viewControllers;
|
||||
}
|
||||
|
||||
- (void)setViewControllers:(NSArray *)viewControllers
|
||||
{
|
||||
// NOTE: As of now this method calls through to setViewControllers:animated: so no need to log/activity here.
|
||||
|
||||
[super setViewControllers:viewControllers];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("Set view controllers of ASNavigationController");
|
||||
as_log_info(ASNodeLog(), "Set view controllers of %@ to %@ animated: %d", self, ASGetDescriptionValueString(viewControllers), animated);
|
||||
[super setViewControllers:viewControllers animated:animated];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("Push view controller on ASNavigationController");
|
||||
as_log_info(ASNodeLog(), "Pushing %@ onto %@", viewController, self);
|
||||
[super pushViewController:viewController animated:animated];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("Pop view controller from ASNavigationController");
|
||||
UIViewController *viewController = [super popViewControllerAnimated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped %@ from %@", viewController, self);
|
||||
[self visibilityDepthDidChange];
|
||||
return viewController;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASNetworkImageNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
//
|
||||
// ASNetworkImageNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASNetworkImageNode.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
@@ -19,8 +27,9 @@
|
||||
#import <AsyncDisplayKit/ASImageNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
|
||||
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#if PIN_REMOTE_IMAGE
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
|
||||
#endif
|
||||
|
||||
@@ -59,6 +68,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
unsigned int downloaderImplementsSetProgress:1;
|
||||
unsigned int downloaderImplementsSetPriority:1;
|
||||
unsigned int downloaderImplementsAnimatedImage:1;
|
||||
unsigned int downloaderImplementsCancelWithResume:1;
|
||||
} _downloaderFlags;
|
||||
|
||||
// Immutable and set on init only. We don't need to lock in this case.
|
||||
@@ -86,6 +96,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
_downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
|
||||
_downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
|
||||
_downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)];
|
||||
_downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)];
|
||||
|
||||
_cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
|
||||
_cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)];
|
||||
@@ -99,7 +110,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
#if PIN_REMOTE_IMAGE
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
|
||||
#else
|
||||
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
|
||||
@@ -108,7 +119,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self _cancelImageDownload];
|
||||
[self _cancelImageDownloadWithResumePossibility:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Public methods -- must lock
|
||||
@@ -128,7 +139,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
if (shouldCancelAndClear) {
|
||||
ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first.");
|
||||
_URL = nil;
|
||||
[self _locked_cancelDownloadAndClearImage];
|
||||
[self _locked_cancelDownloadAndClearImageWithResumePossibility:NO];
|
||||
}
|
||||
|
||||
[self _locked__setImage:image];
|
||||
@@ -156,15 +167,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASDisplayNodeAssert(_imageWasSetExternally == NO, @"Setting a URL to an ASNetworkImageNode after setting an image changes its behavior from an ASImageNode to an ASNetworkImageNode. If this is what you want, set the image to nil first.");
|
||||
|
||||
_imageWasSetExternally = NO;
|
||||
|
||||
if (ASObjectIsEqual(URL, _URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self _locked_cancelImageDownload];
|
||||
ASDisplayNodeAssert(_imageWasSetExternally == NO, @"Setting a URL to an ASNetworkImageNode after setting an image changes its behavior from an ASImageNode to an ASNetworkImageNode. If this is what you want, set the image to nil first.");
|
||||
|
||||
_imageWasSetExternally = NO;
|
||||
|
||||
[self _locked_cancelImageDownloadWithResumePossibility:NO];
|
||||
|
||||
_imageLoaded = NO;
|
||||
|
||||
@@ -300,7 +311,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
- (BOOL)placeholderShouldPersist
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return (self.image == nil && _URL != nil);
|
||||
return (self.image == nil && self.animatedImage == nil && _URL != nil);
|
||||
}
|
||||
|
||||
/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
|
||||
@@ -386,7 +397,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
return;
|
||||
}
|
||||
|
||||
[self _cancelDownloadAndClearImage];
|
||||
[self _cancelDownloadAndClearImageWithResumePossibility:YES];
|
||||
}
|
||||
|
||||
- (void)didEnterPreloadState
|
||||
@@ -401,17 +412,16 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier
|
||||
{
|
||||
__instanceLock__.lock();
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// Getting a result back for a different download identifier, download must not have been successfully canceled
|
||||
if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage);
|
||||
[self _locked_setCurrentImageQuality:progress];
|
||||
[self _locked__setImage:progressImage];
|
||||
|
||||
__instanceLock__.unlock();
|
||||
}
|
||||
|
||||
- (void)_updateProgressImageBlockOnDownloaderIfNeeded
|
||||
@@ -436,12 +446,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
// Unbind from the previous download.
|
||||
if (oldDownloadIDForProgressBlock != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Disabled progress images for %@ id: %@", self, oldDownloadIDForProgressBlock);
|
||||
[_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:oldDownloadIDForProgressBlock];
|
||||
}
|
||||
|
||||
// Bind to the current download.
|
||||
if (newDownloadIDForProgressBlock != nil) {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
as_log_verbose(ASImageLoadingLog(), "Enabled progress images for %@ id: %@", self, newDownloadIDForProgressBlock);
|
||||
[_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) {
|
||||
[weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier];
|
||||
} callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock];
|
||||
@@ -470,15 +482,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_cancelDownloadAndClearImage
|
||||
- (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
[self _locked_cancelDownloadAndClearImage];
|
||||
[self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume];
|
||||
}
|
||||
|
||||
- (void)_locked_cancelDownloadAndClearImage
|
||||
- (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume
|
||||
{
|
||||
[self _locked_cancelImageDownload];
|
||||
[self _locked_cancelImageDownloadWithResumePossibility:storeResume];
|
||||
|
||||
// Destruction of bigger images on the main thread can be expensive
|
||||
// and can take some time, so we dispatch onto a bg queue to
|
||||
@@ -500,26 +512,33 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
if (_cacheFlags.cacheSupportsClearing) {
|
||||
if (_URL != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL);
|
||||
[_cache clearFetchedImageFromCacheWithURL:_URL];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_cancelImageDownload
|
||||
- (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
[self _locked_cancelImageDownload];
|
||||
[self _locked_cancelImageDownloadWithResumePossibility:storeResume];
|
||||
}
|
||||
|
||||
- (void)_locked_cancelImageDownload
|
||||
- (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume
|
||||
{
|
||||
if (!_downloadIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_downloadIdentifier) {
|
||||
if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download w resume for %@ id: %@", self, _downloadIdentifier);
|
||||
[_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier];
|
||||
} else {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, _downloadIdentifier);
|
||||
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||
}
|
||||
}
|
||||
_downloadIdentifier = nil;
|
||||
|
||||
_cacheUUID = nil;
|
||||
@@ -540,6 +559,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
url = _URL;
|
||||
}
|
||||
|
||||
|
||||
downloadIdentifier = [_downloader downloadImageWithURL:url
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
downloadProgress:NULL
|
||||
@@ -548,6 +568,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
finished(imageContainer, error, downloadIdentifier);
|
||||
}
|
||||
}];
|
||||
as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url);
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
@@ -563,6 +584,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
if (cancelAndReattempt) {
|
||||
if (downloadIdentifier != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, downloadIdentifier);
|
||||
[_downloader cancelImageDownloadForIdentifier:downloadIdentifier];
|
||||
}
|
||||
[self _downloadImageWithCompletion:finished];
|
||||
@@ -643,13 +665,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
});
|
||||
} else {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
void (^finished)(id <ASImageContainerProtocol>, NSError *, id downloadIdentifier) = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) {
|
||||
auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) {
|
||||
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], url);
|
||||
|
||||
// Grab the lock for the rest of the block
|
||||
ASDN::MutexLocker l(strongSelf->__instanceLock__);
|
||||
|
||||
@@ -691,6 +715,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
_cacheUUID = cacheUUID;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL);
|
||||
[_cache cachedImageWithURL:URL
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
completion:^(id <ASImageContainerProtocol> imageContainer) {
|
||||
@@ -706,6 +731,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
if ([imageContainer asdk_image] == nil && _downloader != nil) {
|
||||
[self _downloadImageWithCompletion:finished];
|
||||
} else {
|
||||
as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL);
|
||||
finished(imageContainer, nil, nil);
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
//
|
||||
// ASNodeController.h
|
||||
// AsyncDisplayKit
|
||||
// ASNodeController+Beta.h
|
||||
// Texture
|
||||
//
|
||||
// Created by Hannah Troisi for Scott Goodson on 1/27/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> // for ASInterfaceState protocol
|
||||
|
||||
// Until an ASNodeController can be provided in place of an ASCellNode, some apps may prefer to have
|
||||
// nodes keep their controllers alive (and a weak reference from controller to node)
|
||||
#define INVERT_NODE_CONTROLLER_OWNERSHIP 0
|
||||
|
||||
/* ASNodeController is currently beta and open to change in the future */
|
||||
@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> : NSObject <ASInterfaceStateDelegate>
|
||||
|
||||
#if INVERT_NODE_CONTROLLER_OWNERSHIP
|
||||
@property (nonatomic, weak) DisplayNodeType node;
|
||||
#else
|
||||
@property (nonatomic, strong) DisplayNodeType node;
|
||||
#endif
|
||||
|
||||
- (void)loadNode;
|
||||
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
//
|
||||
// ASNodeController.mm
|
||||
// AsyncDisplayKit
|
||||
// ASNodeController+Beta.m
|
||||
// Texture
|
||||
//
|
||||
// Created by Hannah Troisi for Scott Goodson on 1/27/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 "ASNodeController+Beta.h"
|
||||
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
|
||||
#if INVERT_NODE_CONTROLLER_OWNERSHIP
|
||||
|
||||
@interface ASDisplayNode (ASNodeController)
|
||||
@property (nonatomic, strong) ASNodeController *asdkNodeController;
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (ASNodeController)
|
||||
|
||||
- (ASNodeController *)asdkNodeController
|
||||
{
|
||||
return objc_getAssociatedObject(self, @selector(asdkNodeController));
|
||||
}
|
||||
|
||||
- (void)setAsdkNodeController:(ASNodeController *)asdkNodeController
|
||||
{
|
||||
objc_setAssociatedObject(self, @selector(asdkNodeController), asdkNodeController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@implementation ASNodeController
|
||||
|
||||
@synthesize node = _node;
|
||||
@@ -40,6 +70,9 @@
|
||||
{
|
||||
_node = node;
|
||||
_node.interfaceStateDelegate = self;
|
||||
#if INVERT_NODE_CONTROLLER_OWNERSHIP
|
||||
_node.asdkNodeController = self;
|
||||
#endif
|
||||
}
|
||||
|
||||
// subclass overrides
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASPagerFlowLayout.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 2/12/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASPagerFlowLayout.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 2/12/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -21,6 +26,7 @@
|
||||
|
||||
@end
|
||||
|
||||
//TODO make this an ASCollectionViewLayout
|
||||
@implementation ASPagerFlowLayout
|
||||
|
||||
- (ASCollectionView *)asCollectionView
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASPagerNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 12/7/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASPagerNode.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 12/7/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
35
Source/ASRangeManagingNode.h
Normal file
35
Source/ASRangeManagingNode.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// ASRangeManagingNode.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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
|
||||
@class ASCellNode;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Basically ASTableNode or ASCollectionNode.
|
||||
*/
|
||||
@protocol ASRangeManagingNode <NSObject, ASTraitEnvironment>
|
||||
|
||||
/**
|
||||
* Retrieve the index path for the given node, if it's a member of this container.
|
||||
*
|
||||
* @param node The node.
|
||||
* @return The index path, or nil if the node is not part of this container.
|
||||
*/
|
||||
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASRunLoopQueue.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Rahul Malik on 3/7/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <Foundation/Foundation.h>
|
||||
@@ -46,6 +51,8 @@ AS_SUBCLASSING_RESTRICTED
|
||||
|
||||
+ (instancetype)sharedDeallocationQueue;
|
||||
|
||||
- (void)test_drain;
|
||||
|
||||
- (void)releaseObjectInBackground:(id)object;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
//
|
||||
// ASRunLoopQueue.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Rahul Malik on 3/7/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <cstdlib>
|
||||
#import <deque>
|
||||
#import <vector>
|
||||
@@ -72,16 +80,20 @@ static void runLoopSourceCallback(void *info) {
|
||||
return;
|
||||
}
|
||||
// The scope below is entered while already locked. @autorelease is crucial here; see PR 2890.
|
||||
NSInteger count;
|
||||
@autoreleasepool {
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSLog(@"ASDeallocQueue Processing: %lu objects destroyed", weakSelf->_queue.size());
|
||||
#endif
|
||||
// Sometimes we release 10,000 objects at a time. Don't hold the lock while releasing.
|
||||
std::deque<id> currentQueue = weakSelf->_queue;
|
||||
count = currentQueue.size();
|
||||
ASSignpostStartCustom(ASSignpostDeallocQueueDrain, self, count);
|
||||
weakSelf->_queue = std::deque<id>();
|
||||
weakSelf->_queueLock.unlock();
|
||||
currentQueue.clear();
|
||||
}
|
||||
ASSignpostEndCustom(ASSignpostDeallocQueueDrain, self, count, ASSignpostColorDefault);
|
||||
});
|
||||
|
||||
CFRunLoopRef runloop = CFRunLoopGetCurrent();
|
||||
@@ -137,6 +149,29 @@ static void runLoopSourceCallback(void *info) {
|
||||
_thread = nil;
|
||||
}
|
||||
|
||||
- (void)test_drain
|
||||
{
|
||||
[self performSelector:@selector(_test_drain) onThread:_thread withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)_test_drain
|
||||
{
|
||||
while (true) {
|
||||
@autoreleasepool {
|
||||
_queueLock.lock();
|
||||
std::deque<id> currentQueue = _queue;
|
||||
_queue = std::deque<id>();
|
||||
_queueLock.unlock();
|
||||
|
||||
if (currentQueue.empty()) {
|
||||
return;
|
||||
} else {
|
||||
currentQueue.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_stop
|
||||
{
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
@@ -158,6 +193,9 @@ static void runLoopSourceCallback(void *info) {
|
||||
NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance.
|
||||
ASDN::RecursiveMutex _internalQueueLock;
|
||||
|
||||
// In order to not pollute the top-level activities, each queue has 1 root activity.
|
||||
os_activity_t _rootActivity;
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSTimer *_runloopQueueLoggingTimer;
|
||||
#endif
|
||||
@@ -167,8 +205,69 @@ static void runLoopSourceCallback(void *info) {
|
||||
|
||||
@end
|
||||
|
||||
#if AS_KDEBUG_ENABLE
|
||||
/**
|
||||
* This is real, private CA API. Valid as of iOS 10.
|
||||
*/
|
||||
typedef enum {
|
||||
kCATransactionPhasePreLayout,
|
||||
kCATransactionPhasePreCommit,
|
||||
kCATransactionPhasePostCommit,
|
||||
} CATransactionPhase;
|
||||
|
||||
@interface CATransaction (Private)
|
||||
+ (void)addCommitHandler:(void(^)(void))block forPhase:(CATransactionPhase)phase;
|
||||
+ (int)currentState;
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation ASRunLoopQueue
|
||||
|
||||
#if AS_KDEBUG_ENABLE
|
||||
+ (void)load
|
||||
{
|
||||
[self registerCATransactionObservers];
|
||||
}
|
||||
|
||||
+ (void)registerCATransactionObservers
|
||||
{
|
||||
static BOOL privateCAMethodsExist;
|
||||
static dispatch_block_t preLayoutHandler;
|
||||
static dispatch_block_t preCommitHandler;
|
||||
static dispatch_block_t postCommitHandler;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
privateCAMethodsExist = [CATransaction respondsToSelector:@selector(addCommitHandler:forPhase:)];
|
||||
privateCAMethodsExist &= [CATransaction respondsToSelector:@selector(currentState)];
|
||||
if (!privateCAMethodsExist) {
|
||||
NSLog(@"Private CA methods are gone.");
|
||||
}
|
||||
preLayoutHandler = ^{
|
||||
ASSignpostStartCustom(ASSignpostCATransactionLayout, 0, [CATransaction currentState]);
|
||||
};
|
||||
preCommitHandler = ^{
|
||||
int state = [CATransaction currentState];
|
||||
ASSignpostEndCustom(ASSignpostCATransactionLayout, 0, state, ASSignpostColorDefault);
|
||||
ASSignpostStartCustom(ASSignpostCATransactionCommit, 0, state);
|
||||
};
|
||||
postCommitHandler = ^{
|
||||
ASSignpostEndCustom(ASSignpostCATransactionCommit, 0, [CATransaction currentState], ASSignpostColorDefault);
|
||||
// Can't add new observers inside an observer. rdar://problem/31253952
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self registerCATransactionObservers];
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
if (privateCAMethodsExist) {
|
||||
[CATransaction addCommitHandler:preLayoutHandler forPhase:kCATransactionPhasePreLayout];
|
||||
[CATransaction addCommitHandler:preCommitHandler forPhase:kCATransactionPhasePreCommit];
|
||||
[CATransaction addCommitHandler:postCommitHandler forPhase:kCATransactionPhasePostCommit];
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AS_KDEBUG_ENABLE
|
||||
|
||||
- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock
|
||||
{
|
||||
if (self = [super init]) {
|
||||
@@ -179,6 +278,15 @@ static void runLoopSourceCallback(void *info) {
|
||||
_batchSize = 1;
|
||||
_ensureExclusiveMembership = YES;
|
||||
|
||||
// We don't want to pollute the top-level app activities with run loop batches, so we create one top-level
|
||||
// activity per queue, and each batch activity joins that one instead.
|
||||
_rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
{
|
||||
// Log a message identifying this queue into the queue's root activity.
|
||||
as_activity_scope_verbose(_rootActivity);
|
||||
as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self);
|
||||
}
|
||||
|
||||
// Self is guaranteed to outlive the observer. Without the high cost of a weak pointer,
|
||||
// __unsafe_unretained allows us to avoid flagging the memory cycle detector.
|
||||
__unsafe_unretained __typeof__(self) weakSelf = self;
|
||||
@@ -246,7 +354,7 @@ static void runLoopSourceCallback(void *info) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASProfilingSignpostStart(0, self);
|
||||
ASSignpostStart(ASSignpostRunLoopQueueBatch);
|
||||
|
||||
// Snatch the next batch of items.
|
||||
NSInteger maxCountToProcess = MIN(internalQueueCount, self.batchSize);
|
||||
@@ -281,16 +389,17 @@ static void runLoopSourceCallback(void *info) {
|
||||
}
|
||||
|
||||
// itemsToProcess will be empty if _queueConsumer == nil so no need to check again.
|
||||
if (itemsToProcess.empty() == false) {
|
||||
#if ASRunloopQueueLoggingEnabled
|
||||
NSLog(@"<%@> - Starting processing of: %ld", self, itemsToProcess.size());
|
||||
#endif
|
||||
auto count = itemsToProcess.size();
|
||||
if (count > 0) {
|
||||
as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
auto itemsEnd = itemsToProcess.cend();
|
||||
for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) {
|
||||
_queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1);
|
||||
#if ASRunloopQueueLoggingEnabled
|
||||
NSLog(@"<%@> - Finished processing 1 item", self);
|
||||
#endif
|
||||
__unsafe_unretained id value = *iterator;
|
||||
_queueConsumer(value, isQueueDrained && iterator == itemsEnd - 1);
|
||||
as_log_verbose(ASDisplayLog(), "processed %@", value);
|
||||
}
|
||||
if (count > 1) {
|
||||
as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +409,7 @@ static void runLoopSourceCallback(void *info) {
|
||||
CFRunLoopWakeUp(_runLoop);
|
||||
}
|
||||
|
||||
ASProfilingSignpostEnd(0, self);
|
||||
ASSignpostEnd(ASSignpostRunLoopQueueBatch);
|
||||
}
|
||||
|
||||
- (void)enqueue:(id)object
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASScrollNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASDisplayNode.h>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASScrollNode.m
|
||||
// AsyncDisplayKit
|
||||
// ASScrollNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASScrollNode.h>
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//
|
||||
// ASSectionController.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Adlai Holler on 1/19/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -17,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A protocol that your section controllers should conform to,
|
||||
* in addition to IGListSectionType, in order to be used with AsyncDisplayKit.
|
||||
* in order to be used with AsyncDisplayKit.
|
||||
*
|
||||
* @note Your supplementary view source should conform to @c ASSupplementaryNodeSource.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
//
|
||||
// ASSupplementaryNodeSource.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Created by Adlai Holler on 1/19/17.
|
||||
// Copyright © 2017 Facebook. All rights reserved.
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASTabBarController.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Garrett Moon on 5/10/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
//
|
||||
// ASTabBarController.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Garrett Moon on 5/10/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASTabBarController.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
@implementation ASTabBarController
|
||||
{
|
||||
@@ -68,12 +74,18 @@ ASVisibilityDepthImplementation;
|
||||
|
||||
- (void)setSelectedIndex:(NSUInteger)selectedIndex
|
||||
{
|
||||
as_activity_create_for_scope("Set selected index of ASTabBarController");
|
||||
as_log_info(ASNodeLog(), "Selected tab %tu of %@", selectedIndex, self);
|
||||
|
||||
[super setSelectedIndex:selectedIndex];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)setSelectedViewController:(__kindof UIViewController *)selectedViewController
|
||||
{
|
||||
as_activity_create_for_scope("Set selected view controller of ASTabBarController");
|
||||
as_log_info(ASNodeLog(), "Selected view controller %@ of %@", selectedViewController, self);
|
||||
|
||||
[super setSelectedViewController:selectedViewController];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
25
Source/ASTableNode+Beta.h
Normal file
25
Source/ASTableNode+Beta.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// ASTableNode+Beta.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/ASTableNode.h>
|
||||
|
||||
@protocol ASBatchFetchingDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASTableNode (Beta)
|
||||
|
||||
@property (nonatomic, weak) id<ASBatchFetchingDelegate> batchFetchingDelegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,20 +1,25 @@
|
||||
//
|
||||
// ASTableNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Steven Ramkumar on 11/4/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
|
||||
#import <AsyncDisplayKit/ASTableView.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASRangeManagingNode.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -26,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* ASTableNode is a node based class that wraps an ASTableView. It can be used
|
||||
* as a subnode of another node, and provide room for many (great) features and improvements later on.
|
||||
*/
|
||||
@interface ASTableNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol>
|
||||
@interface ASTableNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol, ASRangeManagingNode>
|
||||
|
||||
- (instancetype)init; // UITableViewStylePlain
|
||||
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
|
||||
@@ -37,11 +42,30 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (weak, nonatomic) id <ASTableDelegate> delegate;
|
||||
@property (weak, nonatomic) id <ASTableDataSource> dataSource;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -tableNode:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the table will be flipped.
|
||||
* If the value of this property is YES, the first cell node will be at the bottom of the table (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
|
||||
/**
|
||||
* YES to automatically adjust the contentOffset when cells are inserted or deleted above
|
||||
* visible cells, maintaining the users' visible scroll position.
|
||||
*
|
||||
* @note This is only applied to non-animated updates. For animated updates, there is no way to
|
||||
* synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations.
|
||||
*
|
||||
* default is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether users can select a row.
|
||||
* If the value of this property is YES (the default), users can select rows. If you set it to NO, they cannot select rows. Setting this property affects cell selection only when the table view is not in editing mode. If you want to restrict selection of cells in editing mode, use `allowsSelectionDuringEditing`.
|
||||
@@ -158,7 +182,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion;
|
||||
|
||||
/**
|
||||
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
|
||||
* Perform a batch of updates asynchronously with animations in the batch. This method must be called from the main thread.
|
||||
* The data source must be updated to reflect the changes before the update block completes.
|
||||
*
|
||||
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
//
|
||||
// ASTableNode.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Steven Ramkumar on 11/4/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASTableNode.h>
|
||||
#import <AsyncDisplayKit/ASTableNode+Beta.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASTableViewInternal.h>
|
||||
@@ -36,6 +43,8 @@
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelection;
|
||||
@property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing;
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset;
|
||||
@end
|
||||
|
||||
@implementation _ASTablePendingState
|
||||
@@ -49,6 +58,8 @@
|
||||
_allowsMultipleSelection = NO;
|
||||
_allowsMultipleSelectionDuringEditing = NO;
|
||||
_inverted = NO;
|
||||
_leadingScreensForBatching = 2;
|
||||
_automaticallyAdjustsContentOffset = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -60,15 +71,12 @@
|
||||
@interface ASTableNode ()
|
||||
{
|
||||
ASDN::RecursiveMutex _environmentStateLock;
|
||||
id<ASBatchFetchingDelegate> _batchFetchingDelegate;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) _ASTablePendingState *pendingState;
|
||||
@end
|
||||
|
||||
@interface ASTableView ()
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass;
|
||||
@end
|
||||
|
||||
@implementation ASTableNode
|
||||
|
||||
#pragma mark Lifecycle
|
||||
@@ -80,7 +88,7 @@
|
||||
[self setViewBlock:^{
|
||||
// Variable will be unused if event logging is off.
|
||||
__unused __typeof__(self) strongSelf = weakSelf;
|
||||
return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil eventLog:ASDisplayNodeGetEventLog(strongSelf)];
|
||||
return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
@@ -127,18 +135,20 @@
|
||||
[self.rangeController clearContents];
|
||||
}
|
||||
|
||||
- (void)didExitPreloadState
|
||||
{
|
||||
[super didExitPreloadState];
|
||||
[self.rangeController clearPreloadedData];
|
||||
}
|
||||
|
||||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
|
||||
{
|
||||
[super interfaceStateDidChange:newState fromState:oldState];
|
||||
[ASRangeController layoutDebugOverlayIfNeeded];
|
||||
}
|
||||
|
||||
- (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.
|
||||
[[self view] layoutIfNeeded];
|
||||
}
|
||||
|
||||
#if ASRangeControllerLoggingEnabled
|
||||
- (void)didEnterVisibleState
|
||||
{
|
||||
@@ -153,6 +163,12 @@
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)didExitPreloadState
|
||||
{
|
||||
[super didExitPreloadState];
|
||||
[self.rangeController clearPreloadedData];
|
||||
}
|
||||
|
||||
#pragma mark Setter / Getter
|
||||
|
||||
// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection
|
||||
@@ -196,6 +212,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching
|
||||
{
|
||||
_ASTablePendingState *pendingState = self.pendingState;
|
||||
if (pendingState) {
|
||||
pendingState.leadingScreensForBatching = leadingScreensForBatching;
|
||||
} else {
|
||||
ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist");
|
||||
self.view.leadingScreensForBatching = leadingScreensForBatching;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)leadingScreensForBatching
|
||||
{
|
||||
_ASTablePendingState *pendingState = self.pendingState;
|
||||
if (pendingState) {
|
||||
return pendingState.leadingScreensForBatching;
|
||||
} else {
|
||||
return self.view.leadingScreensForBatching;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset
|
||||
{
|
||||
_ASTablePendingState *pendingState = self.pendingState;
|
||||
if (pendingState) {
|
||||
pendingState.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset;
|
||||
} else {
|
||||
ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist");
|
||||
self.view.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)automaticallyAdjustsContentOffset
|
||||
{
|
||||
_ASTablePendingState *pendingState = self.pendingState;
|
||||
if (pendingState) {
|
||||
return pendingState.automaticallyAdjustsContentOffset;
|
||||
} else {
|
||||
return self.view.automaticallyAdjustsContentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id <ASTableDelegate>)delegate
|
||||
{
|
||||
if ([self pendingState]) {
|
||||
@@ -327,6 +385,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBatchFetchingDelegate:(id<ASBatchFetchingDelegate>)batchFetchingDelegate
|
||||
{
|
||||
_batchFetchingDelegate = batchFetchingDelegate;
|
||||
}
|
||||
|
||||
- (id<ASBatchFetchingDelegate>)batchFetchingDelegate
|
||||
{
|
||||
return _batchFetchingDelegate;
|
||||
}
|
||||
|
||||
#pragma mark ASRangeControllerUpdateRangeProtocol
|
||||
|
||||
- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASTableView.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -26,13 +33,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
* Asynchronous UITableView with Intelligent Preloading capabilities.
|
||||
*
|
||||
* @discussion ASTableView is a true subclass of UITableView, meaning it is pointer-compatible with code that
|
||||
* currently uses UITableView
|
||||
*
|
||||
* The main difference is that asyncDataSource expects -nodeForRowAtIndexPath, an ASCellNode, and
|
||||
* the heightForRowAtIndexPath: method is eliminated (as are the performance problems caused by it).
|
||||
* This is made possible because ASCellNodes can calculate their own size, and preload ahead of time.
|
||||
*
|
||||
* @note ASTableNode is strongly recommended over ASTableView. This class is provided for adoption convenience.
|
||||
*/
|
||||
@interface ASTableView : UITableView
|
||||
@@ -45,29 +45,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* YES to automatically adjust the contentOffset when cells are inserted or deleted above
|
||||
* visible cells, maintaining the users' visible scroll position.
|
||||
*
|
||||
* @note This is only applied to non-animated updates. For animated updates, there is no way to
|
||||
* synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations.
|
||||
*
|
||||
* default is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL automaticallyAdjustsContentOffset;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableView (Deprecated)
|
||||
@@ -85,6 +62,30 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style ASDISPLAYNODE_DEPRECATED_MSG("Please use ASTableNode instead of ASTableView.");
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
|
||||
|
||||
|
||||
/**
|
||||
* YES to automatically adjust the contentOffset when cells are inserted or deleted above
|
||||
* visible cells, maintaining the users' visible scroll position.
|
||||
*
|
||||
* @note This is only applied to non-animated updates. For animated updates, there is no way to
|
||||
* synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations.
|
||||
*
|
||||
* default is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL automaticallyAdjustsContentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead.");
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in full mode.
|
||||
*
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASTableView.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -15,7 +22,6 @@
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASBatchFetching.h>
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
@@ -25,7 +31,7 @@
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASTableNode.h>
|
||||
#import <AsyncDisplayKit/ASTableNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASRangeController.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASTableLayoutController.h>
|
||||
@@ -46,6 +52,26 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return __val; \
|
||||
}
|
||||
|
||||
#define UITABLEVIEW_RESPONDS_TO_SELECTOR() \
|
||||
({ \
|
||||
static BOOL superResponds; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
superResponds = [UITableView instancesRespondToSelector:_cmd]; \
|
||||
}); \
|
||||
superResponds; \
|
||||
})
|
||||
|
||||
@interface UITableView (ScrollViewDelegate)
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASCellNode<->UITableViewCell bridging.
|
||||
|
||||
@@ -57,7 +83,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
@interface _ASTableViewCell : UITableViewCell
|
||||
@property (nonatomic, weak) id<_ASTableViewCellDelegate> delegate;
|
||||
@property (nonatomic, weak) ASCellNode *node;
|
||||
@property (nonatomic, strong, readonly) ASCellNode *node;
|
||||
@property (nonatomic, strong) ASCollectionElement *element;
|
||||
@end
|
||||
|
||||
@implementation _ASTableViewCell
|
||||
@@ -76,9 +103,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
[super didTransitionToState:state];
|
||||
}
|
||||
|
||||
- (void)setNode:(ASCellNode *)node
|
||||
- (ASCellNode *)node
|
||||
{
|
||||
_node = node;
|
||||
return self.element.node;
|
||||
}
|
||||
|
||||
- (void)setElement:(ASCollectionElement *)element
|
||||
{
|
||||
_element = element;
|
||||
ASCellNode *node = element.node;
|
||||
|
||||
if (node) {
|
||||
self.backgroundColor = node.backgroundColor;
|
||||
@@ -101,19 +134,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
|
||||
{
|
||||
[super setSelected:selected animated:animated];
|
||||
[_node __setSelectedFromUIKit:selected];
|
||||
[self.node __setSelectedFromUIKit:selected];
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
|
||||
{
|
||||
[super setHighlighted:highlighted animated:animated];
|
||||
[_node __setHighlightedFromUIKit:highlighted];
|
||||
[self.node __setHighlightedFromUIKit:highlighted];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
// Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells
|
||||
self.node = nil;
|
||||
// Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells
|
||||
self.element = nil;
|
||||
[super prepareForReuse];
|
||||
}
|
||||
|
||||
@@ -122,7 +155,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
#pragma mark -
|
||||
#pragma mark ASTableView
|
||||
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate>
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView>
|
||||
{
|
||||
ASTableViewProxy *_proxyDataSource;
|
||||
ASTableViewProxy *_proxyDelegate;
|
||||
@@ -136,11 +169,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
// When we update our data controller in response to an interactive move,
|
||||
// we don't want to tell the table view about the change (it knows!)
|
||||
BOOL _updatingInResponseToInteractiveMove;
|
||||
BOOL _inverted;
|
||||
|
||||
// The top cell node that was visible before the update.
|
||||
__weak ASCellNode *_contentOffsetAdjustmentTopVisibleNode;
|
||||
// The y-offset of the top visible row's origin before the update.
|
||||
CGFloat _contentOffsetAdjustmentTopVisibleNodeOffset;
|
||||
CGFloat _leadingScreensForBatching;
|
||||
BOOL _automaticallyAdjustsContentOffset;
|
||||
|
||||
CGPoint _deceleratingVelocity;
|
||||
|
||||
@@ -156,10 +192,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
CGFloat _nodesConstrainedWidth;
|
||||
BOOL _queuedNodeHeightUpdate;
|
||||
BOOL _isDeallocating;
|
||||
NSMutableSet *_cellsForVisibilityUpdates;
|
||||
NSHashTable<_ASTableViewCell *> *_cellsForVisibilityUpdates;
|
||||
|
||||
// CountedSet because UIKit may display the same element in multiple cells e.g. during animations.
|
||||
NSCountedSet<ASCollectionElement *> *_visibleElements;
|
||||
|
||||
BOOL _remeasuringCellNodes;
|
||||
NSMutableSet *_cellsForLayoutUpdates;
|
||||
NSHashTable<ASCellNode *> *_cellsForLayoutUpdates;
|
||||
|
||||
// See documentation on same property in ASCollectionView
|
||||
BOOL _hasEverCheckedForBatchFetchingDueToUpdate;
|
||||
@@ -183,6 +222,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
unsigned int scrollViewWillBeginDragging:1;
|
||||
unsigned int scrollViewDidEndDragging:1;
|
||||
unsigned int scrollViewWillEndDragging:1;
|
||||
unsigned int scrollViewDidEndDecelerating:1;
|
||||
unsigned int tableNodeWillDisplayNodeForRow:1;
|
||||
unsigned int tableViewWillDisplayNodeForRow:1;
|
||||
unsigned int tableViewWillDisplayNodeForRowDeprecated:1;
|
||||
@@ -260,8 +300,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
return [self _initWithFrame:frame style:style dataControllerClass:nil owningNode:nil eventLog:nil];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame style:style])) {
|
||||
return nil;
|
||||
}
|
||||
_cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
_cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
if (!dataControllerClass) {
|
||||
dataControllerClass = [[self class] dataControllerClass];
|
||||
}
|
||||
|
||||
_layoutController = [[ASTableLayoutController alloc] initWithTableView:self];
|
||||
|
||||
_rangeController = [[ASRangeController alloc] init];
|
||||
@@ -269,12 +323,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_rangeController.dataSource = self;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[dataControllerClass alloc] initWithDataSource:self eventLog:eventLog];
|
||||
_dataController = [[dataControllerClass alloc] initWithDataSource:self node:tableNode eventLog:eventLog];
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.environmentDelegate = self;
|
||||
|
||||
_leadingScreensForBatching = 2.0;
|
||||
_batchContext = [[ASBatchContext alloc] init];
|
||||
_visibleElements = [[NSCountedSet alloc] init];
|
||||
|
||||
_automaticallyAdjustsContentOffset = NO;
|
||||
|
||||
@@ -287,25 +341,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
|
||||
|
||||
[self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
return [self _initWithFrame:frame style:style dataControllerClass:nil eventLog:nil];
|
||||
}
|
||||
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame style:style])) {
|
||||
return nil;
|
||||
}
|
||||
_cellsForVisibilityUpdates = [NSMutableSet set];
|
||||
_cellsForLayoutUpdates = [NSMutableSet set];
|
||||
if (!dataControllerClass) {
|
||||
dataControllerClass = [[self class] dataControllerClass];
|
||||
}
|
||||
|
||||
[self configureWithDataControllerClass:dataControllerClass eventLog:eventLog];
|
||||
|
||||
if (!AS_AT_LEAST_IOS9) {
|
||||
_retainedLayer = self.layer;
|
||||
@@ -433,6 +468,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)];
|
||||
_asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)];
|
||||
_asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)];
|
||||
_asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)];
|
||||
_asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
|
||||
_asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)];
|
||||
_asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)];
|
||||
@@ -541,11 +577,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType];
|
||||
}
|
||||
|
||||
- (ASTableNode *)tableNode
|
||||
{
|
||||
return (ASTableNode *)ASViewToDisplayNode(self);
|
||||
}
|
||||
|
||||
- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
return _dataController.visibleMap;
|
||||
@@ -651,7 +682,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (NSArray<ASCellNode *> *)visibleNodes
|
||||
{
|
||||
NSArray<ASCollectionElement *> *elements = [self visibleElementsForRangeController:_rangeController];
|
||||
auto elements = [self visibleElementsForRangeController:_rangeController];
|
||||
return ASArrayByFlatMapping(elements, ASCollectionElement *e, e.node);
|
||||
}
|
||||
|
||||
@@ -874,11 +905,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:kCellReuseIdentifier forIndexPath:indexPath];
|
||||
cell.delegate = self;
|
||||
|
||||
ASCellNode *node = [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node;
|
||||
ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath];
|
||||
cell.element = element;
|
||||
ASCellNode *node = element.node;
|
||||
if (node) {
|
||||
[_rangeController configureContentView:cell.contentView forCellNode:node];
|
||||
|
||||
cell.node = node;
|
||||
}
|
||||
|
||||
return cell;
|
||||
@@ -941,7 +972,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASCellNode *cellNode = [cell node];
|
||||
ASCollectionElement *element = cell.element;
|
||||
[_visibleElements addObject:element];
|
||||
ASCellNode *cellNode = element.node;
|
||||
cellNode.scrollView = tableView;
|
||||
|
||||
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
|
||||
@@ -967,7 +1000,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASCellNode *cellNode = [cell node];
|
||||
ASCollectionElement *element = cell.element;
|
||||
[_visibleElements removeObject:element];
|
||||
ASCellNode *cellNode = element.node;
|
||||
|
||||
[_rangeController setNeedsUpdate];
|
||||
|
||||
@@ -1166,6 +1201,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) {
|
||||
[super scrollViewDidScroll:scrollView];
|
||||
return;
|
||||
}
|
||||
// If a scroll happenes the current range mode needs to go to full
|
||||
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
|
||||
if (ASInterfaceStateIncludesVisible(interfaceState)) {
|
||||
@@ -1185,6 +1224,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
{
|
||||
if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) {
|
||||
[super scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
|
||||
return;
|
||||
}
|
||||
CGPoint contentOffset = scrollView.contentOffset;
|
||||
_deceleratingVelocity = CGPointMake(
|
||||
contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
|
||||
@@ -1193,7 +1236,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
if (targetContentOffset != NULL) {
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset];
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity];
|
||||
}
|
||||
|
||||
if (_asyncDelegateFlags.scrollViewWillEndDragging) {
|
||||
@@ -1201,8 +1244,25 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) {
|
||||
[super scrollViewDidEndDecelerating:scrollView];
|
||||
return;
|
||||
}
|
||||
_deceleratingVelocity = CGPointZero;
|
||||
|
||||
if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
|
||||
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) {
|
||||
[super scrollViewWillBeginDragging:scrollView];
|
||||
return;
|
||||
}
|
||||
for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) {
|
||||
[[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging
|
||||
inScrollView:scrollView
|
||||
@@ -1215,6 +1275,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
||||
{
|
||||
if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) {
|
||||
[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
|
||||
return;
|
||||
}
|
||||
for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) {
|
||||
[[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging
|
||||
inScrollView:scrollView
|
||||
@@ -1225,6 +1289,38 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Misc
|
||||
|
||||
- (BOOL)inverted
|
||||
{
|
||||
return _inverted;
|
||||
}
|
||||
|
||||
- (void)setInverted:(BOOL)inverted
|
||||
{
|
||||
_inverted = inverted;
|
||||
}
|
||||
|
||||
- (CGFloat)leadingScreensForBatching
|
||||
{
|
||||
return _leadingScreensForBatching;
|
||||
}
|
||||
|
||||
- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching
|
||||
{
|
||||
_leadingScreensForBatching = leadingScreensForBatching;
|
||||
}
|
||||
|
||||
- (BOOL)automaticallyAdjustsContentOffset
|
||||
{
|
||||
return _automaticallyAdjustsContentOffset;
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset
|
||||
{
|
||||
_automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset;
|
||||
}
|
||||
|
||||
#pragma mark - Scroll Direction
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
@@ -1296,6 +1392,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASBatchFetchingDelegate>)batchFetchingDelegate
|
||||
{
|
||||
return self.tableNode.batchFetchingDelegate;
|
||||
}
|
||||
|
||||
- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes
|
||||
{
|
||||
// Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided
|
||||
@@ -1317,12 +1418,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return;
|
||||
}
|
||||
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset];
|
||||
[self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero];
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset
|
||||
- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity
|
||||
{
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset)) {
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset, velocity)) {
|
||||
[self _beginBatchFetching];
|
||||
}
|
||||
}
|
||||
@@ -1352,33 +1453,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return _rangeController;
|
||||
}
|
||||
|
||||
- (NSArray<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
|
||||
- (NSHashTable<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
// Calling indexPathsForVisibleRows will trigger UIKit to call reloadData if it never has, which can result
|
||||
// in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast.
|
||||
if (CGRectIsEmpty(bounds)) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSArray *visibleIndexPaths = self.indexPathsForVisibleRows;
|
||||
|
||||
// In some cases (grouped-style tables with particular geometry) indexPathsForVisibleRows will return extra index paths.
|
||||
// This is a very serious issue because we rely on the fact that any node that is marked Visible is hosted inside of a cell,
|
||||
// or else we may not mark it invisible before the node is released. See testIssue2252.
|
||||
// Calling indexPathForCell: and cellForRowAtIndexPath: are both pretty expensive – this is the quickest approach we have.
|
||||
// It would be possible to cache this NSPredicate as an ivar, but that would require unsafeifying self and calling @c bounds
|
||||
// for each item. Since the performance cost is pretty small, prefer simplicity.
|
||||
if (self.style == UITableViewStyleGrouped && visibleIndexPaths.count != self.visibleCells.count) {
|
||||
visibleIndexPaths = [visibleIndexPaths filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, NSDictionary<NSString *,id> * _Nullable bindings) {
|
||||
return CGRectIntersectsRect(bounds, [self rectForRowAtIndexPath:indexPath]);
|
||||
}]];
|
||||
}
|
||||
|
||||
ASElementMap *map = _dataController.visibleMap;
|
||||
return ASArrayByFlatMapping(visibleIndexPaths, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]);
|
||||
return ASPointerTableByFlatMapping(_visibleElements, id element, element);
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController
|
||||
@@ -1410,10 +1487,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
|
||||
- (void)rangeController:(ASRangeController *)rangeController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) {
|
||||
updates();
|
||||
[changeSet executeCompletionHandlerWithFinished:NO];
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
}
|
||||
@@ -1424,6 +1502,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
if (self.test_enableSuperUpdateCallLogging) {
|
||||
NSLog(@"-[super reloadData]");
|
||||
}
|
||||
updates();
|
||||
[super reloadData];
|
||||
// Flush any range changes that happened as part of submitting the reload.
|
||||
[_rangeController updateIfNeeded];
|
||||
@@ -1438,6 +1517,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
LOG(@"--- UITableView beginUpdates");
|
||||
[super beginUpdates];
|
||||
|
||||
updates();
|
||||
|
||||
for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
NSArray<NSIndexPath *> *indexPaths = change.indexPaths;
|
||||
UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions;
|
||||
@@ -1546,7 +1627,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
[changeSet executeCompletionHandlerWithFinished:YES];
|
||||
}
|
||||
|
||||
#pragma mark - ASDataControllerDelegate
|
||||
#pragma mark - ASDataControllerSource
|
||||
|
||||
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Not currently supported for tables. Will be added when the collection API stabilizes.
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ASCellNodeBlock block = nil;
|
||||
@@ -1665,6 +1752,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size
|
||||
{
|
||||
NSIndexPath *indexPath = [self indexPathForNode:element.node];
|
||||
if (indexPath == nil) {
|
||||
ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented.");
|
||||
return YES;
|
||||
}
|
||||
CGRect rect = [self rectForRowAtIndexPath:indexPath];
|
||||
|
||||
/**
|
||||
@@ -1678,13 +1769,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return (fabs(rect.size.height - size.height) < FLT_EPSILON);
|
||||
}
|
||||
|
||||
#pragma mark - ASDataControllerEnvironmentDelegate
|
||||
|
||||
- (id<ASTraitEnvironment>)dataControllerEnvironment
|
||||
{
|
||||
return self.tableNode;
|
||||
}
|
||||
|
||||
#pragma mark - _ASTableViewCellDelegate
|
||||
|
||||
- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell
|
||||
@@ -1751,21 +1835,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (!sizeChanged || _queuedNodeHeightUpdate || _remeasuringCellNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
_queuedNodeHeightUpdate = YES;
|
||||
[self performSelector:@selector(requeryNodeHeights)
|
||||
withObject:nil
|
||||
afterDelay:0
|
||||
inModes:@[ NSRunLoopCommonModes ]];
|
||||
}
|
||||
|
||||
// Cause UITableView to requery for the new height of this node
|
||||
- (void)requeryNodeHeights
|
||||
{
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASTableViewInternal.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 26/10/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -37,7 +42,7 @@
|
||||
*
|
||||
* @param eventLog An event log passed through to the data controller.
|
||||
*/
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog;
|
||||
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog;
|
||||
|
||||
/// Set YES and we'll log every time we call [super insertRows…] etc
|
||||
@property (nonatomic) BOOL test_enableSuperUpdateCallLogging;
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASTableViewProtocols.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASTextNode+Beta.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Luke on 1/25/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <UIKit/UIKit.h>
|
||||
@@ -16,6 +21,20 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// When enabled, use ASTextNode2 for subclasses, random instances, or all instances of ASTextNode.
|
||||
// See ASAvailability.h declaration of ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE for a compile-time option.
|
||||
typedef NS_OPTIONS(NSUInteger, ASTextNodeExperimentOptions) {
|
||||
// All subclass instances use the experimental implementation.
|
||||
ASTextNodeExperimentSubclasses = 1 << 0,
|
||||
// Random instances of ASTextNode (50% chance) (not subclasses) use experimental impl.
|
||||
// Useful for profiling with apps that have no custom text node subclasses.
|
||||
ASTextNodeExperimentRandomInstances = 1 << 1,
|
||||
// All instances of ASTextNode itself use experimental implementation. Supersedes `.randomInstances`.
|
||||
ASTextNodeExperimentAllInstances = 1 << 2,
|
||||
// Add highlighting etc. for debugging.
|
||||
ASTextNodeExperimentDebugging = 1 << 3
|
||||
};
|
||||
|
||||
@interface ASTextNode ()
|
||||
|
||||
/**
|
||||
@@ -33,6 +52,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
|
||||
|
||||
/**
|
||||
* Opt in to an experimental implementation of text node. The implementation may improve performance and correctness,
|
||||
* but may not support all features and has not been thoroughly tested in production.
|
||||
*
|
||||
* @precondition You may not call this after allocating any text nodes. You may only call this once.
|
||||
*/
|
||||
+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options;
|
||||
|
||||
/**
|
||||
* Returns YES if this node is using the experimental implementation. NO otherwise. Will not change.
|
||||
*/
|
||||
@property (atomic, readonly) BOOL usingExperiment;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
//
|
||||
// ASTextNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
#if ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE
|
||||
#import <AsyncDisplayKit/ASTextNode2.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASTextNodeDelegate;
|
||||
|
||||
#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE
|
||||
|
||||
/**
|
||||
* Highlight styles.
|
||||
*/
|
||||
@@ -225,6 +238,13 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
|
||||
@end
|
||||
|
||||
#else
|
||||
|
||||
@interface ASTextNode : ASTextNode2
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @abstract Text node delegate.
|
||||
*/
|
||||
@@ -280,6 +300,8 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
|
||||
@end
|
||||
|
||||
#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE
|
||||
|
||||
@interface ASTextNode (Unavailable)
|
||||
|
||||
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;
|
||||
@@ -312,6 +334,6 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
//
|
||||
// ASTextNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASTextNode2.h>
|
||||
|
||||
#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE
|
||||
#import <AsyncDisplayKit/ASTextNode+Beta.h>
|
||||
|
||||
#include <mutex>
|
||||
@@ -27,6 +37,7 @@
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASHashing.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
/**
|
||||
@@ -44,24 +55,8 @@ static const CGFloat ASTextNodeHighlightLightOpacity = 0.11;
|
||||
static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22;
|
||||
static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute";
|
||||
|
||||
struct ASTextNodeDrawParameter {
|
||||
CGRect bounds;
|
||||
UIColor *backgroundColor;
|
||||
};
|
||||
|
||||
#pragma mark - ASTextKitRenderer
|
||||
|
||||
// Not used at the moment but handy to have
|
||||
/*ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGRect(CGRect rect)
|
||||
{
|
||||
return ((*(NSUInteger *)&rect.origin.x << 10 ^ *(NSUInteger *)&rect.origin.y) + (*(NSUInteger *)&rect.size.width << 10 ^ *(NSUInteger *)&rect.size.height));
|
||||
}*/
|
||||
|
||||
ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGSize(CGSize size)
|
||||
{
|
||||
return ((*(NSUInteger *)&size.width << 10 ^ *(NSUInteger *)&size.height));
|
||||
}
|
||||
|
||||
@interface ASTextNodeRendererKey : NSObject
|
||||
@property (assign, nonatomic) ASTextKitAttributes attributes;
|
||||
@property (assign, nonatomic) CGSize constrainedSize;
|
||||
@@ -71,7 +66,17 @@ ASDISPLAYNODE_INLINE NSUInteger ASHashFromCGSize(CGSize size)
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return _attributes.hash() ^ ASHashFromCGSize(_constrainedSize);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic warning "-Wpadded"
|
||||
struct {
|
||||
size_t attributesHash;
|
||||
CGSize constrainedSize;
|
||||
#pragma clang diagnostic pop
|
||||
} data = {
|
||||
_attributes.hash(),
|
||||
_constrainedSize
|
||||
};
|
||||
return ASHashBytes(&data, sizeof(data));
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(ASTextNodeRendererKey *)object
|
||||
@@ -119,6 +124,42 @@ static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes,
|
||||
return renderer;
|
||||
}
|
||||
|
||||
#pragma mark - ASTextNodeDrawParameter
|
||||
|
||||
@interface ASTextNodeDrawParameter : NSObject {
|
||||
@package
|
||||
ASTextKitAttributes _rendererAttributes;
|
||||
UIColor *_backgroundColor;
|
||||
UIEdgeInsets _textContainerInsets;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeDrawParameter
|
||||
|
||||
- (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttributes
|
||||
backgroundColor:(/*nullable*/ UIColor *)backgroundColor
|
||||
textContainerInsets:(UIEdgeInsets)textContainerInsets
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_rendererAttributes = rendererAttributes;
|
||||
_backgroundColor = backgroundColor;
|
||||
_textContainerInsets = textContainerInsets;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASTextKitRenderer *)rendererForBounds:(CGRect)bounds
|
||||
{
|
||||
CGRect rect = UIEdgeInsetsInsetRect(bounds, _textContainerInsets);
|
||||
return rendererForAttributes(_rendererAttributes, rect.size);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASTextNode
|
||||
|
||||
@interface ASTextNode () <UIGestureRecognizerDelegate>
|
||||
|
||||
@end
|
||||
@@ -143,24 +184,10 @@ static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes,
|
||||
NSRange _highlightRange;
|
||||
ASHighlightOverlayLayer *_activeHighlightLayer;
|
||||
|
||||
ASTextNodeDrawParameter _drawParameter;
|
||||
|
||||
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
||||
}
|
||||
@dynamic placeholderEnabled;
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[super initialize];
|
||||
|
||||
if (self != [ASTextNode class]) {
|
||||
// Prevent custom drawing in subclasses
|
||||
ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASTextNode class], self, @selector(drawRect:withParameters:isCancelled:isRasterizing:)), @"Subclass %@ must not override drawRect:withParameters:isCancelled:isRasterizing: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASTextNode class]));
|
||||
}
|
||||
}
|
||||
|
||||
static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (instancetype)init
|
||||
@@ -225,7 +252,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
NSMutableArray *result = [super propertiesForDescription];
|
||||
NSString *plainString = [self _plainStringForDescription];
|
||||
if (plainString.length > 0) {
|
||||
[result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0];
|
||||
[result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(plainString) }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -242,14 +269,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - ASDisplayNode
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
// We discard the backing store and renderer to prevent the very large
|
||||
// memory overhead of maintaining these for all text nodes. They can be
|
||||
// regenerated when layout is necessary.
|
||||
[super clearContents]; // ASDisplayNode will set layer.contents = nil
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
@@ -264,27 +283,55 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
if (!super.supportsLayerBacking) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// If the text contains any links, return NO.
|
||||
NSAttributedString *attributedText = self.attributedText;
|
||||
NSRange range = NSMakeRange(0, attributedText.length);
|
||||
for (NSString *linkAttributeName in _linkAttributeNames) {
|
||||
__block BOOL hasLink = NO;
|
||||
[attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
||||
hasLink = (value != nil);
|
||||
*stop = YES;
|
||||
}];
|
||||
if (hasLink) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Renderer Management
|
||||
|
||||
- (ASTextKitRenderer *)_renderer
|
||||
{
|
||||
CGSize constrainedSize = self.threadSafeBounds.size;
|
||||
return [self _rendererWithBoundsSlow:{.size = constrainedSize}];
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return [self _locked_renderer];
|
||||
}
|
||||
|
||||
- (ASTextKitRenderer *)_rendererWithBoundsSlow:(CGRect)bounds
|
||||
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
bounds.size.width -= (_textContainerInset.left + _textContainerInset.right);
|
||||
bounds.size.height -= (_textContainerInset.top + _textContainerInset.bottom);
|
||||
return rendererForAttributes([self _rendererAttributes], bounds.size);
|
||||
return [self _locked_rendererWithBounds:bounds];
|
||||
}
|
||||
|
||||
|
||||
- (ASTextKitAttributes)_rendererAttributes
|
||||
- (ASTextKitRenderer *)_locked_renderer
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
return [self _locked_rendererWithBounds:[self _locked_threadSafeBounds]];
|
||||
}
|
||||
|
||||
- (ASTextKitRenderer *)_locked_rendererWithBounds:(CGRect)bounds
|
||||
{
|
||||
bounds = UIEdgeInsetsInsetRect(bounds, _textContainerInset);
|
||||
return rendererForAttributes([self _locked_rendererAttributes], bounds.size);
|
||||
}
|
||||
|
||||
- (ASTextKitAttributes)_locked_rendererAttributes
|
||||
{
|
||||
return {
|
||||
.attributedString = _attributedText,
|
||||
.truncationAttributedString = [self _locked_composedTruncationText],
|
||||
@@ -334,7 +381,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
[self setNeedsDisplay];
|
||||
|
||||
ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:{.size = constrainedSize}];
|
||||
ASTextKitRenderer *renderer = [self _locked_rendererWithBounds:{.size = constrainedSize}];
|
||||
CGSize size = renderer.size;
|
||||
if (_attributedText.length > 0) {
|
||||
self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText];
|
||||
@@ -449,30 +496,23 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
_drawParameter = {
|
||||
.backgroundColor = self.backgroundColor,
|
||||
.bounds = self.bounds
|
||||
};
|
||||
return nil;
|
||||
return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes]
|
||||
backgroundColor:self.backgroundColor
|
||||
textContainerInsets:_textContainerInset];
|
||||
}
|
||||
|
||||
|
||||
- (void)drawRect:(CGRect)bounds withParameters:(id <NSObject>)p isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASTextNodeDrawParameter drawParameter = _drawParameter;
|
||||
CGRect drawParameterBounds = drawParameter.bounds;
|
||||
UIColor *backgroundColor = isRasterizing ? nil : drawParameter.backgroundColor;
|
||||
ASTextNodeDrawParameter *drawParameter = (ASTextNodeDrawParameter *)parameters;
|
||||
UIColor *backgroundColor = (isRasterizing || drawParameter == nil) ? nil : drawParameter->_backgroundColor;
|
||||
UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero;
|
||||
ASTextKitRenderer *renderer = [drawParameter rendererForBounds:bounds];
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
ASDisplayNodeAssert(context, @"This is no good without a context.");
|
||||
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _rendererWithBoundsSlow:drawParameterBounds];
|
||||
CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top);
|
||||
|
||||
// Fill background
|
||||
if (backgroundColor != nil) {
|
||||
@@ -481,8 +521,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
}
|
||||
|
||||
// Draw text
|
||||
[renderer drawInContext:context bounds:drawParameterBounds];
|
||||
|
||||
[renderer drawInContext:context bounds:bounds];
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@@ -509,7 +548,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
ASTextKitRenderer *renderer = [self _locked_renderer];
|
||||
NSRange visibleRange = renderer.firstVisibleRange;
|
||||
NSAttributedString *attributedString = _attributedText;
|
||||
NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, attributedString.length));
|
||||
@@ -815,7 +854,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
|
||||
NSArray *rects = [[self _locked_renderer] rectsForTextRange:textRange measureOption:measureOption];
|
||||
NSMutableArray *adjustedRects = [NSMutableArray array];
|
||||
|
||||
for (NSValue *rectValue in rects) {
|
||||
@@ -833,7 +872,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
CGRect rect = [[self _renderer] trailingRect];
|
||||
CGRect rect = [[self _locked_renderer] trailingRect];
|
||||
return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding);
|
||||
}
|
||||
|
||||
@@ -841,7 +880,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
CGRect frame = [[self _renderer] frameForTextRange:textRange];
|
||||
CGRect frame = [[self _locked_renderer] frameForTextRange:textRange];
|
||||
return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding);
|
||||
}
|
||||
|
||||
@@ -871,7 +910,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
UIGraphicsBeginImageContext(size);
|
||||
[self.placeholderColor setFill];
|
||||
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
ASTextKitRenderer *renderer = [self _locked_renderer];
|
||||
NSRange visibleRange = renderer.firstVisibleRange;
|
||||
|
||||
// cap height is both faster and creates less subpixel blending
|
||||
@@ -949,7 +988,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
NSRange visibleRange = NSMakeRange(0, 0);
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
visibleRange = [self _renderer].firstVisibleRange;
|
||||
visibleRange = [self _locked_renderer].firstVisibleRange;
|
||||
}
|
||||
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
|
||||
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
|
||||
@@ -1131,15 +1170,9 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)shadowPadding
|
||||
{
|
||||
return [self shadowPaddingWithRenderer:[self _renderer]];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
return renderer.shadower.shadowPadding;
|
||||
return [self _locked_renderer].shadower.shadowPadding;
|
||||
}
|
||||
|
||||
#pragma mark - Truncation Message
|
||||
@@ -1203,8 +1236,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
return renderer.isTruncated;
|
||||
return [[self _locked_renderer] isTruncated];
|
||||
}
|
||||
|
||||
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
|
||||
@@ -1240,7 +1272,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
return [[self _renderer] lineCount];
|
||||
return [[self _locked_renderer] lineCount];
|
||||
}
|
||||
|
||||
#pragma mark - Truncation Message
|
||||
@@ -1352,6 +1384,70 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
}
|
||||
#endif
|
||||
|
||||
static ASDN::Mutex _experimentLock;
|
||||
static ASTextNodeExperimentOptions _experimentOptions;
|
||||
static BOOL _hasAllocatedNode;
|
||||
|
||||
+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
ASDN::MutexLocker lock(_experimentLock);
|
||||
|
||||
// They must call this before allocating any text nodes.
|
||||
ASDisplayNodeAssertFalse(_hasAllocatedNode);
|
||||
|
||||
_experimentOptions = options;
|
||||
|
||||
// Set superclass of all subclasses to ASTextNode2
|
||||
if (options & ASTextNodeExperimentSubclasses) {
|
||||
unsigned int classCount;
|
||||
Class originalClass = [ASTextNode class];
|
||||
Class newClass = [ASTextNode2 class];
|
||||
Class *classes = objc_copyClassList(&classCount);
|
||||
for (int i = 0; i < classCount; i++) {
|
||||
Class c = classes[i];
|
||||
if (class_getSuperclass(c) == originalClass) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
class_setSuperclass(c, newClass);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
free(classes);
|
||||
}
|
||||
|
||||
if (options & ASTextNodeExperimentDebugging) {
|
||||
[ASTextNode2 enableDebugging];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+ (id)allocWithZone:(struct _NSZone *)zone
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
ASDN::MutexLocker lock(_experimentLock);
|
||||
_hasAllocatedNode = YES;
|
||||
});
|
||||
|
||||
// All instances || (random instances && rand() != 0)
|
||||
BOOL useExperiment = (_experimentOptions & ASTextNodeExperimentAllInstances)
|
||||
|| ((_experimentOptions & ASTextNodeExperimentRandomInstances)
|
||||
&& (arc4random_uniform(2) != 0));
|
||||
|
||||
if (useExperiment) {
|
||||
return (ASTextNode *)[ASTextNode2 allocWithZone:zone];
|
||||
} else {
|
||||
return [super allocWithZone:zone];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)usingExperiment
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNode (Deprecated)
|
||||
@@ -1377,3 +1473,10 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else
|
||||
|
||||
@implementation ASTextNode
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
247
Source/ASTextNode2.h
Normal file
247
Source/ASTextNode2.h
Normal file
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// ASTextNode2.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/ASControlNode.h>
|
||||
|
||||
#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE
|
||||
// Import this to get ASTextNodeHighlightStyle
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#else
|
||||
@protocol ASTextNodeDelegate;
|
||||
|
||||
/**
|
||||
* Highlight styles.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
/**
|
||||
* Highlight style for text on a light background.
|
||||
*/
|
||||
ASTextNodeHighlightStyleLight,
|
||||
|
||||
/**
|
||||
* Highlight style for text on a dark background.
|
||||
*/
|
||||
ASTextNodeHighlightStyleDark
|
||||
};
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@abstract Draws interactive rich text.
|
||||
@discussion Backed by the code in TextExperiment folder, on top of CoreText.
|
||||
*/
|
||||
@interface ASTextNode2 : ASControlNode
|
||||
|
||||
/**
|
||||
@abstract The styled text displayed by the node.
|
||||
@discussion Defaults to nil, no text is shown.
|
||||
For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
||||
|
||||
#pragma mark - Truncation
|
||||
|
||||
/**
|
||||
@abstract The attributedText to use when the text must be truncated.
|
||||
@discussion Defaults to a localized ellipsis character.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedText;
|
||||
|
||||
/**
|
||||
@summary The second attributed string appended for truncation.
|
||||
@discussion This string will be highlighted on touches.
|
||||
@default nil
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *additionalTruncationMessage;
|
||||
|
||||
/**
|
||||
@abstract Determines how the text is truncated to fit within the receiver's maximum size.
|
||||
@discussion Defaults to NSLineBreakByWordWrapping.
|
||||
@note Setting a truncationMode in attributedString will override the truncation mode set here.
|
||||
*/
|
||||
@property (nonatomic, assign) NSLineBreakMode truncationMode;
|
||||
|
||||
/**
|
||||
@abstract If the text node is truncated. Text must have been sized first.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign, getter=isTruncated) BOOL truncated;
|
||||
|
||||
/**
|
||||
@abstract The maximum number of lines to render of the text before truncation.
|
||||
@default 0 (No limit)
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
|
||||
|
||||
/**
|
||||
@abstract The number of lines in the text. Text must have been sized first.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) NSUInteger lineCount;
|
||||
|
||||
/**
|
||||
* An array of path objects representing the regions where text should not be displayed.
|
||||
*
|
||||
* @discussion The default value of this property is an empty array. You can
|
||||
* assign an array of UIBezierPath objects to exclude text from one or more regions in
|
||||
* the text node's bounds. You can use this property to have text wrap around images,
|
||||
* shapes or other text like a fancy magazine.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) NSArray<UIBezierPath *> *exclusionPaths;
|
||||
|
||||
#pragma mark - Placeholders
|
||||
|
||||
/**
|
||||
* @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES.
|
||||
*
|
||||
* @discussion Defaults to NO. When YES, it draws rectangles for each line of text,
|
||||
* following the true shape of the text's wrapping. This visually mirrors the overall
|
||||
* shape and weight of paragraphs, making the appearance of the finished text less jarring.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL placeholderEnabled;
|
||||
|
||||
/**
|
||||
@abstract The placeholder color.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIColor *placeholderColor;
|
||||
|
||||
/**
|
||||
@abstract Inset each line of the placeholder.
|
||||
*/
|
||||
@property (nonatomic, assign) UIEdgeInsets placeholderInsets;
|
||||
|
||||
#pragma mark - Shadow
|
||||
|
||||
/**
|
||||
@abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA.
|
||||
|
||||
@property (nonatomic, assign) CGColorRef shadowColor;
|
||||
@property (nonatomic, assign) CGFloat shadowOpacity;
|
||||
@property (nonatomic, assign) CGSize shadowOffset;
|
||||
@property (nonatomic, assign) CGFloat shadowRadius;
|
||||
*/
|
||||
|
||||
/**
|
||||
@abstract The number of pixels used for shadow padding on each side of the receiver.
|
||||
@discussion Each inset will be less than or equal to zero, so that applying
|
||||
UIEdgeInsetsRect(boundingRectForText, shadowPadding)
|
||||
will return a CGRect large enough to fit both the text and the appropriate shadow padding.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) UIEdgeInsets shadowPadding;
|
||||
|
||||
#pragma mark - Positioning
|
||||
|
||||
/**
|
||||
@abstract Returns an array of rects bounding the characters in a given text range.
|
||||
@param textRange A range of text. Must be valid for the receiver's string.
|
||||
@discussion Use this method to detect all the different rectangles a given range of text occupies.
|
||||
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
|
||||
a line break, the rects returned will be on opposite sides and different lines). The rects returned
|
||||
are in the coordinate system of the receiver.
|
||||
*/
|
||||
- (NSArray<NSValue *> *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract Returns an array of rects used for highlighting the characters in a given text range.
|
||||
@param textRange A range of text. Must be valid for the receiver's string.
|
||||
@discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies.
|
||||
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
|
||||
a line break, the rects returned will be on opposite sides and different lines). The rects returned
|
||||
are in the coordinate system of the receiver. This method is useful for visual coordination with a
|
||||
highlighted range of text.
|
||||
*/
|
||||
- (NSArray<NSValue *> *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract Returns a bounding rect for the given text range.
|
||||
@param textRange A range of text. Must be valid for the receiver's string.
|
||||
@discussion The height of the frame returned is that of the receiver's line-height; adjustment for
|
||||
cap-height and descenders is not performed. This method raises an exception if textRange is not
|
||||
a valid substring range of the receiver's string.
|
||||
*/
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract Returns the trailing rectangle of space in the receiver, after the final character.
|
||||
@discussion Use this method to detect which portion of the receiver is not occupied by characters.
|
||||
The rect returned is in the coordinate system of the receiver.
|
||||
*/
|
||||
- (CGRect)trailingRect AS_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
/**
|
||||
@abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName.
|
||||
*/
|
||||
@property (nonatomic, copy) NSArray<NSString *> *linkAttributeNames;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether the receiver has an entity at a given point.
|
||||
@param point The point, in the receiver's coordinate system.
|
||||
@param attributeNameOut The name of the attribute at the point. Can be NULL.
|
||||
@param rangeOut The ultimate range of the found text. Can be NULL.
|
||||
@result YES if an entity exists at `point`; NO otherwise.
|
||||
*/
|
||||
- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract The style to use when highlighting text.
|
||||
*/
|
||||
@property (nonatomic, assign) ASTextNodeHighlightStyle highlightStyle;
|
||||
|
||||
/**
|
||||
@abstract The range of text highlighted by the receiver. Changes to this property are not animated by default.
|
||||
*/
|
||||
@property (nonatomic, assign) NSRange highlightRange;
|
||||
|
||||
/**
|
||||
@abstract Set the range of text to highlight, with optional animation.
|
||||
|
||||
@param highlightRange The range of text to highlight.
|
||||
|
||||
@param animated Whether the text should be highlighted with an animation.
|
||||
*/
|
||||
- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
@abstract Responds to actions from links in the text node.
|
||||
@discussion The delegate must be set before the node is loaded, and implement
|
||||
textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for
|
||||
the long press gesture recognizer to be installed.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASTextNodeDelegate> delegate;
|
||||
|
||||
/**
|
||||
@abstract If YES and a long press is recognized, touches are cancelled. Default is NO
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL longPressCancelsTouches;
|
||||
|
||||
/**
|
||||
@abstract if YES will not intercept touches for non-link areas of the text. Default is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL passthroughNonlinkTouches;
|
||||
|
||||
+ (void)enableDebugging;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTextNode2 (Unavailable)
|
||||
|
||||
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;
|
||||
|
||||
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
1171
Source/ASTextNode2.mm
Normal file
1171
Source/ASTextNode2.mm
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASVideoNode.h
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@@ -141,6 +148,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param videoNode The videoNode
|
||||
*/
|
||||
- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode;
|
||||
/**
|
||||
* @abstract Delegate method invoked when an error occurs while trying trying to load an asset
|
||||
* @param videoNode The videoNode.
|
||||
* @param key The key of value that failed to load.
|
||||
* @param asset The asset.
|
||||
* @param error The error that occurs.
|
||||
*/
|
||||
- (void)videoNode:(ASVideoNode *)videoNode didFailToLoadValueForKey:(NSString *)key asset:(AVAsset *)asset error:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
//
|
||||
// ASVideoNode.mm
|
||||
// AsyncDisplayKit
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
@@ -51,6 +58,7 @@ static NSString * const kRate = @"rate";
|
||||
unsigned int delegateVideoNodeDidSetCurrentItem:1;
|
||||
unsigned int delegateVideoNodeDidStallAtTimeInterval:1;
|
||||
unsigned int delegateVideoNodeDidRecoverFromStall:1;
|
||||
unsigned int delegateVideoNodeDidFailToLoadValueForKey:1;
|
||||
} _delegateFlags;
|
||||
|
||||
BOOL _shouldBePlaying;
|
||||
@@ -101,6 +109,9 @@ static NSString * const kRate = @"rate";
|
||||
[self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
_lastPlaybackTime = kCMTimeZero;
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -143,6 +154,9 @@ static NSString * const kRate = @"rate";
|
||||
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error];
|
||||
if (keyStatus == AVKeyValueStatusFailed) {
|
||||
NSLog(@"Asset loading failed with error: %@", error);
|
||||
if (_delegateFlags.delegateVideoNodeDidFailToLoadValueForKey) {
|
||||
[self.delegate videoNode:self didFailToLoadValueForKey:key asset:asset error:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,6 +607,7 @@ static NSString * const kRate = @"rate";
|
||||
_delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)];
|
||||
_delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)];
|
||||
_delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)];
|
||||
_delegateFlags.delegateVideoNodeDidFailToLoadValueForKey = [delegate respondsToSelector:@selector(videoNode:didFailToLoadValueForKey:asset:error:)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,6 +722,13 @@ static NSString * const kRate = @"rate";
|
||||
|
||||
#pragma mark - Playback observers
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification
|
||||
{
|
||||
if (self.shouldBePlaying && self.isVisible) {
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didPlayToEnd:(NSNotification *)notification
|
||||
{
|
||||
self.playerState = ASVideoNodePlayerStateFinished;
|
||||
@@ -818,6 +840,9 @@ static NSString * const kRate = @"rate";
|
||||
_timeObserver = nil;
|
||||
[self removePlayerItemObservers:_currentPlayerItem];
|
||||
[self removePlayerObservers:_player];
|
||||
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASVideoPlayerNode.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Erekle on 5/6/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
//
|
||||
// ASVideoPlayerNode.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Erekle on 5/6/16.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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 <Foundation/Foundation.h>
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#if TARGET_OS_IOS
|
||||
@@ -250,6 +257,11 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)createControls
|
||||
@@ -670,8 +682,10 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
||||
|
||||
- (void)didTapFullScreenButton:(ASButtonNode*)node
|
||||
{
|
||||
if (_delegateFlags.delegateDidTapFullScreenButtonNode) {
|
||||
[_delegate didTapFullScreenButtonNode:node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)beginSeek
|
||||
{
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
//
|
||||
// ASViewController.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 16/09/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
//
|
||||
// ASViewController.mm
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 16/09/15.
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. 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
|
||||
//
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASViewController.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#define AS_LOG_VISIBILITY_CHANGES 0
|
||||
|
||||
@implementation ASViewController
|
||||
{
|
||||
BOOL _ensureDisplayed;
|
||||
@@ -166,7 +169,11 @@ ASVisibilityDidMoveToParentViewController;
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
as_activity_create_for_scope("ASViewController will appear");
|
||||
as_log_debug(ASNodeLog(), "View controller %@ will appear", self);
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
_ensureDisplayed = YES;
|
||||
|
||||
// A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc.
|
||||
@@ -188,7 +195,7 @@ ASVisibilityDepthImplementation;
|
||||
- (void)visibilityDepthDidChange
|
||||
{
|
||||
ASLayoutRangeMode rangeMode = ASLayoutRangeModeForVisibilityDepth(self.visibilityDepth);
|
||||
#if AS_LOG_VISIBILITY_CHANGES
|
||||
#if ASEnableVerboseLogging
|
||||
NSString *rangeModeString;
|
||||
switch (rangeMode) {
|
||||
case ASLayoutRangeModeMinimum:
|
||||
@@ -210,7 +217,7 @@ ASVisibilityDepthImplementation;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
NSLog(@"Updating visibility of:%@ to: %@ (visibility depth: %d)", self, rangeModeString, self.visibilityDepth);
|
||||
as_log_verbose(ASNodeLog(), "Updating visibility of %@ to: %@ (visibility depth: %zd)", self, rangeModeString, self.visibilityDepth);
|
||||
#endif
|
||||
[self updateCurrentRangeModeWithModeIfPossible:rangeMode];
|
||||
}
|
||||
@@ -281,6 +288,8 @@ ASVisibilityDepthImplementation;
|
||||
ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection;
|
||||
|
||||
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) {
|
||||
as_activity_scope_verbose(as_activity_create("Propagate ASViewController trait collection", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_debug(ASNodeLog(), "Propagating new traits for %@: %@", self, NSStringFromASPrimitiveTraitCollection(traitCollection));
|
||||
self.node.primitiveTraitCollection = traitCollection;
|
||||
|
||||
NSArray<id<ASLayoutElement>> *children = [self.node sublayoutElements];
|
||||
@@ -292,6 +301,7 @@ ASVisibilityDepthImplementation;
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
// Once we've propagated all the traits, layout this node.
|
||||
// Remeasure the node with the latest constrained size – old constrained size may be incorrect.
|
||||
as_activity_scope_verbose(as_activity_create("Layout ASViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
[_node layoutThatFits:[self nodeConstrainedSize]];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user