mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
398 lines
13 KiB
Objective-C
398 lines
13 KiB
Objective-C
//
|
|
// FullImageViewController.m
|
|
// Signal
|
|
//
|
|
// Created by Dylan Bourgeois on 11/11/14.
|
|
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "FullImageViewController.h"
|
|
#import "DJWActionSheet.h"
|
|
#import "TSAttachmentStream.h"
|
|
|
|
#define kImageViewCornerRadius 5.0f
|
|
|
|
#define kMinZoomScale 1.0f
|
|
#define kMaxZoomScale 8.0f
|
|
#define kTargetDoubleTapZoom 3.0f
|
|
|
|
#define kBackgroundAlpha 0.6f
|
|
|
|
@interface FullImageViewController () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
|
|
|
|
@property (nonatomic, strong) UIView *backgroundView;
|
|
|
|
@property (nonatomic, strong) UIScrollView *scrollView;
|
|
|
|
@property (nonatomic, strong) UIImageView *imageView;
|
|
|
|
@property (nonatomic, strong) UITapGestureRecognizer *singleTap;
|
|
@property (nonatomic, strong) UITapGestureRecognizer *doubleTap;
|
|
|
|
@property (nonatomic, strong) UIButton *shareButton;
|
|
|
|
@property CGRect originRect;
|
|
@property BOOL isPresenting;
|
|
|
|
@property TSAttachmentStream *attachment;
|
|
@property TSInteraction *interaction;
|
|
|
|
@end
|
|
|
|
@implementation FullImageViewController
|
|
|
|
|
|
- (instancetype)initWithAttachment:(TSAttachmentStream*)attachment fromRect:(CGRect)rect forInteraction:(TSInteraction*)interaction {
|
|
self = [super initWithNibName:nil bundle:nil];
|
|
|
|
if (self) {
|
|
self.attachment = attachment;
|
|
self.imageView.image = self.image;
|
|
self.originRect = rect;
|
|
self.interaction = interaction;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (UIImage*)image{
|
|
return self.attachment.image;
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
[self initializeBackground];
|
|
[self initializeScrollView];
|
|
[self initializeImageView];
|
|
[self initializeGestureRecognizers];
|
|
|
|
[self populateImageView:self.image];
|
|
}
|
|
|
|
|
|
- (void)didReceiveMemoryWarning {
|
|
[super didReceiveMemoryWarning];
|
|
}
|
|
|
|
|
|
#pragma mark - Initializers
|
|
|
|
- (void)initializeBackground
|
|
{
|
|
self.imageView.backgroundColor = [UIColor colorWithWhite:0 alpha:kBackgroundAlpha];
|
|
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:kBackgroundAlpha];
|
|
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
self.backgroundView = [[UIView alloc] initWithFrame:CGRectInset(self.view.bounds, -512, -512)];
|
|
self.backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:kBackgroundAlpha];
|
|
|
|
[self.view addSubview:self.backgroundView];
|
|
}
|
|
|
|
- (void)initializeScrollView
|
|
{
|
|
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
|
|
self.scrollView.delegate = self;
|
|
self.scrollView.zoomScale = 1.0f;
|
|
self.scrollView.maximumZoomScale = kMaxZoomScale;
|
|
self.scrollView.scrollEnabled = NO;
|
|
[self.view addSubview:self.scrollView];
|
|
}
|
|
|
|
- (void)initializeImageView
|
|
{
|
|
self.imageView = [[UIImageView alloc]initWithFrame:self.originRect];
|
|
self.imageView.layer.cornerRadius = kImageViewCornerRadius;
|
|
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
|
|
self.imageView.userInteractionEnabled = YES;
|
|
self.imageView.clipsToBounds = YES;
|
|
self.imageView.layer.allowsEdgeAntialiasing = YES;
|
|
[self.scrollView addSubview:self.imageView];
|
|
|
|
}
|
|
|
|
- (void)populateImageView:(UIImage*)image
|
|
{
|
|
if (image) {
|
|
self.imageView.image = image;
|
|
}
|
|
}
|
|
|
|
- (void)initializeGestureRecognizers
|
|
{
|
|
self.doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(imageDoubleTapped:)];
|
|
self.doubleTap.numberOfTapsRequired = 2;
|
|
|
|
self.singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(imageSingleTapped:)];
|
|
[self.singleTap requireGestureRecognizerToFail:self.doubleTap];
|
|
|
|
self.singleTap.delegate = self;
|
|
self.doubleTap.delegate = self;
|
|
|
|
[self.view addGestureRecognizer:self.singleTap];
|
|
[self.view addGestureRecognizer:self.doubleTap];
|
|
}
|
|
|
|
- (void) initializeShareButton
|
|
{
|
|
CGFloat buttonRadius = 50.0f;
|
|
CGFloat x = 14.0f;
|
|
CGFloat y = self.view.bounds.size.height - buttonRadius - 10.0f;
|
|
|
|
self.shareButton = [[UIButton alloc]initWithFrame:CGRectMake(x, y, buttonRadius, buttonRadius)];
|
|
[self.shareButton addTarget:self action:@selector(shareButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
|
[self.shareButton setImage:[UIImage imageNamed:@"savephoto"] forState:UIControlStateNormal];
|
|
|
|
[self.view addSubview:self.shareButton];
|
|
}
|
|
|
|
#pragma mark - Gesture Recognizers
|
|
|
|
- (void)imageDoubleTapped:(UITapGestureRecognizer*)doubleTap
|
|
{
|
|
CGPoint tap = [doubleTap locationInView:doubleTap.view];
|
|
CGPoint convertCoord = [self.scrollView convertPoint:tap fromView:doubleTap.view];
|
|
CGRect targetZoomRect;
|
|
UIEdgeInsets targetInsets;
|
|
|
|
CGSize zoom ;
|
|
|
|
if (self.scrollView.zoomScale == 1.0f) {
|
|
zoom = CGSizeMake(self.view.bounds.size.width / kTargetDoubleTapZoom, self.view.bounds.size.height / kTargetDoubleTapZoom);
|
|
targetZoomRect = CGRectMake(convertCoord.x - (zoom.width/2.0f), convertCoord.y - (zoom.height/2.0f), zoom.width, zoom.height);
|
|
targetInsets = [self contentInsetForScrollView:kTargetDoubleTapZoom];
|
|
} else {
|
|
zoom = CGSizeMake(self.view.bounds.size.width * self.scrollView.zoomScale, self.view.bounds.size.height * self.scrollView.zoomScale);
|
|
targetZoomRect = CGRectMake(convertCoord.x - (zoom.width/2.0f), convertCoord.y - (zoom.height/2.0f), zoom.width, zoom.height);
|
|
targetInsets = [self contentInsetForScrollView:1.0f];
|
|
}
|
|
|
|
self.view.userInteractionEnabled = NO;
|
|
|
|
[CATransaction begin];
|
|
[CATransaction setCompletionBlock:^{
|
|
self.scrollView.contentInset = targetInsets;
|
|
self.view.userInteractionEnabled = YES;
|
|
}];
|
|
[self.scrollView zoomToRect:targetZoomRect animated:YES];
|
|
[CATransaction commit];
|
|
|
|
}
|
|
|
|
- (void)imageSingleTapped:(UITapGestureRecognizer*)singleTap
|
|
{
|
|
[self dismiss];
|
|
}
|
|
|
|
#pragma mark - Presentation
|
|
|
|
-(void)presentFromViewController:(UIViewController*)viewController
|
|
{
|
|
_isPresenting = YES;
|
|
self.view.userInteractionEnabled = NO;
|
|
[self.view addSubview:self.imageView];
|
|
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
|
self.view.alpha = 0;
|
|
|
|
[viewController presentViewController:self animated:NO completion:^{
|
|
[UIView animateWithDuration:0.4f
|
|
delay:0
|
|
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
|
|
animations:^(){
|
|
self.view.alpha = 1.0f;
|
|
self.imageView.frame = [self resizedFrameForImageView:self.image.size];
|
|
self.imageView.center = CGPointMake(self.view.bounds.size.width/2.0f, self.view.bounds.size.height/2.0f);
|
|
} completion:^(BOOL completed){
|
|
self.scrollView.frame = self.view.bounds;
|
|
[self.scrollView addSubview:self.imageView];
|
|
[self updateLayouts];
|
|
[self initializeShareButton];
|
|
self.view.userInteractionEnabled = YES;
|
|
_isPresenting = NO;
|
|
}];
|
|
}];
|
|
|
|
}
|
|
|
|
- (void)dismiss
|
|
{
|
|
self.view.userInteractionEnabled = NO;
|
|
[UIView animateWithDuration:0.4f
|
|
delay:0
|
|
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
|
|
animations:^(){
|
|
self.backgroundView.backgroundColor = [UIColor clearColor];
|
|
self.scrollView.alpha = 0;
|
|
self.view.alpha = 0;
|
|
} completion:^(BOOL completed){
|
|
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Update Layout
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
{
|
|
[self updateLayouts];
|
|
}
|
|
|
|
|
|
- (void) updateLayouts
|
|
{
|
|
if (_isPresenting) {
|
|
return;
|
|
}
|
|
|
|
self.scrollView.frame = self.view.bounds;
|
|
self.imageView.frame = [self resizedFrameForImageView:self.image.size];
|
|
self.scrollView.contentSize = self.imageView.frame.size;
|
|
self.scrollView.contentInset = [self contentInsetForScrollView:self.scrollView.zoomScale];
|
|
}
|
|
|
|
|
|
#pragma mark - Resizing
|
|
|
|
- (CGRect)resizedFrameForImageView:(CGSize)imageSize {
|
|
CGRect frame = self.view.bounds;
|
|
CGSize screenSize = CGSizeMake(frame.size.width * self.scrollView.zoomScale, frame.size.height * self.scrollView.zoomScale);
|
|
CGSize targetSize = screenSize;
|
|
|
|
if ([self isImagePortrait]) {
|
|
if ([self getAspectRatioForCGSize:screenSize] < [self getAspectRatioForCGSize:imageSize]) {
|
|
targetSize.width = screenSize.height / [self getAspectRatioForCGSize:imageSize];
|
|
} else {
|
|
targetSize.height = screenSize.width * [self getAspectRatioForCGSize:imageSize];
|
|
}
|
|
} else {
|
|
if ([self getAspectRatioForCGSize:screenSize] > [self getAspectRatioForCGSize:imageSize]) {
|
|
targetSize.height = screenSize.width * [self getAspectRatioForCGSize:imageSize];
|
|
} else {
|
|
targetSize.width = screenSize.height / [self getAspectRatioForCGSize:imageSize];
|
|
}
|
|
}
|
|
|
|
frame.size = targetSize;
|
|
frame.origin = CGPointMake(0, 0);
|
|
return frame;
|
|
}
|
|
|
|
- (UIEdgeInsets)contentInsetForScrollView:(CGFloat)targetZoomScale {
|
|
UIEdgeInsets inset = UIEdgeInsetsZero;
|
|
|
|
CGSize boundsSize = self.scrollView.bounds.size;
|
|
CGSize contentSize = self.image.size;
|
|
CGSize minSize;
|
|
|
|
if ([self isImagePortrait]) {
|
|
if ([self getAspectRatioForCGSize:boundsSize] < [self getAspectRatioForCGSize:contentSize]) {
|
|
minSize.height = boundsSize.height;
|
|
minSize.width = minSize.height / [self getAspectRatioForCGSize:contentSize];
|
|
} else {
|
|
minSize.width = boundsSize.width;
|
|
minSize.height = minSize.width * [self getAspectRatioForCGSize:contentSize];
|
|
}
|
|
} else {
|
|
if ([self getAspectRatioForCGSize:boundsSize] > [self getAspectRatioForCGSize:contentSize]) {
|
|
minSize.width = boundsSize.width;
|
|
minSize.height = minSize.width * [self getAspectRatioForCGSize:contentSize];
|
|
} else {
|
|
minSize.height = boundsSize.height;
|
|
minSize.width = minSize.height / [self getAspectRatioForCGSize:contentSize];
|
|
}
|
|
}
|
|
|
|
CGSize finalSize = self.view.bounds.size;
|
|
|
|
minSize.width *= targetZoomScale;
|
|
minSize.height *= targetZoomScale;
|
|
|
|
if (minSize.height > finalSize.height && minSize.width > finalSize.width) {
|
|
inset = UIEdgeInsetsZero;
|
|
} else {
|
|
CGFloat dy = boundsSize.height - minSize.height;
|
|
CGFloat dx = boundsSize.width - minSize.width;
|
|
|
|
dy = (dy > 0) ? dy : 0;
|
|
dx = (dx > 0) ? dx : 0;
|
|
|
|
inset.top = dy/2.0f;
|
|
inset.bottom = dy/2.0f;
|
|
inset.left = dx/2.0f;
|
|
inset.right = dx/2.0f;
|
|
}
|
|
return inset;
|
|
}
|
|
|
|
#pragma mark - UIScrollViewDelegate
|
|
|
|
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
|
|
return self.imageView;
|
|
}
|
|
|
|
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
|
|
|
scrollView.contentInset = [self contentInsetForScrollView:scrollView.zoomScale];
|
|
|
|
if (self.scrollView.scrollEnabled == NO) {
|
|
self.scrollView.scrollEnabled = YES;
|
|
}
|
|
}
|
|
|
|
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
|
|
self.scrollView.scrollEnabled = (scale > 1);
|
|
self.scrollView.contentInset = [self contentInsetForScrollView:scale];
|
|
}
|
|
|
|
#pragma mark - Utility
|
|
|
|
- (BOOL)isImagePortrait
|
|
{
|
|
return ([self getAspectRatioForCGSize:self.image.size] > 1.0f);
|
|
}
|
|
|
|
- (CGFloat)getAspectRatioForCGSize:(CGSize)size
|
|
{
|
|
return size.height / size.width;
|
|
}
|
|
|
|
#pragma mark - Actions
|
|
|
|
-(void)shareButtonTapped:(UIButton*)sender
|
|
{
|
|
[DJWActionSheet showInView:self.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Save to Camera Roll", @"Copy"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
|
|
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
|
|
|
|
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex){
|
|
__block TSInteraction *interaction = [self interaction];
|
|
[self dismissViewControllerAnimated:YES completion:^{
|
|
[interaction remove];
|
|
}];
|
|
|
|
} else {
|
|
switch (tappedButtonIndex) {
|
|
case 0:
|
|
UIImageWriteToSavedPhotosAlbum(self.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
|
|
break;
|
|
case 1:
|
|
[[UIPasteboard generalPasteboard] setImage:self.image];
|
|
break;
|
|
default:
|
|
DDLogWarn(@"Illegal Action sheet field #%ld <%s>",(long)tappedButtonIndex, __PRETTY_FUNCTION__);
|
|
break;
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Saving images to Camera Roll
|
|
|
|
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
|
|
contextInfo:(void *)contextInfo
|
|
{
|
|
if (error)
|
|
{
|
|
DDLogWarn(@"There was a problem saving <%@> to camera roll from %s ", error.localizedDescription ,__PRETTY_FUNCTION__);
|
|
}
|
|
}
|
|
|
|
@end
|