() Implementation and preservation of graffiti, and preservation of graffiti
Use touchesMoved to obtain each touch point and store it in an array.
In the drawRect method, these points are generated cyclically. When I = 0, the CGContextMoveToPoint method is used to move them to the start point. The other points are connected through the CGContextAddLineToPoint method.
The problem is that there is only one starting point. If you draw a line, the last ending point will be connected to the starting point. The other problem is that you cannot roll back to the previous one, because there is no starting point for each record, it only records every mobile microelement.
Therefore, an array should be used to store a line (from touch to stop touch) and a large array should be used to store these small arrays. To facilitate the description, set the large array to S and the small array to C.
In the touchesBegan method, create a small array of C1, load the starting point into C1, load C1 into S, and call setNeedsDisplay to update the points on the screen.
In the touchesMoved method, use the lastObject method of S to retrieve the current C. In this case, C1 is taken and the new vertex is added to C1. Note that because all pointers point to the array, As long as C1 is changed, then we changed C1 in C. Do not forget to call setNeedsDisplay to update the points on the screen.
In the touchesEnded method, we can find that the process is the same as that of the touchesMoved method. Therefore, you only need to call touchesMoved once and pass in your own parameters.
In this way, not only all vertices are recorded, but also the starting and ending positions are recorded.
At first, I thought that each drawRect would only redraw the last array, but I found that the previous line would disappear. After thinking, I found that the drawRect would automatically clear the screen before each call, later, I thought it was very scientific. I thought that I had to manually clear the screen when using single chip microcomputer for UI. This is indeed convenient.
In this case, you only need to call the removeAllObjects method of the S array to clear the screen, And the removeLastObject method of the S array must be called for rollback.
The specific process is as follows:
First, create a new S array and override the get Method for initialization:
@property (nonatomic, strong) NSMutableArray *totalPathPoints;
- (NSMutableArray *)totalPathPoints{ if (_totalPathPoints == nil) { _totalPathPoints = [NSMutableArray array]; } return _totalPathPoints;}
The start operation is to create a word group C, add the current vertex, and finally load the S: Do not forget to update the screen!
-(Void) touchesBegan :( NSSet *) touches withEvent :( UIEvent *) event {UITouch * touch = [touches anyObject]; CGPoint startPos = [touch locationInView: touch. view]; // each time a touch starts, an array is created to store all vertices in the touch process (path of the touch process) NSMutableArray * pathPoints = [NSMutableArray array]; [pathPoints addObject: [NSValue valueWithCGPoint: startPos]; // Add all vertices of this path to the large array [self. totalPathPoints addObject: pathPoints]; [self setNeedsDisplay];}
The move operation adds the current vertex to the last element of the S array: Because the pointer is used, you do not need to assign a value after the change.
-(Void) touchesMoved :( NSSet *) touches withEvent :( UIEvent *) event {UITouch * touch = [touches anyObject]; CGPoint pos = [touch locationInView: touch. view]; // retrieves the array NSMutableArray * pathPoints = [self. totalPathPoints lastObject]; [pathPoints addObject: [NSValue valueWithCGPoint: pos]; [self setNeedsDisplay];}
The operation of the terminal is exactly the same as that of the move operation. Therefore, a move operation is called:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ [self touchesMoved:touches withEvent:event];}
The last is the drawRect method, because the screen is cleared every time the drawRect method is called, so we need to redraw all the points and lines.
Using a double-layer loop, the outer layer obtains C from S, and the inner layer traverses the vertices in C. Similarly, the first vertex is used as the start point (MoveToPoint), followed by the Link (AddLineToPoint ).
Here, we will review the Quartz2D plotting method. First, we will obtain the context, call the plotting function, set the status, and finally present the image in the context on the View using the Stroke or Fill method.
-(Void) drawRect :( CGRect) rect {CGContextRef ctx = UIGraphicsGetCurrentContext (); // note that each drawRect will clear the screen, so you need to redraw all lines. For (NSMutableArray * pathPoints in self. totalPathPoints) {for (int I = 0; I <pathPoints. count; I ++) {// a path CGPoint pos = [pathPoints [I] CGPointValue]; if (I = 0) {CGContextMoveToPoint (ctx, pos. x, pos. y);} else {CGContextAddLineToPoint (ctx, pos. x, pos. y) ;}} CGContextSetLineCap (ctx, kCGLineCapRound); CGContextSetLineJoin (ctx, kCGLineJoinRound); CGContextSetLineWidth (ctx, 5); CGContextStrokePath (ctx );}
It is very easy to roll back and clear the screen. You only need to remove the last or all elements in S:
- (void)clear{ [self.totalPathPoints removeAllObjects]; [self setNeedsDisplay];}- (void)back{ [self.totalPathPoints removeLastObject]; [self setNeedsDisplay];}
To achieve this, you must first obtain the View content of the drawing board. You can add a category to the UIImage to capture the View. Note that when the context is enabled, the View should end.
+ (Instancetype) captureWithView :( UIView *) view {// 1. enable context. The second parameter is whether opaque NO is transparent. This prevents extra space occupation (for example, a circle will show a box), and the third parameter is the scaling ratio, 0.0 indicates no scaling. Uigraphicsbeginimagecontextwitexceptions (view. frame. size, NO, 0.0); // 2. render the layer of the controller view to the context [view. layer renderInContext: UIGraphicsGetCurrentContext ()]; // 3. retrieve image UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext (); // 4. end context UIGraphicsEndImageContext (); return newImage ;}
In this way, you can get the View on The View.
To save the image, use the UIImageWriteToSavedPhotosAlbum function. Note that this is a C function:
/*** Save the image to the user album ** @ param image the image to be saved * @ param completionTarget the object of the method called after completion * @ param completionSelector the method called after completion * @ param contextInfo: additional context message, it is generally blank */void UIImageWriteToSavedPhotosAlbum (UIImage * image, id completionTarget, SEL completionSelector, void * contextInfo );
It should be noted that after the function is called, parameters need to be passed in. The official suggestions on the above function are as follows:
// Adds a photo to the saved photos album. The optional completionSelector should have the form:// - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
Therefore, you should strictly follow this requirement to write the callback function after completion, but note that the method name is image: didFinishSavingWithError: contextInfo:. In other words, the method name only includes the description part and the colon, the parameter type and name are not included.
You should call it like this:
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
And implement this method: note that a technique is used here. If a successful error occurs, it will be nil, which will enter else; otherwise, it will enter the conditional branch of error.
Here we use a third-party class library MBProgressHUD as the indicator, and use the version further encapsulated by instructor Yan Jie. Thank you here.
-(Void) image :( UIImage *) image didFinishSavingWithError :( NSError *) error contextInfo :( void *) contextInfo {if (error) {// failed to save [MBProgressHUD showError: @ "failed to save"];} else {// saved successfully [MBProgressHUD showSuccess: @ "saved successfully"];}
Note that the system will ask the user whether to allow the App to access the album when saving the photo for the first time. If the photo album is not allowed, the user will not play the box in the future. You need to manually enable it by modifying the privacy settings-> in the album, if the storage fails, you can guide the user to perform the operation.
To save multiple paths, you can also use CGMultablePathRef to create paths and add them to the context for rendering. Note that it is not an OC object and therefore cannot be directly placed into an array, this method is troublesome.
You can also use the UIBezierPath object to implement this method. This method is called a beiser curve object. Each UIBezierPath corresponds to a complete curve. You can simply understand the UIBezierPath in the C language corresponding to CGMutablePathRef in OC. The advantage of using OC is that it is more friendly and concise.
The use of UIBezierPath for plotting is very simple. For example, to draw a straight line from (0, 0) to (100,100): it is basically consistent with the previous process, with the benefit of object-oriented.
UIBezierPath *path = [UIBezierPath bezierPath];[path moveToPoint:CGPointZero];[path addLineToPoint:CGPointMake(100,100)];[path stroke];
This is easy to implement. Just create an array of UIBezierPath objects, and each complete touch process corresponds to a UIBezierPath.
Create a new UIBezierPath at the start point, use moveToPoint to record the start point, add UIBezierPath to the object array, and update the screen.
-(Void) touchesBegan :( NSSet *) touches withEvent :( UIEvent *) event {// 1. obtain the current touch point UITouch * touch = [touches anyObject]; CGPoint startPos = [touch locationInView: touch. view]; // 2. create a new path UIBezierPath * currenPath = [UIBezierPath bezierPath]; currenPath. lineCapStyle = kCGLineCapRound; currenPath. lineJoinStyle = kCGLineJoinRound; // set the starting point [currenPath moveToPoint: startPos]; // 3. add the path to the array [self. paths addObject: currenPath]; [self setNeedsDisplay];}
The current vertex is obtained at the time of moving and ending. The last UIBezierPath object is retrieved and added using the addLineToPoint method:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint pos = [touch locationInView:touch.view]; UIBezierPath *currentPath = [self.paths lastObject]; [currentPath addLineToPoint:pos]; [self setNeedsDisplay];}- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ [self touchesMoved:touches withEvent:event];}
In the drawRect method, retrieve each path and call the stroke Method for drawing. Here, you can set the global status, such as the line width.
for (UIBezierPath *path in self.paths) { path.lineWidth = 10; [path stroke];}