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; ...@@ -84,7 +84,7 @@ CGFloat const kInputAccessoryHeight = 44.0f;
// used to constraint any presented view. iPad always presents in a separate // used to constraint any presented view. iPad always presents in a separate
// popover. // popover.
- (BOOL)canPresentView { - (BOOL)canPresentView {
return KeyboardObserverHelper.keyboardLayoutGuide || IsIPadIdiom(); return IsIPadIdiom() || KeyboardObserverHelper.keyboardLayoutGuide;
} }
#pragma mark - Public #pragma mark - Public
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@protocol LayoutGuideProvider; @protocol EdgeLayoutGuideProvider;
// Struct to track the current keyboard state. // Struct to track the current keyboard state.
typedef struct { typedef struct {
...@@ -49,7 +49,7 @@ typedef struct { ...@@ -49,7 +49,7 @@ typedef struct {
// Best layout guide for the keyboard including the prediction part of it. |nil| // Best layout guide for the keyboard including the prediction part of it. |nil|
// if the keyboard is not present or found. // if the keyboard is not present or found.
// This can break on any iOS update to keyboard architecture. // This can break on any iOS update to keyboard architecture.
@property(class, readonly, nonatomic) id<LayoutGuideProvider> @property(class, readonly, nonatomic) id<EdgeLayoutGuideProvider>
keyboardLayoutGuide; keyboardLayoutGuide;
// Flag that indicates if the keyboard is on screen. // Flag that indicates if the keyboard is on screen.
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#import "ios/chrome/browser/ui/util/keyboard_observer_helper.h" #import "ios/chrome/browser/ui/util/keyboard_observer_helper.h"
#include "base/logging.h"
#include "ios/chrome/browser/ui/util/ui_util.h" #include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h" #import "ios/chrome/common/ui_util/constraints_ui_util.h"
...@@ -83,8 +84,22 @@ ...@@ -83,8 +84,22 @@
return nil; return nil;
} }
+ (id<LayoutGuideProvider>)keyboardLayoutGuide { + (id<EdgeLayoutGuideProvider>)keyboardLayoutGuide {
return [self keyboardLayoutGuideInHostView:self.keyboardView]; // [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 #pragma mark - Keyboard Notifications
...@@ -108,23 +123,32 @@ ...@@ -108,23 +123,32 @@
#pragma mark - Private #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 // This searches in the passed view hierarchy for the best Layout Guide for the
// keyboard. // keyboard. Unexpected behaviour on iPad.
+ (id<LayoutGuideProvider>)keyboardLayoutGuideInHostView:(UIView*)hostView { + (id<EdgeLayoutGuideProvider>)keyboardLayoutGuideInHostView:(UIView*)hostView
withName:(NSString*)name {
DCHECK(!IsIPadIdiom());
for (UIView* subview in hostView.subviews) { for (UIView* subview in hostView.subviews) {
// Currently only tested on X-iOS12, 6+-iOS11 and 7+-iOS10. iPhoneX, iOS 11 if ([NSStringFromClass([subview class]) containsString:name]) {
// and 12 uses "Dock" and iOS 10 uses "Backdrop". iPhone6+, iOS 11 uses return [self topEdgeLayoutGuideForView:subview];
// "Dock".
if ([NSStringFromClass([subview class]) containsString:@"Dock"] ||
[NSStringFromClass([subview class]) containsString:@"Backdrop"]) {
return subview;
} }
id<LayoutGuideProvider> found = // Continue searching recursively.
[self keyboardLayoutGuideInHostView:subview]; id<EdgeLayoutGuideProvider> found =
[self keyboardLayoutGuideInHostView:subview withName:name];
if (found) { if (found) {
return found; return found;
} }
} }
return nil; return nil;
} }
......
...@@ -50,14 +50,32 @@ inline ChromeDirectionalEdgeInsets ChromeDirectionalEdgeInsetsMake( ...@@ -50,14 +50,32 @@ inline ChromeDirectionalEdgeInsets ChromeDirectionalEdgeInsetsMake(
return insets; return insets;
} }
// Defines a protocol for common -...Anchor methods of UIView and UILayoutGuide. // Defines a protocol for the edge anchor methods of UIView and UILayoutGuide.
@protocol LayoutGuideProvider<NSObject> @protocol EdgeLayoutGuideProvider <NSObject>
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* leadingAnchor; @property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* leadingAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* trailingAnchor; @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* topAnchor;
@property(nonatomic, readonly, strong) NSLayoutYAxisAnchor* bottomAnchor; @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* widthAnchor;
@property(nonatomic, readonly, strong) NSLayoutDimension* heightAnchor; @property(nonatomic, readonly, strong) NSLayoutDimension* heightAnchor;
@property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* centerXAnchor; @property(nonatomic, readonly, strong) NSLayoutXAxisAnchor* centerXAnchor;
...@@ -133,24 +151,24 @@ void AddSameCenterYConstraint(UIView* unused_parentView, ...@@ -133,24 +151,24 @@ void AddSameCenterYConstraint(UIView* unused_parentView,
// Adds constraints to make two views' size and center equal by pinning leading, // Adds constraints to make two views' size and center equal by pinning leading,
// trailing, top and bottom anchors. // trailing, top and bottom anchors.
void AddSameConstraints(id<LayoutGuideProvider> view1, void AddSameConstraints(id<EdgeLayoutGuideProvider> view1,
id<LayoutGuideProvider> view2); id<EdgeLayoutGuideProvider> view2);
// Constraints all sides of |innerView| and |outerView| together, with // Constraints all sides of |innerView| and |outerView| together, with
// |innerView| inset by |insets|. // |innerView| inset by |insets|.
void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView, void AddSameConstraintsWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView, id<EdgeLayoutGuideProvider> outerView,
ChromeDirectionalEdgeInsets insets); ChromeDirectionalEdgeInsets insets);
// Adds constraints to make |innerView| leading, trailing, top and bottom // Adds constraints to make |innerView| leading, trailing, top and bottom
// anchors equals to |outerView| safe area (or view bounds) anchors. // 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. // Constraints |side_flags| of |view1| and |view2| together.
// Example usage: AddSameConstraintsToSides(view1, view2, // Example usage: AddSameConstraintsToSides(view1, view2,
// LayoutSides::kTop|LayoutSides::kLeading) // LayoutSides::kTop|LayoutSides::kLeading)
void AddSameConstraintsToSides(id<LayoutGuideProvider> view1, void AddSameConstraintsToSides(id<EdgeLayoutGuideProvider> view1,
id<LayoutGuideProvider> view2, id<EdgeLayoutGuideProvider> view2,
LayoutSides side_flags); LayoutSides side_flags);
// Constraints |side_flags| sides of |innerView| and |outerView| together, with // Constraints |side_flags| sides of |innerView| and |outerView| together, with
...@@ -160,8 +178,8 @@ void AddSameConstraintsToSides(id<LayoutGuideProvider> view1, ...@@ -160,8 +178,8 @@ void AddSameConstraintsToSides(id<LayoutGuideProvider> view1,
// 10, 5}) - This will constraint innerView to be inside of outerView, with // 10, 5}) - This will constraint innerView to be inside of outerView, with
// leading/trailing inset by 10 and top/bottom inset by 5. // leading/trailing inset by 10 and top/bottom inset by 5.
// Edge insets for sides not listed in |side_flags| are ignored. // Edge insets for sides not listed in |side_flags| are ignored.
void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView, void AddSameConstraintsToSidesWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView, id<EdgeLayoutGuideProvider> outerView,
LayoutSides side_flags, LayoutSides side_flags,
ChromeDirectionalEdgeInsets insets); ChromeDirectionalEdgeInsets insets);
...@@ -172,12 +190,12 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView, ...@@ -172,12 +190,12 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
// the padding is optional so that the inner views are not artificially // 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 // shortened when fixed-size cells cut into that padding. The padding is added
// between |outerView| and |innerView|. // between |outerView| and |innerView|.
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView, void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<LayoutGuideProvider> innerView, id<EdgeLayoutGuideProvider> innerView,
CGFloat padding); CGFloat padding);
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView, void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<LayoutGuideProvider> topInnerView, id<EdgeLayoutGuideProvider> topInnerView,
id<LayoutGuideProvider> bottomInnerView, id<EdgeLayoutGuideProvider> bottomInnerView,
CGFloat padding); CGFloat padding);
#pragma mark - Safe Area. #pragma mark - Safe Area.
......
...@@ -12,6 +12,24 @@ ...@@ -12,6 +12,24 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #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, void ApplyVisualConstraints(NSArray* constraints,
NSDictionary* subviewsDictionary) { NSDictionary* subviewsDictionary) {
ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary, ApplyVisualConstraintsWithMetricsAndOptions(constraints, subviewsDictionary,
...@@ -90,8 +108,8 @@ void AddSameCenterYConstraint(UIView* unused_parentView, ...@@ -90,8 +108,8 @@ void AddSameCenterYConstraint(UIView* unused_parentView,
AddSameCenterYConstraint(subview1, subview2); AddSameCenterYConstraint(subview1, subview2);
} }
void AddSameConstraints(id<LayoutGuideProvider> view1, void AddSameConstraints(id<EdgeLayoutGuideProvider> view1,
id<LayoutGuideProvider> view2) { id<EdgeLayoutGuideProvider> view2) {
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
[view1.leadingAnchor constraintEqualToAnchor:view2.leadingAnchor], [view1.leadingAnchor constraintEqualToAnchor:view2.leadingAnchor],
[view1.trailingAnchor constraintEqualToAnchor:view2.trailingAnchor], [view1.trailingAnchor constraintEqualToAnchor:view2.trailingAnchor],
...@@ -100,8 +118,8 @@ void AddSameConstraints(id<LayoutGuideProvider> view1, ...@@ -100,8 +118,8 @@ void AddSameConstraints(id<LayoutGuideProvider> view1,
]]; ]];
} }
void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView, void AddSameConstraintsWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView, id<EdgeLayoutGuideProvider> outerView,
ChromeDirectionalEdgeInsets insets) { ChromeDirectionalEdgeInsets insets) {
AddSameConstraintsToSidesWithInsets( AddSameConstraintsToSidesWithInsets(
innerView, outerView, innerView, outerView,
...@@ -110,19 +128,19 @@ void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView, ...@@ -110,19 +128,19 @@ void AddSameConstraintsWithInsets(id<LayoutGuideProvider> innerView,
insets); insets);
} }
void PinToSafeArea(id<LayoutGuideProvider> innerView, UIView* outerView) { void PinToSafeArea(id<EdgeLayoutGuideProvider> innerView, UIView* outerView) {
AddSameConstraints(innerView, outerView.safeAreaLayoutGuide); AddSameConstraints(innerView, outerView.safeAreaLayoutGuide);
} }
void AddSameConstraintsToSides(id<LayoutGuideProvider> view1, void AddSameConstraintsToSides(id<EdgeLayoutGuideProvider> view1,
id<LayoutGuideProvider> view2, id<EdgeLayoutGuideProvider> view2,
LayoutSides side_flags) { LayoutSides side_flags) {
AddSameConstraintsToSidesWithInsets( AddSameConstraintsToSidesWithInsets(
view1, view2, side_flags, ChromeDirectionalEdgeInsetsMake(0, 0, 0, 0)); view1, view2, side_flags, ChromeDirectionalEdgeInsetsMake(0, 0, 0, 0));
} }
void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView, void AddSameConstraintsToSidesWithInsets(id<EdgeLayoutGuideProvider> innerView,
id<LayoutGuideProvider> outerView, id<EdgeLayoutGuideProvider> outerView,
LayoutSides side_flags, LayoutSides side_flags,
ChromeDirectionalEdgeInsets insets) { ChromeDirectionalEdgeInsets insets) {
NSMutableArray* constraints = [[NSMutableArray alloc] init]; NSMutableArray* constraints = [[NSMutableArray alloc] init];
...@@ -150,15 +168,15 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView, ...@@ -150,15 +168,15 @@ void AddSameConstraintsToSidesWithInsets(id<LayoutGuideProvider> innerView,
[NSLayoutConstraint activateConstraints:constraints]; [NSLayoutConstraint activateConstraints:constraints];
} }
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView, void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<LayoutGuideProvider> innerView, id<EdgeLayoutGuideProvider> innerView,
CGFloat padding) { CGFloat padding) {
AddOptionalVerticalPadding(outerView, innerView, innerView, padding); AddOptionalVerticalPadding(outerView, innerView, innerView, padding);
} }
void AddOptionalVerticalPadding(id<LayoutGuideProvider> outerView, void AddOptionalVerticalPadding(id<EdgeLayoutGuideProvider> outerView,
id<LayoutGuideProvider> topInnerView, id<EdgeLayoutGuideProvider> topInnerView,
id<LayoutGuideProvider> bottomInnerView, id<EdgeLayoutGuideProvider> bottomInnerView,
CGFloat padding) { CGFloat padding) {
NSLayoutConstraint* topPaddingConstraint = [topInnerView.topAnchor NSLayoutConstraint* topPaddingConstraint = [topInnerView.topAnchor
constraintGreaterThanOrEqualToAnchor:outerView.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