他们主动布局(autolayout)环境的图像编辑器

hi,all:

在经过了一番犹豫之后。我决定将我自己做的这个小APP的源代码发布给大家:

其出发点是和大家一起学习iOS开发。仅供学习參考之用。

之前代码是托管与gitlab

上的,今天我将其pull到github上来了,大家能够自行下载:git clone [email protected]:lihux/twentyThousandTomatoes.git没有安装git或者不会用的童鞋,

请猛戳github地址:https://github.com/lihux/twentyThousandTomatoes。进去之后选

择download zip下载就可以。

在大部分APP(尤其是社交类的,如qq)常常会有更换头像的场景:点击用户

载入头像,载入出系统图片,用户点击选中某张图片之后。能够对图片进行放缩和

拖动,已更改圆形裁剪框圈定的图片部分。

例如以下图即为qq的头像选取编辑界面:

图1.qq照片编辑界面

界面中能够对图片进行放大、缩小,拖动,白色圆环区域表示点击确定时将要

裁剪的范围。留意上图的动画,qq总是可以确保圆环全然被图片所覆盖,假设拖动

或者放缩使得图片以外的黑色区域进入了圆环。图片会自己主动弹回刚好可以全然覆盖

的状态。鉴于CSDN上传图片2M的限制,上面的gif图非常短。感兴趣的同学能够打开

QQ自己体验一把(在改动个人头像功能中)。

如今我们也要实现一个类似功能的界面。而且是在autolayout环境下。同一时候支

持横竖屏。这比QQ的图片选取页面又复杂了一些:QQ仅仅支持竖屏的情况,不须要

考虑横屏时的情况和横竖屏切换的问题。

以下具体讨论。

一、预期效果

用户从相冊或者相机中选取/拍摄一张照片,载入到图片编辑界面,用户能够拖

动、放缩照片。使圆形选取框中截图到合适的图像作为用户头像。效果图例如以下图所

示:

用户在拖动、放缩时要保证圆环区域所有被图片所覆盖。这样才干确保裁剪出

来的照片刚好可以撑满整个圆形区域。同一时候,由于我们支持横屏布局。因此还要确保

竖屏切换横屏(或者反之)之后。圆环仍在正确的区域。

图2.竖屏效果

图3.横屏效果

整个界面满足了上述用户交互需求之外。还要在用户点击确定的时候,将圆

形区域的图片裁剪下来,实现图片编辑的功能。

二、实现细节

2.1基本思路

在实现上,这个页面能够分为两大块:一块是scrollview的设置:contentSize、

contentInset、zoomScale等等;还有一块是剪切框的实现(白色圆环、外围半透明蒙

层),以及横竖屏切换时剪切框怎样变化等;而这两块又不是全然独立的:scrollview

的非常多交互都依赖于剪切框:最小放缩不能小于剪切框、移动不能超出剪切框的范

围等。

能够觉得。scrollview的属性依赖于剪切框的属性。

而剪切框在横屏或者竖屏

的时候大小位置是保持不变的。因此,我们非常自然的得到这样一个思路:先确定剪切

框。横竖屏都没问题了,再通过剪切框确定scrollview。

2.2剪切框的实现

从图二中能够看出剪切框是一个比較特殊的界面:圆形虚线框内部是全然透明

的(clearColor or alpha = 0),而外围的填充部分则是半透明效果(blackColor and

alpha = 0.2)。常规的通过view的嵌套设置alpha、backgroundColor和layer.cornerRadius

是不行的。由于view的alpha属性具有“遗传性”:父view的alpha将直接作用于全部

的子view上去,这时我们就要考虑通过更底层的画图方式直接在一个view上完毕剪

切框的绘制工作。

我们在storyboard中加入一个view(称之为:maskView)。加入约束使其和scrollview

大小、尺寸全然保持一致。将这个view的class改为TTPhotoMaskView:一个我们

定制的view,在其drawRect方法中。绘制剪切框,绘制示意图例如以下:

图4.剪切框绘制

1.绘制两条封闭的线,一条是方形的。刚好覆盖整个view的边界。还一条是圆

形的虚线裁剪框;

2.使用奇偶原则对这两条封闭曲线进行色彩填充。使得方框和圆形框之间的区域

填充(黑色。alpha=0.2),而圆形框内部不进行填充(透明)。

详细实现代码例如以下:

