今天,工作当中用到了第三方代码SAVideoRangeSlider,现在特此记录一下学习的内容
整体来说这个代码还是比较简单的
这是结果:
下面是我对代码的注释理解
- (void)viewDidLoad
{
[super viewDidLoad];
self.myActivityIndicator.hidden = YES;
//临时路径
NSString *tempDir = NSTemporaryDirectory();
self.tmpVideoPath = [tempDir stringByAppendingPathComponent:@"tmpMov.mov"];
//加载本地mov
NSBundle *mainBundle = [NSBundle mainBundle];
self.originalVideoPath = [mainBundle pathForResource: @"thaiPhuketKaronBeach" ofType: @"MOV"];
NSURL *videoFileUrl = [NSURL fileURLWithPath:self.originalVideoPath];
//创建SAVideoRangeSlider
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
self.mySAVideoRangeSlider = [[SAVideoRangeSlider alloc] initWithFrame:CGRectMake(10, 200, self.view.frame.size.width-20, 70) videoUrl:videoFileUrl ];
[self.mySAVideoRangeSlider setPopoverBubbleSize:200 height:100];
} else {
self.mySAVideoRangeSlider = [[SAVideoRangeSlider alloc] initWithFrame:CGRectMake(10, 100, self.view.frame.size.width-20, 50) videoUrl:videoFileUrl ];
//设置字体大小,泡泡大小
self.mySAVideoRangeSlider.bubleText.font = [UIFont systemFontOfSize:12];
[self.mySAVideoRangeSlider setPopoverBubbleSize:120 height:60];
}
// Yellow 设置颜色
self.mySAVideoRangeSlider.topBorder.backgroundColor = [UIColor colorWithRed: 0.996 green: 0.951 blue: 0.502 alpha: 1];
self.mySAVideoRangeSlider.bottomBorder.backgroundColor = [UIColor colorWithRed: 0.992 green: 0.902 blue: 0.004 alpha: 1];
//设置代理
self.mySAVideoRangeSlider.delegate = self;
//加入视图
[self.view addSubview:self.mySAVideoRangeSlider];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - IBAction
//播放视频
- (IBAction)showOriginalVideo:(id)sender {
[self playMovie:self.originalVideoPath];
}
//播放剪裁好的视频
- (IBAction)showTrimmedVideo:(UIButton *)sender {
//如果有 删除临时文件
[self deleteTmpFile];
//把文件地址转为url
NSURL *videoFileUrl = [NSURL fileURLWithPath:self.originalVideoPath];
//转为AVAsset资源对象
AVAsset *anAsset = [[AVURLAsset alloc] initWithURL:videoFileUrl options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetMediumQuality]) {
//创建一个输出
self.exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetPassthrough];
// Implementation continues.
//设置url以及类型
NSURL *furl = [NSURL fileURLWithPath:self.tmpVideoPath];
self.exportSession.outputURL = furl;
self.exportSession.outputFileType = AVFileTypeQuickTimeMovie;
//设置剪裁时间
CMTime start = CMTimeMakeWithSeconds(self.startTime, anAsset.duration.timescale);
CMTime duration = CMTimeMakeWithSeconds(self.stopTime-self.startTime, anAsset.duration.timescale);
CMTimeRange range = CMTimeRangeMake(start, duration);
self.exportSession.timeRange = range;
self.trimBtn.hidden = YES;
self.myActivityIndicator.hidden = NO;
[self.myActivityIndicator startAnimating];
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([self.exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[self.exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
NSLog(@"NONE");
dispatch_async(dispatch_get_main_queue(), ^{
[self.myActivityIndicator stopAnimating];
self.myActivityIndicator.hidden = YES;
self.trimBtn.hidden = NO;
[self playMovie:self.tmpVideoPath];
});
break;
}
}];
}
}
#pragma mark - Other
//如果有文件 删除临时文件
-(void)deleteTmpFile{
NSURL *url = [NSURL fileURLWithPath:self.tmpVideoPath];
NSFileManager *fm = [NSFileManager defaultManager];
BOOL exist = [fm fileExistsAtPath:url.path];
NSError *err;
if (exist) {
[fm removeItemAtURL:url error:&err];
NSLog(@"file deleted");
if (err) {
NSLog(@"file remove error, %@", err.localizedDescription );
}
} else {
NSLog(@"no file by that name");
}
}
-(void)playMovie: (NSString *) path{
NSURL *url = [NSURL fileURLWithPath:path];
MPMoviePlayerViewController *theMovie = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[self presentMoviePlayerViewControllerAnimated:theMovie];
theMovie.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
[theMovie.moviePlayer play];
}
#pragma mark - SAVideoRangeSliderDelegate
- (void)videoRange:(SAVideoRangeSlider *)videoRange didChangeLeftPosition:(CGFloat)leftPosition rightPosition:(CGFloat)rightPosition
{
self.startTime = leftPosition;
self.stopTime = rightPosition;
self.timeLabel.text = [NSString stringWithFormat:@"%f - %f", leftPosition, rightPosition];
}
- (void)viewDidUnload {
[self setMyActivityIndicator:nil];
[self setTrimBtn:nil];
[super viewDidUnload];
}
在此贴出部分,剩余的都可以自己理解了
SAVideoRangeSlider.h
@protocol SAVideoRangeSliderDelegate;
@interface SAVideoRangeSlider : UIView
@property (nonatomic, weak) id <SAVideoRangeSliderDelegate> delegate; //代理
@property (nonatomic) CGFloat leftPosition; //左边位置
@property (nonatomic) CGFloat rightPosition; //右边位置
@property (nonatomic, strong) UILabel *bubleText; //泡泡上显示的label
@property (nonatomic, strong) UIView *topBorder; //顶部view
@property (nonatomic, strong) UIView *bottomBorder; //底部view
@property (nonatomic, assign) NSInteger maxGap;
@property (nonatomic, assign) NSInteger minGap;
//初始化方法
- (id)initWithFrame:(CGRect)frame videoUrl:(NSURL *)videoUrl;
//设置泡泡的大小
- (void)setPopoverBubbleSize: (CGFloat) width height:(CGFloat)height;
@end
@protocol SAVideoRangeSliderDelegate <NSObject>
@optional
//显示时间的代理
- (void)videoRange:(SAVideoRangeSlider *)videoRange didChangeLeftPosition:(CGFloat)leftPosition rightPosition:(CGFloat)rightPosition;
//拖动结束后的代理
- (void)videoRange:(SAVideoRangeSlider *)videoRange didGestureStateEndedLeftPosition:(CGFloat)leftPosition rightPosition:(CGFloat)rightPosition;
@end
SAVideoRangeSlider.m
#import "SAVideoRangeSlider.h"
@interface SAVideoRangeSlider ()
@property (nonatomic, strong) AVAssetImageGenerator *imageGenerator;//获得Asset的图像缩略图,预览图
@property (nonatomic, strong) UIView *bgView;
@property (nonatomic, strong) UIView *centerView;
@property (nonatomic, strong) NSURL *videoUrl;
@property (nonatomic, strong) SASliderLeft *leftThumb;//左边框
@property (nonatomic, strong) SASliderRight *rightThumb;//右边框
@property (nonatomic) CGFloat frame_width;
@property (nonatomic) Float64 durationSeconds;
@property (nonatomic, strong) SAResizibleBubble *popoverBubble;//泡泡label
@end
@implementation SAVideoRangeSlider
#define SLIDER_BORDERS_SIZE 6.0f
#define BG_VIEW_BORDERS_SIZE 3.0f
/**
初始化方法
@param frame slider的范围
@param videoUrl 视频资源的url
@returns 实例
*/
- (id)initWithFrame:(CGRect)frame videoUrl:(NSURL *)videoUrl
{
self = [super initWithFrame:frame];
if (self) {
_frame_width = frame.size.width;
int thumbWidth = ceil(frame.size.width*0.05);//返回大于或者等于指定表达式的最小整数
//创建背景框
_bgView = [[UIControl alloc] initWithFrame:CGRectMake(thumbWidth-BG_VIEW_BORDERS_SIZE, 0, frame.size.width-(thumbWidth*2)+BG_VIEW_BORDERS_SIZE*2, frame.size.height)];
_bgView.layer.borderColor = [UIColor grayColor].CGColor;
_bgView.layer.borderWidth = BG_VIEW_BORDERS_SIZE;
[self addSubview:_bgView];
_videoUrl = videoUrl;
_topBorder = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, SLIDER_BORDERS_SIZE)];
_topBorder.backgroundColor = [UIColor colorWithRed: 0.996 green: 0.951 blue: 0.502 alpha: 1];
[self addSubview:_topBorder];
_bottomBorder = [[UIView alloc] initWithFrame:CGRectMake(0, frame.size.height-SLIDER_BORDERS_SIZE, frame.size.width, SLIDER_BORDERS_SIZE)];
_bottomBorder.backgroundColor = [UIColor colorWithRed: 0.992 green: 0.902 blue: 0.004 alpha: 1];
[self addSubview:_bottomBorder];
//创建一个左边拖动的View,解决按不到的问题,thumbWidth+10
_leftThumb = [[SASliderLeft alloc] initWithFrame:CGRectMake(0, 0, thumbWidth+10, frame.size.height)];
_leftThumb.contentMode = UIViewContentModeLeft;
_leftThumb.userInteractionEnabled = YES;
_leftThumb.clipsToBounds = YES;
_leftThumb.backgroundColor = [UIColor clearColor];
_leftThumb.layer.borderWidth = 0;
[self addSubview:_leftThumb];
//添加一个拖动手势
UIPanGestureRecognizer *leftPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleLeftPan:)];
[_leftThumb addGestureRecognizer:leftPan];
//创建一个右边拖动的View,解决按不到的问题,thumbWidth+10
_rightThumb = [[SASliderRight alloc] initWithFrame:CGRectMake(0, 0, thumbWidth+10, frame.size.height)];
_rightThumb.contentMode = UIViewContentModeRight;
_rightThumb.userInteractionEnabled = YES;
_rightThumb.clipsToBounds = YES;
_rightThumb.backgroundColor = [UIColor clearColor];
[self addSubview:_rightThumb];
UIPanGestureRecognizer *rightPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightPan:)];
[_rightThumb addGestureRecognizer:rightPan];
//初始化左右的位置
_rightPosition = frame.size.width;
_leftPosition = 0;
//创建中心的view,加入手势
_centerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
_centerView.backgroundColor = [UIColor clearColor];
[self addSubview:_centerView];
//加入手势
UIPanGestureRecognizer *centerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleCenterPan:)];
[_centerView addGestureRecognizer:centerPan];
//创建泡泡label
_popoverBubble = [[SAResizibleBubble alloc] initWithFrame:CGRectMake(0, -50, 100, 50)];
_popoverBubble.alpha = 0;
_popoverBubble.backgroundColor = [UIColor clearColor];
[self addSubview:_popoverBubble];
_bubleText = [[UILabel alloc] initWithFrame:_popoverBubble.frame];
_bubleText.font = [UIFont boldSystemFontOfSize:20];
_bubleText.backgroundColor = [UIColor clearColor];
_bubleText.textColor = [UIColor blackColor];
_bubleText.textAlignment = UITextAlignmentCenter;
[_popoverBubble addSubview:_bubleText];
[self getMovieFrame];
[self setNeedsDisplay];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
//设置泡泡的大小
-(void)setPopoverBubbleSize: (CGFloat) width height:(CGFloat)height{
CGRect currentFrame = _popoverBubble.frame;
currentFrame.size.width = width;
currentFrame.size.height = height;
currentFrame.origin.y = -height;
_popoverBubble.frame = currentFrame;
currentFrame.origin.x = 0;
currentFrame.origin.y = 0;
_bubleText.frame = currentFrame;
}
//设置最大剪裁时间
-(void)setMaxGap:(NSInteger)maxGap{
_leftPosition = 0;
_rightPosition = _frame_width*maxGap/_durationSeconds;
_maxGap = maxGap;
}
//设置最小剪裁时间
-(void)setMinGap:(NSInteger)minGap{
_leftPosition = 0;
_rightPosition = _frame_width*minGap/_durationSeconds;
_minGap = minGap;
}
//通知界面刷新截选得时间
- (void)delegateNotification
{
if ([_delegate respondsToSelector:@selector(videoRange:didChangeLeftPosition:rightPosition:)]){
[_delegate videoRange:self didChangeLeftPosition:self.leftPosition rightPosition:self.rightPosition];
}
}
#pragma mark - Gestures
- (void)handleLeftPan:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self];//返回偏移量
_leftPosition += translation.x;
if (_leftPosition < 0) {
_leftPosition = 0;
}
//判断可否移动
if (
(_rightPosition-_leftPosition <= _leftThumb.frame.size.width+_rightThumb.frame.size.width) ||
((self.maxGap > 0) && (self.rightPosition-self.leftPosition > self.maxGap)) ||
((self.minGap > 0) && (self.rightPosition-self.leftPosition < self.minGap))
){
_leftPosition -= translation.x;
}
// 注意一旦你完成上述的移动,将translation重置为0十分重要。否则translation每次都会叠加,很快你的view就会移除屏幕!
[gesture setTranslation:CGPointZero inView:self];
[self setNeedsLayout];
[self delegateNotification];
}
_popoverBubble.alpha = 1;
[self setTimeLabel];
if (gesture.state == UIGestureRecognizerStateEnded){
[self hideBubble:_popoverBubble];
}
}
- (void)handleRightPan:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self];
_rightPosition += translation.x;
if (_rightPosition < 0) {
_rightPosition = 0;
}
if (_rightPosition > _frame_width){
_rightPosition = _frame_width;
}
if (_rightPosition-_leftPosition <= 0){
_rightPosition -= translation.x;
}
if ((_rightPosition-_leftPosition <= _leftThumb.frame.size.width+_rightThumb.frame.size.width) ||
((self.maxGap > 0) && (self.rightPosition-self.leftPosition > self.maxGap)) ||
((self.minGap > 0) && (self.rightPosition-self.leftPosition < self.minGap))){
_rightPosition -= translation.x;
}
[gesture setTranslation:CGPointZero inView:self];
[self setNeedsLayout];
[self delegateNotification];
}
_popoverBubble.alpha = 1;
[self setTimeLabel];
if (gesture.state == UIGestureRecognizerStateEnded){
[self hideBubble:_popoverBubble];
}
}
- (void)handleCenterPan:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self];
_leftPosition += translation.x;
_rightPosition += translation.x;
if (_rightPosition > _frame_width || _leftPosition < 0){
_leftPosition -= translation.x;
_rightPosition -= translation.x;
}
[gesture setTranslation:CGPointZero inView:self];
[self setNeedsLayout];
[self delegateNotification];
}
_popoverBubble.alpha = 1;
[self setTimeLabel];
if (gesture.state == UIGestureRecognizerStateEnded){
[self hideBubble:_popoverBubble];
}
}
//重绘整个截选框的位置
- (void)layoutSubviews
{
CGFloat inset = _leftThumb.frame.size.width / 2;
_leftThumb.center = CGPointMake(_leftPosition+inset, _leftThumb.frame.size.height/2);
_rightThumb.center = CGPointMake(_rightPosition-inset, _rightThumb.frame.size.height/2);
_topBorder.frame = CGRectMake(_leftThumb.frame.origin.x + _leftThumb.frame.size.width, 0, _rightThumb.frame.origin.x - _leftThumb.frame.origin.x - _leftThumb.frame.size.width/2, SLIDER_BORDERS_SIZE);
_bottomBorder.frame = CGRectMake(_leftThumb.frame.origin.x + _leftThumb.frame.size.width, _bgView.frame.size.height-SLIDER_BORDERS_SIZE, _rightThumb.frame.origin.x - _leftThumb.frame.origin.x - _leftThumb.frame.size.width/2, SLIDER_BORDERS_SIZE);
_centerView.frame = CGRectMake(_leftThumb.frame.origin.x + _leftThumb.frame.size.width, _centerView.frame.origin.y, _rightThumb.frame.origin.x - _leftThumb.frame.origin.x - _leftThumb.frame.size.width, _centerView.frame.size.height);
CGRect frame = _popoverBubble.frame;
frame.origin.x = _centerView.frame.origin.x+_centerView.frame.size.width/2-frame.size.width/2;
_popoverBubble.frame = frame;
}
#pragma mark - Video
-(void)getMovieFrame{
//得到url的资源,转为asset
AVAsset *myAsset = [[AVURLAsset alloc] initWithURL:_videoUrl options:nil];
//初始化AVAssetImageGenerator
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset];
//定义最大尺寸
if ([self isRetina]){
self.imageGenerator.maximumSize = CGSizeMake(_bgView.frame.size.width*2, _bgView.frame.size.height*2);
} else {
self.imageGenerator.maximumSize = CGSizeMake(_bgView.frame.size.width, _bgView.frame.size.height);
}
int picWidth = 49;
// First image
//创建第一张预览图
NSError *error;
CMTime actualTime;
CGImageRef halfWayImage = [self.imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:&actualTime error:&error];
if (halfWayImage != NULL) {
UIImage *videoScreen;
if ([self isRetina]){
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage scale:2.0 orientation:UIImageOrientationUp];
} else {
videoScreen = [[UIImage alloc] initWithCGImage:halfWayImage];
}
UIImageView *tmp = [[UIImageView alloc] initWithImage:videoScreen];
[_bgView addSubview:tmp];
//得到预览图的标准宽
picWidth = tmp.frame.size.width;
CGImageRelease(halfWayImage);
}
//得到秒数
_durationSeconds = CMTimeGetSeconds([myAsset duration]);
//通过view的宽得到预览图的张数
int picsCnt = ceil(_bgView.frame.size.width / picWidth);
NSLog(@"%d",picsCnt);
//创建一个可变数组
NSMutableArray *allTimes = [[NSMutableArray alloc] init];
int time4Pic = 0;
//得到每张预览图应该有的CMTime
for (int i=1; i<picsCnt; i++){
time4Pic = i*picWidth;
CMTime timeFrame = CMTimeMakeWithSeconds(_durationSeconds/_bgView.frame.size.width*time4Pic, 600);
[allTimes addObject:[NSValue valueWithCMTime:timeFrame]];
}
NSArray *times = allTimes;
__block int i = 1;
//根据CMTime 取出image
[self.imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *videoScreen;
if ([self isRetina]){
videoScreen = [[UIImage alloc] initWithCGImage:image scale:2.0 orientation:UIImageOrientationUp];
} else {
videoScreen = [[UIImage alloc] initWithCGImage:image];
}
//这里修改了预览图加载慢的问题,放到主线程加载
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *tmp = [[UIImageView alloc] initWithImage:videoScreen];
int all = (i+1)*tmp.frame.size.width;
CGRect currentFrame = tmp.frame;
currentFrame.origin.x = i*currentFrame.size.width;
//如果最后一张图超出范围,缩小
if (all > _bgView.frame.size.width){
int delta = all - _bgView.frame.size.width;
currentFrame.size.width -= delta;
}
tmp.frame = currentFrame;
i++;
[_bgView addSubview:tmp];
});
}
if (result == AVAssetImageGeneratorFailed) {
NSLog(@"Failed with error: %@", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled) {
NSLog(@"Canceled");
}
}];
}
#pragma mark - Properties
//返回左边时间范围
- (CGFloat)leftPosition
{
return _leftPosition * _durationSeconds / _frame_width;
}
//返回右边时间范围
- (CGFloat)rightPosition
{
return _rightPosition * _durationSeconds / _frame_width;
}
#pragma mark - Bubble
//隐藏泡泡的方法
- (void)hideBubble:(UIView *)popover
{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
animations:^(void) {
_popoverBubble.alpha = 1.0;//取消泡泡label的消失
}
completion:nil];
//通过代理显示在界面上label的时间位置
if ([_delegate respondsToSelector:@selector(videoRange:didGestureStateEndedLeftPosition:rightPosition:)]){
[_delegate videoRange:self didGestureStateEndedLeftPosition:self.leftPosition rightPosition:self.rightPosition];
}
}
//刷新label的时间方法
-(void) setTimeLabel{
self.bubleText.text = [self trimIntervalStr];
}
//显示截取时间的秒数
-(NSString *)trimDurationStr{
int delta = floor(self.rightPosition - self.leftPosition);
return [NSString stringWithFormat:@"%d", delta];
}
//获得时间
-(NSString *)trimIntervalStr{
NSString *from = [self timeToStr:self.leftPosition];
NSString *to = [self timeToStr:self.rightPosition];
return [NSString stringWithFormat:@"%@ - %@", from, to];
}
#pragma mark - Helpers
//获得时间
- (NSString *)timeToStr:(CGFloat)time
{
// time - seconds
NSInteger min = floor(time / 60);
NSInteger sec = floor(time - min * 60);
NSString *minStr = [NSString stringWithFormat:min >= 10 ? @"%i" : @"0%i", min];
NSString *secStr = [NSString stringWithFormat:sec >= 10 ? @"%i" : @"0%i", sec];
return [NSString stringWithFormat:@"%@:%@", minStr, secStr];
}
//是否是高清屏
-(BOOL)isRetina{
return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
([UIScreen mainScreen].scale == 2.0));
}
@end
SAResizibleBubble.m
#import "SAResizibleBubble.h"
@implementation SAResizibleBubble
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect
{
General Declarations
//得到颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
Color Declarations
//泡泡上面的颜色
UIColor* bubbleGradientTop = [UIColor colorWithRed: 1 green: 0.939 blue: 0.743 alpha: 1];
//泡泡底部的颜色
UIColor* bubbleGradientBottom = [UIColor colorWithRed: 1 green: 0.817 blue: 0.053 alpha: 1];
//高亮的颜色,背景阴影
UIColor* bubbleHighlightColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 1];
//画笔颜色,一圈阴影
UIColor* bubbleStrokeColor = [UIColor colorWithRed: 0.173 green: 0.173 blue: 0.173 alpha: 1];
Gradient Declarations
NSArray* bubbleGradientColors = [NSArray arrayWithObjects:
(id)bubbleGradientTop.CGColor,
(id)bubbleGradientBottom.CGColor, nil];
//泡泡渐变的位置
CGFloat bubbleGradientLocations[] = {0, 1};
//创建一个渐变对象
CGGradientRef bubbleGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)bubbleGradientColors, bubbleGradientLocations);
Shadow Declarations
UIColor* outerShadow = [UIColor blackColor];
CGSize outerShadowOffset = CGSizeMake(0.1, 6.1);
CGFloat outerShadowBlurRadius = 13;
UIColor* highlightShadow = bubbleHighlightColor;
CGSize highlightShadowOffset = CGSizeMake(0.1, 2.1);
CGFloat highlightShadowBlurRadius = 0;
Frames
CGRect bubbleFrame = self.bounds;
Subframes
//箭头frame
CGRect arrowFrame = CGRectMake(CGRectGetMinX(bubbleFrame) + floor((CGRectGetWidth(bubbleFrame) - 59) * 0.50462 + 0.5), CGRectGetMinY(bubbleFrame) + CGRectGetHeight(bubbleFrame) - 46, 59, 46);
Bubble Drawing
//画泡泡
UIBezierPath* bubblePath = [UIBezierPath bezierPath];
[bubblePath moveToPoint: CGPointMake(CGRectGetMaxX(bubbleFrame) - 12, CGRectGetMinY(bubbleFrame) + 28.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMaxX(bubbleFrame) - 12, CGRectGetMaxY(bubbleFrame) - 27.5)];
[bubblePath addCurveToPoint: CGPointMake(CGRectGetMaxX(bubbleFrame) - 25, CGRectGetMaxY(bubbleFrame) - 14.5) controlPoint1: CGPointMake(CGRectGetMaxX(bubbleFrame) - 12, CGRectGetMaxY(bubbleFrame) - 20.32) controlPoint2: CGPointMake(CGRectGetMaxX(bubbleFrame) - 17.82, CGRectGetMaxY(bubbleFrame) - 14.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMinX(arrowFrame) + 40.5, CGRectGetMaxY(arrowFrame) - 13.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMinX(arrowFrame) + 29.5, CGRectGetMaxY(arrowFrame) - 0.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMinX(arrowFrame) + 18.5, CGRectGetMaxY(arrowFrame) - 13.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMinX(bubbleFrame) + 26.5, CGRectGetMaxY(bubbleFrame) - 14.5)];
[bubblePath addCurveToPoint: CGPointMake(CGRectGetMinX(bubbleFrame) + 13.5, CGRectGetMaxY(bubbleFrame) - 27.5) controlPoint1: CGPointMake(CGRectGetMinX(bubbleFrame) + 19.32, CGRectGetMaxY(bubbleFrame) - 14.5) controlPoint2: CGPointMake(CGRectGetMinX(bubbleFrame) + 13.5, CGRectGetMaxY(bubbleFrame) - 20.32)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMinX(bubbleFrame) + 13.5, CGRectGetMinY(bubbleFrame) + 28.5)];
[bubblePath addCurveToPoint: CGPointMake(CGRectGetMinX(bubbleFrame) + 26.5, CGRectGetMinY(bubbleFrame) + 15.5) controlPoint1: CGPointMake(CGRectGetMinX(bubbleFrame) + 13.5, CGRectGetMinY(bubbleFrame) + 21.32) controlPoint2: CGPointMake(CGRectGetMinX(bubbleFrame) + 19.32, CGRectGetMinY(bubbleFrame) + 15.5)];
[bubblePath addLineToPoint: CGPointMake(CGRectGetMaxX(bubbleFrame) - 25, CGRectGetMinY(bubbleFrame) + 15.5)];
[bubblePath addCurveToPoint: CGPointMake(CGRectGetMaxX(bubbleFrame) - 12, CGRectGetMinY(bubbleFrame) + 28.5) controlPoint1: CGPointMake(CGRectGetMaxX(bubbleFrame) - 17.82, CGRectGetMinY(bubbleFrame) + 15.5) controlPoint2: CGPointMake(CGRectGetMaxX(bubbleFrame) - 12, CGRectGetMinY(bubbleFrame) + 21.32)];
[bubblePath closePath];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, outerShadowOffset, outerShadowBlurRadius, outerShadow.CGColor);
CGContextBeginTransparencyLayer(context, NULL);
[bubblePath addClip];
CGRect bubbleBounds = CGPathGetPathBoundingBox(bubblePath.CGPath);
CGContextDrawLinearGradient(context, bubbleGradient,
CGPointMake(CGRectGetMidX(bubbleBounds), CGRectGetMinY(bubbleBounds)),
CGPointMake(CGRectGetMidX(bubbleBounds), CGRectGetMaxY(bubbleBounds)),
0);
CGContextEndTransparencyLayer(context);
// Bubble Inner Shadow
CGRect bubbleBorderRect = CGRectInset([bubblePath bounds], -highlightShadowBlurRadius, -highlightShadowBlurRadius);
bubbleBorderRect = CGRectOffset(bubbleBorderRect, -highlightShadowOffset.width, -highlightShadowOffset.height);
bubbleBorderRect = CGRectInset(CGRectUnion(bubbleBorderRect, [bubblePath bounds]), -1, -1);
UIBezierPath* bubbleNegativePath = [UIBezierPath bezierPathWithRect: bubbleBorderRect];
[bubbleNegativePath appendPath: bubblePath];
bubbleNegativePath.usesEvenOddFillRule = YES;
CGContextSaveGState(context);
{
CGFloat xOffset = highlightShadowOffset.width + round(bubbleBorderRect.size.width);
CGFloat yOffset = highlightShadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
highlightShadowBlurRadius,
highlightShadow.CGColor);
[bubblePath addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(bubbleBorderRect.size.width), 0);
[bubbleNegativePath applyTransform: transform];
[[UIColor grayColor] setFill];
[bubbleNegativePath fill];
}
CGContextRestoreGState(context);
CGContextRestoreGState(context);
[bubbleStrokeColor setStroke];
bubblePath.lineWidth = 1;
[bubblePath stroke];
Cleanup
CGGradientRelease(bubbleGradient);
CGColorSpaceRelease(colorSpace);
}
@end
参考文章:UIBezierPath贝塞尔弧线常用方法记 UIBezierPath的基本使用 UIBezierPath类 CMTimeMake和CMTimeMakeWithSeconds 详解