// // TAPMediaDetailViewController.m // TapTalk // // Created by Dominic Vedericho on 29/1/19. // #import "TAPMediaDetailViewController.h" #import "TAPMediaDetailPreviewViewController.h" #import "TAPMediaDetailView.h" #import "TAPMediaDetailPreviewView.h" //#import "AppDelegate.h" @interface TAPMediaDetailViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate, TAPMediaDetailViewDelegate, TAPMediaDetailPreviewViewControllerDelegate, UIScrollViewDelegate> @property (strong, nonatomic) UIImage *thumbnailImage; @property (nonatomic) CGRect thumbnailFrame; @property (nonatomic) CGPoint startPanPoint; @property (nonatomic) CGPoint lastPanPoint; @property (nonatomic) CGFloat lastXGap; @property (nonatomic) CGFloat lastYGap; @property (nonatomic) BOOL isHeaderFooterViewShown; @property (nonatomic) BOOL isActiveIndexSet; @property (nonatomic) BOOL isWillTransitionCalled; @property (nonatomic) NSInteger currentActiveIndex; @property (strong, nonatomic) NSMutableDictionary *viewControllerDictionary; @property (weak, nonatomic) TAPMediaDetailPreviewViewController *currentViewController; - (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer; - (void)handleTap:(UITapGestureRecognizer *)tapGestureRecognizer; - (void)handleDoubleTap:(UITapGestureRecognizer *)tapGestureRecognizer; - (void)dismissSelf; - (void)showFinishSavingImageState; - (void)removeSaveImageLoadingView; @end @implementation TAPMediaDetailViewController #pragma mark - Lifecycle - (id)init { self = [super init]; if(self) { _contentMode = UIViewContentModeScaleAspectFill; } return self; } - (void)loadView { [super loadView]; _viewControllerDictionary = [[NSMutableDictionary alloc] init]; self.view.backgroundColor = [UIColor clearColor]; _mediaDetailView = [[TAPMediaDetailView alloc] initWithFrame:[TAPBaseView frameWithoutNavigationBar]]; self.mediaDetailView.contentMode = self.contentMode; self.mediaDetailView.delegate = self; if (self.mediaDetailViewControllerType == TAPMediaDetailViewControllerTypeImage) { [self.mediaDetailView setMediaDetailViewType:TAPMediaDetailViewTypeImage]; } else if (self.mediaDetailViewControllerType == TAPMediaDetailViewControllerTypeVideo) { [self.mediaDetailView setMediaDetailViewType:TAPMediaDetailViewTypeVideo]; } [self.view addSubview:self.mediaDetailView]; _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.mediaDetailView.pageViewController.delegate = self; self.mediaDetailView.pageViewController.dataSource = self; [self addChildViewController:self.mediaDetailView.pageViewController]; [self.mediaDetailView.pageViewController didMoveToParentViewController:self]; [self.mediaDetailView.pageViewController.view addGestureRecognizer:self.panGestureRecognizer]; for (UIView *view in self.mediaDetailView.pageViewController.view.subviews) { if ([view isKindOfClass:[UIScrollView class]]) { [(UIScrollView *)view setDelegate:self]; } } _isActiveIndexSet = NO; if(self.isActiveIndexSet) { [self setActiveIndex:self.currentActiveIndex]; _isActiveIndexSet = NO; } else { TAPMediaDetailPreviewViewController *initialViewController = [self viewControllerAtIndex:0]; _currentViewController = initialViewController; initialViewController.view.backgroundColor = [UIColor clearColor]; NSArray *viewControllers = [NSArray arrayWithObject:initialViewController]; [self.mediaDetailView.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; } //Remove swipe to back gesture if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } //Show header footer info view _isHeaderFooterViewShown = YES; [self.mediaDetailView showHeaderAndCaptionView:self.isHeaderFooterViewShown animated:NO]; [self.mediaDetailView setMediaDetailInfoWithMessage:self.message]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ #pragma mark - UIPageViewController #pragma mark Data Source - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { NSInteger index = [(TAPMediaDetailPreviewViewController *)viewController index]; if (index == 0) { return nil; } index--; return [self viewControllerAtIndex:index]; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { NSInteger index = [(TAPMediaDetailPreviewViewController *)viewController index]; index++; if (index == [self.imageArray count]) { return nil; } return [self viewControllerAtIndex:index]; } #pragma mark Delegate - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers { _isWillTransitionCalled = YES; TAPMediaDetailPreviewViewController *pendingViewController = (TAPMediaDetailPreviewViewController *)[pendingViewControllers objectAtIndex:0]; NSInteger index = pendingViewController.index; _currentActiveIndex = index; _currentViewController = pendingViewController; if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerWillChangeToPage:)]) { [self.delegate mediaDetailViewControllerWillChangeToPage:index]; } } - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed { if(!completed) { TAPMediaDetailPreviewViewController *previousViewController = (TAPMediaDetailPreviewViewController *)[previousViewControllers objectAtIndex:0]; NSInteger index = previousViewController.index; _currentActiveIndex = index; _currentViewController = previousViewController; if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerWillChangeToPage:)]) { [self.delegate mediaDetailViewControllerWillChangeToPage:index]; } } _isWillTransitionCalled = NO; } #pragma mark - Delegate #pragma mark MediaDetailView - (void)mediaDetailViewDidFinishOpeningAnimation { if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerDidFinishOpeningAnimation)]) { [self.delegate mediaDetailViewControllerDidFinishOpeningAnimation]; } } - (void)mediaDetailViewDidFinishClosingAnimation { if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerDidFinishClosingAnimation)]) { [self.delegate mediaDetailViewControllerDidFinishClosingAnimation]; } [self.view removeFromSuperview]; [self removeFromParentViewController]; } - (void)mediaDetailViewWillStartOpeningAnimation { if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerWillStartOpeningAnimation)]) { [self.delegate mediaDetailViewControllerWillStartOpeningAnimation]; } } - (void)mediaDetailViewWillStartClosingAnimation { if([self.delegate respondsToSelector:@selector(mediaDetailViewControllerWillStartClosingAnimation)]) { [self.delegate mediaDetailViewControllerWillStartClosingAnimation]; } } - (void)mediaDetailViewDidTappedSaveImageButton { [self.mediaDetailView setSaveLoadingAsFinishedState:NO]; [self.mediaDetailView showSaveLoadingView:YES]; UIImage *currentImage = [self.imageArray firstObject]; UIImageWriteToSavedPhotosAlbum(currentImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); } - (void)mediaDetailViewDidTappedSaveVideoButton { } - (void)mediaDetailViewDidTappedBackButton { [self.mediaDetailView showHeaderAndCaptionView:NO animated:YES]; [self dismissSelf]; } #pragma mark MediaDetailPreviewViewController - (void)mediaDetailPreviewViewControllerDidHandleSingleTap { _isHeaderFooterViewShown = !self.isHeaderFooterViewShown; [self.mediaDetailView showHeaderAndCaptionView:self.isHeaderFooterViewShown animated:YES]; } - (void)mediaDetailPreviewViewControllerDidHandleDoubleTap { } #pragma mark UIScrollView - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if(!self.isWillTransitionCalled) { return; } CGFloat movementPercentage = (scrollView.contentOffset.x - CGRectGetWidth(self.mediaDetailView.pageViewController.view.frame))/CGRectGetWidth(self.mediaDetailView.pageViewController.view.frame); if(movementPercentage > 0.0f) { //Move to the left TAPMediaDetailPreviewViewController *currentViewController = [self.viewControllerDictionary objectForKey:[NSNumber numberWithInteger:self.currentActiveIndex - 1]]; CGFloat maxGap = (CGRectGetWidth(self.mediaDetailView.pageViewController.view.frame) - CGRectGetWidth(currentViewController.mediaDetailPreviewView.frame)) * 4.0f; CGFloat currentViewControllerX = fabs(movementPercentage) * maxGap; TAPMediaDetailPreviewViewController *nextViewController = [self.viewControllerDictionary objectForKey:[NSNumber numberWithInteger:self.currentActiveIndex]]; CGFloat nextViewControllerX = -(maxGap - currentViewControllerX); nextViewController.mediaDetailPreviewView.frame = CGRectMake(nextViewControllerX, CGRectGetMinY(nextViewController.mediaDetailPreviewView.frame), CGRectGetWidth(nextViewController.mediaDetailPreviewView.frame), CGRectGetHeight(nextViewController.mediaDetailPreviewView.frame)); currentViewController.mediaDetailPreviewView.frame = CGRectMake(currentViewControllerX, CGRectGetMinY(currentViewController.mediaDetailPreviewView.frame), CGRectGetWidth(currentViewController.mediaDetailPreviewView.frame), CGRectGetHeight(currentViewController.mediaDetailPreviewView.frame)); } else if(movementPercentage < 0.0f) { //Move to the right TAPMediaDetailPreviewViewController *currentViewController = [self.viewControllerDictionary objectForKey:[NSNumber numberWithInteger:self.currentActiveIndex + 1]]; CGFloat maxGap = (CGRectGetWidth(self.mediaDetailView.pageViewController.view.frame) - CGRectGetWidth(currentViewController.mediaDetailPreviewView.frame)) * 2.0f; CGFloat currentViewControllerX = -(fabs(movementPercentage) * maxGap); TAPMediaDetailPreviewViewController *nextViewController = [self.viewControllerDictionary objectForKey:[NSNumber numberWithInteger:self.currentActiveIndex]]; CGFloat nextViewControllerX = maxGap + currentViewControllerX; nextViewController.mediaDetailPreviewView.frame = CGRectMake(nextViewControllerX, CGRectGetMinY(nextViewController.mediaDetailPreviewView.frame), CGRectGetWidth(nextViewController.mediaDetailPreviewView.frame), CGRectGetHeight(nextViewController.mediaDetailPreviewView.frame)); currentViewController.mediaDetailPreviewView.frame = CGRectMake(currentViewControllerX, CGRectGetMinY(currentViewController.mediaDetailPreviewView.frame), CGRectGetWidth(currentViewController.mediaDetailPreviewView.frame), CGRectGetHeight(currentViewController.mediaDetailPreviewView.frame)); } } #pragma mark - Custom Method - (void)setMediaDetailViewControllerType:(TAPMediaDetailViewControllerType)mediaDetailViewControllerType { _mediaDetailViewControllerType = mediaDetailViewControllerType; } - (TAPMediaDetailPreviewViewController *)viewControllerAtIndex:(NSUInteger)index { TAPMediaDetailPreviewViewController *mediaDetailPreviewViewController = [[TAPMediaDetailPreviewViewController alloc] init]; if (self.mediaDetailViewControllerType == TAPMediaDetailViewControllerTypeImage) { [mediaDetailPreviewViewController setMediaDetailPreviewViewControllerType:TAPMediaDetailPreviewViewControllerTypeImage]; } else if (self.mediaDetailViewControllerType == TAPMediaDetailViewControllerTypeVideo) { [mediaDetailPreviewViewController setMediaDetailPreviewViewControllerType:TAPMediaDetailPreviewViewControllerTypeVideo]; } mediaDetailPreviewViewController.index = index; if(self.thumbnailImageArray != nil && [self.thumbnailImageArray count] > 0 && index < [self.thumbnailImageArray count]) { mediaDetailPreviewViewController.thumbnailImage = [self.thumbnailImageArray objectAtIndex:index]; } if(self.imageLocalNameArray != nil && [self.imageLocalNameArray count] > 0 && index < [self.imageLocalNameArray count]) { mediaDetailPreviewViewController.imageLocalName = [self.imageLocalNameArray objectAtIndex:index]; } if(self.imageArray != nil && [self.imageArray count] > 0 && index < [self.imageArray count]) { mediaDetailPreviewViewController.image = [self.imageArray objectAtIndex:index]; } mediaDetailPreviewViewController.delegate = self; [self.viewControllerDictionary setObject:mediaDetailPreviewViewController forKey:[NSNumber numberWithInteger:index]]; return mediaDetailPreviewViewController; } - (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer { CGPoint location = [panGestureRecognizer locationInView:self.view]; CGFloat minimumGapToAction = 50.0f; if(panGestureRecognizer.state == UIGestureRecognizerStateBegan) { //Hide header and footer view [self.mediaDetailView showHeaderAndCaptionView:NO animated:NO]; _startPanPoint = location; _lastPanPoint = location; } else if(panGestureRecognizer.state == UIGestureRecognizerStateChanged) { CGFloat lastX = self.lastPanPoint.x; CGFloat currentX = location.x; CGFloat currentXGap = currentX - lastX; CGFloat lastY = self.lastPanPoint.y; CGFloat currentY = location.y; CGFloat currentYGap = currentY - lastY; CGFloat newX = CGRectGetMinX(self.mediaDetailView.movementView.frame) + currentXGap; CGFloat newY = CGRectGetMinY(self.mediaDetailView.movementView.frame) + currentYGap; CGRect pageViewControllerView = self.mediaDetailView.movementView.frame; self.mediaDetailView.movementView.frame = CGRectMake(newX, newY, CGRectGetWidth(pageViewControllerView), CGRectGetHeight(pageViewControllerView)); _lastPanPoint = location; _lastXGap = currentXGap; _lastYGap = currentYGap; CGFloat firstY = self.startPanPoint.y; CGFloat endY = location.y; CGFloat endYGap = endY - firstY; if(endYGap < 0.0f) { endYGap *= -1; } CGFloat alphaAdjuster = 0.2f; //Add more alpha from gap counter CGFloat alphaDecreaserDivider = 2.0f; //How many times from gap to 0.0f alpha CGFloat backgroundAlpha = (1.0f - (endYGap/(minimumGapToAction * alphaDecreaserDivider))) + alphaAdjuster; self.mediaDetailView.backgroundView.alpha = backgroundAlpha; } else if(panGestureRecognizer.state == UIGestureRecognizerStateEnded) { CGFloat firstY = self.startPanPoint.y; CGFloat endY = location.y; CGFloat endYGap = endY - firstY; if(endYGap < 0.0f) { if(endYGap > -minimumGapToAction) { //Cancel [UIView animateWithDuration:0.3f delay:0.0f usingSpringWithDamping:0.8f initialSpringVelocity:0.8f options:UIViewAnimationOptionCurveEaseOut animations:^{ self.mediaDetailView.movementView.frame = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.mediaDetailView.movementView.frame), CGRectGetHeight(self.mediaDetailView.movementView.frame)); self.mediaDetailView.backgroundView.alpha = 1.0f; } completion:nil]; //Show header and footer view if (self.isHeaderFooterViewShown) { [self.mediaDetailView showHeaderAndCaptionView:YES animated:YES]; } return; } //Dismiss Action [self dismissSelf]; } else { if(endYGap < minimumGapToAction) { //Cancel [UIView animateWithDuration:0.3f delay:0.0f usingSpringWithDamping:0.8f initialSpringVelocity:0.8f options:UIViewAnimationOptionCurveEaseOut animations:^{ self.mediaDetailView.movementView.frame = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.mediaDetailView.movementView.frame), CGRectGetHeight(self.mediaDetailView.movementView.frame)); self.mediaDetailView.backgroundView.alpha = 1.0f; } completion:nil]; //Show header and footer view if (self.isHeaderFooterViewShown) { [self.mediaDetailView showHeaderAndCaptionView:YES animated:YES]; } return; } //Dismiss Action [self dismissSelf]; } } } - (void)showToViewController:(UIViewController *)viewController thumbnailImage:(UIImage *)thumbnailImage thumbnailFrame:(CGRect)thumbnailFrame { [viewController addChildViewController:self]; [self didMoveToParentViewController:viewController]; self.view.backgroundColor = [UIColor clearColor]; [viewController.view addSubview:self.view]; _thumbnailFrame = thumbnailFrame; _thumbnailImage = thumbnailImage; [self.mediaDetailView animateOpeningWithThumbnailFrame:thumbnailFrame thumbnailImage:thumbnailImage]; } - (void)dismissSelf { UIImage *thumbnailImage = [UIImage imageNamed:@"blank-image" inBundle:[TAPUtil currentBundle] compatibleWithTraitCollection:nil]; if(self.currentViewController != nil) { thumbnailImage = [self.currentViewController currentImage]; } [self.mediaDetailView animateClosingWithThumbnailFrame:self.thumbnailFrame thumbnailImage:thumbnailImage]; } - (void)setActiveIndex:(NSInteger)activeIndex { if(![self isViewLoaded]) { _isActiveIndexSet = YES; _currentActiveIndex = activeIndex; return; } TAPMediaDetailPreviewViewController *nextController = [self viewControllerAtIndex:activeIndex]; _currentViewController = nextController; if (nextController) { NSArray *viewControllers = @[nextController]; // This changes the View Controller and calls the presentationIndexForPageViewController datasource method [self.mediaDetailView.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil]; } } //Override completionSelector method of save image to gallery - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { [self showFinishSavingImageState]; } //Override completionSelector method of save video to gallery - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { [self showFinishSavingImageState]; } - (void)showFinishSavingImageState { [self.mediaDetailView setSaveLoadingAsFinishedState:YES]; [self performSelector:@selector(removeSaveImageLoadingView) withObject:nil afterDelay:1.0f]; } - (void)removeSaveImageLoadingView { [self.mediaDetailView showSaveLoadingView:NO]; } @end