Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions BFRImageViewController/BFRBackLoadedImageSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void(^onHiResDownloadComplete)(UIImage * _Nullable, NSError * _Nullable);

/*! This class allows you to show an image that you already have available initially, while loading a higher fidelity version in the background which will replace the lower fidelity one. This class assumes that the new image will have the same aspect ratio as the old one. */
@interface BFRBackLoadedImageSource : NSObject

/*! The image that is available for use right away. */
@property (strong, nonatomic, readonly, nonnull) UIImage *image;

/*! This is called on the main thread when the higher resolution image is finished loading. */
@property (copy) void (^ _Nonnull onHighResImageLoaded)(UIImage * _Nullable highResImage);
/*! This is called on the main thread when the higher resolution image is finished loading. Assign to this if you wish to do any specific logic when the download completes. NOTE: Do not attempt to assign the image to any @c BFRImageContainerViewController, this is done for you. Use this block soley for any other business logic you might have to carry out. */
@property (copy) onHiResDownloadComplete _Nullable onCompletion;

/*! Use initWithInitialImage:hiResURL instead. */
- (instancetype _Nullable)init NS_UNAVAILABLE;
Expand Down
19 changes: 12 additions & 7 deletions BFRImageViewController/BFRBackLoadedImageSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "BFRBackLoadedImageSource.h"
#import "BFRImageViewerConstants.h"
#import <UIKit/UIKit.h>
#import <PINRemoteImage/PINRemoteImage.h>
#import <PINRemoteImage/PINImageView+PINRemoteImage.h>
Expand Down Expand Up @@ -41,15 +42,19 @@ - (instancetype)initWithInitialImage:(UIImage *)image hiResURL:(NSURL *)url {
- (void)loadHighFidelityImage {
[[PINRemoteImageManager sharedImageManager] downloadImageWithURL:self.url options:PINRemoteImageManagerDisallowAlternateRepresentations progressDownload:nil completion:^(PINRemoteImageManagerResult * _Nonnull result) {
dispatch_async(dispatch_get_main_queue(), ^{
if (result.image) {
if (self.onHighResImageLoaded != nil) {
dispatch_async(dispatch_get_main_queue(), ^ {
self.onHighResImageLoaded(result.image);
});
if (self.onCompletion != nil) {
if (result.image) {
self.onCompletion(result.image, nil);
} else {
NSLog(@"BFRImageViewer: Unable to load high resolution photo via backloading.");
NSError *downloadError = [NSError errorWithDomain:HI_RES_IMG_ERROR_DOMAIN
code:HI_RES_IMG_ERROR_CODE
userInfo:@{NSLocalizedFailureReasonErrorKey:[NSString stringWithFormat:@"Failed to download an image for high resolution url %@", self.url.absoluteString]}];
self.onCompletion(nil, downloadError);
}
} else {
NSLog(@"BFRImageViewer: Unable to load high resolution photo via backloading.");
}

[[NSNotificationCenter defaultCenter] postNotificationName:NOTE_HI_RES_IMG_DOWNLOADED object:result.image];
});
}];
}
Expand Down
5 changes: 4 additions & 1 deletion BFRImageViewController/BFRImageContainerViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
/*! A helper integer to simplify using this view controller inside a @c UIPagerViewController when swiping between views. */
@property (nonatomic, assign) NSUInteger pageIndex;

/*! Assigning YES to this property will make the background transparent. */
/*! Assigning YES to this property will make the background transparent. You typically don't set this property yourself, instead, the value is derived from the containing @c BFRImageViewController instance. */
@property (nonatomic, getter=isUsingTransparentBackground) BOOL useTransparentBackground;

/*! Assigning YES to this property will disable long pressing media to present the activity view controller. You typically don't set this property yourself, instead, the value is derived from the containing @c BFRImageViewController instance. */
@property (nonatomic, getter=shouldDisableSharingLongPress) BOOL disableSharingLongPress;

/*! If there is more than one image in the containing @c BFRImageViewController - this property is set to YES to make swiping from image to image easier. */
@property (nonatomic, getter=shouldDisableHorizontalDrag) BOOL disableHorizontalDrag;

Expand Down
44 changes: 26 additions & 18 deletions BFRImageViewController/BFRImageContainerViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import "BFRImageContainerViewController.h"
#import "BFRBackLoadedImageSource.h"
#import "BFRImageViewerConstants.h"
#import <Photos/Photos.h>
#import <DACircularProgress/DACircularProgressView.h>
#import <PINRemoteImage/PINRemoteImage.h>
Expand Down Expand Up @@ -53,6 +54,10 @@ - (void)viewDidLoad {
self.scrollView = [self createScrollView];
[self.view addSubview:self.scrollView];

// Animator - used to snap the image back to the center when done dragging
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.scrollView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handlePop) name:NOTE_VC_POPPED object:nil];

// Fetch image - or just display it
if ([self.imgSrc isKindOfClass:[NSURL class]]) {
self.progressView = [self createProgressView];
Expand All @@ -74,21 +79,12 @@ - (void)viewDidLoad {
[self.view addSubview:self.progressView];
[self retrieveImageFromURL];
} else if ([self.imgSrc isKindOfClass:[BFRBackLoadedImageSource class]]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleHiResImageDownloaded:) name:NOTE_HI_RES_IMG_DOWNLOADED object:nil];
self.imgLoaded = ((BFRBackLoadedImageSource *)self.imgSrc).image;
[self addImageToScrollView];

__weak BFRImageContainerViewController *weakSelf = self;
((BFRBackLoadedImageSource *)self.imgSrc).onHighResImageLoaded = ^ (UIImage *highResImage) {
weakSelf.imgLoaded = highResImage;
weakSelf.imgView.image = weakSelf.imgLoaded;
};
} else {
[self showError];
}

// Animator - used to snap the image back to the center when done dragging
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.scrollView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handlePop) name:@"ViewControllerPopped" object:nil];
}

