Imitation red book] plus a small effect on the image of the label


Recent projects need to imitate the red book in the relevant functions of the notes, which allows users to tag the image of the function is one of the key, the following are some of the ideas to achieve this function.

Imitation red book] plus a small effect on the image of the label
little red book label function

Functional requirements analysis

Playing with the little red book, summed up some of the features of the label to play the demand.

  1. Click on the picture to pop up the input box, enter the label information in the click position to generate a label.
  2. Click on the label on the dots can switch different styles, there are about two directions, straight lines and slashes of the two styles, the number of tags 1~3, up to a total of 12 styles.
  3. Click the text on the label to edit the label.
  4. Any part of the label can be deleted.
  5. Drag any part of the label to move the label.
  6. In the case of non editable state, click on the image to hide / show all tags.


With the demand, the following analysis to achieve one by one.

1, the proportion of coordinates, ViewModel

First of all, this small tag needs to respond to touch events, so intend to inherit the UIView way to achieve it, it is clear that the label from the origin, lines, text and other components. The first requirement in creating a view, need to determine the label view location, taking into account the picture itself is likely to zoom fit different size equipment, so the position of the view can be used to label the proportion of coordinates, the coordinate value for 0~1, finally multiplied by the width and height of the parent get the exact coordinates of the parent in the coordinate system.

Imitation red book] plus a small effect on the image of the label
using proportional coordinates

is also a separate TagViewModel to save said a tag data required to create a label is to create a TagViewModel label, just need to accept and deal with the views of ViewModel can generate labels.

@interface TagViewModel: NSObject / @property text (nonatomic, strong) array NSMutableArray< TagModel *> *tagModels; / / label relative to the parent view coordinates in the coordinate system, such as (0.5, 0.5) is located in the center of the parent representative @property (nonatomic, assign) CGPoint coordinate; / / @property style (nonatomic, assign) TagViewStyle style; @property / order marks (nonatomic, assign) NSUInteger index; / / initialize - (instancetype) initWithArray: (NSArray< TagModel *> *) tagModels coordinate: (CGPoint) coordinate; / / style - (void) - (void) resetStyle; styleToggle; synchronizeAngle; @end - (void)

A TagViewModel on behalf of a label, a label may contain several paragraphs of text, and the need to draw the appropriate style in the angle of the data, so a definition of a TagModel.

@interface TagModel: NSObject / @property text (nonatomic, copy) NSString *name (nonatomic, copy); @property NSString *value; / / @property (nonatomic, assign) of CGFloat angle @property; / / text position (nonatomic, assign) CGSize textSize (nonatomic, assign); @property CGPoint textPosition; / / initialize - (instancetype) (initWithName: NSString name value: (NSString) * * value; @end)
2, layer rendering

The tab view as the smallest unit provided to the outside, and its internal components (text, lines, the original heart) due to no response to the incident, chose to do so than the drawing layer, with a view to drawing will be slightly more efficient.
a tab view is mainly composed of text, lines, circles, you can use CATextLayer and CAShapeLayer to achieve, the structure of the following figure. The first step is to determine the width and height of the tab view, which is determined by the radius of the slash and the height of the text.

Imitation red book] plus a small effect on the image of the label
gray area for text area

then you can identify the width and height of the center of the picture, the text of the underline layer, and finally according to underline the location of the text layer. The method of oblique line drawing is based on the angle of the TagModel and the radius of the oblique line to calculate the starting point (Center) and the end point (a point on the circle) with a trigonometric function.

3, event response chain

Draw the layer, then handle the touch. In the second to fifth point of demand, the event response chain. Due to the need to deal with different types of touch events, where I used the UIGestureRecognize to add a view click, long press, sliding gestures. When a tab view receives the click event, it needs to determine whether they need to handle the click event, such as clicking the origin area, according to the long text area or just point to the blank area in the view. So in the view class labels need to override the UIView -pointInside: withEvent: method and -hitTest: withEvent: method, the former to determine whether the incident point falls in this view, the latter is used to return to the superior view need to accept this view is what event – view itself, or view nil.

Imitation red book] plus a small effect on the image of the label
event response chain

WithEvent: code in the -pointInside: method to determine whether the point is located in the center of the circle or text:

