微博 Demo 性能优化技巧
预排版
预渲染
异步绘制
全局并发控制
更高效的异步图片加载
其他可以改进的地方
如何评测界面的流畅度
高效布局文本和图片
YYText框架
YYText框架根据文字的内容、字体大小 去计算文本行高的思想是很值得学习的 ,在页面布局复杂的时候 比masonry
好用很多,能够大幅度减少卡顿。 YYText框架 主要是 利用了 YYTextContainer
TextLinePositionModifier
这几个处理文本的类。
比如下面的代码
_textLabel.textLayout = feed.layout.textLayout;
YYControl
YYControl
是更轻量级 代替UIImageView
的控件
#import <UIKit/UIKit.h>
#import "YYKit.h"
@interface YYControl : UIView
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, copy) void (^touchBlock)(YYControl *view, YYGestureRecognizerState state, NSSet *touches, UIEvent *event);
@property (nonatomic, copy) void (^longPressBlock)(YYControl *view, CGPoint point);
@end
CALayer (YYWebImage)
YYWebImage 是CALayer
高效异步下载图片的分类
下面开始详细介绍使用方法
1.手动布局
在网络请求获取到数据的时候去计算布局layout
for (WBStatus *status in item.statuses) {
WBStatusLayout *layout = [[WBStatusLayout alloc] initWithStatus:status style:WBLayoutStyleTimeline];
[_layouts addObject:layout];
}
2.在layout中 通过具体的数据内容去计算各种文本和图片的位置布局
@implementation WBStatusLayout
- (instancetype)initWithStatus:(WBStatus *)status style:(WBLayoutStyle)style {
if (!status || !status.user) return nil;
self = [super init];
_status = status;
_style = style;
[self layout];
return self;
}
- (void)_layout {
_marginTop = kWBCellTopMargin;
_titleHeight = 0;
_profileHeight = 0;
_textHeight = 0;
_retweetHeight = 0;
_retweetTextHeight = 0;
_retweetPicHeight = 0;
_retweetCardHeight = 0;
_picHeight = 0;
_cardHeight = 0;
_toolbarHeight = kWBCellToolbarHeight;
_marginBottom = kWBCellToolbarBottomMargin;
// 文本排版,计算布局
[self _layoutTitle];
[self _layoutProfile];
[self _layoutRetweet];
if (_retweetHeight == 0) {
[self _layoutPics];
if (_picHeight == 0) {
[self _layoutCard];
}
}
[self _layoutText];
[self _layoutTag];
[self _layoutToolbar];
// 计算高度
_height = 0;
_height += _marginTop;
_height += _titleHeight;
_height += _profileHeight;
_height += _textHeight;
if (_retweetHeight > 0) {
_height += _retweetHeight;
} else if (_picHeight > 0) {
_height += _picHeight;
} else if (_cardHeight > 0) {
_height += _cardHeight;
}
if (_tagHeight > 0) {
_height += _tagHeight;
} else {
if (_picHeight > 0 || _cardHeight > 0) {
_height += kWBCellPadding;
}
}
_height += _toolbarHeight;
_height += _marginBottom;
}
/// 文本
- (void)_layoutText {
_textHeight = 0;
_textLayout = nil;
NSMutableAttributedString *text = [self _textWithStatus:_status
isRetweet:NO
fontSize:kWBCellTextFontSize
textColor:kWBCellTextNormalColor];
if (text.length == 0) return;
WBTextLinePositionModifier *modifier = [WBTextLinePositionModifier new];
modifier.font = [UIFont fontWithName:@"Heiti SC" size:kWBCellTextFontSize];
modifier.paddingTop = kWBCellPaddingText;
modifier.paddingBottom = kWBCellPaddingText;
YYTextContainer *container = [YYTextContainer new];
container.size = CGSizeMake(kWBCellContentWidth, HUGE);
container.linePositionModifier = modifier;
_textLayout = [YYTextLayout layoutWithContainer:container text:text];
if (!_textLayout) return;
_textHeight = [modifier heightForLineCount:_textLayout.rowCount];
}
- (void)_layoutPicsWithStatus:(WBStatus *)status isRetweet:(BOOL)isRetweet {
if (isRetweet) {
_retweetPicSize = CGSizeZero;
_retweetPicHeight = 0;
} else {
_picSize = CGSizeZero;
_picHeight = 0;
}
if (status.pics.count == 0) return;
CGSize picSize = CGSizeZero;
CGFloat picHeight = 0;
CGFloat len1_3 = (kWBCellContentWidth + kWBCellPaddingPic) / 3 - kWBCellPaddingPic;
len1_3 = CGFloatPixelRound(len1_3);
switch (status.pics.count) {
case 1: {
WBPicture *pic = _status.pics.firstObject;
WBPictureMetadata *bmiddle = pic.bmiddle;
if (pic.keepSize || bmiddle.width < 1 || bmiddle.height < 1) {
CGFloat maxLen = kWBCellContentWidth / 2.0;
maxLen = CGFloatPixelRound(maxLen);
picSize = CGSizeMake(maxLen, maxLen);
picHeight = maxLen;
} else {
CGFloat maxLen = len1_3 * 2 + kWBCellPaddingPic;
if (bmiddle.width < bmiddle.height) {
picSize.width = (float)bmiddle.width / (float)bmiddle.height * maxLen;
picSize.height = maxLen;
} else {
picSize.width = maxLen;
picSize.height = (float)bmiddle.height / (float)bmiddle.width * maxLen;
}
picSize = CGSizePixelRound(picSize);
picHeight = picSize.height;
}
} break;
case 2: case 3: {
picSize = CGSizeMake(len1_3, len1_3);
picHeight = len1_3;
} break;
case 4: case 5: case 6: {
picSize = CGSizeMake(len1_3, len1_3);
picHeight = len1_3 * 2 + kWBCellPaddingPic;
} break;
default: { // 7, 8, 9
picSize = CGSizeMake(len1_3, len1_3);
picHeight = len1_3 * 3 + kWBCellPaddingPic * 2;
} break;
}
if (isRetweet) {
_retweetPicSize = picSize;
_retweetPicHeight = picHeight;
} else {
_picSize = picSize;
_picHeight = picHeight;
}
}
- WBStatusView 初始化的创建
@implementation WBStatusView {
BOOL _touchRetweetView;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (frame.size.width == 0 && frame.size.height == 0) {
frame.size.width = kScreenWidth;
frame.size.height = 1;
}
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor clearColor];
self.exclusiveTouch = YES;
@weakify(self);
_contentView = [UIView new];
_contentView.width = kScreenWidth;
_contentView.height = 1;
_contentView.backgroundColor = [UIColor whiteColor];
_profileView = [WBStatusProfileView new];
[_contentView addSubview:_profileView];
_textLabel = [YYLabel new];
_textLabel.left = kWBCellPadding;
_textLabel.width = kWBCellContentWidth;
_textLabel.textVerticalAlignment = YYTextVerticalAlignmentTop;
_textLabel.displaysAsynchronously = YES;
_textLabel.ignoreCommonProperties = YES;
_textLabel.fadeOnAsynchronouslyDisplay = NO;
_textLabel.fadeOnHighlight = NO;
_textLabel.highlightTapAction = ^(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect) {
if ([weak_self.cell.delegate respondsToSelector:@selector(cell:didClickInLabel:textRange:)]) {
[weak_self.cell.delegate cell:weak_self.cell didClickInLabel:(YYLabel *)containerView textRange:range];
}
};
[_contentView addSubview:_textLabel];
NSMutableArray *picViews = [NSMutableArray new];
for (int i = 0; i < 9; i++) {
YYControl *imageView = [YYControl new];
imageView.size = CGSizeMake(100, 100);
imageView.hidden = YES;
imageView.clipsToBounds = YES;
imageView.backgroundColor = kWBCellHighlightColor;
imageView.exclusiveTouch = YES;
imageView.touchBlock = ^(YYControl *view, YYGestureRecognizerState state, NSSet *touches, UIEvent *event) {
if (![weak_self.cell.delegate respondsToSelector:@selector(cell:didClickImageAtIndex:)]) return;
if (state == YYGestureRecognizerStateEnded) {
UITouch *touch = touches.anyObject;
CGPoint p = [touch locationInView:view];
if (CGRectContainsPoint(view.bounds, p)) {
[weak_self.cell.delegate cell:weak_self.cell didClickImageAtIndex:i];
}
}
};
UIView *badge = [UIImageView new];
badge.userInteractionEnabled = NO;
badge.contentMode = UIViewContentModeScaleAspectFit;
badge.size = CGSizeMake(56 / 2, 36 / 2);
badge.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
badge.right = imageView.width;
badge.bottom = imageView.height;
badge.hidden = YES;
[imageView addSubview:badge];
[picViews addObject:imageView];
[_contentView addSubview:imageView];
}
_picViews = picViews;
return self;
}
- 通过计算好的layout 去摆放各种文字 图片控件
- (void)setLayout:(WBStatusLayout *)layout {
_layout = layout;
self.height = layout.height;
_contentView.top = layout.marginTop;
_contentView.height = layout.height - layout.marginTop - layout.marginBottom;
_textLabel.top = top;
_textLabel.height = layout.textHeight;
_textLabel.textLayout = layout.textLayout;
top += layout.textHeight;
//优先级是 转发->图片->卡片
if (layout.retweetHeight > 0) {
_retweetBackgroundView.top = top;
_retweetBackgroundView.height = layout.retweetHeight;
_retweetBackgroundView.hidden = NO;
_retweetTextLabel.top = top;
_retweetTextLabel.height = layout.retweetTextHeight;
_retweetTextLabel.textLayout = layout.retweetTextLayout;
_retweetTextLabel.hidden = NO;
if (layout.retweetPicHeight > 0) {
//布局图片
[self _setImageViewWithTop:_retweetTextLabel.bottom isRetweet:YES];
} else {
[self _hideImageViews];
if (layout.retweetCardHeight > 0) {
_cardView.top = _retweetTextLabel.bottom;
_cardView.hidden = NO;
[_cardView setWithLayout:layout isRetweet:YES];
}
}
} else if (layout.picHeight > 0) {
[self _setImageViewWithTop:top isRetweet:NO];
} else if (layout.cardHeight > 0) {
_cardView.top = top;
_cardView.hidden = NO;
[_cardView setWithLayout:layout isRetweet:NO];
}
if (layout.tagHeight > 0) {
_tagView.hidden = NO;
[_tagView setWithLayout:layout];
_tagView.centerY = _contentView.height - kWBCellToolbarHeight - layout.tagHeight / 2;
} else {
_tagView.hidden = YES;
}
}
- (void)_hideImageViews {
for (UIImageView *imageView in _picViews) {
imageView.hidden = YES;
}
}
//布局图片
- (void)_setImageViewWithTop:(CGFloat)imageTop isRetweet:(BOOL)isRetweet {
CGSize picSize = isRetweet ? _layout.retweetPicSize : _layout.picSize;
NSArray *pics = isRetweet ? _layout.status.retweetedStatus.pics : _layout.status.pics;
int picsCount = (int)pics.count;
for (int i = 0; i < 9; i++) {
UIView *imageView = _picViews[i];
if (i >= picsCount) {
[imageView.layer cancelCurrentImageRequest];
imageView.hidden = YES;
} else {
CGPoint origin = {0};
switch (picsCount) {
case 1: {
origin.x = kWBCellPadding;
origin.y = imageTop;
} break;
case 4: {
origin.x = kWBCellPadding + (i % 2) * (picSize.width + kWBCellPaddingPic);
origin.y = imageTop + (int)(i / 2) * (picSize.height + kWBCellPaddingPic);
} break;
default: {
origin.x = kWBCellPadding + (i % 3) * (picSize.width + kWBCellPaddingPic);
origin.y = imageTop + (int)(i / 3) * (picSize.height + kWBCellPaddingPic);
} break;
}
imageView.frame = (CGRect){.origin = origin, .size = picSize};
imageView.hidden = NO;
[imageView.layer removeAnimationForKey:@"contents"];
WBPicture *pic = pics[i];
UIView *badge = imageView.subviews.firstObject;
switch (pic.largest.badgeType) {
case WBPictureBadgeTypeNone: {
if (badge.layer.contents) {
badge.layer.contents = nil;
badge.hidden = YES;
}
} break;
case WBPictureBadgeTypeLong: {
badge.layer.contents = (__bridge id)([WBStatusHelper imageNamed:@"timeline_image_longimage"].CGImage);
badge.hidden = NO;
} break;
case WBPictureBadgeTypeGIF: {
badge.layer.contents = (__bridge id)([WBStatusHelper imageNamed:@"timeline_image_gif"].CGImage);
badge.hidden = NO;
} break;
}
@weakify(imageView);
[imageView.layer setImageWithURL:pic.bmiddle.url
placeholder:nil
options:YYWebImageOptionAvoidSetImage
completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
@strongify(imageView);
if (!imageView) return;
if (image && stage == YYWebImageStageFinished) {
int width = pic.bmiddle.width;
int height = pic.bmiddle.height;
CGFloat scale = (height / width) / (imageView.height / imageView.width);
if (scale < 0.99 || isnan(scale)) { // 宽图把左右两边裁掉
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.layer.contentsRect = CGRectMake(0, 0, 1, 1);
} else { // 高图只保留顶部
imageView.contentMode = UIViewContentModeScaleToFill;
imageView.layer.contentsRect = CGRectMake(0, 0, 1, (float)width / height);
}
((YYControl *)imageView).image = image;
if (from != YYWebImageFromMemoryCacheFast) {
CATransition *transition = [CATransition animation];
transition.duration = 0.15;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
transition.type = kCATransitionFade;
[imageView.layer addAnimation:transition forKey:@"contents"];
}
}
}];
}
}
}
YYTextContainer
TextLinePositionModifier
如何计算文本布局的内部实现大家可以自己学习一下
参考来源
https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
网友评论