In most apps (especially social, such as QQ) there will often be a change of Avatar scene: Click on the user to load the avatar, loading the system picture, the user click on the selected image, you can zoom and drag the picture, has changed the circle cut the picture part of the box. For QQ's Avatar Select Edit interface:
Figure 1.QQ Photo Editing interface
In the interface, you can zoom in, zoom out, drag, and the white circle area indicates the range you want to crop when you click OK. Attention to the animation, QQ is always able to ensure that the ring is completely covered by the picture, if dragged or retracted so that the black area outside the picture into the ring, the picture will automatically bounce back just to fully cover the state, given the CSDN upload image 2M limit, the above GIF is very short, Interested students can open QQ oneself experience a (in the Change profile picture function).
Now we also want to implement a similar function of the interface, and is in the AutoLayout environment, while supporting the horizontal screen, which is more complicated than the QQ Picture Selection page: QQ only support vertical screen situation, do not need to consider the situation of horizontal screen and the problem of screen switching. discussed in detail below.
First, the expected effect
The user selects/takes a photo from the album or camera, loads it into the image editing interface, and the user can drag and drop the photo to make the circle marquee fit into the image as the user's avatar. As shown in the following:
When the user drags and shrinks, make sure that the area of the ring is covered by the picture so that the cropped photo is just enough to fill the entire circular area. Also, because we support a horizontal screen layout, make sure that the ring is still in the correct area after the vertical screen is switched across the screen (or vice versa).
Figure 2. Vertical screen Effect
Figure 3: Horizontal screen effect
The entire interface satisfies the above user interaction needs, but also in the user click OK, the circular area of the picture cut down, to achieve the function of image editing.
II. details of implementation
2.1 Basic Ideas
In implementation, this page can be divided into two chunks: one is the ScrollView: Contentsize, Contentinset, Zoomscale, and so on, and the other is the implementation of the Cut box (white ring, outer translucent mask), As well as how to change the shear frame when the screen is switched, and so on; these two pieces are not completely separate: ScrollView's many interactions depend on the cut box: The minimum indent cannot be less than the cut box, the move cannot exceed the clipping frame range, and so on. It can be assumed that the properties of the ScrollView depend on the properties of the Cut box. While the shear box in the horizontal screen or vertical screen when the size position is unchanged, so we naturally get such a thought: first determine the Cut box, the screen is no problem, and then through the shear box to determine the ScrollView.
2.2 Implementation of the shear box
As you can see from figure two, the shear box is a special interface: The inside of the round dashed box is completely transparent (clearcolor or alpha = 0), while the outer padding is translucent (blackcolor and alpha = 0.2), It is not possible to set alpha, BackgroundColor, and Layer.cornerradius through the nesting of the view, because the alpha attribute of view is "hereditary": the alpha of the parent view acts directly on all child view, At this point, we need to consider a more low-level drawing method directly on a view to complete the work of the Cut box.
We add a view (called: Maskview) in storyboard and add a constraint to make it fully consistent with the ScrollView size and size. Change the class of this view to Ttphotomaskview: A custom view, in its DrawRect method, draw the Cut box, drawn as follows:
Figure 4: Clipping frame drawing
1. Draw two closed lines, one is square, just cover the entire view of the boundary, but also a round of the dotted line clipping box;
2. Use the parity principle to color fill the two closed curves so that the area between the box and the circle is filled (black, alpha=0.2), and the inside of the round box is not filled (transparent).
The specific implementation code is as follows:
-(void) DrawRect: (CGRect) rect { cgfloat width = rect.size.width; cgfloat height = rect.size.height; //pickingfieldwidth: Diameter of the round box CGFloat pickingfieldwidth = width < height ? (Width - kwidthgap) : (HEIGHT&NBSP;-&NBSP;KHEIGHTGAP); cgcontextref contextref = uigraphicsgetcurrentcontext (); cgcontextsavegstate (CONTEXTREF); cgcontextsetrgbfillcolor (contextref, 0, 0, 0, 0.35); cgcontextsetlinewidth (contextref, 3); // Calculates the frame: self.pickingfieldrect = cgrectmake of the cut-off square of a circular box (width - pickingfieldwidth) / 2, (height - pickingfieldwidth) / 2, pickingFieldWidth, Pickingfieldwidth) //Create a circular box uibezierpath: uibezierpath *pickingfieldpath = [uibezierpath bezierpathwithovalinrect: self.pickingfieldrect]; //Create a perimeter box uibezierpath: UIBezierPath *bezierPathRect = [UIBezierPath bezierPathWithRect:rect]; //Add the round frame path to the generous frame path so that the following is populated with the odd-even fill rule: [ bezierpathrect appendpath:pickingfieldpath]; //fill using the parity rule bezierPathRect.usesEvenOddFillRule = YES; [bezierpathrect fill]; cgcontextsetlinewidth (CONTEXTREF,&NBSP;2) ; &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;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; }
Now consider how to handle the problem of the screen: our shear frame is directly hand-painted through the UIView DrawRect method, so it is not possible to re-layout the cut box through Automatic layout (AutoLayout).
The solution is to redraw the circular clipping frame when the screen is switched on or off. No longer used in IOS8 willrotatetointerfaceorientation: (uiinterfaceorientation) tointerfaceorientation Duration: ( Nstimeinterval) duration to get the screen rotation event, IOS8 later uses the new willtransitiontotraitcollection: (Uitraitcollection *) newcollection Withtransitioncoordinator: (ID) Coordinator to replace.
So in this method, we force the crop box to redraw (Maskview):
#pragma mark-uicontentcontainer protocol-(void) Willtransitiontotraitcollection: (uitraitcollection *) newcollection Withtransitioncoordinator: (ID) Coordinator {[Super Willtransitiontotraitcollection:newcollection Withtransitioncoordinator:coordinator]; [Self.maskview Setneedsdisplay]; }
So our cut-off box is finished, so let's set the ScrollView to meet our interaction expectations.
Settings for 2.3 ScrollView
First look at the hierarchy of the entire view: ScrollView has a imageview full of scrollview as a content view of ScrollView, with a clipping frame view (mask View), these three view are bounds consistent with the root view through constraints.
Figure 5.view Hierarchy
As mentioned above, the various properties of scrollview depend on the freehand cut box. The position and size of the round cut box may change after each turn, so we have to readjust the properties of the ScrollView after each Maskview DrawRect method call. So we add an agent in Maskview, set this proxy to the Viewcontroller where Maskview is located, and each time the redraw occurs, the Viewcontroller adjusts the properties of the ScrollView through proxy method notification:
TTPhotoMaskView.h @protocol ttphotomaskviewdelegate-(void) Pickingfieldrectchangedto: (cgrect) rect; @end @interface Ttphotomaskview:uiview @property (nonatomic, weak) ID delegate; @end
Added in Maskview's DrawRect method: where Pickingfieldrect is the "frame" of the doughnut-cut box, containing its origin and size information relative to Maskview.
if ([Self.delegate respondstoselector: @selector (pickingfieldrectchangedto:)]) {[Self.delegate Pickingfieldrectch AngedTo:self.pickingFieldRect]; }
The next step is to implement the Pickingfieldrectchangedto method in our Viewcontroller and adjust the ScrollView:
#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, &NBSP;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; } }
The following is a detailed explanation of the role of each of the above settings, first of all in an Apple Official document (Scroll View Programming Guide for IOS) on the image to simply look at the meaning and role of contentsize and Contentinset:
Figure 6. Contentsize and Contentinset properties of Uiscrollview
Contentsize is the size of the content you want to show in ScrollView, depending on the size of the content, we are here to complete the uncompressed display of a picture, so here in the step 2 set Contentsize to the size of the picture (Image.size).
Contentinset can be understood as the content of the upper and lower left and right "white" spacing, the default value is (0,0,0,0), Contentinset marked by the white plus contentsize is a scrollview can slide all areas. Here we don't want to let the sliding area of the content (picture) go beyond the position of the circular clipping frame, which can be done by subtly speaking the spacing between the upper and lower left and right edges of the cut box ring and view as ScrollView's Contentinset, which is what step 1 does, It ensures that when the finger is dragged over the picture, the round cut box always fills the picture.
ScrollView support for zooming in and out is very simple, you simply set the maximum and minimum multiples of the retraction, and then in the Agent function (UIView *) Viewforzoominginscrollview: (Uiscrollview *) Returns the view to zoom in the ScrollView. Here the main need to determine the minimum scale size of the ScrollView, to meet when the minimum amount of time when the picture is a shorter dimension (long or wide) and the circular shear frame tangent, which is able to shrink the minimum value, because if the picture can not be filled with the cut box:
Figure 7. When minimized, the cut box must be tangent to a shorter side
Step 4 only in viewdidload when the implementation, that is, the first time to enter the picture editing page, you need to force adjust the scrollview of the current Zoomscale, so that the picture in a suitable size display.
At this point, the entire function completed, run a program, look at the effect, reached the expected:
Figure 8. Turn screen effect
Figure 9. Dragging and zooming
Iii. Summary
Load the picture into the scrollview, shrink it, drag it, and then crop it as part of the main function of the picture editor, seemingly simple function needs, scrutiny up but everywhere is the pit, must be in-depth thinking of each of the details, the use of good UIView DrawRect method, It can be achieved by combining the characteristics of the ScrollView.
This example has the following two main points to pay attention to:
1. The realization of the circular shearing frame and the processing of the shearing frame after rotating the screen in the AutoLayout environment;
2.scrollView property settings, you must combine the actual size of the loaded picture, the position and size of the circular shear frame information to dynamically adjust ScrollView contentsize, Contentinset and other properties.
iOS crazy detailed automatic layout (AutoLayout) Slice editor implementation