- (void)viewWillLayoutSubviews {
Expand Down Expand Up @@ -180,12 +176,14 @@ - (FLAnimatedImageView *)createImageView {
[resizableImageView addGestureRecognizer:doubleImgTap];

// Share options
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showActivitySheet:)];
[resizableImageView addGestureRecognizer:longPress];
if (self.shouldDisableSharingLongPress == NO) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showActivitySheet:)];
[resizableImageView addGestureRecognizer:longPress];
[singleImgTap requireGestureRecognizerToFail:longPress];
}

// Ensure the single tap doesn't fire when a user attempts to double tap
[singleImgTap requireGestureRecognizerToFail:doubleImgTap];
[singleImgTap requireGestureRecognizerToFail:longPress];

// Dragging to dismiss
UIPanGestureRecognizer *panImg = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleDrag:)];
Expand All @@ -205,6 +203,16 @@ - (void)addImageToScrollView {
}
}

#pragma mark - Backloaded Image Notification
- (void)handleHiResImageDownloaded:(NSNotification *)note {
UIImage *hiResImg = note.object;

if (hiResImg && [hiResImg isKindOfClass:[UIImage class]]) {
self.imgLoaded = hiResImg;
self.imgView.image = self.imgLoaded;
}
}

#pragma mark - Gesture Recognizer Delegate
// If we have more than one image, this will cancel out dragging horizontally to make it easy to navigate between images
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
Expand Down Expand Up @@ -411,18 +419,18 @@ - (void)retrieveImageFromURL {

#pragma mark - Misc. Methods
- (void)dismissUI {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DismissUI" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTE_VC_SHOULD_DISMISS object:nil];
}

- (void)dimissUIFromDraggingGesture {
// If we drag the image away to close things, don't do the custom dismissal transition
[[NSNotificationCenter defaultCenter] postNotificationName:@"DimissUIFromDraggingGesture" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTE_VC_SHOULD_DISMISS_FROM_DRAGGING object:nil];
}

- (void)showError {
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Whoops" message:@"Looks like we ran into an issue loading the image, sorry about that!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *closeAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
[[NSNotificationCenter defaultCenter] postNotificationName:@"ImageLoadingError" object:nil];
UIAlertController *controller = [UIAlertController alertControllerWithTitle:ERROR_TITLE message:ERROR_MESSAGE preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *closeAction = [UIAlertAction actionWithTitle:GENERAL_OK style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
[[NSNotificationCenter defaultCenter] postNotificationName:NOTE_IMG_FAILED object:nil];
}];
[controller addAction:closeAction];
[self presentViewController:controller animated:YES completion:nil];
Expand Down
3 changes: 2 additions & 1 deletion BFRImageViewController/BFRImageTransitionAnimator.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "BFRImageTransitionAnimator.h"
#import "BFRImageViewerConstants.h"

@interface BFRImageTransitionAnimator()

Expand All @@ -32,7 +33,7 @@ - (instancetype)init {
self.desiredContentMode = UIViewContentModeScaleAspectFill;
self.animationDuration = DEFAULT_ANIMATION_DURATION;
self.dismissWithoutCustomTransition = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCancelCustomTransitionNotification:) name:@"CancelCustomDismissalTransition" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCancelCustomTransitionNotification:) name:NOTE_VC_SHOULD_CANCEL_CUSTOM_TRANSITION object:nil];
}

return self;
Expand Down
5 changes: 4 additions & 1 deletion BFRImageViewController/BFRImageViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
/*! Initializes an instance of @C BFRImageViewController from the image source provided. The array can contain a mix of @c NSURL, @c UIImage, @c PHAsset, or @c NSStrings of URLS. This can be a mix of all these types, or just one. Additionally, this customizes the user interface to defer showing some of its user interface elements, such as the close button, until it's been fully popped.*/
- (instancetype _Nullable)initForPeekWithImageSource:(NSArray * _Nonnull)images;

/*! Assigning YES to this property will make the background transparent. */
/*! Assigning YES to this property will make the background transparent. Default is NO. */
@property (nonatomic, getter=isUsingTransparentBackground) BOOL useTransparentBackground;

