forked from SVGKit/SVGKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDetailViewController.m
More file actions
794 lines (630 loc) · 30.5 KB
/
Copy pathDetailViewController.m
File metadata and controls
794 lines (630 loc) · 30.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
//
// DetailViewController.m
// iOSDemo
//
// Created by adam on 29/09/2012.
// Copyright (c) 2012 na. All rights reserved.
//
#import "DetailViewController.h"
#import "MasterViewController.h"
#import "NodeList+Mutable.h"
#import "SVGKFastImageView.h"
#import "SVGKLayeredImageView.h"
#import "SVGKSourceURL.h"
#import "SVGKSourceLocalFile.h"
#import "SVGKSourceNSData.h"
#import "SVGKSourceString.h"
@interface ImageLoadingOptions : NSObject
@property(nonatomic) BOOL requiresLayeredImageView;
@property(nonatomic) CGSize overrideImageSize;
@property(nonatomic) float overrideImageRenderScale;
@property(nonatomic,retain) SVGKSourceLocalFile* localFileSource;
- (id)initWithSource:(SVGKSource*) source;
@end
@implementation ImageLoadingOptions
- (id)initWithSource:(SVGKSource*) source
{
self = [super init];
if (self) {
if( [source isKindOfClass:[SVGKSourceLocalFile class]])
self.localFileSource = (SVGKSourceLocalFile*) source;
else
; // cannot auto-select loading options for anything except local files / bundle files
self.overrideImageRenderScale = 1.0;
self.overrideImageSize = CGSizeZero;
}
return self;
}
@end
@interface DetailViewController ()
@property (nonatomic, retain) UIPopoverController *popoverController;
@property (nonatomic, retain) NSDate* startParseTime, * endParseTime;
- (void)loadSVGFrom:(SVGKSource *) svgSource;
- (void)shakeHead;
/** Apple's NSTimer class is an old OS X class that doesn't place nicely with ObjC blocks
(Apple needs to upgrade it). It sets up some nasty retain cycles and you can't set
the .userInfo after its started (a side-effect of the internal retain'ing Apple does)
By storing this reference here, we can give a block a reference in advance, while also
giving the block's output to the timer as .userinfo at creation time
*/
@property (nonatomic, retain) NSTimer* tickerLoadingApplesNSTimerSucks;
@end
@implementation DetailViewController
@synthesize scrollViewForSVG;
@synthesize toolbar, popoverController, contentView, detailItem;
@synthesize viewActivityIndicator;
@synthesize exportText = _exportText;
@synthesize layerExporter = _layerExporter;
@synthesize tapGestureRecognizer = _tapGestureRecognizer;
@synthesize exportLog = _exportLog;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)dealloc {
self.popoverController = nil;
self.toolbar = nil;
self.detailItem = nil;
self.sourceOfCurrentDocument = nil;
self.exportText = nil;
self.exportLog = nil;
self.layerExporter = nil;
self.scrollViewForSVG = nil;
self.contentView = nil;
self.viewActivityIndicator = nil;
[super dealloc];
}
-(void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:
[[[UIBarButtonItem alloc] initWithTitle:@"Debug" style:UIBarButtonItemStyleBordered target:self action:@selector(showHideBorder:)] autorelease],
[[[UIBarButtonItem alloc] initWithTitle:@"Animate" style:UIBarButtonItemStyleBordered target:self action:@selector(animate:)] autorelease],
nil];
}
CALayer* lastTappedLayer;
CGFloat lastTappedLayerOriginalBorderWidth;
CGColorRef lastTappedLayerOriginalBorderColor;
CATextLayer *textLayerForLastTappedLayer;
-(void) deselectTappedLayer
{
if( lastTappedLayer != nil )
{
#if ALLOW_SVGKFASTIMAGEVIEW_TO_DO_HIT_TESTING
if( [self.contentView isKindOfClass:[SVGKFastImageView class]])
{
[lastTappedLayer removeFromSuperlayer]; // nothing else needed
}
else
#endif
{
lastTappedLayer.borderWidth = lastTappedLayerOriginalBorderWidth;
lastTappedLayer.borderColor = lastTappedLayerOriginalBorderColor;
}
[textLayerForLastTappedLayer removeFromSuperlayer];
textLayerForLastTappedLayer = nil;
lastTappedLayer = nil;
}
}
-(NSString*) layerInfo:(CALayer*) l
{
return [NSString stringWithFormat:@"%@:%@", [l class], NSStringFromCGRect(l.frame)];
}
/**
Example of how to handle gaps on an SVG
*/
-(void) handleTapGesture:(UITapGestureRecognizer*) recognizer
{
CGPoint p = [recognizer locationInView:self.contentView];
#if ALLOW_SVGKFASTIMAGEVIEW_TO_DO_HIT_TESTING // look how much code this requires! It's insane! Use SVGKLayeredImageView instead if you need hit-testing!
SVGKImage* svgImage = nil; // ONLY used for the hacky code below that demonstrates how complex hit-testing is on an SVGKFastImageView
/**
WARNING:
Whenever you're using SVGKFastImageView, it "hides" the raw CALayers from you, and Apple
doesn't provide an easy way around this (we do it this way because there are missing methods
and bugs in Apple's UIScrollView, which SVGKFastImageView fixes).
So, you cannot do a hittest on "SVGKFastImageView.layer" - that will always return the root,
empty, full-size layer.
Instead, you have to hit-test the layer INSIDE the fast imageview.
--------
HOWEVER: YOU SHOULD NOT BE DOING THIS!
IF YOU NEED TO DO HIT-TESTING, USE SVGKLayeredImageView (as per the docs!)
THE EXAMPLE CODE HERE SHOWS YOU HOW YOU COULD, IN THEORY, DO HIT-TESTING WITH EITHER, BUT IT
IS HIGHLY RECOMMENDED NEVER TO USE HIT-TESTING WITH SVGKFastImageView!
*/
#endif
CALayer* layerForHitTesting;
#if ALLOW_SVGKFASTIMAGEVIEW_TO_DO_HIT_TESTING // look how much code this requires! It's insane! Use SVGKLayeredImageView instead if you need hit-testing!
if( [self.contentView isKindOfClass:[SVGKFastImageView class]])
{
layerForHitTesting = ((SVGKFastImageView*)self.contentView).image.CALayerTree;
svgImage = ((SVGKFastImageView*)self.contentView).image;
/**
ALSO, because SVGKFastImageView DOES NOT ALTER the underlying layers when it zooms
(the zoom is handled "fast" and done internally at 100% accuracy),
any zoom will be ignored for the hit-test - we have to MANUALLY apply the zoom
*/
CGSize scaleConvertImageToViewForHitTest = CGSizeMake( self.contentView.bounds.size.width / svgImage.size.width, self.contentView.bounds.size.height / svgImage.size.height ); // this is a copy/paste of the internal "SCALING" logic used in SVGKFastImageView
p = CGPointApplyAffineTransform( p, CGAffineTransformInvert( CGAffineTransformMakeScale( scaleConvertImageToViewForHitTest.width, scaleConvertImageToViewForHitTest.height)) ); // must do the OPPOSITE of the zoom (to convert the 'seeming' point to the 'actual' point
}
else
#endif
layerForHitTesting = self.contentView.layer;
CALayer* hitLayer = [layerForHitTesting hitTest:p];
if( hitLayer == lastTappedLayer )
[self deselectTappedLayer]; // do this both ways, but have to do it AFTER the if-test because it nil's one of the if-phrases!
else
{
[self deselectTappedLayer]; // do this both ways, but have to do it AFTER the if-test because it nil's one of the if-phrases!
#if ALLOW_SVGKFASTIMAGEVIEW_TO_DO_HIT_TESTING // look how much code this requires! It's insane! Use SVGKLayeredImageView instead if you need hit-testing!
self.title = @""; // reset it so that we can conditionally set it - but also ensuring this code is included in the #if
if( [self.contentView isKindOfClass:[SVGKFastImageView class]])
{
/** NEVER DO THIS - this is a proof-of-concept, but instead you should ALWAYS
use SVGKLayeredImageView if you want to do hit-testing!
*/
self.title = @"WARNING: don't use SVGKFastImageView for hit-testing";
/**
Because SVGKFastImageView "hides" the layers, any visual changes we make
will NOT be reflected on-screen.
Instead, we have to put a NEW layer over the top
*/
CALayer* absolutePositionedCloneLayer = [svgImage newCopyPositionedAbsoluteOfLayer:hitLayer];
lastTappedLayer = [[CALayer alloc] init];
lastTappedLayer.frame = absolutePositionedCloneLayer.frame;
[absolutePositionedCloneLayer release];
/**
ALSO, because SVGKFastImageView DOES NOT ALTER the underlying layers when it zooms
(the zoom is handled "fast" and done internally at 100% accuracy),
any zoom will be ignored for the new layer - we have to MANUALLY apply the zoom
*/
CGSize scaleConvertImageToView = CGSizeMake( self.contentView.bounds.size.width / svgImage.size.width, self.contentView.bounds.size.height / svgImage.size.height ); // this is a copy/paste of the internal "SCALING" logic used in SVGKFastImageView
lastTappedLayer.frame = CGRectApplyAffineTransform( lastTappedLayer.frame, CGAffineTransformMakeScale(scaleConvertImageToView.width, scaleConvertImageToView.height));
[self.contentView.layer addSublayer:lastTappedLayer];
}
else
#endif
lastTappedLayer = hitLayer;
if( lastTappedLayer != nil )
{
lastTappedLayerOriginalBorderColor = lastTappedLayer.borderColor;
lastTappedLayerOriginalBorderWidth = lastTappedLayer.borderWidth;
lastTappedLayer.borderColor = [UIColor greenColor].CGColor;
lastTappedLayer.borderWidth = 3.0;
#if SHOW_DEBUG_INFO_ON_EACH_TAPPED_LAYER
/** mtrubnikov's code for adding a text overlay showing exactly what you tapped
*/
NSString* textToDraw = [NSString stringWithFormat:@"%@ (%@): {%.1f, %.1f} {%.1f, %.1f}", hitLayer.name, [hitLayer class], lastTappedLayer.frame.origin.x, lastTappedLayer.frame.origin.y, lastTappedLayer.frame.size.width, lastTappedLayer.frame.size.height];
UIFont* fontToDraw = [UIFont fontWithName:@"Helvetica"
size:14.0f];
CGSize sizeOfTextRect = [textToDraw sizeWithFont:fontToDraw];
textLayerForLastTappedLayer = [[[CATextLayer alloc] init] autorelease];
[textLayerForLastTappedLayer setFont:@"Helvetica"];
[textLayerForLastTappedLayer setFontSize:14.0f];
[textLayerForLastTappedLayer setFrame:CGRectMake(0, 0, sizeOfTextRect.width, sizeOfTextRect.height)];
[textLayerForLastTappedLayer setString:textToDraw];
[textLayerForLastTappedLayer setAlignmentMode:kCAAlignmentLeft];
[textLayerForLastTappedLayer setForegroundColor:[UIColor redColor].CGColor];
[textLayerForLastTappedLayer setContentsScale:[[UIScreen mainScreen] scale]];
[textLayerForLastTappedLayer setShouldRasterize:NO];
[self.contentView.layer addSublayer:textLayerForLastTappedLayer];
/*
* mtrubnikov's code for adding a text overlay showing exactly what you tapped*/
#endif
}
}
}
#pragma mark - CRITICAL: this method makes Apple render SVGs in sharp focus
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)finalScale
{
/** NB: very important! The "finalScale" paramter to this method is SLIGHTLY DIFFERENT from the scale that Apple reports in the other delegate methods
This is very confusing, clearly it's bit of a hack - the other methods get called
at slightly the wrong time, and so their data is slightly wrong (out-by-one animation step).
ONLY the values passed as params to this method are correct!
*/
/**
Apple's implementation of zooming is EXTREMELY poorly designed; it's a hack onto a class
that was only designed to do panning (hence the name: uiSCROLLview)
So ... "zooming" via a UIScrollView is NOT integrated with UIView
rendering - in a UIView subclass, you CANNOT KNOW whether you have been "zoomed"
(i.e.: had your view contents ruined horribly by Apple's class)
The three lines that follow are - allegedly - Apple's preferred way of handling
the situation. Note that we DO NOT SET view.frame! According to official docs,
view.frame is UNDEFINED (this is very worrying, breaks a huge amount of UIKit-related code,
but that's how Apple has documented / implemented it!)
*/
view.transform = CGAffineTransformIdentity; // this alters view.frame! But *not* view.bounds
view.bounds = CGRectApplyAffineTransform( view.bounds, CGAffineTransformMakeScale(finalScale, finalScale));
[view setNeedsDisplay];
/**
Workaround for another bug in Apple's hacks for UIScrollView:
- when you reset the transform, as advised by Apple, you "break" Apple's memory of the scroll factor.
... because they "forgot" to store it anywhere (they read your view.transform as if it were a private
variable inside UIScrollView! This causes MANY bugs in applications :( )
*/
self.scrollViewForSVG.minimumZoomScale /= finalScale;
self.scrollViewForSVG.maximumZoomScale /= finalScale;
}
#pragma mark - rest of class
- (void)setDetailItem:(id)newDetailItem {
if (detailItem != newDetailItem) {
[self deselectTappedLayer]; // do this first because it DEPENDS UPON the type of self.contentView BEFORE the change in value
[detailItem release];
if( newDetailItem != nil )
{
detailItem = [newDetailItem retain];
// FIXME: re-write this class so that this method does NOT require self.view to exist
[self view]; // Apple's design to trigger the creation of view. Original design of THIS class is that it breaks if view isn't already existing
[self loadSVGFrom:newDetailItem];
}
}
if (self.popoverController) {
[self.popoverController dismissPopoverAnimated:YES];
}
}
-(void) willLoadNewResource
{
// update the view
self.subViewLoadingPopup.hidden = FALSE;
self.progressLoading.progress = 0;
[self.viewActivityIndicator startAnimating];
/** Move the gesture recognizer off the old view */
if( self.contentView != nil
&& self.tapGestureRecognizer != nil )
[self.contentView removeGestureRecognizer:self.tapGestureRecognizer];
[self.labelParseTime removeFromSuperview]; // we'll re-add to the new one
[self.contentView removeFromSuperview];
}
/**
If you want to emulate Apple's @2x naming system for UIImage, you can...
*/
-(void) preProcessImageFor2X:(ImageLoadingOptions*) options
{
#if ALLOW_2X_STYLE_SCALING_OF_SVGS_AS_AN_EXAMPLE
if( [options.localFileSource.filePath hasSuffix:@"@2x"])
{
SVGKSourceLocalFile* modifiedSource = [[options.localFileSource copy] autorelease];
modifiedSource.filePath = [modifiedSource.filePath substringToIndex:modifiedSource.filePath.length - @"@2x".length];
options.overrideImageRenderScale = 2.0;
options.requiresLayeredImageView = true;
options.localFileSource = modifiedSource;
}
#endif
}
/**
If you want to emulate Apple's @WxH naming system for UIImage, you can...
*/
-(void) preProcessImageForAt160x240:(ImageLoadingOptions*) options
{
if( [options.localFileSource.filePath hasSuffix:@"@160x240"]) // could be any 999x999 you want, up to you to implement!
{
SVGKSourceLocalFile* modifiedSource = [[options.localFileSource copy] autorelease];
modifiedSource.filePath = [modifiedSource.filePath substringToIndex:modifiedSource.filePath.length - @"@160x240".length];
options.overrideImageSize = CGSizeMake( 160, 240 );
options.localFileSource = modifiedSource;
}
}
/**
Sometimes you HAVE to use an SVGKLayeredImageView...
*/
-(void) preProcessImageCheckWorkaroundAppleBugInGradientImages:(ImageLoadingOptions*) options
{
if(
[options.localFileSource.filePath isEqualToString:@"Monkey"] // Monkey uses layer-animations, so REQUIRES the layered version of SVGKImageView
|| [options.localFileSource.filePath isEqualToString:@"RainbowWing"] // RainbowWing uses gradient-fills, so REQUIRES the layered version of SVGKImageView
|| [options.localFileSource.filePath isEqualToString:@"imagetag-layered"] // uses gradients for prettiness
|| [options.localFileSource.filePath isEqualToString:@"parent-clip"] // uses layer animations
)
{
/**
NB: special-case handling here -- this is included AS AN EXAMPLE so you can see the differences.
MONKEY.SVG -- CAAnimation of layers
-----
The problem: Apple's code doesn't allow us to support CoreAnimation *and* make image loading easy.
The solution: there are two versions of SVGKImageView - a "normal" one, and a "weaker one that supports CoreAnimation"
In this demo, we setup the Monkey.SVG to allow layer-based animation...
RAINBOWWING.SVG -- Gradient-fills of shapes
-----
The problem: Apple's renderInContext has a major bug where it ignores CALayer masks
The solution: there are two versions of SVGKImageView - a "normal" one, and a "weaker one that doesnt use renderInContext"
*/
options.requiresLayeredImageView = true;
}
}
- (void)loadSVGFrom:(SVGKSource *) svgSource
{
[self willLoadNewResource];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; // makes the animation appear
self.startParseTime = self.endParseTime = [NSDate date]; // reset them
SVGKImage *document = nil;
ImageLoadingOptions* loadingOptions = [[[ImageLoadingOptions alloc] initWithSource:svgSource] autorelease];
/** Detect URL vs file */
self.startParseTime = [NSDate date];
if( [svgSource isKindOfClass:[SVGKSourceURL class]])
{
@try
{
/**
This would work, but won't let us read any errors:
document = [SVGKImage imageWithContentsOfURL:[NSURL URLWithString:name]];
so, instead, we create an SVGKSource explicitly (as this demo app is taking
user-input, and we have no idea how valid it is!)
*/
document = [SVGKImage imageWithSource:svgSource];
[self internalLoadedResource:svgSource withOptions:loadingOptions parserOutput:(document==nil)?nil:document.parseErrorsAndWarnings createImageViewFromDocument:document];
}
@catch( NSException* e )
{
[[[[UIAlertView alloc] initWithTitle:@"SVG load failed" message:[NSString stringWithFormat:@"Error = %@", e] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
[self internalLoadedResource:svgSource withOptions:loadingOptions parserOutput:nil createImageViewFromDocument:nil];
}
}
else if( [svgSource isKindOfClass:[SVGKSourceLocalFile class]])
{
/** This demo shows different images being used in different ways.
Here we setup special conditions based on the filename etc:
*/
[self preProcessImageFor2X:loadingOptions];
[self preProcessImageForAt160x240:loadingOptions];
[self preProcessImageCheckWorkaroundAppleBugInGradientImages:loadingOptions];
/** Detect the magic name(s) for the nil-demos */
if( svgSource == nil )
{
/** This demonstrates / tests what happens if you create an SVGKLayeredImageView with a nil SVGKImage
*/
[self didLoadNewResourceCreatingImageView:[[[SVGKLayeredImageView alloc] initWithCoder:nil] autorelease]];
}
else
{
/**
the actual loading of the SVG file
*/
#if LOAD_SYNCHRONOUSLY
document = [SVGKImage imageNamed:[name stringByAppendingPathExtension:@"svg"]];
[self internalLoadedResource:name withOptions:loadingOptions createImageViewFromDocument:document];
#else
SVGKParser* parser = [SVGKImage imageWithSource:svgSource
onCompletion:^(SVGKImage *loadedImage, SVGKParseResult* parseResult)
{
[self.tickerLoadingApplesNSTimerSucks invalidate];
dispatch_async(dispatch_get_main_queue(), ^{
// must be on main queue since this affects the UIKit GUI!
[self internalLoadedResource:svgSource withOptions:loadingOptions parserOutput:parseResult createImageViewFromDocument:loadedImage];
});
}];
self.tickerLoadingApplesNSTimerSucks = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(tickLoadingSVG:) userInfo:parser repeats:TRUE];
#endif
}
}
else
{
[[[[UIAlertView alloc] initWithTitle:@"SVG load failed" message:[NSString stringWithFormat:@"Unknown kind of source. Should be a recognized SVGKSource subclass. Was actually : %@", [svgSource class]] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
[self internalLoadedResource:nil withOptions:loadingOptions parserOutput:nil createImageViewFromDocument:nil];
}
}
/** Updates the on-screen loading bar */
-(void) tickLoadingSVG:(NSTimer*) timer
{
SVGKParser* parser = (SVGKParser*) timer.userInfo;
dispatch_async(dispatch_get_main_queue(),
^{
// must be on main queue since this affects the UIKit GUI!
self.progressLoading.progress = parser.currentParseRun.parseProgressFractionApproximate;
});
}
/**
Creates an appopriate SVGKImageView to display the loaded SVGKImage, and triggers the post-processing
of on-screen displays
*/
-(void) internalLoadedResource:(SVGKSource*) source withOptions:(ImageLoadingOptions*) loadingOptions parserOutput:(SVGKParseResult*) parseResult createImageViewFromDocument:(SVGKImage*) document
{
self.endParseTime = [NSDate date];
SVGKImageView* newContentView = nil;
if( loadingOptions.overrideImageRenderScale != 1.0 )
document.scale = loadingOptions.overrideImageRenderScale;
if( document == nil )
{
if( parseResult == nil )
{
[[[[UIAlertView alloc] initWithTitle:@"SVG parse failed" message:@"Total failure. See console log" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
}
else
{
[[[[UIAlertView alloc] initWithTitle:@"SVG parse failed" message:[NSString stringWithFormat:@"Summary: %@",parseResult] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
}
newContentView = nil; // signals to the rest of this method: the load failed
}
else
{
if( document.parseErrorsAndWarnings.rootOfSVGTree != nil )
{
//NSLog(@"[%@] Freshly loaded document (name = %@) has size = %@", [self class], name, NSStringFromCGSize(document.size) );
/** NB: the SVG Spec says that the "correct" way to upscale or downscale an SVG is by changing the
SVG Viewport. SVGKit automagically does this for you if you ever set a value to image.scale */
if( ! CGSizeEqualToSize( CGSizeZero, loadingOptions.overrideImageSize ) )
document.size = loadingOptions.overrideImageSize; // preferred way to scale an SVG! (standards compliant!)
if( loadingOptions.requiresLayeredImageView )
{
newContentView = [[[SVGKLayeredImageView alloc] initWithSVGKImage:document] autorelease];
}
else
{
newContentView = [[[SVGKFastImageView alloc] initWithSVGKImage:document] autorelease];
NSLog(@"[%@] WARNING: workaround for Apple bugs: UIScrollView spams tiny changes to the transform to the content view; currently, we have NO WAY of efficiently measuring whether or not to re-draw the SVGKImageView. As a temporary solution, we are DISABLING the SVGKImageView's auto-redraw-at-higher-resolution code - in general, you do NOT want to do this", [self class]);
((SVGKFastImageView*)newContentView).disableAutoRedrawAtHighestResolution = TRUE;
}
}
if( parseResult.errorsFatal.count > 0 )
{
[[[[UIAlertView alloc] initWithTitle:@"SVG parse failed" message:[NSString stringWithFormat:@"%@",parseResult] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
// [[[[UIAlertView alloc] initWithTitle:@"SVG parse failed" message:[NSString stringWithFormat:@"%i fatal errors, %i warnings. First fatal = %@",[document.parseErrorsAndWarnings.errorsFatal count],[document.parseErrorsAndWarnings.errorsRecoverable count]+[document.parseErrorsAndWarnings.warnings count], ((NSError*)[document.parseErrorsAndWarnings.errorsFatal objectAtIndex:0]).localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show];
newContentView = nil; // signals to the rest of this method: the load failed
}
}
self.sourceOfCurrentDocument = source;
[self didLoadNewResourceCreatingImageView:newContentView];
}
/**
Reconfigures the view to display the newly-loaded image, and display meta info
about how long it took to parse, etc
*/
-(void) didLoadNewResourceCreatingImageView:(SVGKImageView*) newContentView
{
if( newContentView != nil )
{
/**
* NB: at this point we're guaranteed to have a "new" replacemtent ready for self.contentView
*/
/******* swap the new contentview in ************/
self.contentView = newContentView;
if( self.labelParseTime == nil )
{
self.labelParseTime = [[[UILabel alloc] init] autorelease];
self.labelParseTime.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.labelParseTime.backgroundColor = [UIColor colorWithWhite:1 alpha:0.5];
self.labelParseTime.textColor = [UIColor blackColor];
self.labelParseTime.text = @"(parsing)";
}
/** Workaround for Apple 10 years old bug in OS X that they ported to iOS :( */
self.labelParseTime.frame = CGRectMake( 0, 0, self.contentView.bounds.size.width, 20.0 );
[self.contentView addSubview:self.labelParseTime];
/** set the border for new item */
self.contentView.showBorder = FALSE;
/** Move the gesture recognizer onto the new one */
if( self.tapGestureRecognizer == nil )
{
self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
}
[self.contentView addGestureRecognizer:self.tapGestureRecognizer];
[self.scrollViewForSVG addSubview:self.contentView];
[self.scrollViewForSVG setContentSize: self.contentView.frame.size];
float screenToDocumentSizeRatio = self.scrollViewForSVG.frame.size.width / self.contentView.frame.size.width;
self.scrollViewForSVG.minimumZoomScale = MIN( 1, screenToDocumentSizeRatio );
self.scrollViewForSVG.maximumZoomScale = MAX( 1, screenToDocumentSizeRatio );
self.title = self.sourceOfCurrentDocument.keyForAppleDictionaries;
self.labelParseTime.text = [NSString stringWithFormat:@"%@ (parsed: %.2f secs, rendered: %.2f secs)", self.sourceOfCurrentDocument.keyForAppleDictionaries, [self.endParseTime timeIntervalSinceDate:self.startParseTime], self.contentView.timeIntervalForLastReRenderOfSVGFromMemory ];
/** Fast image view renders asynchronously, so we have to wait for a callback that its finished a render... */
[self.contentView addObserver:self forKeyPath:@"timeIntervalForLastReRenderOfSVGFromMemory" options:0 context:nil];
/**
EXAMPLE:
How to find particular nodes in the tree, after parsing.
In this case, we search for all SVG <g> tags, which usually mean grouped-objects in Inkscape etc:
NodeList* elementsUsingTagG = [document.DOMDocument getElementsByTagName:@"g"];
NSLog( @"[%@] checking for SVG standard set of elements with XML tag/node of <g>: %@", [self class], elementsUsingTagG.internalArray );
*/
}
[self.viewActivityIndicator stopAnimating];
self.subViewLoadingPopup.hidden = TRUE;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if( [keyPath isEqualToString:@"timeIntervalForLastReRenderOfSVGFromMemory" ] )
{
self.labelParseTime.text = [NSString stringWithFormat:@"%@ (parsed: %.2f secs, rendered: %.2f secs)", self.sourceOfCurrentDocument.keyForAppleDictionaries, [self.endParseTime timeIntervalSinceDate:self.startParseTime], self.contentView.timeIntervalForLastReRenderOfSVGFromMemory ];
[self.contentView removeObserver:self forKeyPath:@"timeIntervalForLastReRenderOfSVGFromMemory"];
}
}
- (IBAction)animate:(id)sender {
if ([self.sourceOfCurrentDocument.keyForAppleDictionaries isEqualToString:@"Monkey"]) {
[self shakeHead];
} else if ([self.sourceOfCurrentDocument.keyForAppleDictionaries isEqualToString:@"parent-clip"]) {
[self moveGreenSquare];
}
}
- (void)shakeHead {
CALayer *layer = [self.contentView.image layerWithIdentifier:@"head"];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.duration = 0.25f;
animation.autoreverses = YES;
animation.repeatCount = 100000;
animation.fromValue = [NSNumber numberWithFloat:0.1f];
animation.toValue = [NSNumber numberWithFloat:-0.1f];
[layer addAnimation:animation forKey:@"shakingHead"];
}
- (void)moveGreenSquare {
CALayer *layer = [self.contentView.image layerWithIdentifier:@"greensquare"];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.duration = 1;
animation.autoreverses = YES;
animation.repeatCount = 100000;
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 50)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(-50, -50)];
[layer addAnimation:animation forKey:@"moveGreenSquare"];
}
- (IBAction) showHideBorder:(id)sender
{
self.contentView.showBorder = ! self.contentView.showBorder;
/**
NB: normally, the following would NOT be needed - the SVGKImageView would automatically
detect it needs to be re-drawn.
But ... because we're doing zooming in this class, and Apple's zooming causes huge performance problems,
we disabled the auto-redraw in the loadResource: method above.
So, now, we have to manually tell the SVGKImageView to redraw
*/
[self.contentView setNeedsDisplay];
}
#pragma mark - Split view
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)argumentPopoverController
{
barButtonItem.title = NSLocalizedString(@"Master", @"Master");
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
self.popoverController = argumentPopoverController;
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
// Called when the view is shown again in the split view, invalidating the button and popover controller.
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
self.popoverController = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.contentView;
}
#pragma mark Export
- (IBAction)exportLayers:(id)sender {
if (_layerExporter) {
return;
}
_layerExporter = [[CALayerExporter alloc] initWithView:contentView];
_layerExporter.delegate = self;
UITextView* textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
UIViewController* textViewController = [[[UIViewController alloc] init] autorelease];
[textViewController setView:textView];
UIPopoverController* exportPopover = [[UIPopoverController alloc] initWithContentViewController:textViewController];
[exportPopover setDelegate:self];
[exportPopover presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
_exportText = textView;
_exportText.text = @"exporting...";
_exportLog = [[NSMutableString alloc] init];
[_layerExporter startExport];
}
- (void) layerExporter:(CALayerExporter*)exporter didParseLayer:(CALayer*)layer withStatement:(NSString*)statement
{
//NSLog(@"%@", statement);
[_exportLog appendString:statement];
[_exportLog appendString:@"\n"];
}
- (void)layerExporterDidFinish:(CALayerExporter *)exporter
{
_exportText.text = _exportLog;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)pc
{
[_exportText release];
_exportText = nil;
[_layerExporter release];
_layerExporter = nil;
[pc release];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
@end