<span style="font-size:18px;">-(void)drawRect:(CGRect)rect
{
    CGFloat width = rect.size.width;
    CGFloat height = rect.size.height;
    //pickingFieldWidth:圆形框的直径
    CGFloat pickingFieldWidth = width < height ?

(width - kWidthGap) : (height - kHeightGap);
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    CGContextSaveGState(contextRef);
    CGContextSetRGBFillColor(contextRef, 0, 0, 0, 0.35);
    CGContextSetLineWidth(contextRef, 3);
    //计算圆形框的外切正方形的frame:
    self.pickingFieldRect = CGRectMake((width - pickingFieldWidth) / 2, (height - pickingFieldWidth) / 2, pickingFieldWidth, pickingFieldWidth);
    //创建圆形框UIBezierPath:
    UIBezierPath *pickingFieldPath = [UIBezierPath bezierPathWithOvalInRect:self.pickingFieldRect];
    //创建外围慷慨框UIBezierPath:
    UIBezierPath *bezierPathRect = [UIBezierPath bezierPathWithRect:rect];
    //将圆形框path加入到慷慨框path上去,以便以下用奇偶填充法则进行区域填充:
    [bezierPathRect appendPath:pickingFieldPath];
    //填充使用奇偶法则
    bezierPathRect.usesEvenOddFillRule = YES;
    [bezierPathRect fill];
    CGContextSetLineWidth(contextRef, 2);
    CGContextSetRGBStrokeColor(contextRef, 255, 255, 255, 1);
    CGFloat dash[2] = {4,4};
    [pickingFieldPath setLineDash:dash count:2 phase:0];
    [pickingFieldPath stroke];
    CGContextRestoreGState(contextRef);
    self.layer.contentsGravity = kCAGravityCenter;
}
</span>

如今再来考虑怎样处理横竖屏的问题:我们的剪切框是直接通过UIView的drawRect

方法直接手绘上去的,因此无法通过自己主动布局(autolayout)对剪切框进行又一次布局。

解决的办法是在屏幕发生横竖屏切换的时候又一次绘制圆形剪切框。在iOS8中不再使

用willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

duration:(NSTimeInterval)duration来获取屏幕旋转事件了。iOS8以后的使用新的

willTransitionToTraitCollection:(UITraitCollection *)newCollection

withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

来取代。

因此我们在这种方法中,强制裁剪框重绘(maskview):

<span style="font-size:18px;">#pragma mark - UIContentContainer protocol
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
    [self.maskView setNeedsDisplay];
}
</span>

这样我们的剪切框就顺利完毕了,接下来我们来设置scrollview,使其满足我

们的交互预期。

2.3scrollview的设置

首先来看一下整个view的层级结构:scrollview有一个撑满整个scrollview的

imageView作为scrollview的content view。在scrollView之上盖着一个剪切框的view

(mask view),这三个view都通过约束保持和根view的bounds一致。

图5.view的层级结构

上面提到,scrollview的各种属性的设置都要依赖于手绘出的剪切框。而圆形

剪切框的位置、大小在每次转屏之后可能发生变化,因此我们必需要在每次maskView

的drawRect方法调用之后都又一次调整一下scrollview的属性。

因此我们在maskView

中加入一个代理,将这个代理设置为maskview所在的viewController。每次当重绘

发生后就通过代理方法通知viewcontroller调整scrollview的各项属性:

<span style="font-size:18px;">//  TTPhotoMaskView.h
@protocol TTPhotoMaskViewDelegate <NSObject>

- (void)pickingFieldRectChangedTo:(CGRect) rect;

@end

@interface TTPhotoMaskView : UIView

@property (nonatomic, weak) id <TTPhotoMaskViewDelegate> delegate;

@end</span>

在maskView的drawRect方法中加入:当中pickingFieldRect即为圆环剪切框

的“frame”,包括其相对于maskView的origin和size信息。

<span style="font-size:18px;">    if ([self.delegate respondsToSelector:@selector(pickingFieldRectChangedTo:)]) {
        [self.delegate pickingFieldRectChangedTo:self.pickingFieldRect];
    }
</span>

接下来就是在我们的viewController中实现pickingFieldRectChangedTo方法,

调整scrollView:

<span style="font-size:18px;">#pragma mark - TTPhotoMaskViewDelegate
- (void)pickingFieldRectChangedTo:(CGRect)rect
{
    self.pickingFieldRect = rect;
    CGFloat topGap = rect.origin.y;
    CGFloat leftGap = rect.origin.x;
    self.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(topGap, leftGap, topGap, leftGap);
    //step 1: setup contentInset
    self.scrollView.contentInset = UIEdgeInsetsMake(topGap, leftGap, topGap, leftGap);

    CGFloat maskCircleWidth = rect.size.width;
    CGSize imageSize = self.originImage.size;
    //setp 2: setup contentSize:
    self.scrollView.contentSize = imageSize;
    CGFloat minimunZoomScale = imageSize.width < imageSize.height ? maskCircleWidth / imageSize.width : maskCircleWidth / imageSize.height;
    CGFloat maximumZoomScale = 5;
    //step 3: setup minimum and maximum zoomScale
    self.scrollView.minimumZoomScale = minimunZoomScale;
    self.scrollView.maximumZoomScale = maximumZoomScale;
    self.scrollView.zoomScale = self.scrollView.zoomScale < minimunZoomScale ?

minimunZoomScale : self.scrollView.zoomScale;

    //step 4: setup current zoom scale if needed:
    if (self.needAdjustScrollViewZoomScale) {
        CGFloat temp = self.view.bounds.size.width < self.view.bounds.size.height ?

self.view.bounds.size.width : self.view.bounds.size.height;
        minimunZoomScale = imageSize.width < imageSize.height ? temp / imageSize.width : temp / imageSize.height;
        self.scrollView.zoomScale = minimunZoomScale;
        self.needAdjustScrollViewZoomScale = NO;
    }
}
</span>

以下来具体解析一下上面每一步设置的作用,首先以一张苹果官方文档(Scroll

View Programming Guide for iOS)上的图片来简单看一下contentSize和contentInset

的意义和作用:

图6.UIScrollView的contentSize和contentInset属性示意图

contentSize是你在scrollView中要展示的内容(content)的大小,详细值要根

据content的尺寸而定,我们这里是要完整的无压缩的展示一个图片的内容,因此这里

在step 2中将contentSize设为图片(image.size)的size同等大小。

contentInset能够理解为展示内容的上下左右“留白”的间距。默认值为(0,0。

0。0),contentInset所标示的留白加上contentSize才是一个scrollView所能滑动的

所有区域。

这里我们不想让content(图片)的滑动区域超出圆形剪切框的位置,能够

通过巧妙的讲剪切框圆环和view的上下左右边缘的间距作为scrollView的contentInset。

这就是step 1做的事情,它确保了手指在图片上拖动的时候圆形剪切框总能填满图片

的内容。

scrollView对于放大缩小的支持很easy。你仅仅需设置放缩的最大和最小倍数,

