1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | #import "RNSVGRenderable.h"
|
10 | #import "RNSVGClipPath.h"
|
11 | #import "RNSVGMask.h"
|
12 | #import "RNSVGViewBox.h"
|
13 | #import "RNSVGVectorEffect.h"
|
14 | #import "RNSVGBezierElement.h"
|
15 | #import "RNSVGMarker.h"
|
16 | #import "RNSVGMarkerPosition.h"
|
17 |
|
18 | @implementation RNSVGRenderable
|
19 | {
|
20 | NSMutableDictionary *_originProperties;
|
21 | NSArray<NSString *> *_lastMergedList;
|
22 | NSArray<NSString *> *_attributeList;
|
23 | NSArray<RNSVGLength *> *_sourceStrokeDashArray;
|
24 | CGFloat *_strokeDashArrayData;
|
25 | CGPathRef _srcHitPath;
|
26 | CGPathRef _hitArea;
|
27 | }
|
28 |
|
29 | static RNSVGRenderable * _contextElement;
|
30 | + (RNSVGRenderable *)contextElement { return _contextElement; }
|
31 | + (void)setContextElement:(RNSVGRenderable *)contextElement { _contextElement = contextElement; }
|
32 |
|
33 | - (id)init
|
34 | {
|
35 | if (self = [super init]) {
|
36 | _fillOpacity = 1;
|
37 | _strokeOpacity = 1;
|
38 | _strokeWidth = [RNSVGLength lengthWithNumber:1];
|
39 | _fillRule = kRNSVGCGFCRuleNonzero;
|
40 | }
|
41 | return self;
|
42 | }
|
43 |
|
44 | - (void)invalidate
|
45 | {
|
46 | _sourceStrokeDashArray = nil;
|
47 | if (self.dirty || self.merging) {
|
48 | return;
|
49 | }
|
50 | _srcHitPath = nil;
|
51 | [super invalidate];
|
52 | self.dirty = true;
|
53 | }
|
54 |
|
55 | - (void)setFill:(RNSVGBrush *)fill
|
56 | {
|
57 | if (fill == _fill) {
|
58 | return;
|
59 | }
|
60 | [self invalidate];
|
61 | _fill = fill;
|
62 | }
|
63 |
|
64 | - (void)setFillOpacity:(CGFloat)fillOpacity
|
65 | {
|
66 | if (fillOpacity == _fillOpacity) {
|
67 | return;
|
68 | }
|
69 | [self invalidate];
|
70 | _fillOpacity = fillOpacity;
|
71 | }
|
72 |
|
73 | - (void)setFillRule:(RNSVGCGFCRule)fillRule
|
74 | {
|
75 | if (fillRule == _fillRule) {
|
76 | return;
|
77 | }
|
78 | [self invalidate];
|
79 | _fillRule = fillRule;
|
80 | }
|
81 |
|
82 | - (void)setStroke:(RNSVGBrush *)stroke
|
83 | {
|
84 | if (stroke == _stroke) {
|
85 | return;
|
86 | }
|
87 | [self invalidate];
|
88 | _stroke = stroke;
|
89 | }
|
90 |
|
91 | - (void)setStrokeOpacity:(CGFloat)strokeOpacity
|
92 | {
|
93 | if (strokeOpacity == _strokeOpacity) {
|
94 | return;
|
95 | }
|
96 | [self invalidate];
|
97 | _strokeOpacity = strokeOpacity;
|
98 | }
|
99 |
|
100 | - (void)setStrokeWidth:(RNSVGLength*)strokeWidth
|
101 | {
|
102 | if ([strokeWidth isEqualTo:_strokeWidth]) {
|
103 | return;
|
104 | }
|
105 | [self invalidate];
|
106 | _strokeWidth = strokeWidth;
|
107 | }
|
108 |
|
109 | - (void)setStrokeLinecap:(CGLineCap)strokeLinecap
|
110 | {
|
111 | if (strokeLinecap == _strokeLinecap) {
|
112 | return;
|
113 | }
|
114 | [self invalidate];
|
115 | _strokeLinecap = strokeLinecap;
|
116 | }
|
117 |
|
118 | - (void)setStrokeJoin:(CGLineJoin)strokeLinejoin
|
119 | {
|
120 | if (strokeLinejoin == _strokeLinejoin) {
|
121 | return;
|
122 | }
|
123 | [self invalidate];
|
124 | _strokeLinejoin = strokeLinejoin;
|
125 | }
|
126 |
|
127 | - (void)setStrokeMiterlimit:(CGFloat)strokeMiterlimit
|
128 | {
|
129 | if (strokeMiterlimit == _strokeMiterlimit) {
|
130 | return;
|
131 | }
|
132 | [self invalidate];
|
133 | _strokeMiterlimit = strokeMiterlimit;
|
134 | }
|
135 |
|
136 | - (void)setStrokeDasharray:(NSArray<RNSVGLength *> *)strokeDasharray
|
137 | {
|
138 | if (strokeDasharray == _strokeDasharray) {
|
139 | return;
|
140 | }
|
141 | [self invalidate];
|
142 | _strokeDasharray = strokeDasharray;
|
143 | }
|
144 |
|
145 | - (void)setStrokeDashoffset:(CGFloat)strokeDashoffset
|
146 | {
|
147 | if (strokeDashoffset == _strokeDashoffset) {
|
148 | return;
|
149 | }
|
150 | [self invalidate];
|
151 | _strokeDashoffset = strokeDashoffset;
|
152 | }
|
153 |
|
154 | - (void)setVectorEffect:(RNSVGVectorEffect)vectorEffect
|
155 | {
|
156 | if (vectorEffect == _vectorEffect) {
|
157 | return;
|
158 | }
|
159 | [self invalidate];
|
160 | _vectorEffect = vectorEffect;
|
161 | }
|
162 |
|
163 | - (void)setPropList:(NSArray<NSString *> *)propList
|
164 | {
|
165 | if (propList == _propList) {
|
166 | return;
|
167 | }
|
168 |
|
169 | _propList = _attributeList = propList;
|
170 | [self invalidate];
|
171 | }
|
172 |
|
173 | - (void)dealloc
|
174 | {
|
175 | CGPathRelease(_hitArea);
|
176 | _sourceStrokeDashArray = nil;
|
177 | if (_strokeDashArrayData) {
|
178 | free(_strokeDashArrayData);
|
179 | }
|
180 | _strokeDashArrayData = nil;
|
181 | }
|
182 |
|
183 | UInt32 saturate(CGFloat value) {
|
184 | return value <= 0 ? 0 : value >= 255 ? 255 : (UInt32)value;
|
185 | }
|
186 |
|
187 | - (void)renderTo:(CGContextRef)context rect:(CGRect)rect
|
188 | {
|
189 | self.dirty = false;
|
190 | // This needs to be painted on a layer before being composited.
|
191 | CGContextSaveGState(context);
|
192 | CGContextConcatCTM(context, self.matrix);
|
193 | CGContextConcatCTM(context, self.transforms);
|
194 | CGContextSetAlpha(context, self.opacity);
|
195 |
|
196 | [self beginTransparencyLayer:context];
|
197 |
|
198 | if (self.mask) {
|
199 | // https://www.w3.org/TR/SVG11/masking.html#MaskElement
|
200 | RNSVGMask *_maskNode = (RNSVGMask*)[self.svgView getDefinedMask:self.mask];
|
201 | CGRect bounds = CGContextGetClipBoundingBox(context);
|
202 | CGSize boundsSize = bounds.size;
|
203 | CGFloat height = boundsSize.height;
|
204 | CGFloat width = boundsSize.width;
|
205 | NSUInteger iheight = (NSUInteger)height;
|
206 | NSUInteger iwidth = (NSUInteger)width;
|
207 | NSUInteger npixels = iheight * iwidth;
|
208 | CGRect drawBounds = CGRectMake(0, 0, width, height);
|
209 |
|
210 | // Allocate pixel buffer and bitmap context for mask
|
211 | NSUInteger bytesPerPixel = 4;
|
212 | NSUInteger bitsPerComponent = 8;
|
213 | NSUInteger bytesPerRow = bytesPerPixel * iwidth;
|
214 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
215 | UInt32 * pixels = (UInt32 *) calloc(npixels, sizeof(UInt32));
|
216 | CGContextRef bcontext = CGBitmapContextCreate(pixels, iwidth, iheight, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
217 |
|
218 | // Clip to mask bounds and render the mask
|
219 | CGFloat x = [self relativeOn:[_maskNode x]
|
220 | relative:width];
|
221 | CGFloat y = [self relativeOn:[_maskNode y]
|
222 | relative:height];
|
223 | CGFloat w = [self relativeOn:[_maskNode maskwidth]
|
224 | relative:width];
|
225 | CGFloat h = [self relativeOn:[_maskNode maskheight]
|
226 | relative:height];
|
227 | CGRect maskBounds = CGRectMake(x, y, w, h);
|
228 | CGContextClipToRect(bcontext, maskBounds);
|
229 | [_maskNode renderLayerTo:bcontext rect:rect];
|
230 |
|
231 | // Apply luminanceToAlpha filter primitive
|
232 | // https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
|
233 | UInt32 * currentPixel = pixels;
|
234 | for (NSUInteger i = 0; i < npixels; i++) {
|
235 | UInt32 color = *currentPixel;
|
236 |
|
237 | UInt32 r = color & 0xFF;
|
238 | UInt32 g = (color >> 8) & 0xFF;
|
239 | UInt32 b = (color >> 16) & 0xFF;
|
240 |
|
241 | CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
|
242 | *currentPixel = saturate(luma) << 24;
|
243 | currentPixel++;
|
244 | }
|
245 |
|
246 | // Create mask image and release memory
|
247 | CGImageRef maskImage = CGBitmapContextCreateImage(bcontext);
|
248 | CGColorSpaceRelease(colorSpace);
|
249 | CGContextRelease(bcontext);
|
250 | free(pixels);
|
251 |
|
252 | // Render content of current SVG Renderable to image
|
253 | UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
|
254 | CGContextRef newContext = UIGraphicsGetCurrentContext();
|
255 | CGContextTranslateCTM(newContext, 0.0, height);
|
256 | CGContextScaleCTM(newContext, 1.0, -1.0);
|
257 | [self renderLayerTo:newContext rect:rect];
|
258 | CGImageRef contentImage = CGBitmapContextCreateImage(newContext);
|
259 | UIGraphicsEndImageContext();
|
260 |
|
261 | // Blend current element and mask
|
262 | UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
|
263 | newContext = UIGraphicsGetCurrentContext();
|
264 | CGContextTranslateCTM(newContext, 0.0, height);
|
265 | CGContextScaleCTM(newContext, 1.0, -1.0);
|
266 |
|
267 | CGContextSetBlendMode(newContext, kCGBlendModeCopy);
|
268 | CGContextDrawImage(newContext, drawBounds, maskImage);
|
269 | CGImageRelease(maskImage);
|
270 |
|
271 | CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
|
272 | CGContextDrawImage(newContext, drawBounds, contentImage);
|
273 | CGImageRelease(contentImage);
|
274 |
|
275 | CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
|
276 | UIGraphicsEndImageContext();
|
277 |
|
278 | // Render blended result into current render context
|
279 | CGContextDrawImage(context, drawBounds, blendedImage);
|
280 | CGImageRelease(blendedImage);
|
281 | } else {
|
282 | [self renderLayerTo:context rect:rect];
|
283 | }
|
284 | [self endTransparencyLayer:context];
|
285 |
|
286 | CGContextRestoreGState(context);
|
287 |
|
288 | [self renderMarkers:context path:self.path rect:&rect];
|
289 | }
|
290 |
|
291 | - (void)prepareStrokeDash:(NSUInteger)count strokeDasharray:(NSArray<RNSVGLength *> *)strokeDasharray {
|
292 | if (strokeDasharray != _sourceStrokeDashArray) {
|
293 | CGFloat *dash = _strokeDashArrayData;
|
294 | _strokeDashArrayData = realloc(dash, sizeof(CGFloat) * count);
|
295 | if (!_strokeDashArrayData) {
|
296 | free(dash);
|
297 | return;
|
298 | }
|
299 | _sourceStrokeDashArray = strokeDasharray;
|
300 | for (NSUInteger i = 0; i < count; i++) {
|
301 | _strokeDashArrayData[i] = (CGFloat)[self relativeOnOther:strokeDasharray[i]];
|
302 | }
|
303 | }
|
304 | }
|
305 |
|
306 | - (void)renderMarkers:(CGContextRef)context path:(CGPathRef)path rect:(const CGRect *)rect {
|
307 | RNSVGMarker *markerStart = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerStart];
|
308 | RNSVGMarker *markerMid = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerMid];
|
309 | RNSVGMarker *markerEnd = (RNSVGMarker*)[self.svgView getDefinedMarker:self.markerEnd];
|
310 | if (markerStart || markerMid || markerEnd) {
|
311 | _contextElement = self;
|
312 | NSArray<RNSVGMarkerPosition*>* positions = [RNSVGMarkerPosition fromCGPath:path];
|
313 | CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1;
|
314 | for (RNSVGMarkerPosition* position in positions) {
|
315 | RNSVGMarkerType type = [position type];
|
316 | switch (type) {
|
317 | case kStartMarker:
|
318 | [markerStart renderMarker:context rect:*rect position:position strokeWidth:width];
|
319 | break;
|
320 |
|
321 | case kMidMarker:
|
322 | [markerMid renderMarker:context rect:*rect position:position strokeWidth:width];
|
323 | break;
|
324 |
|
325 | case kEndMarker:
|
326 | [markerEnd renderMarker:context rect:*rect position:position strokeWidth:width];
|
327 | break;
|
328 |
|
329 | default:
|
330 | break;
|
331 | }
|
332 | }
|
333 | _contextElement = nil;
|
334 | }
|
335 | }
|
336 |
|
337 | - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect
|
338 | {
|
339 | CGPathRef path = self.path;
|
340 | if (!path) {
|
341 | path = [self getPath:context];
|
342 | if (!self.path) {
|
343 | self.path = CGPathRetain(path);
|
344 | }
|
345 | [self setHitArea:path];
|
346 | const CGRect fillBounds = CGPathGetBoundingBox(path);
|
347 | const CGRect strokeBounds = CGPathGetBoundingBox(self.strokePath);
|
348 | self.pathBounds = CGRectUnion(fillBounds, strokeBounds);
|
349 | }
|
350 | const CGRect pathBounds = self.pathBounds;
|
351 |
|
352 | CGAffineTransform current = CGContextGetCTM(context);
|
353 | CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM);
|
354 | CGRect clientRect = CGRectApplyAffineTransform(pathBounds, svgToClientTransform);
|
355 |
|
356 | self.clientRect = clientRect;
|
357 |
|
358 | if (_vectorEffect == kRNSVGVectorEffectNonScalingStroke) {
|
359 | path = CGPathCreateCopyByTransformingPath(path, &svgToClientTransform);
|
360 | CGContextConcatCTM(context, CGAffineTransformInvert(svgToClientTransform));
|
361 | }
|
362 |
|
363 | CGAffineTransform vbmatrix = self.svgView.getViewBoxTransform;
|
364 | CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms);
|
365 | CGAffineTransform matrix = CGAffineTransformConcat(transform, vbmatrix);
|
366 |
|
367 | CGRect bounds = CGRectMake(0, 0, CGRectGetWidth(clientRect), CGRectGetHeight(clientRect));
|
368 | CGPoint mid = CGPointMake(CGRectGetMidX(pathBounds), CGRectGetMidY(pathBounds));
|
369 | CGPoint center = CGPointApplyAffineTransform(mid, matrix);
|
370 |
|
371 | self.bounds = bounds;
|
372 | if (!isnan(center.x) && !isnan(center.y)) {
|
373 | self.center = center;
|
374 | }
|
375 | self.frame = clientRect;
|
376 |
|
377 | if (self.skip || self.opacity == 0) {
|
378 | return;
|
379 | }
|
380 |
|
381 | if (!self.fill && !self.stroke) {
|
382 | return;
|
383 | }
|
384 |
|
385 | CGPathDrawingMode mode = kCGPathStroke;
|
386 | BOOL fillColor = NO;
|
387 | [self clip:context];
|
388 |
|
389 | BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd;
|
390 |
|
391 | if (self.fill) {
|
392 | if (self.fill.class == RNSVGBrush.class) {
|
393 | CGContextSetFillColorWithColor(context, [self.tintColor CGColor]);
|
394 | fillColor = YES;
|
395 | } else {
|
396 | fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity];
|
397 | }
|
398 |
|
399 | if (fillColor) {
|
400 | mode = evenodd ? kCGPathEOFill : kCGPathFill;
|
401 | } else {
|
402 | CGContextSaveGState(context);
|
403 | CGContextAddPath(context, path);
|
404 | evenodd ? CGContextEOClip(context) : CGContextClip(context);
|
405 | [self.fill paint:context
|
406 | opacity:self.fillOpacity
|
407 | painter:[self.svgView getDefinedPainter:self.fill.brushRef]
|
408 | bounds:pathBounds
|
409 | ];
|
410 | CGContextRestoreGState(context);
|
411 |
|
412 | if (!self.stroke) {
|
413 | return;
|
414 | }
|
415 | }
|
416 | }
|
417 |
|
418 | if (self.stroke) {
|
419 | CGFloat width = self.strokeWidth ? [self relativeOnOther:self.strokeWidth] : 1;
|
420 | CGContextSetLineWidth(context, width);
|
421 | CGContextSetLineCap(context, self.strokeLinecap);
|
422 | CGContextSetLineJoin(context, self.strokeLinejoin);
|
423 | NSArray<RNSVGLength *>* strokeDasharray = self.strokeDasharray;
|
424 | NSUInteger count = strokeDasharray.count;
|
425 |
|
426 | if (count) {
|
427 | [self prepareStrokeDash:count strokeDasharray:strokeDasharray];
|
428 | if (_strokeDashArrayData) {
|
429 | CGContextSetLineDash(context, self.strokeDashoffset, _strokeDashArrayData, count);
|
430 | }
|
431 | }
|
432 |
|
433 | if (!fillColor) {
|
434 | CGContextAddPath(context, path);
|
435 | CGContextReplacePathWithStrokedPath(context);
|
436 | CGContextClip(context);
|
437 | }
|
438 |
|
439 | BOOL strokeColor;
|
440 |
|
441 | if (self.stroke.class == RNSVGBrush.class) {
|
442 | CGContextSetStrokeColorWithColor(context,[self.tintColor CGColor]);
|
443 | strokeColor = YES;
|
444 | } else {
|
445 | strokeColor = [self.stroke applyStrokeColor:context opacity:self.strokeOpacity];
|
446 | }
|
447 |
|
448 | if (strokeColor && fillColor) {
|
449 | mode = evenodd ? kCGPathEOFillStroke : kCGPathFillStroke;
|
450 | } else if (!strokeColor) {
|
451 | // draw fill
|
452 | if (fillColor) {
|
453 | CGContextAddPath(context, path);
|
454 | CGContextDrawPath(context, mode);
|
455 | }
|
456 |
|
457 | // draw stroke
|
458 | CGContextAddPath(context, path);
|
459 | CGContextReplacePathWithStrokedPath(context);
|
460 | evenodd ? CGContextEOClip(context) : CGContextClip(context);
|
461 |
|
462 | [self.stroke paint:context
|
463 | opacity:self.strokeOpacity
|
464 | painter:[self.svgView getDefinedPainter:self.stroke.brushRef]
|
465 | bounds:pathBounds
|
466 | ];
|
467 | return;
|
468 | }
|
469 | }
|
470 |
|
471 | CGContextAddPath(context, path);
|
472 | CGContextDrawPath(context, mode);
|
473 | }
|
474 |
|
475 | - (void)setHitArea:(CGPathRef)path
|
476 | {
|
477 | if (_srcHitPath == path) {
|
478 | return;
|
479 | }
|
480 | _srcHitPath = path;
|
481 | CGPathRelease(_hitArea);
|
482 | CGPathRelease(self.strokePath);
|
483 | _hitArea = CGPathCreateCopy(path);
|
484 | self.strokePath = nil;
|
485 | if (self.stroke && self.strokeWidth) {
|
486 | // Add stroke to hitArea
|
487 | CGFloat width = [self relativeOnOther:self.strokeWidth];
|
488 | self.strokePath = CGPathRetain(CFAutorelease(CGPathCreateCopyByStrokingPath(path, nil, width, self.strokeLinecap, self.strokeLinejoin, self.strokeMiterlimit)));
|
489 | // TODO add dashing
|
490 | // CGPathCreateCopyByDashingPath(CGPathRef _Nullable path, const CGAffineTransform * _Nullable transform, CGFloat phase, const CGFloat * _Nullable lengths, size_t count)
|
491 | }
|
492 | }
|
493 |
|
494 | - (BOOL)isUserInteractionEnabled
|
495 | {
|
496 | return NO;
|
497 | }
|
498 |
|
499 | // hitTest delegate
|
500 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
501 | {
|
502 | if (!_hitArea) {
|
503 | return nil;
|
504 | }
|
505 |
|
506 | if (self.active) {
|
507 | if (!event) {
|
508 | self.active = NO;
|
509 | }
|
510 | return self;
|
511 | }
|
512 |
|
513 | CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix);
|
514 | transformed = CGPointApplyAffineTransform(transformed, self.invTransform);
|
515 |
|
516 | if (!CGRectContainsPoint(self.pathBounds, transformed)) {
|
517 | return nil;
|
518 | }
|
519 |
|
520 | BOOL evenodd = self.fillRule == kRNSVGCGFCRuleEvenodd;
|
521 | if (!CGPathContainsPoint(_hitArea, nil, transformed, evenodd) &&
|
522 | !CGPathContainsPoint(self.strokePath, nil, transformed, NO)) {
|
523 | return nil;
|
524 | }
|
525 |
|
526 | if (self.clipPath) {
|
527 | RNSVGClipPath *clipNode = (RNSVGClipPath*)[self.svgView getDefinedClipPath:self.clipPath];
|
528 | if ([clipNode isSimpleClipPath]) {
|
529 | CGPathRef clipPath = [self getClipPath];
|
530 | if (clipPath && !CGPathContainsPoint(clipPath, nil, transformed, clipNode.clipRule == kRNSVGCGFCRuleEvenodd)) {
|
531 | return nil;
|
532 | }
|
533 | } else {
|
534 | RNSVGRenderable *clipGroup = (RNSVGRenderable*)clipNode;
|
535 | if (![clipGroup hitTest:transformed withEvent:event]) {
|
536 | return nil;
|
537 | }
|
538 | }
|
539 | }
|
540 |
|
541 | return self;
|
542 | }
|
543 |
|
544 | - (NSArray<NSString *> *)getAttributeList
|
545 | {
|
546 | return _attributeList;
|
547 | }
|
548 |
|
549 | - (void)mergeProperties:(__kindof RNSVGRenderable *)target
|
550 | {
|
551 | NSArray<NSString *> *targetAttributeList = [target getAttributeList];
|
552 |
|
553 | if (targetAttributeList.count == 0) {
|
554 | return;
|
555 | }
|
556 | self.merging = true;
|
557 |
|
558 | NSMutableArray* attributeList = [self.propList mutableCopy];
|
559 | _originProperties = [[NSMutableDictionary alloc] init];
|
560 |
|
561 | for (NSString *key in targetAttributeList) {
|
562 | [_originProperties setValue:[self valueForKey:key] forKey:key];
|
563 | if (![attributeList containsObject:key]) {
|
564 | [attributeList addObject:key];
|
565 | [self setValue:[target valueForKey:key] forKey:key];
|
566 | }
|
567 | }
|
568 |
|
569 | _lastMergedList = targetAttributeList;
|
570 | _attributeList = [attributeList copy];
|
571 | self.merging = false;
|
572 | }
|
573 |
|
574 | - (void)resetProperties
|
575 | {
|
576 | self.merging = true;
|
577 | for (NSString *key in _lastMergedList) {
|
578 | [self setValue:[_originProperties valueForKey:key] forKey:key];
|
579 | }
|
580 |
|
581 | _lastMergedList = nil;
|
582 | _attributeList = _propList;
|
583 | self.merging = false;
|
584 | }
|
585 |
|
586 | @end
|