chromium/ios/chrome/browser/overlays/ui_bundled/overlay_presentation_context_view_controller.mm

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/overlays/ui_bundled/overlay_presentation_context_view_controller.h"

#import <cmath>

#import "base/apple/foundation_util.h"
#import "ios/chrome/browser/overlays/ui_bundled/overlay_presentation_controller.h"

namespace {
// Returns YES if `size1` and `size2` are less than 0.5 different in both width
// and height.
BOOL CGSizeAlmostEqualToSize(CGSize size1, CGSize size2) {
  const CGFloat kMinimumSizeChange = 0.5;
  return std::fabs(size1.height - size2.height) <= kMinimumSizeChange &&
         std::fabs(size1.width - size2.width) <= kMinimumSizeChange;
}
}  // namespace

@interface OverlayPresentationContextViewController ()
// The view used to lay out the presentation context.  The presentation context
// view is resized to match `layoutView`'s frame.
@property(nonatomic, weak) UIView* layoutView;
// Whether the presented view controller is presented using an
// OverlayPresentationController whose `resizesPresentationContainer` property
// returns YES.
@property(nonatomic, readonly) BOOL presentedViewControllerResizesContainer;
@end

@implementation OverlayPresentationContextViewController

#pragma mark - Accessors

- (BOOL)presentedViewControllerResizesContainer {
  // The non-strict cast returns nil if the presented UIViewController does not
  // use an OverlayPresentationController.  This results in this selector
  // returning NO for these UIViewControllers.
  return base::apple::ObjCCast<OverlayPresentationController>(
             self.presentedViewController.presentationController)
      .resizesPresentationContainer;
}

#pragma mark - UIViewController

- (void)viewDidLayoutSubviews {
  UIView* view = self.view;
  UIView* containerView = self.presentationController.containerView;
  UIView* newLayoutView = [self currentLayoutView];
  UIWindow* window = newLayoutView.window;

  CGRect oldLayoutFrame = self.layoutView.frame;
  CGRect newLayoutFrame = [window convertRect:newLayoutView.bounds
                                     fromView:newLayoutView];
  if (CGSizeAlmostEqualToSize(containerView.bounds.size, oldLayoutFrame.size) &&
      CGSizeAlmostEqualToSize(newLayoutFrame.size, oldLayoutFrame.size)) {
    return;
  }
  self.layoutView = newLayoutView;

  // Lay out the presentation context and its container view to match
  // `layoutView`, if size has changed.
  if (self.layoutView) {
    containerView.frame = [containerView.superview convertRect:newLayoutFrame
                                                      fromView:window];
    view.frame = [view.superview convertRect:newLayoutFrame fromView:window];
  } else {
    containerView.frame = CGRectZero;
    view.frame = CGRectZero;
  }
  // If `layoutView` is not laid out using constraints, its frame may have been
  // updated by the container and presentation context layout above.  Reset the
  // frame by converting back from the window coordinates.
  if (self.presentedViewControllerResizesContainer &&
      self.layoutView.translatesAutoresizingMaskIntoConstraints) {
    self.layoutView.frame =
        [self.layoutView.superview convertRect:newLayoutFrame fromView:window];
  }
}

- (void)presentViewController:(UIViewController*)viewController
                     animated:(BOOL)animated
                   completion:(void (^)(void))completion {
  // Trigger a layout of the presentation context before presenting.  This
  // allows the presentation context to be resized appropriately during the the
  // presentation animation.
  [self.view setNeedsLayout];

  [super presentViewController:viewController
                      animated:animated
                    completion:completion];
}

- (void)dismissViewControllerAnimated:(BOOL)animated
                           completion:(void (^)(void))completion {
  // Create an updated completion block that triggers layout after the dismissal
  // finishes.  This will resize the presentation context to CGRectZero so that
  // touches can be handled by the underlying browser UI once the presented
  // overlay is removed.
  [super dismissViewControllerAnimated:animated
                            completion:^{
                              if (completion)
                                completion();
                              [self.view setNeedsLayout];
                            }];
}

#pragma mark - Private

// Returns the current layout view.
- (UIView*)currentLayoutView {
  // If there is no overlay UI displayed using presentation or containment, the
  // presentation context should be laid out with an empty frame to allow
  // touches to pass freely to the underlying browser UI.
  UIViewController* presentedViewController = self.presentedViewController;
  if (!presentedViewController) {
    return nil;
  }

  // If overlay UI is displayed using custom UIViewController presentation with
  // an OverlayPresentationController that resizes the container view, the
  // presentation context should match the presented view's container.
  if (self.presentedViewControllerResizesContainer) {
    return presentedViewController.presentationController.containerView;
  }

  // For all other UIViewController presentation, the context should be laid out
  // to match the presenter view.
  return self.presentingViewController.view;
}

@end