Implementation of the Automatic Layout (autolayout) Plug-In editor for iOS,
In most apps (especially social networking, such as qq), there are often scenarios where the Avatar is changed: Click the user to load the Avatar, and load the system image. After the user clicks to select an image, you can scale down or drag an image, and you have changed the area of the image enclosed in the circular cropping box. For example, the qq Avatar selection and editing page is as follows:
Figure 1. qq photo editing page
You can zoom in, zoom out, and drag the image on the page. The white ring area indicates the range to be cropped when you click OK. Note that qq can always ensure that the ring is completely covered by the image. If the black area outside the image is moved into the ring by dragging or shrinking, the picture will automatically pop back to the state where it can be completely covered, in view of the limit of 2 MB for CSDN to upload images, the gif image above is very short. If you are interested, you can open QQ and try it yourself (in the personal Avatar modification function ).
Now we also need to implement a similar function interface, which is in the autolayout environment and supports horizontal and vertical screens. This is more complicated than the QQ image selection page: QQ only supports portrait screens. You do not need to consider landscape screens and landscape screen switching. The following is a detailed description.
I. Expected results
You can select or take a photo from an album or camera and load it to the image editing page. You can drag or scale down the photo so that the right image is displayed in the circle selection box as your avatar. As shown in:
When dragging and scaling, you must ensure that all the ring areas are covered by images. In this way, the cropped photos can fill the entire circular area. At the same time, because we support horizontal screen layout, we also need to ensure that after the landscape screen is switched (or vice versa), the ring is still in the correct area.
Figure 2. Landscape Effect
Figure 3. Landscape Effect
The entire interface meets the above user interaction requirements. When you click OK, you must crop the images in the circular area to edit the images.
II. Implementation Details
2.1 Basic Ideas
In terms of implementation, this page can be divided into two major parts: one is scrollview settings: contentSize, contentInset, zoomScale, and so on; the other is the implementation of the cut box (White Ring, peripheral translucent masked layer), and how the cut box changes during the horizontal and vertical screen switching; and the two are not completely independent: many interactions of scrollview depend on the cut box: the minimum scale-down cannot be smaller than the cut box, and the Movement cannot exceed the cut box range. It can be considered that the attributes of scrollview depend on the attributes of the cut box. The size and position of the cut box remain unchanged when the screen is landscape or landscape. Therefore, we naturally get the idea: Determine the cut box first, and there is no problem with the screen, then, use the cut box to determine the scrollview.
2.2 Implementation of the cut box
From the figure 2, we can see that the cut box is a special interface: the inside of the circular dotted box is completely transparent (clearColor or alpha = 0 ), the filling Area of the periphery is semi-transparent (blackColor and alpha = 0.2). Normally, alpha, backgroundColor, and layer are set through nested view. cornerRadius does not work, because the alpha attribute of the view is "hereditary": the alpha of the parent view is directly applied to all child views, at this time, we need to consider using a lower-layer drawing method to draw a cut box directly on a view.
We add a view (called maskView) to the storyboard, and add constraints to make it consistent with the size and size of the scrollview. Change the class of this view to TTPhotoMaskView: a custom view. In its drawRect method, draw a cut box and draw it as follows:
Figure 4. Create a cut box
1. Draw two closed lines. One is a square line that just overwrites the border of the entire view and the other is a circular dotted line crop box;
2. use the parity principle to fill the two closed curves with colors, so that the area between the box and the circle box is filled (black, alpha = 0.2), while the inside of the circle 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: the diameter of the circular box CGFloat pickingFieldWidth = width
Now let's look at how to deal with the problem of horizontal and vertical screens: Our cut boxes are hand-drawn directly through the drawRect method of UIView, so we cannot use autolayout) re-layout the cut box.
The solution is to re-draw a circular cut box when switching between the screen and the screen. In iOS8, container :( UIInterfaceOrientation) toInterfaceOrientation duration :( NSTimeInterval) is no longer used to obtain screen rotation events. In iOS8, the new willTransitionToTraitCollection (UITraitCollection *) newCollection interval :( id) instead of coordinator.
Therefore, in this method, force the cropping frame to be repainted (maskview ):
#pragma mark - UIContentContainer protocol - (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id)coordinator { [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; [self.maskView setNeedsDisplay]; }
In this way, the cut box is successfully completed. Next we will set scrollview to meet our interaction expectations.
2.3 scrollview settings
First, let's take a look at the hierarchical structure of the entire view: scrollview has an imageView full of the entire scrollview as the content view of the scrollview, and a view (mask view) of the cut box is built on the scrollView ), all three views maintain consistency with the bounds of the Root view through constraints.
Figure 5. view hierarchy
As mentioned above, the settings of various attributes of scrollview depend on the hand-drawn cut box. The position and size of the circular cut box may change after each screen rotation. Therefore, we must re-adjust the attributes of the scrollview after each call to the drawRect method of the maskView. Therefore, we add a proxy in the maskView to set this proxy to the viewController where the maskview is located. Every time a re-painting occurs, we notify the viewcontroller to adjust the attributes of the scrollview through the proxy method:
// TTPhotoMaskView.h @protocol TTPhotoMaskViewDelegate - (void)pickingFieldRectChangedTo:(CGRect) rect; @end @interface TTPhotoMaskView : UIView @property (nonatomic, weak) id delegate; @end
In the drawRect method of maskView, add: pickingFieldRect is the "frame" of the ring cut box, which contains the origin and size information relative to maskView.
if ([self.delegate respondsToSelector:@selector(pickingFieldRectChangedTo:)]) { [self.delegate pickingFieldRectChangedTo:self.pickingFieldRect]; }
Next we will implement the pickingFieldRectChangedTo method in our viewController and adjust 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, 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; } }
Next we will explain in detail the role of each step above. First, we will use an official Apple document (Scroll View Programming Guide for iOS) the following figure shows the meanings and functions of contentSize and contentInset:
Figure 6. The contentSize and contentInset attributes of UIScrollView
ContentSize is the size of the content you want to display in scrollView. The specific value depends on the size of the content, here we want to completely display the content of an image without compression, so here we set contentSize to image (image. size.
ContentInset can be understood as the margin between the top, bottom, left, and right of the displayed content. The default value is (0, 0, 0). The white space indicated by contentInset and contentSize are all areas that a scrollView can slide. Here we do not want to let the sliding area of the content (image) go beyond the position of the circular cut box. We can use the gap between the cut box ring and the top, bottom, and left edges of the view as the contentInset of the scrollView, this is what step 1 does. It ensures that the circular cut box can always fill the content of the image when the finger is dragged on the image.
ScrollView supports zoom-in and zoom-out. You only need to set the maximum and minimum multiples of the scale-in. Then, in the proxy function (UIView *) viewForZoomingInScrollView :( UIScrollView *) return the view to be scaled in scrollView. Here, we need to determine the minimum scaling size of the scrollview to satisfy the tangent of a dimension (long or wide) That is scaled to the shorter size of the image and a circular shear box, this is the minimum value that can be scaled down, because the cut box cannot be filled if the image is scaled down:
Figure 7. scale down to the hour, the cut box must be tangent to the shorter side
Step 4 is only executed when viewDidLoad is enabled. That is, when you enter the image editing page for the first time, you need to force adjust the current zoomScale of scrollview so that the image is displayed in a proper size.
So far, the entire function has been completed, run the program, and check the effect, as expected:
Figure 8. Screen Rotation
Figure 9. Drag and zoom
Iii. Summary
Loading an image into a scrollview, shrinking it, dragging it, and cropping part of it is the main function of the image editor. The seemingly simple functional requirements are all in the dark, you must think deeply about every detail of the details, make good use of the drawRect method of UIView, and combine the features of scrollview to achieve this.
The following two examples are worth noting:
1. Implementation of the circular cut box and processing of the cut box after rotating the screen in the autolayout environment;
2. scrollView attributes must be dynamically adjusted based on the actual size of the loaded image, the position and size of the circular cut box, and other attributes of scrollView.