No / / tab view view, is determined by the piontInside:withEvent: method in the - (UIView) hitTest: (CGPoint) point withEvent: (UIEvent * event) {UIView *view = [super hitTest:point withEvent:event]; return view;} / / override superclass methods - (BOOL) pointInside: (CGPoint) point withEvent: (UIEvent * event) {if ([self centerContainsPoint:point inset:0] &! [self textLayerContainsPoint:point inset:CGPointMake; &! (-5, -5) {return}]) NO; return [super pointInside: point withEvent:event];} / / position is in the center region - (BOOL) centerContainsPoint: (CGPoint) position inset: (CGFloat insetRadius) {CGPoint centerPosition = CGPointMake (self.layer.bounds.size.width/2, self.layer.bounds.size.height/2) UIB; EzierPath *path = [UIBezierPath bezierPathWithArcCenter:centerPosition radius:kUnderLineLayerRadius+insetRadius startAngle:0 endAngle:M_PI*2 clockwise:YES]; return [path containsPoint:position];} / / position is in a textLayer - (BOOL) textLayerContainsPoint: (CGPoint) point inset: (CGPoint insetXY) {BOOL cantainsPoint = NO; for (CATextLayer *textLayer in _textLayers) {if (textLayer.presentationLayer.opacity = = 0) {continue}; CGRect textRect = CGRectInset (textLayer.frame, insetXY.x, insetXY.y); if (CGRectContainsPoint (textRect, point)) {cantainsPoint = YES; break return cantainsPoint;}}};

If the final judgment of a tab view need to handle this time with just one touch, began to view the label every gesture and the matched point to determine whether the method of center and text, can achieve second to fifth points in the functional requirements.

4, layer animation

In the second and sixth points of the demand, you need to change the style of the tabbed view and display / hide tags in animation. The idea is also to animation animation layer and add CAAnimation, animation can be achieved by using the beginTime property to CAKeyFrameAnimation or CABasicAnimation sequence, and using CAAnimationTransaction to do some animation after treatment.

Imitation red book] plus a small effect on the image of the label
Be careful

Itself in the CAAnimation layer of the animation can be directly attribute assignment, will generate the default animation, but this animation not customconfiguration, like different properties just mentioned order can not achieve, attributes directly modify these layer value will make animation a homogeneous implementation. For example, the line is a CAShapeLayer code, layer.strokeEnd = 0 can produce lines to recover the animation, but not let the words disappear after the animation occurs, so I had to change a way to achieve.

Hide animation code:

