Fix media detail presentation

- video view now scales during presentation
- no "swooping" when presenting large res images

// FREEBIE
pull/1/head
Michael Kirk 7 years ago
parent 7c2bfdfb1f
commit 3582ab42db

@ -49,6 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) UIScrollView *scrollView;
@property (nonatomic) UIView *mediaView;
@property (nonatomic) UIView *presentationView;
@property (nonatomic) UIButton *shareButton;
@ -68,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) UIBarButtonItem *videoPlayBarButton;
@property (nonatomic, nullable) UIBarButtonItem *videoPauseBarButton;
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *mediaViewConstraints;
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *presentationViewConstraints;
@property (nonatomic, nullable) NSLayoutConstraint *mediaViewBottomConstraint;
@property (nonatomic, nullable) NSLayoutConstraint *mediaViewLeadingConstraint;
@property (nonatomic, nullable) NSLayoutConstraint *mediaViewTopConstraint;
@ -199,6 +200,14 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self updateMinZoomScale];
[self centerMediaViewConstraints];
}
- (void)updateMinZoomScale
{
CGSize viewSize = self.scrollView.bounds.size;
@ -260,6 +269,11 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(self.mediaView);
[scrollView addSubview:self.mediaView];
self.mediaViewLeadingConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
self.mediaViewTopConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeTop];
self.mediaViewTrailingConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
self.mediaViewBottomConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
self.mediaView.contentMode = UIViewContentModeScaleAspectFit;
self.mediaView.userInteractionEnabled = YES;
self.mediaView.clipsToBounds = YES;
@ -271,6 +285,19 @@ NS_ASSUME_NONNULL_BEGIN
self.mediaView.layer.minificationFilter = kCAFilterTrilinear;
self.mediaView.layer.magnificationFilter = kCAFilterTrilinear;
// The presentationView is only used during present/dismiss animations.
// It's a static image of the media content.
UIImageView *presentationView = [[UIImageView alloc] initWithImage:self.image];
self.presentationView = presentationView;
[self.view addSubview:presentationView];
presentationView.hidden = YES;
presentationView.clipsToBounds = YES;
presentationView.layer.allowsEdgeAntialiasing = YES;
presentationView.layer.minificationFilter = kCAFilterTrilinear;
presentationView.layer.magnificationFilter = kCAFilterTrilinear;
presentationView.contentMode = UIViewContentModeScaleAspectFit;
[self applyInitialMediaViewConstraints];
if (self.isVideo) {
@ -361,42 +388,38 @@ NS_ASSUME_NONNULL_BEGIN
- (void)applyInitialMediaViewConstraints
{
if (self.mediaViewConstraints.count > 0) {
[NSLayoutConstraint deactivateConstraints:self.mediaViewConstraints];
if (self.presentationViewConstraints.count > 0) {
[NSLayoutConstraint deactivateConstraints:self.presentationViewConstraints];
}
CGRect convertedRect =
[self.mediaView.superview convertRect:self.originRect fromView:[UIApplication sharedApplication].keyWindow];
CGRect convertedRect = [self.presentationView.superview convertRect:self.originRect
fromView:[UIApplication sharedApplication].keyWindow];
NSMutableArray<NSLayoutConstraint *> *mediaViewConstraints = [NSMutableArray new];
self.mediaViewConstraints = mediaViewConstraints;
NSMutableArray<NSLayoutConstraint *> *presentationViewConstraints = [NSMutableArray new];
self.presentationViewConstraints = presentationViewConstraints;
[mediaViewConstraints addObjectsFromArray:[self.mediaView autoSetDimensionsToSize:convertedRect.size]];
[mediaViewConstraints addObjectsFromArray:@[
[self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:convertedRect.origin.y],
[self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:convertedRect.origin.x]
[presentationViewConstraints
addObjectsFromArray:[self.presentationView autoSetDimensionsToSize:convertedRect.size]];
[presentationViewConstraints addObjectsFromArray:@[
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:convertedRect.origin.y],
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:convertedRect.origin.x]
]];
}
- (void)applyFinalMediaViewConstraints
{
if (self.mediaViewConstraints.count > 0) {
[NSLayoutConstraint deactivateConstraints:self.mediaViewConstraints];
if (self.presentationViewConstraints.count > 0) {
[NSLayoutConstraint deactivateConstraints:self.presentationViewConstraints];
}
NSMutableArray<NSLayoutConstraint *> *mediaViewConstraints = [NSMutableArray new];
self.mediaViewConstraints = mediaViewConstraints;
NSMutableArray<NSLayoutConstraint *> *presentationViewConstraints = [NSMutableArray new];
self.presentationViewConstraints = presentationViewConstraints;
self.mediaViewLeadingConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
self.mediaViewTopConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeTop];
self.mediaViewTrailingConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
self.mediaViewBottomConstraint = [self.mediaView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[mediaViewConstraints addObjectsFromArray:@[
self.mediaViewTopConstraint,
self.mediaViewTrailingConstraint,
self.mediaViewBottomConstraint,
self.mediaViewLeadingConstraint
[presentationViewConstraints addObjectsFromArray:@[
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeLeading],
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeTop],
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeTrailing],
[self.presentationView autoPinEdgeToSuperviewEdge:ALEdgeBottom]
]];
}
@ -708,14 +731,22 @@ NS_ASSUME_NONNULL_BEGIN
self.view.userInteractionEnabled = NO;
// We want to animate the tapped media from it's position in the previous VC
// to it's resting place in the center of this view controller.
//
// Rather than animating the actual media view in place, we animate the presentationView, which is a static
// image of the media content. Animating the actual media view is problematic for a couple reasons:
// 1. The media view ultimately lives in a zoomable scrollView. Getting both original positioning and the final positioning
// correct, involves manipulating the zoomScale and position simultaneously, which results in non-linear movement,
// especially noticeable on high resolution images.
// 2. For Video views, the AVPlayerLayer content does not scale with the presentation animation. So you instead get a full scale
// video, wherein only the cropping is animated.
// Using a simple image view allows us to address both these problems relatively easily.
self.view.alpha = 0.0;
// Our zoomScale must == 1 in order for our initialMediaViewConstraints to align with the
// "zoomed" view's pre-presentation position.
OWSAssert(self.scrollView.zoomScale == 1.0);
[self.mediaView.superview layoutIfNeeded];
self.mediaView.layer.cornerRadius = OWSMessageCellCornerRadius;
self.mediaView.hidden = YES;
self.presentationView.hidden = NO;
self.presentationView.layer.cornerRadius = OWSMessageCellCornerRadius;
[viewController presentViewController:navController
animated:NO
@ -727,9 +758,7 @@ NS_ASSUME_NONNULL_BEGIN
self.view.alpha = 1.0;
}];
// Make sure imageView is layed out before we update it's frame in the next
// animation.
[self.mediaView.superview layoutIfNeeded];
[self.presentationView.superview layoutIfNeeded];
[self applyFinalMediaViewConstraints];
// 2. Animate imageView from it's initial position, which should match where it was
@ -739,9 +768,8 @@ NS_ASSUME_NONNULL_BEGIN
delay:0.08
options:UIViewAnimationOptionCurveEaseOut
animations:^(void) {
self.mediaView.layer.cornerRadius = 0;
[self updateMinZoomScale];
[self.mediaView.superview layoutIfNeeded];
self.presentationView.layer.cornerRadius = 0;
[self.presentationView.superview layoutIfNeeded];
// We must lay out once *before* we centerMediaViewConstraints
// because it uses the imageView.frame to build the constraints
@ -752,6 +780,11 @@ NS_ASSUME_NONNULL_BEGIN
self.view.backgroundColor = UIColor.whiteColor;
}
completion:^(BOOL finished) {
// At this point our presentation view should be overlayed perfectly
// with our media view. Swapping them out should be imperceptible.
self.mediaView.hidden = NO;
self.presentationView.hidden = YES;
self.view.userInteractionEnabled = YES;
if (self.isVideo) {
@ -763,14 +796,19 @@ NS_ASSUME_NONNULL_BEGIN
- (void)dismissSelfAnimated:(BOOL)isAnimated completion:(void (^_Nullable)(void))completion
{
self.view.userInteractionEnabled = NO;
[UIApplication sharedApplication].statusBarHidden = NO;
OWSAssert(self.mediaView.superview);
// Swapping mediaView for presentationView will be perceptible if we're not zoomed out all the way.
if (self.scrollView.zoomScale != self.scrollView.minimumZoomScale) {
[self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:YES];
}
[self.mediaView.superview layoutIfNeeded];
self.mediaView.hidden = YES;
self.presentationView.hidden = NO;
// Move the image view pack to it's initial position, i.e. where
// Move the presentationView back to it's initial position, i.e. where
// it sits on the screen in the conversation view.
[self applyInitialMediaViewConstraints];
@ -779,12 +817,8 @@ NS_ASSUME_NONNULL_BEGIN
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^(void) {
[self.mediaView.superview layoutIfNeeded];
// Our zoomScale must == 1 in order for our initialMediaViewConstraints to align with the
// "zoomed" view's pre-presentation position.
self.scrollView.zoomScale = 1.0;
self.mediaView.layer.cornerRadius = OWSMessageCellCornerRadius;
[self.presentationView.superview layoutIfNeeded];
self.presentationView.layer.cornerRadius = OWSMessageCellCornerRadius;
// In case user has hidden bars, which changes background to black.
self.view.backgroundColor = UIColor.whiteColor;

Loading…
Cancel
Save