Commit df1c5ff7 authored by Javier Ernesto Flores Robles's avatar Javier Ernesto Flores Robles Committed by Commit Bot

[iOS][iOS13][MF] Add composed layout guide and fix iPhone X

On iOS 13 the keyboard hierarchy was updated. Composing a layout guide
makes it transparent to the current while staying compatible with iOS
12.

Bug: 996669
Change-Id: I480c9955f911731ac4a30acc83519c9de03ecfdc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1765669
Commit-Queue: Javier Ernesto Flores Robles <javierrobles@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#692042}
parent e7daf77d
......@@ -84,7 +84,7 @@ CGFloat const kInputAccessoryHeight = 44.0f;
// used to constraint any presented view. iPad always presents in a separate
// popover.
- (BOOL)canPresentView {
return KeyboardObserverHelper.keyboardLayoutGuide || IsIPadIdiom();
return IsIPadIdiom() || KeyboardObserverHelper.keyboardLayoutGuide;
}
#pragma mark - Public
......
......@@ -7,7 +7,7 @@
#import <UIKit/UIKit.h>
@protocol LayoutGuideProvider;
@protocol EdgeLayoutGuideProvider;
// Struct to track the current keyboard state.
typedef struct {
......@@ -49,7 +49,7 @@ typedef struct {
// Best layout guide for the keyboard including the prediction part of it. |nil|
// if the keyboard is not present or found.
// This can break on any iOS update to keyboard architecture.
@property(class, readonly, nonatomic) id<LayoutGuideProvider>
@property(class, readonly, nonatomic) id<EdgeLayoutGuideProvider>
keyboardLayoutGuide;
// Flag that indicates if the keyboard is on screen.
......
......@@ -4,6 +4,7 @@
#import "ios/chrome/browser/ui/util/keyboard_observer_helper.h"
#include "base/logging.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
......@@ -83,8 +84,22 @@
return nil;
}
+ (id<LayoutGuideProvider>)keyboardLayoutGuide {
return [self keyboardLayoutGuideInHostView:self.keyboardView];
+ (id<EdgeLayoutGuideProvider>)keyboardLayoutGuide {
// [iOS 13] Sometimes there is an input assistant provided by the system.
// I.e. autocorrection or passwords. A composed guide is needed because
// there isn't a view "hugging" the accessory and the keyboard. This used to
// be merged with the keyboard < iOS 13.
//
// [iOS 13][iOS 12][iPhone][iPhoneX]: "Backdrop" works even when a picker is
// present.
for (NSString* name in @[ @"InputAssistant", @"Backdrop" ]) {
id<EdgeLayoutGuideProvider> layout =
[self keyboardLayoutGuideInHostView:self.keyboardView withName:name];
if (layout) {
return layout;
}
}
return nil;
}
#pragma mark - Keyboard Notifications
......@@ -108,23 +123,32 @@
#pragma mark - Private
// Returns a layout guide defined by the top edge of |view| and its window.
+ (id<EdgeLayoutGuideProvider>)topEdgeLayoutGuideForView:(UIView*)view {
ComposedEdgeLayoutGuide* layoutGuide = [[ComposedEdgeLayoutGuide alloc] init];
layoutGuide.baseLayoutGuide = view.window;
layoutGuide.topAnchorProvider = view;
return layoutGuide;
}
// This searches in the passed view hierarchy for the best Layout Guide for the
// keyboard.
+ (id<LayoutGuideProvider>)keyboardLayoutGuideInHostView:(UIView*)hostView {
// keyboard. Unexpected behaviour on iPad.
+ (id<EdgeLayoutGuideProvider>)keyboardLayoutGuideInHostView:(UIView*)hostView
withName:(NSString*)name {
DCHECK(!IsIPadIdiom());
for (UIView* subview in hostView.subviews) {
// Currently only tested on X-iOS12, 6+-iOS11 and 7+-iOS10. iPhoneX, iOS 11
// and 12 uses "Dock" and iOS 10 uses "Backdrop". iPhone6+, iOS 11 uses
// "Dock".
if ([NSStringFromClass([subview class]) containsString:@"Dock"] ||
[NSStringFromClass([subview class]) containsString:@"Backdrop"]) {
return subview;
if ([NSStringFromClass([subview class]) containsString:name]) {
return [self topEdgeLayoutGuideForView:subview];
}
id<LayoutGuideProvider> found =
[self keyboardLayoutGuideInHostView:subview];
// Continue searching recursively.
id<EdgeLayoutGuideProvider> found =
[self keyboardLayoutGuideInHostView:subview withName:name];
if (found) {
return found;
}
}
return nil;
}
......
......@@ -50,14 +50,32 @@ inline ChromeDirectionalEdgeInsets ChromeDirectionalEdgeInsetsMake(
return insets;
}
// Defines a protocol for common -...Anchor methods of UIView and UILayoutGuide.
@protocol LayoutGuideProvider<NSObject>
// Defines a protocol for the edge anchor methods of UIView and UILayoutGuide.
@protocol EdgeLayoutGuideProvider <NSObject>
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* leadingAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* trailingAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* leftAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* rightAnchor;
@property(nonatomic, readonly, strong) NSLayoutYAxisAnchor* topAnchor;
@property(nonatomic, readonly, strong) NSLayoutYAxisAnchor* bottomAnchor;
@end
// ComposedEdgeLayoutGuide is a layout guide based on the anchors of
// multiple views. It is useful when the details of how the layout guide is
// created shouldn't be exposed.
@interface ComposedEdgeLayoutGuide : NSObject <EdgeLayoutGuideProvider>
// A base layout guide to serve the anchors not defined with a provider.
@property(nonatomic, weak) id<EdgeLayoutGuideProvider> baseLayoutGuide;
// Each of the edge anchors can have a different provider assigned. If none is
// assigned, the respective |baseLayoutGuide| anchor will be returned.
@property(nonatomic, weak) id<EdgeLayoutGuideProvider> leadingAnchorProvider;
@property(nonatomic, weak) id<EdgeLayoutGuideProvider> trailingAnchorProvider;
@property(nonatomic, weak) id<EdgeLayoutGuideProvider> topAnchorProvider;
@property(nonatomic, weak) id<EdgeLayoutGuideProvider> bottomAnchorProvider;
@end
// Defines a protocol for common -...Anchor methods of UIView and UILayoutGuide.
@protocol LayoutGuideProvider <EdgeLayoutGuideProvider>
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* leftAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* rightAnchor;
@property(nonatomic, readonly, strong) NSLayoutDimension* widthAnchor;
@property(nonatomic, readonly, strong) NSLayoutDimension* heightAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* centerXAnchor;
......@@ -133,24 +151,24 @@ void AddSameCenterYConstraint(UIView* unused_parentView,
// Adds constraints to make two views' size and center equal by pinning leading,
// trailing, top and bottom anchors.
void AddSameConstraints(id<LayoutGuideProvider> view1,
id<LayoutGuideProvider> view2);
void AddSameConstraints(id<EdgeLayoutGuideProvider> view1,
id<EdgeLayoutGuideProvider> view2);
// Constraints all sides of |innerView| and |outerView| together, with
// |innerView| inset by |insets|.
void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView,
void AddSameConstraintsWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<EdgeLayoutGuideProvider> outerView,
ChromeDirectionalEdgeInsets insets);
// Adds constraints to make |innerView| leading, trailing, top and bottom
// anchors equals to |outerView| safe area (or view bounds) anchors.
void PinToSafeArea(id<LayoutGuideProvider> innerView, UIView* outerView);
void PinToSafeArea(id<EdgeLayoutGuideProvider> innerView, UIView* outerView);
// Constraints |side_flags| of |view1| and |view2| together.
// Example usage: AddSameConstraintsToSides(view1, view2,
// LayoutSides::kTop|LayoutSides::kLeading)
void AddSameConstraintsToSides(id<LayoutGuideProvider> view1,
id<LayoutGuideProvider> view2,
void AddSameConstraintsToSides(id<EdgeLayoutGuideProvider> view1,
id<EdgeLayoutGuideProvider> view2,
LayoutSides side_flags);
// Constraints |side_flags| sides of |innerView| and |outerView| together, with
......@@ -160,8 +178,8 @@ void AddSameConstraintsToSides(id<LayoutGuideProvider> view1,
// 10, 5}) - This will constraint innerView to be inside of outerView, with
// leading/trailing inset by 10 and top/bottom inset by 5.
// Edge insets for sides not listed in |side_flags| are ignored.
void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView,
void AddSameConstraintsToSidesWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<EdgeLayoutGuideProvider> outerView,
LayoutSides side_flags,
ChromeDirectionalEdgeInsets insets);
......@@ -172,12 +190,12 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
// the padding is optional so that the inner views are not artificially
// shortened when fixed-size cells cut into that padding. The padding is added
// between |outerView| and |innerView|.
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView,
id<LayoutGuideProvider> innerView,
void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<EdgeLayoutGuideProvider> innerView,
CGFloat padding);
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView,
id<LayoutGuideProvider> topInnerView,
id<LayoutGuideProvider> bottomInnerView,
void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<EdgeLayoutGuideProvider> topInnerView,
id<EdgeLayoutGuideProvider> bottomInnerView,
CGFloat padding);
#pragma mark - Safe Area.
......
......@@ -12,6 +12,24 @@
#error "This file requires ARC support."
#endif
@implementation ComposedEdgeLayoutGuide
- (NSLayoutXAxisAnchor*)leadingAnchor {
return self.leadingAnchorProvider.leadingAnchor
?: self.baseLayoutGuide.leadingAnchor;
}
- (NSLayoutXAxisAnchor*)trailingAnchor {
return self.trailingAnchorProvider.trailingAnchor
?: self.baseLayoutGuide.trailingAnchor;
}
- (NSLayoutYAxisAnchor*)topAnchor {
return self.topAnchorProvider.topAnchor ?: self.baseLayoutGuide.topAnchor;
}
- (NSLayoutYAxisAnchor*)bottomAnchor {
return self.bottomAnchorProvider.bottomAnchor
?: self.baseLayoutGuide.bottomAnchor;
}
@end
void ApplyVisualConstraints(NSArray* constraints,
NSDictionary* subviewsDictionary) {
ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
......@@ -90,8 +108,8 @@ void AddSameCenterYConstraint(UIView* unused_parentView,
AddSameCenterYConstraint(subview1, subview2);
}
void AddSameConstraints(id<LayoutGuideProvider> view1,
id<LayoutGuideProvider> view2) {
void AddSameConstraints(id<EdgeLayoutGuideProvider> view1,
id<EdgeLayoutGuideProvider> view2) {
[NSLayoutConstraint activateConstraints:@[
[view1.leadingAnchor constraintEqualToAnchor:view2.leadingAnchor],
[view1.trailingAnchor constraintEqualToAnchor:view2.trailingAnchor],
......@@ -100,8 +118,8 @@ void AddSameConstraints(id<LayoutGuideProvider> view1,
]];
}
void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView,
void AddSameConstraintsWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<EdgeLayoutGuideProvider> outerView,
ChromeDirectionalEdgeInsets insets) {
AddSameConstraintsToSidesWithInsets(
innerView, outerView,
......@@ -110,19 +128,19 @@ void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView,
insets);
}
void PinToSafeArea(id<LayoutGuideProvider> innerView, UIView* outerView) {
void PinToSafeArea(id<EdgeLayoutGuideProvider> innerView, UIView* outerView) {
AddSameConstraints(innerView, outerView.safeAreaLayoutGuide);
}
void AddSameConstraintsToSides(id<LayoutGuideProvider> view1,
id<LayoutGuideProvider> view2,
void AddSameConstraintsToSides(id<EdgeLayoutGuideProvider> view1,
id<EdgeLayoutGuideProvider> view2,
LayoutSides side_flags) {
AddSameConstraintsToSidesWithInsets(
view1, view2, side_flags, ChromeDirectionalEdgeInsetsMake(0, 0, 0, 0));
}
void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView,
void AddSameConstraintsToSidesWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<EdgeLayoutGuideProvider> outerView,
LayoutSides side_flags,
ChromeDirectionalEdgeInsets insets) {
NSMutableArray* constraints = [[NSMutableArray alloc] init];
......@@ -150,15 +168,15 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
[NSLayoutConstraint activateConstraints:constraints];
}
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView,
id<LayoutGuideProvider> innerView,
void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<EdgeLayoutGuideProvider> innerView,
CGFloat padding) {
AddOptionalVerticalPadding(outerView, innerView, innerView, padding);
}
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView,
id<LayoutGuideProvider> topInnerView,
id<LayoutGuideProvider> bottomInnerView,
void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<EdgeLayoutGuideProvider> topInnerView,
id<EdgeLayoutGuideProvider> bottomInnerView,
CGFloat padding) {
NSLayoutConstraint* topPaddingConstraint = [topInnerView.topAnchor
constraintGreaterThanOrEqualToAnchor:outerView.topAnchor
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment