Commit c76f7f26 authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Add ability to specify frames for NamedGuide.

This CL adds the ability to specify constraints for a NamedGuide using
frames and autoresizing masks.  This will be used to support laying out
the NamedGuide corresponding with the voice search button when voice
search is being triggered by the keyboard accessory view, as this view
is in a separate window from the rest of the application's hierarchy.

Bug: 805123
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Icf6207f38629de2f5eac381b4f489f7213ecacd1
Reviewed-on: https://chromium-review.googlesource.com/952779Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545192}
parent 6ffc648f
...@@ -23,14 +23,35 @@ ...@@ -23,14 +23,35 @@
// or one of |view|'s ancestors. If no guide is found, returns nil. // or one of |view|'s ancestors. If no guide is found, returns nil.
+ (instancetype)guideWithName:(GuideName*)name view:(UIView*)view; + (instancetype)guideWithName:(GuideName*)name view:(UIView*)view;
// Resets |constrainedView| and |constrainedFrame|, deactivating constraints
// that were created to support following the view/frame. Note that calling
// this function has no effect on constraints that were created outside of this
// class.
- (void)resetConstraints;
// The GuideName passed on initialization. // The GuideName passed on initialization.
@property(nonatomic, readonly) GuideName* name; @property(nonatomic, readonly) GuideName* name;
// The view to which this guide should be constrained. Setting this property // The view to which this guide should be constrained. Setting this property
// to a new value will update the guide's constraints to match the new view. // to a new value will update the guide's constraints to match the new view.
// Setting to nil removes constraints. // Setting to nil removes constraints. Setting this property to a non-nil value
// will reset |constrainedFrame| to CGRectNull.
@property(nonatomic, weak) UIView* constrainedView; @property(nonatomic, weak) UIView* constrainedView;
// The frame to which this guide should be constrained, in the guide's owning
// view's coordinate system. This can be used to specify locations that don't
// correspond with a particular view, or correspond to views in different
// windows. Setting this property to a new value will update the guide's
// constraints to match the specified frame according to |autoresizingMask|.
// Setting to CGRectNull removes constraints. Setting this property to a non-
// CGRectNull value will reset |constrainedView| to nil.
@property(nonatomic, assign) CGRect constrainedFrame;
// The autoresizing behavior to use when setting up constraints for
// |constrainedFrame|. This property has no effect if |constrainedFrame| is
// CGRectNull.
@property(nonatomic, assign) UIViewAutoresizing autoresizingMask;
@end @end
#endif // IOS_CHROME_BROWSER_UI_UTIL_NAMED_GUIDE_H_ #endif // IOS_CHROME_BROWSER_UI_UTIL_NAMED_GUIDE_H_
...@@ -14,25 +14,43 @@ ...@@ -14,25 +14,43 @@
@interface NamedGuide () @interface NamedGuide ()
// The constraints used to connect the guide to |constrainedView|. // The constraints used to connect the guide to |constrainedView| or
@property(nonatomic, strong) NSArray* constrainedViewConstraints; // |constrainedFrame|.
@property(nonatomic, strong) NSArray* constraints;
// A dummy view that is used to support |constrainedFrame|.
@property(nonatomic, strong) UIView* constrainedFrameView;
// Updates |constraints| to constrain the guide to |view|.
- (void)updateConstraintsWithView:(UIView*)view;
// Updates |constrainedFrameView| according to |constrainedFrame| and
// |autoresizingMask|. This function will lazily instantiate the view if
// necessary and set up constraints so that this layout guide follows the view.
- (void)updateConstrainedFrameView;
@end @end
@implementation NamedGuide @implementation NamedGuide
@synthesize name = _name; @synthesize name = _name;
@synthesize constrainedView = _constrainedView; @synthesize constrainedView = _constrainedView;
@synthesize constrainedViewConstraints = _constrainedViewConstraints; @synthesize constrainedFrame = _constrainedFrame;
@synthesize autoresizingMask = _autoresizingMask;
@synthesize constraints = _constraints;
@synthesize constrainedFrameView = _constrainedFrameView;
- (instancetype)initWithName:(GuideName*)name { - (instancetype)initWithName:(GuideName*)name {
if (self = [super init]) { if (self = [super init]) {
_name = name; _name = name;
_constrainedFrame = CGRectNull;
} }
return self; return self;
} }
- (void)dealloc { - (void)dealloc {
self.constrainedView = nil; _constrainedView = nil;
_constrainedFrame = CGRectNull;
if (_constraints.count)
[NSLayoutConstraint deactivateConstraints:_constraints];
} }
#pragma mark - Accessors #pragma mark - Accessors
...@@ -40,29 +58,58 @@ ...@@ -40,29 +58,58 @@
- (void)setConstrainedView:(UIView*)constrainedView { - (void)setConstrainedView:(UIView*)constrainedView {
if (_constrainedView == constrainedView) if (_constrainedView == constrainedView)
return; return;
// Reset the constrained frame to null if specifying a new constrained view.
if (constrainedView)
self.constrainedFrame = CGRectNull;
_constrainedView = constrainedView; _constrainedView = constrainedView;
if (_constrainedView) { [self updateConstraintsWithView:_constrainedView];
self.constrainedViewConstraints = @[
[self.leadingAnchor
constraintEqualToAnchor:_constrainedView.leadingAnchor],
[self.trailingAnchor
constraintEqualToAnchor:_constrainedView.trailingAnchor],
[self.topAnchor constraintEqualToAnchor:_constrainedView.topAnchor],
[self.bottomAnchor constraintEqualToAnchor:_constrainedView.bottomAnchor]
];
} else {
self.constrainedViewConstraints = nil;
}
} }
- (void)setConstrainedViewConstraints:(NSArray*)constrainedViewConstraints { - (void)setConstrainedFrame:(CGRect)constrainedFrame {
if (_constrainedViewConstraints == constrainedViewConstraints) if (CGRectEqualToRect(_constrainedFrame, constrainedFrame))
return; return;
if (_constrainedViewConstraints.count)
[NSLayoutConstraint deactivateConstraints:_constrainedViewConstraints]; // Reset the constrained view to nil if specifying a new constrained frame.
_constrainedViewConstraints = constrainedViewConstraints; if (!CGRectIsNull(constrainedFrame))
if (_constrainedViewConstraints.count) self.constrainedView = nil;
[NSLayoutConstraint activateConstraints:_constrainedViewConstraints];
_constrainedFrame = constrainedFrame;
[self updateConstrainedFrameView];
}
- (void)setAutoresizingMask:(UIViewAutoresizing)autoresizingMask {
if (_autoresizingMask == autoresizingMask)
return;
_autoresizingMask = autoresizingMask;
[self updateConstrainedFrameView];
}
- (void)setConstraints:(NSArray*)constraints {
if (_constraints == constraints)
return;
if (_constraints.count)
[NSLayoutConstraint deactivateConstraints:_constraints];
_constraints = constraints;
if (_constraints.count)
[NSLayoutConstraint activateConstraints:_constraints];
}
- (void)setConstrainedFrameView:(UIView*)constrainedFrameView {
if (_constrainedFrameView == constrainedFrameView)
return;
if (_constrainedFrameView)
[_constrainedFrameView removeFromSuperview];
_constrainedFrameView = constrainedFrameView;
// The constrained frame view is inserted at the bottom of the owning view's
// hierarchy in an effort to minimize additional rendering costs.
if (_constrainedFrameView)
[self.owningView insertSubview:_constrainedFrameView atIndex:0];
[self updateConstraintsWithView:_constrainedFrameView];
} }
#pragma mark - Public #pragma mark - Public
...@@ -79,4 +126,44 @@ ...@@ -79,4 +126,44 @@
return nil; return nil;
} }
- (void)resetConstraints {
self.constrainedView = nil;
self.constrainedFrame = CGRectNull;
}
#pragma mark - Private
- (void)updateConstraintsWithView:(UIView*)view {
if (view) {
self.constraints = @[
[self.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
[self.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
[self.topAnchor constraintEqualToAnchor:view.topAnchor],
[self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor]
];
} else {
self.constraints = nil;
}
}
- (void)updateConstrainedFrameView {
// Remove the dummy view if |constrainedFrame| is null.
if (CGRectIsNull(self.constrainedFrame)) {
self.constrainedFrameView = nil;
return;
}
// Lazily create the view if necessary and set it up using the specified frame
// and autoresizing mask.
// NOTE: The view's |translatesAutoresizingMaskIntoConstraints| remains set to
// the default value of |YES| in order to leverage UIKit's built in frame =>
// constraint conversion.
if (!self.constrainedFrameView) {
self.constrainedFrameView = [[UIView alloc] init];
self.constrainedFrameView.backgroundColor = [UIColor clearColor];
}
self.constrainedFrameView.frame = self.constrainedFrame;
self.constrainedFrameView.autoresizingMask = self.autoresizingMask;
}
@end @end
...@@ -12,6 +12,15 @@ ...@@ -12,6 +12,15 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
namespace {
// Tests that |guide|'s layoutFrame is equal to |frame|.
void VerifyLayoutFrame(UILayoutGuide* guide, CGRect frame) {
[guide.owningView setNeedsLayout];
[guide.owningView layoutIfNeeded];
EXPECT_TRUE(CGRectEqualToRect(guide.layoutFrame, frame));
}
} // namespace
using NamedGuideTest = PlatformTest; using NamedGuideTest = PlatformTest;
// Tests that guides are reachable after being added to a view. // Tests that guides are reachable after being added to a view.
...@@ -64,7 +73,7 @@ TEST_F(NamedGuideTest, TestGuideOnAncestor) { ...@@ -64,7 +73,7 @@ TEST_F(NamedGuideTest, TestGuideOnAncestor) {
} }
// Tests that resetting the constrained view updates the guide. // Tests that resetting the constrained view updates the guide.
TEST_F(NamedGuideTest, TestConstrainedViewUpdate) { TEST_F(NamedGuideTest, TestConstrainedView) {
GuideName* test_guide = @"NamedGuideTest"; GuideName* test_guide = @"NamedGuideTest";
UIWindow* window = UIWindow* window =
...@@ -81,8 +90,62 @@ TEST_F(NamedGuideTest, TestConstrainedViewUpdate) { ...@@ -81,8 +90,62 @@ TEST_F(NamedGuideTest, TestConstrainedViewUpdate) {
// is updated. // is updated.
for (UIView* subview in view.subviews) { for (UIView* subview in view.subviews) {
guide.constrainedView = subview; guide.constrainedView = subview;
[view setNeedsLayout]; VerifyLayoutFrame(guide, subview.frame);
[view layoutIfNeeded];
EXPECT_TRUE(CGRectEqualToRect(guide.layoutFrame, subview.frame));
} }
} }
// Tests that resetting the constrained frame updates the guide.
TEST_F(NamedGuideTest, TestConstrainedFrame) {
GuideName* test_guide = @"NamedGuideTest";
UIWindow* window =
[[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[window addSubview:view];
NamedGuide* guide = [[NamedGuide alloc] initWithName:test_guide];
[view addLayoutGuide:guide];
// Test updating the guide's |constrainedFrame| to the lower left corner.
const CGRect kLowerLeftCorner = CGRectMake(0, 50, 50, 50);
guide.constrainedFrame = kLowerLeftCorner;
VerifyLayoutFrame(guide, kLowerLeftCorner);
// Tests that updating the view's size stretches the layout frame such that
// it remains the lower left quadrant.
guide.autoresizingMask =
(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleRightMargin);
view.frame = CGRectMake(0, 0, 200, 200);
const CGRect kNewLowerLeftCorner = CGRectMake(0, 100, 100, 100);
VerifyLayoutFrame(guide, kNewLowerLeftCorner);
}
// Tests that setting the |constrainedView| and |contstrainedFrame| correctly
// nullify other properties.
TEST_F(NamedGuideTest, TestConstrainedViewFrameMutex) {
GuideName* test_guide = @"NamedGuideTest";
UIWindow* window =
[[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[window addSubview:view];
UIView* childView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 100)];
[view addSubview:childView];
NamedGuide* guide = [[NamedGuide alloc] initWithName:test_guide];
[view addLayoutGuide:guide];
guide.constrainedView = childView;
// Set the guide's |constrainedFrame| and verify that |constrainedView| is
// reset to nil.
const CGRect kConstrainedFrame = CGRectMake(0, 0, 50, 50);
guide.constrainedFrame = kConstrainedFrame;
EXPECT_FALSE(guide.constrainedView);
VerifyLayoutFrame(guide, kConstrainedFrame);
// Set the guide's |constrainedView| and verify that |constrainedFrame| is
// reset to CGRectNull.
guide.constrainedView = childView;
EXPECT_TRUE(CGRectIsNull(guide.constrainedFrame));
VerifyLayoutFrame(guide, childView.frame);
}
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