然后在代理函数(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView

中返回要缩放的view就可以。

这里主要须要确定的时scrollview的最小缩放尺寸。以满

足当放缩到最小时刚好图片较短的一个维度(长或者宽)和圆形剪切框相切。这是能

够放缩的最小值。由于如在在缩小图片就无法填满剪切框了:

图7.放缩到最小时。剪切框必需要和较短的一边相切

step 4仅仅在viewDidLoad的时候运行,也即第一次进入图片编辑页面的时候,

须要强制调整一下scrollview的当前zoomScale,使得图片在一个合适的尺寸显示

出来。

至此,整个功能完毕,执行一下程序。看一下效果,达到了预期:

图8.转屏效果

图9.拖动和缩放

三、总结

将图片载入进scrollview,对其放缩、拖动然后裁剪当中一部分是图片编辑器

的主要功能,看似简单的功能需求。细究起来却处处是坑,必需要深入的思考当中

的每个细节。利用好UIView的drawRect方法。结合使用scrollview的特性方能得以

实现。

本演示样例主要有下面两点值得关注:

1.圆形剪切框的实现,以及在autolayout环境下旋转屏后剪切框的处理;

2.scrollView的属性设置。必需要结合所载入图片的实际尺寸、圆形剪切框的位置

和大小信息来动态的调整scrollView的contentSize、contentInset和其他财产。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-10-12 21:10:46

他们主动布局(autolayout)环境的图像编辑器的相关文章

iOS 自己主动布局教程

springs和struts的问题 你肯定非常熟悉autosizing masks-也被觉得是springs&struts模式.autosizing mask决定了当一个视图的父视图大小改变时,其自身须要做出什么改变.它有一个灵活的或固定不变的margins(struts)吗?它的宽和高要做出什么改变(springs)? 举个样例,一个宽度灵活的视图,假设其父视图边框,那么它也会对应的变宽.一个视图右边拥有固定的margin,那么它的右边缘将会一直粘住其父视图的右边缘. autosizing系统

UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境. 本章我们将了解一下的内容: 程序运行时,main函数是如何被调用的: 命令行参数是如何被传入到程序中的: 一个典型的内存布局是怎样的: 如何分配内存: 程序如何使用环境变量: 程序终止的各种方式: 跳转(longjmp和setjmp)函数的工作方式,以及如何和栈交互: 进程的资源限制 ? 1 main函数 main函数声明: int main (int argc, char *argv[]); 参数说明: argc:命令行参数个数

UITableViewHeaderFooterView的使用+自己主动布局

UITableViewHeaderFooterView的使用+自己主动布局 使用UITableView的header或footer复用时,假设採用自己主动布局,你会发现有约束冲突,以下这样写能够消除约束冲突: #import <UIKit/UIKit.h> @interface SectionView : UITableViewHeaderFooterView @property (nonatomic, copy) NSString *sectionTitle; @end #import &q

图像技术分析 图像编辑器核心技术

图像技术都是想通的,要举一反三,从中思考出核心,使其变幻莫测.一个合格的图像编辑器所要有的技术框架:

最火爆10大集成开发环境和代码编辑器!总有一款是最适合你的!

什么是集成开发环境和代码编辑器 进群:125240963   即可获取数十套PDF或者零基础入门教程一套哦! 保存和重载代码文件 如果一款集成开发环境或者编辑器不允许你保存现有工作,并在之后重新打开时不能保持你离开时的相同状态,那么它就不是什么集成开发环境. 在环境内运行代码 类似的是,如果你必须退出编辑器来运行Python代码,那么它也就是一个普通的文本编辑器. 支持调试 在程序运行时支持逐步运行代码是所有集成开发环境和大多数优秀代码编辑器必备的核心功能. 语法高亮 支持对代码中的关键词.变量

Xcode iOS布局autolayout和sizeclass的使用

一.关于自动布局(Autolayout) 在Xcode中,自动布局看似是一个很复杂的系统,在真正使用它之前,我也是这么认为的,不过事实并非如此. 我们知道,一款iOS应用,其主要UI组件是由一个个相对独立的可视单元构成,这些可视单元有的主要负责向用户输出有用的信息,有些则负责信息的输入(交互),交互的过程中往往还伴随有动画的效果,已达到整个信息传递的连贯性以及用户体验的细腻感.可视单元,在实际开发中主要是view.button等,那么这些可视单元的关系由两个基本的关系构成:兄弟关系和父子关系,整

【iOS开发-48】九宫格布局案例:自己主动布局、字典转模型运用、id和instancetype差别、xib反复视图运用及与nib关系

本次九宫格案例: (1)导入app.plist和各种图片素材,方便兴许开发.实际开发中,也是如此. (2)把plist中数组导入进来. --由于本案例中app.plist终于是一个数组,数组里面是字典.所以我们须要一个数组类型来接受这个plist文件. --我们利用之前掌握的在变量的getter中进行延迟载入数据. #import "ViewController.h" @interface ViewController () @property(nonatomic,strong) NS

IOS布局笔记一(代码实现自己主动布局)

1.将一个试图放置在其父视图的中央位置,使用限制条件. 2.创建两个限制条件:一个是将目标视图的 center.x 位置排列在其父视图的 center.x 位置,而且另外一个是将目标视图的 center.y 位置排列在其父视图的 center.y 位置. 3.首先在 WildCatViewController.h中加入一个Button // // WildCatViewController.h // AutoLayoutDemo // // Created by wildcat on 14-4-

iOS 8 UI布局 AutoLayout及SizeClass(二)

一.新特性Size Class介绍 随着iOS8系统的发布,一个全新的页面UI布局概念出现,这个新特性将颠覆包括iOS7及之前版本的UI布局方式,这个新特性就是Size Class.Size Class配合Auto Layout可以解决所有(包括iPhone及iPad)iOS设备屏幕尺寸及屏幕旋转时候的UI适配问题 . 二.为什么要使用Size Class 直到iPhone6发布后,目前iOS设备的屏幕尺寸已经有4种了,如图: iPhone6没出现之前,还可以通过代码来适配两种尺寸的UI,但iP