/*! Assigning YES to this property will disable long pressing media to present the activity view controller. Default is NO. */
@property (nonatomic, getter=shouldDisableSharingLongPress) BOOL disableSharingLongPress;

/*! Flag property that toggles the doneButton. Defaults to YES */
@property (nonatomic) BOOL enableDoneButton;

Expand Down
12 changes: 7 additions & 5 deletions BFRImageViewController/BFRImageViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "BFRImageContainerViewController.h"
#import "BFRImageViewerLocalizations.h"
#import "BFRImageTransitionAnimator.h"
#import "BFRImageViewerConstants.h"

@interface BFRImageViewController () <UIPageViewControllerDataSource>

Expand Down Expand Up @@ -88,6 +89,7 @@ - (void)viewDidLoad {
imgVC.pageIndex = self.startingIndex;
imgVC.usedFor3DTouch = self.isBeingUsedFor3DTouch;
imgVC.useTransparentBackground = self.isUsingTransparentBackground;
imgVC.disableSharingLongPress = self.shouldDisableSharingLongPress;
imgVC.disableHorizontalDrag = (self.images.count > 1);
[self.imageViewControllers addObject:imgVC];
}
Expand Down Expand Up @@ -211,7 +213,7 @@ - (void)dismiss {
}

- (void)dismissWithoutCustomAnimation {
[[NSNotificationCenter defaultCenter] postNotificationName:@"CancelCustomDismissalTransition" object:@(1)];
[[NSNotificationCenter defaultCenter] postNotificationName:NOTE_VC_SHOULD_CANCEL_CUSTOM_TRANSITION object:@(1)];

self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self dismissViewControllerAnimated:YES completion:nil];
Expand All @@ -228,10 +230,10 @@ - (void)handleDoneAction {

/*! The images and scrollview are not part of this view controller, so instances of @c BFRimageContainerViewController will post notifications when they are touched for things to happen. */
- (void)registerNotifcations {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismiss) name:@"DismissUI" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismiss) name:@"ImageLoadingError" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handlePop) name:@"ViewControllerPopped" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissWithoutCustomAnimation) name:@"DimissUIFromDraggingGesture" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismiss) name:NOTE_VC_SHOULD_DISMISS object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismiss) name:NOTE_IMG_FAILED object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handlePop) name:NOTE_VC_POPPED object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissWithoutCustomAnimation) name:NOTE_VC_SHOULD_DISMISS_FROM_DRAGGING object:nil];
}

- (BOOL)prefersHomeIndicatorAutoHidden {
Expand Down
28 changes: 28 additions & 0 deletions BFRImageViewController/BFRImageViewerConstants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// BFRImageViewerConstants.h
// BFRImageViewer
//
// Created by Jordan Morgan on 10/5/17.
// Copyright © 2017 Andrew Yates. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface BFRImageViewerConstants : NSObject

// Notifications
extern NSString * const NOTE_VC_POPPED;
extern NSString * const NOTE_HI_RES_IMG_DOWNLOADED;
extern NSString * const NOTE_VC_SHOULD_DISMISS;
extern NSString * const NOTE_VC_SHOULD_DISMISS_FROM_DRAGGING;
extern NSString * const NOTE_VC_SHOULD_CANCEL_CUSTOM_TRANSITION;
extern NSString * const NOTE_IMG_FAILED;

// NSError
extern NSString * const ERROR_TITLE;
extern NSString * const ERROR_MESSAGE;
extern NSString * const GENERAL_OK;
extern NSString * const HI_RES_IMG_ERROR_DOMAIN;
extern NSInteger const HI_RES_IMG_ERROR_CODE;

@end
26 changes: 26 additions & 0 deletions BFRImageViewController/BFRImageViewerConstants.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// BFRImageViewerConstants.m
// BFRImageViewer
//
// Created by Jordan Morgan on 10/5/17.
// Copyright © 2017 Andrew Yates. All rights reserved.
//

#import "BFRImageViewerConstants.h"

@implementation BFRImageViewerConstants

NSString * const NOTE_VC_POPPED = @"ViewControllerPopped";
NSString * const NOTE_HI_RES_IMG_DOWNLOADED = @"HiResDownloadDone";
NSString * const NOTE_VC_SHOULD_DISMISS = @"DismissUI";
NSString * const NOTE_VC_SHOULD_DISMISS_FROM_DRAGGING = @"DimissUIFromDraggingGesture";
NSString * const NOTE_VC_SHOULD_CANCEL_CUSTOM_TRANSITION = @"CancelCustomDismissalTransition";
NSString * const NOTE_IMG_FAILED = @"ImageLoadingError";

NSString * const ERROR_TITLE = @"Whoops";
NSString * const ERROR_MESSAGE = @"Looks like we ran into an issue loading the image, sorry about that!";
NSString * const GENERAL_OK = @"Ok";
NSString * const HI_RES_IMG_ERROR_DOMAIN = @"com.bfrImageViewer.backLoadedImgSource";
NSInteger const HI_RES_IMG_ERROR_CODE = 44;

@end
Binary file modified BFRImageViewController/Resources/lowResImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading