ReaderContentView.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. //
  2. // ReaderContentView.m
  3. // Reader v2.8.7
  4. //
  5. // Created by Julius Oklamcak on 2011-07-01.
  6. // Copyright © 2011-2016 Julius Oklamcak. All rights reserved.
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights to
  11. // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  12. // of the Software, and to permit persons to whom the Software is furnished to
  13. // do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in all
  16. // copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  19. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  23. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  24. //
  25. #import "ReaderConstants.h"
  26. #import "ReaderContentView.h"
  27. #import "ReaderContentPage.h"
  28. #import "ReaderThumbCache.h"
  29. #import <QuartzCore/QuartzCore.h>
  30. @interface ReaderContentView () <UIScrollViewDelegate>
  31. @end
  32. @implementation ReaderContentView
  33. {
  34. UIView *theContainerView;
  35. UIUserInterfaceIdiom userInterfaceIdiom;
  36. ReaderContentPage *theContentPage;
  37. ReaderContentThumb *theThumbView;
  38. CGFloat realMaximumZoom;
  39. CGFloat tempMaximumZoom;
  40. BOOL zoomBounced;
  41. }
  42. #pragma mark - Constants
  43. #define ZOOM_FACTOR 2.0f
  44. #define ZOOM_MAXIMUM 16.0f
  45. #define PAGE_THUMB_SMALL 144
  46. #define PAGE_THUMB_LARGE 240
  47. static void *ReaderContentViewContext = &ReaderContentViewContext;
  48. static CGFloat g_BugFixWidthInset = 0.0f;
  49. #pragma mark - Properties
  50. @synthesize message;
  51. #pragma mark - ReaderContentView functions
  52. static inline CGFloat zoomScaleThatFits(CGSize target, CGSize source)
  53. {
  54. CGFloat w_scale = (target.width / (source.width + g_BugFixWidthInset));
  55. CGFloat h_scale = (target.height / source.height);
  56. return ((w_scale < h_scale) ? w_scale : h_scale);
  57. }
  58. #pragma mark - ReaderContentView class methods
  59. + (void)initialize
  60. {
  61. if (self == [ReaderContentView self]) // Do once - iOS 8.0 UIScrollView bug workaround
  62. {
  63. if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) // Not iPads
  64. {
  65. NSString *iosVersion = [UIDevice currentDevice].systemVersion; // iOS version as a string
  66. if ([@"8.0" compare:iosVersion options:NSNumericSearch] != NSOrderedDescending) // 8.0 and up
  67. {
  68. // if ([@"8.2" compare:iosVersion options:NSNumericSearch] == NSOrderedDescending) // Below 8.2
  69. // {
  70. g_BugFixWidthInset = 2.0f * [[UIScreen mainScreen] scale]; // Reduce width of content view
  71. // }
  72. }
  73. }
  74. }
  75. }
  76. #pragma mark - ReaderContentView instance methods
  77. - (void)updateMinimumMaximumZoom
  78. {
  79. CGFloat zoomScale = zoomScaleThatFits(self.bounds.size, theContentPage.bounds.size);
  80. self.minimumZoomScale = zoomScale; self.maximumZoomScale = (zoomScale * ZOOM_MAXIMUM);
  81. realMaximumZoom = self.maximumZoomScale; tempMaximumZoom = (realMaximumZoom * ZOOM_FACTOR);
  82. }
  83. - (void)centerScrollViewContent
  84. {
  85. CGFloat iw = 0.0f; CGFloat ih = 0.0f; // Content width and height insets
  86. CGSize boundsSize = self.bounds.size; CGSize contentSize = self.contentSize; // Sizes
  87. if (contentSize.width < boundsSize.width) iw = ((boundsSize.width - contentSize.width) * 0.5f);
  88. if (contentSize.height < boundsSize.height) ih = ((boundsSize.height - contentSize.height) * 0.5f);
  89. UIEdgeInsets insets = UIEdgeInsetsMake(ih, iw, ih, iw); // Create (possibly updated) content insets
  90. if (UIEdgeInsetsEqualToEdgeInsets(self.contentInset, insets) == false) self.contentInset = insets;
  91. }
  92. - (instancetype)initWithFrame:(CGRect)frame fileURL:(NSURL *)fileURL page:(NSUInteger)page password:(NSString *)phrase
  93. {
  94. if ((self = [super initWithFrame:frame]))
  95. {
  96. self.scrollsToTop = NO;
  97. self.delaysContentTouches = NO;
  98. self.showsVerticalScrollIndicator = NO;
  99. self.showsHorizontalScrollIndicator = NO;
  100. self.contentMode = UIViewContentModeRedraw;
  101. self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
  102. self.backgroundColor = [UIColor clearColor];
  103. self.autoresizesSubviews = NO;
  104. self.clipsToBounds = NO;
  105. self.delegate = self;
  106. userInterfaceIdiom = [UIDevice currentDevice].userInterfaceIdiom; // User interface idiom
  107. theContentPage = [[ReaderContentPage alloc] initWithURL:fileURL page:page password:phrase];
  108. if (theContentPage != nil) // Must have a valid and initialized content page
  109. {
  110. theContainerView = [[UIView alloc] initWithFrame:theContentPage.bounds];
  111. theContainerView.autoresizesSubviews = NO;
  112. theContainerView.userInteractionEnabled = NO;
  113. theContainerView.contentMode = UIViewContentModeRedraw;
  114. theContainerView.autoresizingMask = UIViewAutoresizingNone;
  115. theContainerView.backgroundColor = [UIColor whiteColor];
  116. #if (READER_SHOW_SHADOWS == TRUE) // Option
  117. theContainerView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
  118. theContainerView.layer.shadowRadius = 4.0f; theContainerView.layer.shadowOpacity = 1.0f;
  119. theContainerView.layer.shadowPath = [UIBezierPath bezierPathWithRect:theContainerView.bounds].CGPath;
  120. #endif // end of READER_SHOW_SHADOWS Option
  121. self.contentSize = theContentPage.bounds.size; [self centerScrollViewContent];
  122. #if (READER_ENABLE_PREVIEW == TRUE) // Option
  123. theThumbView = [[ReaderContentThumb alloc] initWithFrame:theContentPage.bounds]; // Page thumb view
  124. [theContainerView addSubview:theThumbView]; // Add the page thumb view to the container view
  125. #endif // end of READER_ENABLE_PREVIEW Option
  126. [theContainerView addSubview:theContentPage]; // Add the content page to the container view
  127. [self addSubview:theContainerView]; // Add the container view to the scroll view
  128. [self updateMinimumMaximumZoom]; // Update the minimum and maximum zoom scales
  129. self.zoomScale = self.minimumZoomScale; // Set the zoom scale to fit page content
  130. [self addObserver:self forKeyPath:@"frame" options:0 context:ReaderContentViewContext];
  131. }
  132. self.tag = page; // Tag the view with the page number
  133. }
  134. return self;
  135. }
  136. - (void)dealloc
  137. {
  138. [self removeObserver:self forKeyPath:@"frame" context:ReaderContentViewContext];
  139. }
  140. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  141. {
  142. if (context == ReaderContentViewContext) // Our context
  143. {
  144. if ((object == self) && [keyPath isEqualToString:@"frame"])
  145. {
  146. [self centerScrollViewContent]; // Center content
  147. CGFloat oldMinimumZoomScale = self.minimumZoomScale;
  148. [self updateMinimumMaximumZoom]; // Update zoom scale limits
  149. if (self.zoomScale == oldMinimumZoomScale) // Old minimum
  150. {
  151. self.zoomScale = self.minimumZoomScale;
  152. }
  153. else // Check against minimum zoom scale
  154. {
  155. if (self.zoomScale < self.minimumZoomScale)
  156. {
  157. self.zoomScale = self.minimumZoomScale;
  158. }
  159. else // Check against maximum zoom scale
  160. {
  161. if (self.zoomScale > self.maximumZoomScale)
  162. {
  163. self.zoomScale = self.maximumZoomScale;
  164. }
  165. }
  166. }
  167. }
  168. }
  169. }
  170. - (void)showPageThumb:(NSURL *)fileURL page:(NSInteger)page password:(NSString *)phrase guid:(NSString *)guid
  171. {
  172. #if (READER_ENABLE_PREVIEW == TRUE) // Option
  173. CGSize size = ((userInterfaceIdiom == UIUserInterfaceIdiomPad) ? CGSizeMake(PAGE_THUMB_LARGE, PAGE_THUMB_LARGE) : CGSizeMake(PAGE_THUMB_SMALL, PAGE_THUMB_SMALL));
  174. ReaderThumbRequest *request = [ReaderThumbRequest newForView:theThumbView fileURL:fileURL password:phrase guid:guid page:page size:size];
  175. UIImage *image = [[ReaderThumbCache sharedInstance] thumbRequest:request priority:YES]; // Request the page thumb
  176. if ([image isKindOfClass:[UIImage class]]) [theThumbView showImage:image]; // Show image from cache
  177. #endif // end of READER_ENABLE_PREVIEW Option
  178. }
  179. - (id)processSingleTap:(UITapGestureRecognizer *)recognizer
  180. {
  181. return [theContentPage processSingleTap:recognizer];
  182. }
  183. - (CGRect)zoomRectForScale:(CGFloat)scale withCenter:(CGPoint)center
  184. {
  185. CGRect zoomRect; // Centered zoom rect
  186. zoomRect.size.width = (self.bounds.size.width / scale);
  187. zoomRect.size.height = (self.bounds.size.height / scale);
  188. zoomRect.origin.x = (center.x - (zoomRect.size.width * 0.5f));
  189. zoomRect.origin.y = (center.y - (zoomRect.size.height * 0.5f));
  190. return zoomRect;
  191. }
  192. - (void)zoomIncrement:(UITapGestureRecognizer *)recognizer
  193. {
  194. CGFloat zoomScale = self.zoomScale; // Current zoom
  195. CGPoint point = [recognizer locationInView:theContentPage];
  196. if (zoomScale < self.maximumZoomScale) // Zoom in
  197. {
  198. zoomScale *= ZOOM_FACTOR; // Zoom in by zoom factor amount
  199. if (zoomScale > self.maximumZoomScale) zoomScale = self.maximumZoomScale;
  200. CGRect zoomRect = [self zoomRectForScale:zoomScale withCenter:point];
  201. [self zoomToRect:zoomRect animated:YES];
  202. }
  203. else // Handle fully zoomed in
  204. {
  205. if (zoomBounced == NO) // Zoom bounce
  206. {
  207. self.maximumZoomScale = tempMaximumZoom;
  208. [self setZoomScale:tempMaximumZoom animated:YES];
  209. }
  210. else // Zoom all the way out
  211. {
  212. zoomScale = self.minimumZoomScale;
  213. [self setZoomScale:zoomScale animated:YES];
  214. }
  215. }
  216. }
  217. - (void)zoomDecrement:(UITapGestureRecognizer *)recognizer
  218. {
  219. CGFloat zoomScale = self.zoomScale; // Current zoom
  220. CGPoint point = [recognizer locationInView:theContentPage];
  221. if (zoomScale > self.minimumZoomScale) // Zoom out
  222. {
  223. zoomScale /= ZOOM_FACTOR; // Zoom out by zoom factor amount
  224. if (zoomScale < self.minimumZoomScale) zoomScale = self.minimumZoomScale;
  225. CGRect zoomRect = [self zoomRectForScale:zoomScale withCenter:point];
  226. [self zoomToRect:zoomRect animated:YES];
  227. }
  228. else // Handle fully zoomed out
  229. {
  230. zoomScale = self.maximumZoomScale; // Full zoom in
  231. CGRect zoomRect = [self zoomRectForScale:zoomScale withCenter:point];
  232. [self zoomToRect:zoomRect animated:YES];
  233. }
  234. }
  235. - (void)zoomResetAnimated:(BOOL)animated
  236. {
  237. if (self.zoomScale > self.minimumZoomScale) // Reset zoom
  238. {
  239. if (animated) [self setZoomScale:self.minimumZoomScale animated:YES]; else self.zoomScale = self.minimumZoomScale; zoomBounced = NO;
  240. }
  241. }
  242. #pragma mark - UIScrollViewDelegate methods
  243. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
  244. {
  245. return theContainerView;
  246. }
  247. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
  248. {
  249. if (self.zoomScale > realMaximumZoom) // Bounce back to real maximum zoom scale
  250. {
  251. [self setZoomScale:realMaximumZoom animated:YES]; self.maximumZoomScale = realMaximumZoom; zoomBounced = YES;
  252. }
  253. else // Normal scroll view did end zooming
  254. {
  255. if (self.zoomScale < realMaximumZoom) zoomBounced = NO;
  256. }
  257. }
  258. - (void)scrollViewDidZoom:(UIScrollView *)scrollView
  259. {
  260. [self centerScrollViewContent]; // Center content
  261. }
  262. #pragma mark - UIResponder instance methods
  263. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  264. {
  265. [super touchesBegan:touches withEvent:event]; // Message superclass
  266. [message contentView:self touchesBegan:touches]; // Message delegate
  267. }
  268. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
  269. {
  270. [super touchesCancelled:touches withEvent:event]; // Message superclass
  271. }
  272. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  273. {
  274. [super touchesEnded:touches withEvent:event]; // Message superclass
  275. }
  276. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
  277. {
  278. [super touchesMoved:touches withEvent:event]; // Message superclass
  279. }
  280. @end
  281. #pragma mark -
  282. //
  283. // ReaderContentThumb class implementation
  284. //
  285. @implementation ReaderContentThumb
  286. #pragma mark - ReaderContentThumb instance methods
  287. - (instancetype)initWithFrame:(CGRect)frame
  288. {
  289. if ((self = [super initWithFrame:frame])) // Superclass init
  290. {
  291. imageView.contentMode = UIViewContentModeScaleAspectFill;
  292. imageView.clipsToBounds = YES; // Needed for aspect fill
  293. }
  294. return self;
  295. }
  296. @end