Commit 86474bf1 authored by adamta's avatar adamta Committed by Chromium LUCI CQ

[iOS] Sticky omnibox for refactored NTP

This CL allows for the fake omnibox to stick to the top of the NTP when
scrolling through the page.

The width animation before sticking is handled by the
ContentSuggestionsLayout, as it was before. This layout will perform the
initial "stick" once the animation is over.

Once the user scrolls passed the ContentSuggestionsHeader, the fake
omnibox ownership is transferred to the Discover feed, so that it could
stick to the top of the NTP and receive tap actions.

Bug: 1114792
Change-Id: I15d5c32cba1aedfe0756b23c19a55f59d48806ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2622314
Commit-Queue: Adam Trudeau-Arcaro <adamta@google.com>
Reviewed-by: default avatarSergio Collazos <sczs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842835}
parent 5dca5ccd
......@@ -59,6 +59,10 @@ class ReadingListModel;
// omnibox.
- (void)focusFakebox;
// Returns the height of the content suggestions header, not including the
// omnibox.
- (CGFloat)heightAboveFakeOmnibox;
@end
#endif // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_HEADER_VIEW_CONTROLLER_H_
......@@ -150,6 +150,14 @@ const NSString* kScribbleFakeboxElementId = @"fakebox";
[self.accessibilityButton removeObserver:self forKeyPath:@"highlighted"];
}
- (CGFloat)heightAboveFakeOmnibox {
return self.view.frame.size.height -
ntp_header::kFakeOmniboxScrolledToTopMargin -
self.fakeOmnibox.frame.size.height -
ToolbarExpandedHeight(
[UIApplication sharedApplication].preferredContentSizeCategory);
}
#pragma mark - ContentSuggestionsHeaderControlling
- (void)updateFakeOmniboxForOffset:(CGFloat)offset
......
......@@ -19,6 +19,14 @@
// The total scroll height of the NTP.
@property(nonatomic, assign) CGFloat ntpHeight;
// The parent collection view that contains the content suggestions collection
// view.
@property(nonatomic, weak) UICollectionView* parentCollectionView;
// Whether or not the user has scrolled into the feed, transferring ownership of
// the omnibox to allow it to stick to the top of the NTP.
@property(nonatomic, assign) BOOL isScrolledIntoFeed;
// Creates layout with |offset| as additional height. Allows the view's height
// to be increased enough to maintain the scroll position. Only needed if
// Discover feed is enabled.
......
......@@ -115,8 +115,14 @@ layoutAttributesForSupplementaryViewOfKind:(NSString*)kind
if ([kind isEqualToString:UICollectionElementKindSectionHeader] &&
indexPath.section == 0) {
UICollectionView* collectionView = self.collectionView;
CGPoint contentOffset = collectionView.contentOffset;
CGFloat contentOffset;
if (IsRefactoredNTP()) {
contentOffset = self.parentCollectionView.contentOffset.y +
self.collectionView.contentSize.height;
} else {
contentOffset = self.collectionView.contentOffset.y;
}
CGFloat headerHeight = CGRectGetHeight(attributes.frame);
CGPoint origin = attributes.frame.origin;
......@@ -130,8 +136,10 @@ layoutAttributesForSupplementaryViewOfKind:(NSString*)kind
ToolbarExpandedHeight(
[UIApplication sharedApplication].preferredContentSizeCategory) -
topSafeArea;
if (contentOffset.y > minY)
origin.y = contentOffset.y - minY;
if (contentOffset > minY &&
(!IsRefactoredNTP() || !self.isScrolledIntoFeed)) {
origin.y = contentOffset - minY;
}
attributes.frame = {origin, attributes.frame.size};
}
return attributes;
......
......@@ -96,6 +96,9 @@ NSString* const kContentSuggestionsMostVisitedAccessibilityIdentifierPrefix =
// The CollectionViewController scroll position when an scrolling event starts.
@property(nonatomic, assign) int scrollStartPosition;
// The layout of the content suggestions collection view.
@property(nonatomic, strong) ContentSuggestionsLayout* layout;
@end
@implementation ContentSuggestionsViewController
......@@ -115,9 +118,8 @@ NSString* const kContentSuggestionsMostVisitedAccessibilityIdentifierPrefix =
- (instancetype)initWithStyle:(CollectionViewControllerStyle)style
offset:(CGFloat)offset {
_offset = offset;
UICollectionViewLayout* layout =
[[ContentSuggestionsLayout alloc] initWithOffset:offset];
self = [super initWithLayout:layout style:style];
_layout = [[ContentSuggestionsLayout alloc] initWithOffset:offset];
self = [super initWithLayout:_layout style:style];
if (self) {
_collectionUpdater = [[ContentSuggestionsCollectionUpdater alloc] init];
_initialContentOffset = NAN;
......@@ -309,6 +311,9 @@ NSString* const kContentSuggestionsMostVisitedAccessibilityIdentifierPrefix =
[self.collectionView.collectionViewLayout invalidateLayout];
// Ensure initial fake omnibox layout.
[self.headerSynchronizer updateFakeOmniboxOnCollectionScroll];
// TODO(crbug.com/1114792): Plumb the collection view.
self.layout.parentCollectionView =
static_cast<UICollectionView*>(self.view.superview);
}
- (void)viewDidAppear:(BOOL)animated {
......@@ -319,9 +324,7 @@ NSString* const kContentSuggestionsMostVisitedAccessibilityIdentifierPrefix =
// Remove forced height if it was already applied, since the scroll position
// was already maintained.
if (self.offset > 0) {
ContentSuggestionsLayout* layout = static_cast<ContentSuggestionsLayout*>(
self.collectionView.collectionViewLayout);
layout.offset = 0;
self.layout.offset = 0;
}
}
......
......@@ -4,6 +4,7 @@
source_set("ntp") {
sources = [
"new_tab_page_content_delegate.h",
"new_tab_page_controller_delegate.h",
"new_tab_page_header_constants.h",
"new_tab_page_header_constants.mm",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
// Delegate for actions relating to the NTP content.
@protocol NewTabPageContentDelegate
// Reloads content suggestions collection view.
- (void)reloadContentSuggestions;
// Returns the height of the content suggestions header, not including the
// omnibox.
- (CGFloat)heightAboveFakeOmnibox;
@end
#endif // IOS_CHROME_BROWSER_UI_NTP_NEW_TAB_PAGE_CONTENT_DELEGATE_H_
......@@ -22,6 +22,7 @@
#import "ios/chrome/browser/ui/main/scene_state_observer.h"
#import "ios/chrome/browser/ui/ntp/discover_feed_wrapper_view_controller.h"
#import "ios/chrome/browser/ui/ntp/incognito_view_controller.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_view_controller.h"
#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
......@@ -37,7 +38,8 @@
#error "This file requires ARC support."
#endif
@interface NewTabPageCoordinator () <OverscrollActionsControllerDelegate,
@interface NewTabPageCoordinator () <NewTabPageContentDelegate,
OverscrollActionsControllerDelegate,
SceneStateObserver>
// Coordinator for the ContentSuggestions.
......@@ -132,6 +134,10 @@
self.ntpViewController.discoverFeedWrapperViewController =
self.discoverFeedWrapperViewController;
self.ntpViewController.overscrollDelegate = self;
self.ntpViewController.ntpContentDelegate = self;
self.ntpViewController.headerController =
self.contentSuggestionsCoordinator.headerController;
}
base::RecordAction(base::UserMetricsAction("MobileNTPShowMostVisited"));
......@@ -228,7 +234,7 @@
if (IsRefactoredNTP()) {
ios::GetChromeBrowserProvider()->GetDiscoverFeedProvider()->RefreshFeed();
}
[self.contentSuggestionsCoordinator reload];
[self reloadContentSuggestions];
}
- (void)locationBarDidBecomeFirstResponder {
......@@ -329,4 +335,15 @@
return nullptr;
}
#pragma mark - NewTabPageContentDelegate
- (void)reloadContentSuggestions {
[self.contentSuggestionsCoordinator reload];
}
- (CGFloat)heightAboveFakeOmnibox {
return [self.contentSuggestionsCoordinator
.headerController heightAboveFakeOmnibox];
}
@end
......@@ -9,8 +9,10 @@
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_controlling.h"
@class ContentSuggestionsHeaderViewController;
@class ContentSuggestionsViewController;
@class DiscoverFeedWrapperViewController;
@protocol NewTabPageContentDelegate;
@protocol OverscrollActionsControllerDelegate;
// View controller containing all the content presented on a standard,
......@@ -27,6 +29,12 @@
@property(nonatomic, weak) id<OverscrollActionsControllerDelegate>
overscrollDelegate;
// The content suggestions header, containing the fake omnibox and the doodle.
@property(nonatomic, weak) UIViewController* headerController;
// Delegate for actions relating to the NTP content.
@property(nonatomic, weak) id<NewTabPageContentDelegate> ntpContentDelegate;
// Initializes view controller with NTP content view controllers.
// |discoverFeedViewController| represents the Discover feed for suggesting
// articles. |contentSuggestionsViewController| represents other content
......
......@@ -8,9 +8,12 @@
#import "base/check.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizing.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/ntp/discover_feed_wrapper_view_controller.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_content_delegate.h"
#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
#import "ios/chrome/browser/ui/util/named_guide.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
......@@ -18,6 +21,15 @@
#error "This file requires ARC support."
#endif
namespace {
// The offset from the bottom of the content suggestions header before changing
// ownership of the fake omnibox. This value can be a large range of numbers, as
// long as it is larger than the omnibox height (plus an addional offset to make
// it look smooth). Otherwise, the omnibox hides beneath the feed before
// changing ownership.
const CGFloat kOffsetToPinOmnibox = 100;
}
@interface NewTabPageViewController ()
// View controller representing the NTP content suggestions. These suggestions
......@@ -30,6 +42,14 @@
@property(nonatomic, strong)
OverscrollActionsController* overscrollActionsController;
// Whether or not the user has scrolled into the feed, transferring ownership of
// the omnibox to allow it to stick to the top of the NTP.
@property(nonatomic, assign, getter=isScrolledIntoFeed) BOOL scrolledIntoFeed;
// The collection view layout for the uppermost content suggestions collection
// view.
@property(nonatomic, weak) ContentSuggestionsLayout* contentSuggestionsLayout;
@end
@implementation NewTabPageViewController
......@@ -43,6 +63,9 @@
self = [super initWithNibName:nil bundle:nil];
if (self) {
_contentSuggestionsViewController = contentSuggestionsViewController;
// TODO(crbug.com/1114792): Instantiate this depending on the initial scroll
// position.
_scrolledIntoFeed = NO;
}
return self;
......@@ -99,6 +122,11 @@
[self updateOverscrollActionsState];
self.view.backgroundColor = ntp_home::kNTPBackgroundColor();
_contentSuggestionsLayout = static_cast<ContentSuggestionsLayout*>(
self.contentSuggestionsViewController.collectionView
.collectionViewLayout);
_contentSuggestionsLayout.isScrolledIntoFeed = self.isScrolledIntoFeed;
}
- (void)viewDidLayoutSubviews {
......@@ -185,6 +213,23 @@
[self.headerSynchronizer updateFakeOmniboxOnCollectionScroll];
self.scrolledToTop =
scrollView.contentOffset.y >= [self.headerSynchronizer pinnedOffsetY];
// Fixes the content suggestions collection view layout so that the header
// scrolls at the same rate as the rest.
if (scrollView.contentOffset.y > -self.contentSuggestionsViewController
.collectionView.contentSize.height) {
[self.contentSuggestionsViewController.collectionView
.collectionViewLayout invalidateLayout];
}
// Changes ownership of fake omnibox view based on scroll position.
if (!self.isScrolledIntoFeed &&
scrollView.contentOffset.y > -kOffsetToPinOmnibox) {
[self setIsScrolledIntoFeed:YES];
[self stickFakeOmniboxToTop];
} else if (self.isScrolledIntoFeed &&
scrollView.contentOffset.y <= -kOffsetToPinOmnibox) {
[self setIsScrolledIntoFeed:NO];
[self resetFakeOmnibox];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
......@@ -250,4 +295,45 @@
}
}
// Lets this view own the fake omnibox and sticks it to the top of the NTP.
- (void)stickFakeOmniboxToTop {
[self.view addSubview:self.headerController.view];
[NSLayoutConstraint activateConstraints:@[
[self.headerController.view.topAnchor
constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
.topAnchor
constant:-([self.ntpContentDelegate
heightAboveFakeOmnibox] +
2)],
[self.headerController.view.leadingAnchor
constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
.leadingAnchor],
[self.headerController.view.trailingAnchor
constraintEqualToAnchor:self.discoverFeedWrapperViewController.view
.trailingAnchor],
[self.headerController.view.heightAnchor
constraintEqualToConstant:self.headerController.view.frame.size.height],
]];
}
// Gives content suggestions collection view ownership of the fake omnibox for
// the width animation.
- (void)resetFakeOmnibox {
[self.headerController.view removeFromSuperview];
// Reload the content suggestions so that the fake omnibox goes back where it
// belongs. This can probably be optimized by just reloading the header, if
// that doesn't mess up any collection/header interactions.
[self.ntpContentDelegate reloadContentSuggestions];
}
#pragma mark - Setters
// Sets whether or not the NTP is scrolled into the feed and notifies the
// content suggestions layout to avoid it changing the omnibox frame when this
// view controls its position.
- (void)setIsScrolledIntoFeed:(BOOL)scrolledIntoFeed {
_scrolledIntoFeed = scrolledIntoFeed;
self.contentSuggestionsLayout.isScrolledIntoFeed = scrolledIntoFeed;
}
@end
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