个人觉得这个功能还是十分便捷的,有时候设计很经常又要求图片显示区域小,但是点击区域又要很大,这时候就可以使用Hit Test Slop来完成。 hitTestSlop属性是一个UIEdgeInsets类型,可以通过它来扩大或者缩小可点击区域。值得注意的是这个属性在ASDisplayNode类中,所以任何的节点都可以使用它来扩展点击区域。
- (void)setNeedsDisplay { //........... { //........... if (isRasterized == NO && shouldApply == NO) { // We can't release the lock before applying to pending state, or it may be flushed before it can be applied. [ASDisplayNodeGetPendingState(self) setNeedsDisplay]; } } if (isRasterized) { ASPerformBlockOnMainThread(^{ // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node // begins materializing the view / layer hierarchy (locking itself or a descendant) while this node walks up // the tree and requires locking that node to access .rasterizesSubtree. // For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized. ASDisplayNodeAssertMainThread(); ASDisplayNode *rasterizedContainerNode = self.supernode; while (rasterizedContainerNode) { if (rasterizedContainerNode.rasterizesSubtree) { break; } rasterizedContainerNode = rasterizedContainerNode.supernode; } //将根节点标记为dirty [rasterizedContainerNode setNeedsDisplay]; }); } else { if (shouldApply) { // If not rasterized, and the node is loaded (meaning we certainly have a view or layer), send a // message to the view/layer first. This is because __setNeedsDisplay calls as scheduleNodeForDisplay, // which may call -displayIfNeeded. We want to ensure the needsDisplay flag is set now, and then cleared. [viewOrLayer setNeedsDisplay]; } [self __setNeedsDisplay]; } }
- (void)__setNeedsDisplay { BOOL shouldScheduleForDisplay = NO; { MutexLocker l(__instanceLock__); BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); // FIXME: This should not need to recursively display, so create a non-recursive variant. // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) { shouldScheduleForDisplay = YES; } } if (shouldScheduleForDisplay) { [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; } }
- (void)registerAsMainRunloopObserver { ASDisplayNodeAssertMainThread(); staticCFRunLoopObserverRef observer; CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping kCFRunLoopExit); // before exiting a runloop run
@desc ASAsyncTransaction provides the following properties:
Transactions group an arbitrary number of operations, each consisting of an execution block and a completion block. Transactions 将任意多个operations打包在一起,每个Transaction包含一个executionBlok以及一个completionblock
The execution block returns a single object that will be passed to the completion block. execution block将会生成一个对象,然后将这个对象传递给completionblock
Execution blocks added to a transaction will run in parallel on the global background dispatch queues; the completion blocks are dispatched to the callback queue. 添加到transaction的execution blocks将会在后台线程中并行运行,而completion block将会在回调队列中执行。
Every operation completion block is guaranteed to execute, regardless of cancelation. However, execution blocks may be skipped if the transaction is canceled. 只要transaction不被取消每个completion block都会确保执行。
Operation completion blocks are always executed in the order they were added to the transaction, assuming the callback queue is serial of course. completion总是会按照加入到transaction的顺序执行。
@property (nonatomic) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock; @property id value; // set on bg queue by the operation block
value 就是 display block 返回的UIImage,这里会在后台线程中生成UIImage,然后将其保存到value中,这个后面会介绍。最关键的是callAndReleaseCompletionBlock这个方法,CompletionBlock就是在这个方法中调用的,至于callAndReleaseCompletionBlock什么时候调用先留在后面。
// Bit questionable maybe - we can give main thread more CPU time during tracking. if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode]) --maxThreads; #endif if (entry._threadCount < maxThreads) { // we need to spawn another thread
// first thread will take operations in queue order (regardless of priority), other threads will respect priority bool respectPriority = entry._threadCount > 0; ++entry._threadCount; dispatch_async(queue, ^{ std::unique_lock<std::mutex> lock(q._mutex); // go until there are no more pending operations while (!entry._operationQueue.empty()) { Operation operation = entry.popNextOperation(respectPriority); lock.unlock(); if (operation._block) { operation._block(); } operation._group->leave(); operation._block = nil; // the block must be freed while mutex is unlocked lock.lock(); } --entry._threadCount; if (entry._threadCount == 0) { NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen q._entries.erase(queue); } }); } }
//............ if (asynchronously) { // 异步渲染操作被包含在transaction中,transaction可以允许并行处理最终将结果以同步的方式应用到layer的contents属性中 // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently // while synchronizing the final application of the results to the layer's contents property (completionBlock). // 2. 我们先查看我们是否可以将自己添加到父类的transaction容器中 // First, look to see if we are expected to join a parent's transaction container. CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer; // 如果transaction没有存在那么这里将会实例化并将它添加到_ASAsyncTransactionGroup,它会在runloop结束或者即将进入休眠之前提交出去 // In the case that a transaction does not yet exist (such as for an individual node outside of a container), // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. // It will automatically commit the transaction at the end of the runloop. _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; //3. 将displayBlock添加到transaction,transaction将会立即启动执行 // Adding this displayBlock operation to the transaction will start it IMMEDIATELY. // The only function of the transaction commit is to gate the calling of the completionBlock. [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; } else { //同步的情况下直接通过displayBlock生成contents,并通过completionBlock设置到layer.content UIImage *contents = (UIImage *)displayBlock(); completionBlock(contents, NO); } },
- (void)commit { ASDisplayNodeAssertMainThread(); NSAssert(self.state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction"); self.state = ASAsyncTransactionStateCommitted; if ([_operations count] == 0) { // Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously. if (_completionBlock) { _completionBlock(self, NO); } } else { NSAssert(_group != NULL, @"If there are operations, dispatch group should have been created"); //通过_group发出通知将后台得到的UImage通过completeBlock _group->notify(dispatch_get_main_queue(), ^{ [self completeTransaction]; }); } }
- (void)completeTransaction { ASDisplayNodeAssertMainThread(); ASAsyncTransactionState state = self.state; if (state != ASAsyncTransactionStateComplete) { BOOL isCanceled = (state == ASAsyncTransactionStateCanceled); //遍历每个ASAsyncTransactionOperation 让后台生成的image通过ComplateBlock设置到layer.content上 for (ASAsyncTransactionOperation *operation in _operations) { [operation callAndReleaseCompletionBlock:isCanceled]; } // Always setstateto Complete, even if we were cancelled, toblockany extraneous // calls to this method that may have been scheduled for the next runloop // (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled) self.state = ASAsyncTransactionStateComplete;
//通过回调通知当前transation结束 if (_completionBlock) { _completionBlock(self, isCanceled); } } }
flags = _flags; // We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent. //如果没有display方法或者正在将子节点绘制到栅格化的父类上的时候需要创建Context BOOL shouldCreateGraphicsContext = (flags.implementsImageDisplay == NO && rasterizing == NO); //子节点时候需要栅格化 BOOL shouldBeginRasterizing = (rasterizing == NO && flags.rasterizesSubtree); //是否实现了display方法 BOOL usesImageDisplay = flags.implementsImageDisplay; //是否实现了drawRect方法 BOOL usesDrawRect = flags.implementsDrawRect; //如果没有实现display/drawRect方法并且子节点不需要栅格化直接返回 if (usesImageDisplay == NO && usesDrawRect == NO && shouldBeginRasterizing == NO) { // Early exit before requesting more expensive properties like bounds and opaque from the layer. __instanceLock__.unlock(); returnnil; } BOOL opaque = self.opaque; CGRect bounds = self.bounds; UIColor *backgroundColor = self.backgroundColor; CGColorRef borderColor = self.borderColor; CGFloat borderWidth = self.borderWidth; CGFloat contentsScaleForDisplay = _contentsScaleForDisplay; __instanceLock__.unlock();
// Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing. id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil); // Only the -display methods should be called if we can't size the graphics buffer to use. if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) { returnnil; } //.......... //是否栅格化 if (shouldBeginRasterizing) { // Collect displayBlocks for all descendants. // 用于放置子节点的displayBlock在父节点的displayBlock中直接遍历各个子节点的displayBlock NSMutableArray *displayBlocks = [[NSMutableArray alloc] init]; [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; CHECK_CANCELLED_AND_RETURN_NIL();
if (shouldCreateGraphicsContext && !currentContext) { ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size)); return; }
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. [self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters]; //根据情况调用display方法或者drawRect方法 if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly. image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock]; } elseif (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext. [self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; } [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; ASDN_DELAY_FOR_DISPLAY(); };
- (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks { // Skip subtrees that are hidden or zero alpha. if (self.isHidden || self.alpha <= 0.0) { return; } __instanceLock__.lock(); BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); __instanceLock__.unlock();
// if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { [self __layout]; }
// Capture these outside the display block so they are retained. UIColor *backgroundColor = self.backgroundColor; CGRect bounds = self.bounds; CGFloat cornerRadius = self.cornerRadius; BOOL clipsToBounds = self.clipsToBounds;
CGRect frame; // If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint. if (self.rasterizesSubtree) { frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height); } else { CGPoint position = self.position; CGPoint anchorPoint = self.anchorPoint; // Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot. CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform); CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform); CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x, position.y - scaledBoundsSize.height * anchorPoint.y); frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height); }
// Get the display block for this node. asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];
// We'll display something if there is a display block, clipping, translation and/or a background color. BOOL shouldDisplay = displayBlock || backgroundColor || CGPointEqualToPoint(CGPointZero, frame.origin) == NO || clipsToBounds;
// If we should display, then push a transform, draw the background color, and draw the contents. // The transform is popped in a block added after the recursion into subnodes. if (shouldDisplay) { dispatch_block_t pushAndDisplayBlock = ^{ // Push transform relative to parent. CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context);
// Fill background if any. CGColorRef backgroundCGColor = backgroundColor.CGColor; if (backgroundColor && CGColorGetAlpha(backgroundCGColor) > 0.0) { CGContextSetFillColorWithColor(context, backgroundCGColor); CGContextFillRect(context, bounds); }
// If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store). if (displayBlock) { UIImage *image = (UIImage *)displayBlock(); if (image) { BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)); CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal; [image drawInRect:bounds blendMode:blendMode alpha:1]; } } }; [displayBlocks addObject:pushAndDisplayBlock]; }
// Recursively capture displayBlocks for all descendants. for (ASDisplayNode *subnode inself.subnodes) { [subnode _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks]; }
// If we pushed a transform, pop it by adding a display block that does nothing other than that. if (shouldDisplay) { // Since this block is pure, we can store it statically. static dispatch_block_t popBlock; staticdispatch_once_t onceToken; dispatch_once(&onceToken, ^{ popBlock = ^{ CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRestoreGState(context); }; }); [displayBlocks addObject:popBlock]; } }
- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock rasterizing:(BOOL)rasterizing { ASDisplayNodeAssertMainThread(); //........ //是否实现了display方法 BOOL usesImageDisplay = flags.implementsImageDisplay; //是否实现了drawRect方法 BOOL usesDrawRect = flags.implementsDrawRect; //.......... //是否栅格化 if (shouldBeginRasterizing) { //....... } else { displayBlock = ^id{ __block UIImage *image = nil; void (^workWithContext)() = ^{ CGContextRef currentContext = UIGraphicsGetCurrentContext(); // ....... // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs. [self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters]; //根据情况调用display方法或者drawRect方法 if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly. image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock]; } elseif (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext. [self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing]; } [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; ASDN_DELAY_FOR_DISPLAY(); };
/** * Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts */ - (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED { structContext { unowned ASLayout *layout; CGPoint absolutePosition; }; // Queue used to keep track of sublayouts while traversing this layout in a DFS fashion. std::deque<Context> queue; //将_sublayouts数据先填到queue for (ASLayout *sublayout in _sublayouts) { queue.push_back({sublayout, sublayout.position}); } //这个是扁平化的子布局 std::vector<ASLayout *> flattenedSublayouts; while (!queue.empty()) { //从queue中拿出数据 const Context context = std::move(queue.front()); queue.pop_front(); unowned ASLayout *layout = context.layout; // Direct ivar access to avoid retain/release, use existing +1. const NSUInteger sublayoutsCount = layout->_sublayouts.count; const CGPoint absolutePosition = context.absolutePosition; //是否是DisplayNode节点,如果是节点就不需要扁平化,看absolutePosition是否和layout.position是否相同,如果不相同就需要新建一个放到flattenedSublayouts if (ASLayoutIsDisplayNodeType(layout)) { if (sublayoutsCount > 0 || CGPointEqualToPoint(ASCeilPointValues(absolutePosition), layout.position) == NO) { // Only create a new layout if the existing one can't be reused, which means it has either some sublayouts or an invalid absolute position. constauto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement size:layout.size position:absolutePosition sublayouts:@[]]; flattenedSublayouts.push_back(newLayout); } else { flattenedSublayouts.push_back(layout); } } elseif (sublayoutsCount > 0) { //如果是一个布局容器,那么就需要根据节点之间的关系修正positon位置 // Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue. unowned ASLayout *rawSublayouts[sublayoutsCount]; [layout->_sublayouts getObjects:rawSublayouts range:NSMakeRange(0, sublayoutsCount)]; for (NSInteger i = sublayoutsCount - 1; i >= 0; i--) { queue.push_front({rawSublayouts[i], absolutePosition + rawSublayouts[i].position}); } } } //将flattenedSublayouts转换成ASLayout NSArray *array = [NSArray arrayByTransferring:flattenedSublayouts.data() count:flattenedSublayouts.size()]; ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:array]; [layout retainSublayoutElements]; return layout; }
- (void)_layoutSublayouts { ASDisplayNodeAssertThreadAffinity(self); //............ for (ASDisplayNode *node inself.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; } } }
- (void)__layout { //...... BOOL loaded = NO; { //........ // This method will confirm that the layout is up to date (and update if needed). // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). l.unlock(); // 如果过期了就会重新计算,否则使用缓存的布局 [self _u_measureNodeWithBoundsIfNecessary:bounds]; l.lock(); //布局占位图 [self _locked_layoutPlaceholderIfNecessary]; } //调用_layoutSublayouts [self _layoutSublayouts]; if (loaded) { ASPerformBlockOnMainThread(^{ [self layout]; [self _layoutClipCornersIfNeeded]; [self _layoutDidFinish]; }); } [self _fallbackUpdateSafeAreaOnChildren]; }
而__layout 是再layoutIfNeeded中调用的。
- (void)layoutIfNeeded { //....... if (shouldApply) { // The node is loaded and we're on main. // Message the view or layer which in turn will call __layout on us (see -[_ASDisplayLayer layoutSublayers]). [viewOrLayer layoutIfNeeded]; } else if (loaded == NO) { // The node is not loaded and we're not on main. [self __layout]; } }
- (void)setNeedsLayout { //..... if (shouldApply) { // The node is loaded and we're on main. // Quite the opposite of setNeedsDisplay, we must call __setNeedsLayout before messaging // the view or layer to ensure that measurement and implicitly added subnodes have been handled. [self __setNeedsLayout]; [viewOrLayer setNeedsLayout]; } else if (loaded == NO) { // The node is not loaded and we're not on main. [self __setNeedsLayout]; } } - (void)__setNeedsLayout { [self invalidateCalculatedLayout]; } - (void)invalidateCalculatedLayout { _layoutVersion++; }
3.If you want toget the highest performance, you should dotext layout with `YYTextLayout` classin background thread. Here's an example: 如果我们想获得最高的性能,我们需要在后台使用YYTextLayout来做布局
/** The font of the text. */ @property (nullable, nonatomic, strong, readwrite) UIFont *yy_font; - (void)yy_setFont:(nullableUIFont *)font range:(NSRange)range;
/** An NSParagraphStyle object which is used to specify things like line alignment, tab rulers, writing direction, etc. */ @property (nullable, nonatomic, strong, readwrite) NSParagraphStyle *yy_paragraphStyle; - (void)yy_setParagraphStyle:(nullableNSParagraphStyle *)paragraphStyle range:(NSRange)range;
/** Convenience method to set text highlight @param range text range @param color text color (pass nil to ignore) @param backgroundColor text background color when highlight @param userInfo user information dictionary (pass nil to ignore) @param tapAction tap action when user tap the highlight (pass nil to ignore) @param longPressAction long press action when user long press the highlight (pass nil to ignore) */ - (void)yy_setTextHighlightRange:(NSRange)range color:(nullableUIColor *)color backgroundColor:(nullableUIColor *)backgroundColor userInfo:(nullableNSDictionary *)userInfo tapAction:(nullable YYTextAction)tapAction longPressAction:(nullable YYTextAction)longPressAction;
/** Creates and returns an attachment. Example: ContentMode:bottom Alignment:Top. The text The attachment holder ↓ ↓ ─────────┌──────────────────────┐─────── / \ │ │ / ___| / _ \ │ │|| / ___ \ │ │||___ ←── The text line /_/ \_\│ ██████████████ │ \____| ─────────│ ██████████████ │─────── │ ██████████████ │ │ ██████████████ ←───────────────── The attachment content │ ██████████████ │ └──────────────────────┘
@param content The attachment (UIImage/UIView/CALayer). @param contentMode The attachment's content mode in attachment holder @param attachmentSize The attachment holder's size in text layout. @param fontSize The attachment will align to this font. @param alignment The attachment holder's alignment to text line. @return An attributed string, or nil if an error occurs. @since YYText:6.0 */ + (NSMutableAttributedString *)yy_attachmentStringWithContent:(nullable id)content contentMode:(UIViewContentMode)contentMode attachmentSize:(CGSize)attachmentSize alignToFont:(UIFont *)font alignment:(YYTextVerticalAlignment)alignment;
/** The YYTextContainer class defines a region in which textis laid out. YYTextLayout class uses one or more YYTextContainer objects to generate layouts. A YYTextContainer defines rectangular regions (`size` and `insets`) or nonrectangular shapes (`path`), and you can define exclusion paths inside the text container's bounding rectangle so thattext flows aroundthe exclusion path asitis laid out. All methods in this classis thread-safe. Example: ┌─────────────────────────────┐ <------- container │ │ │ asdfasdfasdfasdfasdfa <------------ container insets │ asdfasdfa asdfasdfa │ │ asdfas asdasd │ │ asdfa <----------------------- container exclusion path │ asdfas adfasd │ │ asdfasdfa asdfasdfa │ │ asdfasdfasdfasdfasdfa │ │ │ └─────────────────────────────┘ */
/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. + (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets;
/// Creates a container with the specified path. @param size The path. + (instancetype)containerWithPath:(nullableUIBezierPath *)path;
/** YYTextLayout classis a readonly class stores text layout result. All thepropertyin this classis readonly, and should not be changed. The methods in this classis thread-safe (except someofthe draw methods). example: (layout with a circle exclusion path) ┌──────────────────────────┐ <------ container │ [--------Line0--------] │ <- Row0 │ [--------Line1--------] │ <- Row1 │ [-Line2-] [-Line3-] │ <- Row2 │ [-Line4] [Line5-] │ <- Row3 │ [-Line6-] [-Line7-] │ <- Row4 │ [--------Line8--------] │ <- Row5 │ [--------Line9--------] │ <- Row6 └──────────────────────────┘ */
下面是最常用的两个布局方法:
/** Generate a layout with the given container size andtext.
@param size The text container's size @param text The text (if nil, returns nil). @return A new layout, or nil when an error occurs. */ + (nullable YYTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text;
/** Generate a layout with the given container andtext. @param container The text container (if nil, returns nil). @param text The text (if nil, returns nil). @return A new layout, or nil when an error occurs. */ + (nullable YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text;
第一个基于规定尺寸的,第二个是基于文本容器的。
布局后用得比较多的属性如下:
///< Bounding rect (glyphs) @property (nonatomic, readonly) CGRect textBoundingRect; ///< Bounding size (glyphs and insets, ceil to pixel) @property (nonatomic, readonly) CGSize textBoundingSize; ///< Number of rows @property (nonatomic, readonly) NSUInteger rowCount;
- (CGSize)calculateLayoutWithSize:(CGSize)size { NSAssert([NSThread isMainThread], @"Yoga calculation must be done on main."); NSAssert(self.isEnabled, @"Yoga is not enabled for this view."); //构建当前view为起点的节点树 YGAttachNodesFromViewHierachy(self.view);
static void YGApplyLayoutToViewHierarchy(UIView *view, BOOL preserveOrigin) { NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread.");