PhotoStackView.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //
  2. // PhotoStackView.m
  3. //
  4. // Created by Tom Longo on 16/08/12.
  5. // - Twitter: @tomlongo
  6. // - GitHub: github.com/tomlongo
  7. //
  8. #import <QuartzCore/QuartzCore.h>
  9. #import "PhotoStackView.h"
  10. static BOOL const BorderVisibilityDefault = YES;
  11. static CGFloat const BorderWidthDefault = 5.0f;
  12. static CGFloat const PhotoRotationOffsetDefault = 4.0f;
  13. @interface PhotoStackView()
  14. @property (nonatomic, strong) NSArray *photoViews;
  15. @end
  16. @implementation PhotoStackView
  17. @synthesize borderImage = _borderImage;
  18. @synthesize borderWidth = _borderWidth;
  19. #pragma mark -
  20. #pragma mark Getters and Setters
  21. -(void)setDataSource:(id<PhotoStackViewDataSource>)dataSource {
  22. if(dataSource != _dataSource) {
  23. _dataSource = dataSource;
  24. [self reloadData];
  25. }
  26. }
  27. -(void)setPhotoViews:(NSArray *)photoViews {
  28. // Remove current photo views, ready to be replaced with the fresh batch
  29. for(UIView *view in self.photoViews) {
  30. [view removeFromSuperview];
  31. }
  32. for (UIView *view in photoViews) {
  33. // If there is already a view at this index position, use that photo's transform
  34. // rather than setting a new random rotation (this is to stop the stack from shifting
  35. // when new photos are added after the fact)
  36. if([photoViews indexOfObject:view] < [_photoViews count]) {
  37. UIView *existingViewAtIndex = [_photoViews objectAtIndex:[photoViews indexOfObject:view]];
  38. view.transform = existingViewAtIndex.transform;
  39. } else {
  40. [self makeCrooked:view animated:NO];
  41. }
  42. [self insertSubview:view atIndex:0];
  43. }
  44. _photoViews = photoViews;
  45. }
  46. -(UIImage *)borderImage {
  47. if(!_borderImage) {
  48. _borderImage = [UIImage imageNamed:@"PhotoBorder"];
  49. }
  50. return _borderImage;
  51. }
  52. -(void)setBorderImage:(UIImage *)borderImage {
  53. if(borderImage != _borderImage) {
  54. _borderImage = borderImage;
  55. [self reloadData];
  56. }
  57. }
  58. -(void)setShowBorder:(BOOL)showBorder {
  59. if(showBorder != _showBorder) {
  60. _showBorder = showBorder;
  61. [self reloadData];
  62. }
  63. }
  64. -(CGFloat)borderWidth {
  65. return (self.showBorder) ? _borderWidth : 0;
  66. }
  67. -(void)setBorderWidth:(CGFloat)borderWidth {
  68. if(borderWidth != _borderWidth) {
  69. _borderWidth = borderWidth;
  70. [self reloadData];
  71. }
  72. }
  73. -(void)setRotationOffset:(CGFloat)rotationOffset {
  74. if(rotationOffset != _rotationOffset) {
  75. _rotationOffset = rotationOffset;
  76. [self reloadData];
  77. }
  78. }
  79. -(UIColor *)highlightColor {
  80. if(!_highlightColor) {
  81. _highlightColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.15];
  82. }
  83. return _highlightColor;
  84. }
  85. -(void)setHighlighted:(BOOL)highlighted {
  86. [super setHighlighted:highlighted];
  87. // !!!!!!!!! BUG ATTENTION!!!!!!!!!!
  88. return;
  89. // UIImageView *topPhoto = [[self topPhoto].subviews lastObject];
  90. UIWebView *topPhoto = [[self topPhoto].subviews lastObject];
  91. if(highlighted) {
  92. UIView *view = [[UIView alloc] initWithFrame:topPhoto.bounds];
  93. view.backgroundColor = self.highlightColor;
  94. [topPhoto addSubview:view];
  95. [topPhoto bringSubviewToFront:view];
  96. } else {
  97. [[topPhoto.subviews lastObject] removeFromSuperview];
  98. }
  99. }
  100. #pragma mark -
  101. #pragma mark Animation
  102. -(void)returnToCenter:(UIView *)photo {
  103. [UIView animateWithDuration:0.2
  104. animations:^{
  105. photo.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  106. }];
  107. }
  108. -(void)flickAway:(UIView *)photo withVelocity:(CGPoint)velocity {
  109. if ([self.delegate respondsToSelector:@selector(photoStackView:willFlickAwayPhotoFromIndex:toIndex:)]) {
  110. NSUInteger fromIndex = [self indexOfTopPhoto];
  111. NSUInteger toIndex = [self indexOfTopPhoto]+1;
  112. NSUInteger numberOfPhotos = [self.dataSource numberOfPhotosInPhotoStackView:self];
  113. if (toIndex >= numberOfPhotos) {
  114. toIndex = 0;
  115. }
  116. [self.delegate photoStackView:self willFlickAwayPhotoFromIndex:fromIndex toIndex:toIndex];
  117. }
  118. CGFloat width = CGRectGetWidth(self.bounds);
  119. CGFloat xPos = (velocity.x < 0) ? CGRectGetMidX(self.bounds)-width : CGRectGetMidY(self.bounds)+width;
  120. [UIView animateWithDuration:0.1
  121. animations:^{
  122. photo.center = CGPointMake(xPos, CGRectGetMidY(self.bounds));
  123. }
  124. completion:^(BOOL finished){
  125. [self makeCrooked:photo animated:YES];
  126. [self sendSubviewToBack:photo];
  127. [self makeStraight:[self topPhoto] animated:YES];
  128. [self returnToCenter:photo];
  129. if ([self.delegate respondsToSelector:@selector(photoStackView:didRevealPhotoAtIndex:)]) {
  130. [self.delegate photoStackView:self didRevealPhotoAtIndex:[self indexOfTopPhoto]];
  131. }
  132. }];
  133. }
  134. -(void)rotatePhoto:(UIView *)photo degrees:(NSInteger)degrees animated:(BOOL)animated {
  135. CGFloat radians = M_PI * degrees / 180.0;
  136. CGAffineTransform transform = CGAffineTransformMakeRotation(radians);
  137. if(animated) {
  138. [UIView animateWithDuration:0.2
  139. animations:^{
  140. photo.transform = transform;
  141. }];
  142. } else {
  143. photo.transform = transform;
  144. }
  145. }
  146. -(void)makeCrooked:(UIView *)photo animated:(BOOL)animated {
  147. NSInteger min = -(self.rotationOffset);
  148. NSInteger max = self.rotationOffset;
  149. NSInteger degrees = (arc4random_uniform(max-min+1)) + min;
  150. [self rotatePhoto:photo degrees:degrees animated:animated];
  151. }
  152. -(void)makeStraight:(UIView *)photo animated:(BOOL)animated {
  153. [self rotatePhoto:photo degrees:0 animated:animated];
  154. }
  155. #pragma mark -
  156. #pragma mark Gesture Handlers
  157. -(void)photoPanned:(UIPanGestureRecognizer *)gesture {
  158. if([self.dataSource numberOfPhotosInPhotoStackView:self]<=1)
  159. return;
  160. UIView *topPhoto = [self topPhoto];
  161. CGPoint velocity = [gesture velocityInView:self];
  162. CGPoint translation = [gesture translationInView:self];
  163. if(gesture.state == UIGestureRecognizerStateBegan) {
  164. [self sendActionsForControlEvents:UIControlEventTouchCancel];
  165. if ([self.delegate respondsToSelector:@selector(photoStackView:willStartMovingPhotoAtIndex:)]) {
  166. [self.delegate photoStackView:self willStartMovingPhotoAtIndex:[self indexOfTopPhoto]];
  167. }
  168. }
  169. if(gesture.state == UIGestureRecognizerStateChanged) {
  170. CGFloat xPos = topPhoto.center.x + translation.x;
  171. CGFloat yPos = topPhoto.center.y + translation.y;
  172. topPhoto.center = CGPointMake(xPos, yPos);
  173. [gesture setTranslation:CGPointMake(0, 0) inView:self];
  174. } else if(gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {
  175. if(abs(velocity.x) > 200) {
  176. [self flickAway:topPhoto withVelocity:velocity];
  177. } else {
  178. [self returnToCenter:topPhoto];
  179. }
  180. }
  181. }
  182. -(void)photoTapped:(UITapGestureRecognizer *)gesture {
  183. [self sendActionsForControlEvents:UIControlEventTouchUpInside];
  184. if ([self.delegate respondsToSelector:@selector(photoStackView:didSelectPhotoAtIndex:)]) {
  185. [self.delegate photoStackView:self didSelectPhotoAtIndex:[self indexOfTopPhoto]];
  186. }
  187. }
  188. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  189. [super touchesBegan:touches withEvent:event];
  190. if ([self.delegate respondsToSelector:@selector(photoStackView:didSelectPhotoAtIndex:)]) {
  191. // No need to highlight the photo if delegate does not implement a
  192. // selection handler (ie. nothing happens when they tap it)
  193. [self sendActionsForControlEvents:UIControlStateHighlighted];
  194. }
  195. }
  196. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  197. [super touchesMoved:touches withEvent:event];
  198. [self sendActionsForControlEvents:UIControlEventTouchDragInside];
  199. }
  200. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  201. [super touchesEnded:touches withEvent:event];
  202. [self sendActionsForControlEvents:UIControlEventTouchCancel];
  203. }
  204. #pragma mark -
  205. #pragma mark Other Methods
  206. -(void)flipToNextPhoto{
  207. [self flickAway:[self topPhoto] withVelocity:CGPointMake(-400, 0)];
  208. }
  209. -(void)goToPhotoAtIndex:(NSUInteger)index {
  210. for (UIView *view in self.photoViews) {
  211. if([self.photoViews indexOfObject:view] < index) {
  212. [self sendSubviewToBack:view];
  213. }
  214. }
  215. [self makeStraight:[self topPhoto] animated:NO];
  216. }
  217. -(void)hidePhotoAtIndex:(NSUInteger)index {
  218. if(index < [self.photoViews count]) {
  219. [[self.photoViews objectAtIndex:index] removeFromSuperview];
  220. }
  221. }
  222. -(NSUInteger)indexOfTopPhoto {
  223. return (self.photoViews) ? [self.photoViews indexOfObject:[self topPhoto]] : 0;
  224. }
  225. -(UIView *)topPhoto {
  226. if(self.subviews.count==0)
  227. return nil;
  228. return [self.subviews objectAtIndex:[self.subviews count]-1];
  229. }
  230. -(void)sendActionsForControlEvents:(UIControlEvents)controlEvents {
  231. [super sendActionsForControlEvents:controlEvents];
  232. self.highlighted = (controlEvents == UIControlStateHighlighted) ? YES : NO;
  233. }
  234. #pragma mark -
  235. #pragma mark Setup
  236. -(void)reloadData {
  237. dispatch_async(dispatch_get_main_queue(), ^{
  238. if (!self.dataSource) {
  239. //exit if data source has not been set up yet
  240. self.photoViews = nil;
  241. return;
  242. }
  243. NSInteger numberOfPhotos = [self.dataSource numberOfPhotosInPhotoStackView:self];
  244. NSInteger topPhotoIndex = [self indexOfTopPhoto]; // Keeping track of current photo's top index so that it remains on top if new photos are added
  245. if(numberOfPhotos >= 0) {
  246. NSMutableArray *photoViewsMutable = [[NSMutableArray alloc] initWithCapacity:numberOfPhotos];
  247. UIImage *borderImage = [self.borderImage resizableImageWithCapInsets:UIEdgeInsetsMake(self.borderWidth, self.borderWidth, self.borderWidth, self.borderWidth)];
  248. for (NSUInteger index = 0; index < numberOfPhotos; index++) {
  249. // UIImage *image = [self.dataSource photoStackView:self photoForIndex:index];
  250. NSString* html = [self.dataSource photoStackView:self contentForIndex:index];
  251. // CGSize imageSize = image.size;
  252. // if([self.dataSource respondsToSelector:@selector(photoStackView:photoSizeForIndex:)]){
  253. // imageSize = [self.dataSource photoStackView:self photoSizeForIndex:index];
  254. // }
  255. // UIImageView *photoImageView = [[UIImageView alloc] initWithFrame:(CGRect){CGPointZero, self.frame.size}];
  256. // photoImageView.image = image;
  257. UIWebView* contentView = [[UIWebView alloc] initWithFrame:(CGRect){CGPointZero, self.frame.size}];
  258. contentView.scrollView.scrollEnabled = false;
  259. // NSString *html = [self htmlForJPGImage:image template:nil];
  260. //
  261. // NSString *contentImg = [NSString stringWithFormat:@"%@", stringImage];
  262. // NSString *content =[NSString stringWithFormat:
  263. // @"<html>"
  264. // "<style type=\"text/css\">"
  265. // "<!--"
  266. // "body{font-size:40pt;line-height:60pt;}"
  267. // "-->"
  268. // "</style>"
  269. // "<body>"
  270. // "%@"
  271. // "</body>"
  272. // "</html>"
  273. // , contentImg];
  274. //让self.contentWebView加载content
  275. [contentView loadHTMLString:html baseURL:nil];
  276. UIView *view = [[UIView alloc] initWithFrame:contentView.frame];
  277. view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
  278. view.layer.shouldRasterize = YES; // rasterize the view for faster drawing and smooth edges
  279. if (self.showBorder) {
  280. // Add the background image
  281. if (borderImage) {
  282. // If there is a border image, we need to add a background image view, and add some padding around the photo for the border
  283. CGRect photoFrame = contentView.frame;
  284. photoFrame.origin = CGPointMake(self.borderWidth, self.borderWidth);
  285. contentView.frame = photoFrame;
  286. view.frame = CGRectMake(0, 0, contentView.frame.size.width+(self.borderWidth*2), contentView.frame.size.height+(self.borderWidth*2));
  287. UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:view.frame];
  288. backgroundImageView.image = borderImage;
  289. [view addSubview:backgroundImageView];
  290. } else {
  291. // if there is no boarder image draw one with the CALayer
  292. view.layer.borderWidth = self.borderWidth;
  293. view.layer.borderColor = [[UIColor whiteColor] CGColor];
  294. view.layer.shadowOffset = CGSizeMake(0, 0);
  295. view.layer.shadowOpacity = 0.5;
  296. }
  297. }
  298. [view addSubview:contentView];
  299. view.tag = index;
  300. view.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  301. [photoViewsMutable addObject:view];
  302. }
  303. // Photo views are added to subview in the photoView setter
  304. self.photoViews = photoViewsMutable;
  305. //photoViewsMutable = nil;
  306. [self goToPhotoAtIndex:topPhotoIndex];
  307. }
  308. });
  309. }
  310. -(void)setup {
  311. //Defaults
  312. self.showBorder = BorderVisibilityDefault;
  313. self.borderWidth = BorderWidthDefault;
  314. self.rotationOffset = PhotoRotationOffsetDefault;
  315. // Add Pan Gesture
  316. UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(photoPanned:)];
  317. [panGesture setMaximumNumberOfTouches:1];
  318. panGesture.delegate = self;
  319. [self addGestureRecognizer:panGesture];
  320. // Add Tap Gesture
  321. UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(photoTapped:)];
  322. [tapGesture setNumberOfTapsRequired:1];
  323. tapGesture.delegate = self;
  324. [self addGestureRecognizer:tapGesture];
  325. [self reloadData];
  326. }
  327. -(id)initWithCoder:(NSCoder *)aDecoder {
  328. if ((self = [super initWithCoder:aDecoder])) {
  329. [self setup];
  330. }
  331. return self;
  332. }
  333. -(id)initWithFrame:(CGRect)frame {
  334. if ((self = [super initWithFrame:frame])) {
  335. [self setup];
  336. }
  337. return self;
  338. }
  339. -(void)dealloc {
  340. [self setPhotoViews:nil];
  341. [self setBorderImage:nil];
  342. [self setHighlightColor:nil];
  343. }
  344. @end