- (void) hideWithAnimate: (BOOL) animate if (_viewHidden || {_animating {return}); _animating = YES; CGFloat = duration 1.f; [self animateWithDuration:duration*3 AnimationBlock:^{NSTimeInterval currentTime (CACurrentMediaTime) = CABasicAnimation *animation = [CABasicAnimation; / / the origin of animation]; animation.beginTime = currentTime+duration*2; animation.duration = duration; animation.keyPath = opacity; animation.removedOnCompletion = @ NO; animation.fillMode = kCAFillModeBoth; animation.fromValue = @1; animation.toValue = @0; [_centerPointShapeLayer addAnimation:animation forKey:kAnimationKeyShow]; animation.fromValue = @0.3; [_shadowPointShapeLayer addAnimation:animation forKey:kAnimationKeyShow]; CABasicAnimation *lineAnimation = [CABasicAnimation animation] / underline; lineAnimation.beginTime = currentTime+duration; lineAnimation.duration = duration; lineAnimation.keyPath = @ "strokeEnd"; lineAnimation.removedOnCompletion = NO; lineAnimation.fillMode = kCAFillModeBoth; lineAnimation.fromValue = @1; lineAnimation.toValue = @0; for (CAShapeLayer *shapeLayer in _underLineLayers shapeLayer addAnimation:lineAnimation forKey:kAnimationKeyShow]) {[}; / / text CABasicAnimation *textAnimation [CABasicAnimation = animation]; tex TAnimation.beginTime = 0; textAnimation.duration = duration; textAnimation.keyPath = @ "Opacity"; textAnimation.removedOnCompletion = NO; textAnimation.fillMode = kCAFillModeBoth; textAnimation.fromValue = @1; textAnimation.toValue = @0; for (CATextLayer *textLayer in _textLayers [textLayer addAnimation:textAnimation) {forKey:kAnimationKeyShow]}}; completeBlock:^{_animating = NO; _viewHidden = YES;}];} - (void) animateWithDuration: (CGFloat duration (void) AnimationBlock: (doBlock ^) (completeBlock:) (void) (^) (completeBlock)) {[CATransaction begin]; [CATransaction setDisableActions:NO]; [CATransactio N setAnimationDuration:duration]; [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; [CATransaction setCompletionBlock:^{[CATransaction begin]; [CATransaction setDisableActions:YES]; if (completeBlock) {completeBlock} ([CATransaction); commit];}]; if (doBlock) doBlock ([CATransaction);} {commit]};
  1. Specify the time to start the animation
    animation.beginTime = CACurrentMediaTime () +duration*2; can allow the animation to start after a specified time
  2. Animation.fillMode
    this is a very interesting property, its value is defined in the CAMediaTiming.h CA_EXTERN optional NSString * const kCAFillModeForwards CA_AVAILABLE_STARTING (10.5, 2, 9, 2); CA_EXTERN NSString * const kCAFillModeBackwards CA_AVAILABLE_STARTING (10.5, 2, 9, 2); CA_EXTERN NSString * const kCAFillModeBoth CA_AVAILABLE_STARTING (10.5, 2, 9 CA_EXTERN NSString, 2); * const kCAFillModeRemoved CA_AVAILABLE_STARTING (10.5, 2, 9, 2); by default when given a layer after adding CAAnimation animation is actually changing the properties of layer.presentationLayer, let layer be in the animation presentation layer (presentation) on animation, animation when completed, animation removed from the layer, no change in layer.modelLayer Property, layer will also be removed on presentation. And if you set the animation.rmovedOnCompletion = NO, the animation will not be removed from the layer after the completion, and according to the fillMode attribute determines how to display layer. KCAFillModeForwards// until the beginning of the animation layer animation animation to keep hidden, after the completion of the final state until the beginning of the animation kCAFillModeBackwards// layer in the current state, after the completion of presentation kCAFillModeBoth// from the animation above together until the beginning of the animation layer animation in the current state, after the completion of the final state to keep the animation kCAFillModeRemoved// until the beginning of the animation layer are hidden, the animation is finished out of the presentation
  3. CATransaction
    with [CATransaction begin] to start an animation business, and can be completed in the implementation of their own code. In addition, CATransaction can be combined in a number of CAAnimation, you can also nested CATransaction.
5, label style switch

In the drawing layer, mainly on each text (TagModel) angle on the properties of different directions to draw lines, change the label style is actually changing the angle of the text, so the realization of switching function need to do is to configure a different style, different in the number of the corresponding text view configuration file.
here to choose the angle of related written style in plist, with the style of data label data and plist itself in ViewTagModel to make a match, in order to confirm the current label style. When you need to change the style of the switch directly to the style, and then according to the style of the plist to find the corresponding angle and assignment on the ok. Here plist can be replaced by JSON, the idea is the same, this way, you can let app access to the server to get the latest style to change the style configuration at any time.

#pragma mark - to determine the current style / / to be judged according to the number of tags - (void) resetStyle count {NSInteger = _tagModels.count; if (count = = 0) {NSLog (@ "_tagModels.count = 0"); return;} / / according to the label number out corresponding style data NSDictionary = styleDict[[NSString stringWithFormat:@ *countStyleDict (@ "% @". Count if (countStyleDict) ";!) {NSLog (@" styleDict not found "); return //allKeys;} for all TagViewStyle = [countStyleDict NSArray *allKeys allKeys] TagViewStyle for (NSInteger; / / traversal of i=0; i< allKeys.count; i++) {NSString *styleStr = allKeys[i]; / / key style to come up with a corresponding angle of NSArray *styleArray = countStyleDict[styleStr]; If (styleArray.count = = 0) {continue} / / no point data; / / regardless of the number of tag, here only the first point of judgment label can be considered to verify all / tag data perspective to judge the legitimacy of the NSNumber = *angleNumber (NSNumber*) styleArray[0]; if (_tagModels[0].angle = = [angleNumber floatValue]) {_style [styleStr = integerValue]; NSLog (@ _style @ reset:%@, return (_style));}}}; #pragma mark - switched current style - (void) styleToggle {/ / switching (_style+1) _style =%maxStyle; [self synchronizeAngle];} the style update angle according to the #pragma - mark - synchronizeAngle (void) {NSInteger count = _tagMo Dels.count; if (count = = 0) {NSLog (@ "_tagModels.count = 0"); return;} / / according to the label number out corresponding style data NSDictionary = styleDict[[NSString stringWithFormat:@ *countStyleDict @ "% @", "(count); if (! CountStyleDict) {NSLog (@" styleDict not found "); return according to the pattern out;} / / *styleArray = countStyleDict[[NSString angle data array NSArray stringWithFormat:@ @"% @ "," (_style); if (styleArray.count < _tagModels.count) {NSLog (@ styleArray doesn't long enough "); return;} / / update (NSInteger i=0; i< for; _tagModels.count; i++) {NSNumber = *angleNumber (NSNumber*) styleArray[i]; _tagModels[i].angle = [angleNumber FloatValue];}}
Imitation red book] plus a small effect on the image of the label
style configuration file

The introduction of this small label function on this end, demo free to upload.

Reference material