Commit 5cd74125 authored by Maxime Charland's avatar Maxime Charland Committed by Commit Bot

Chrome Empties: Implement new empty state look for ChromeTVController

In parallel with the current TableViewEmptyView, the new TableViewIllustratedEmptyView will be able to display an image, a title and a subtitle on empty states.
This new class will be accessible from a new helper function in ChromeTableViewController.

(Googlers only links)
Design Doc for Chrome Empties: https://docs.google.com/document/d/1JM2sKT4oghkol51U7_3xv9sfaB26C89CCGa2uwYBgsI/edit?usp=sharing
Mocks of the new Chrome Empties look: https://docs.google.com/presentation/d/1x1lSTOSQ1zkJu87SoigiUhX2f9oHACglR22yYg-lSHo/edit?usp=sharing

Bug: 1098328
Change-Id: Ia54b55d9f2de749879bc0f811bd2629688dd2bb3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2254519
Commit-Queue: Maxime Charland <mcharland@google.com>
Reviewed-by: default avatarSergio Collazos <sczs@chromium.org>
Reviewed-by: default avatarTommy Martino <tmartino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786494}
parent d1e52f66
......@@ -941,7 +941,7 @@ ReadingListSelectionState GetSelectionStateForSelectedCounts(
imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[self addEmptyTableViewWithAttributedMessage:GetReadingListEmptyMessage()
image:emptyImage];
[self updateEmptyTableViewMessageAccessibilityLabel:
[self updateEmptyTableViewAccessibilityLabel:
GetReadingListEmptyMessageA11yLabel()];
self.tableView.alwaysBounceVertical = NO;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
......
......@@ -74,8 +74,11 @@ source_set("presentation") {
source_set("views") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"chrome_empty_table_view_background.h",
"table_view_empty_view.h",
"table_view_empty_view.mm",
"table_view_illustrated_empty_view.h",
"table_view_illustrated_empty_view.mm",
"table_view_loading_view.h",
"table_view_loading_view.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_TABLE_VIEW_CHROME_EMPTY_TABLE_VIEW_BACKGROUND_H_
#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CHROME_EMPTY_TABLE_VIEW_BACKGROUND_H_
// Protocol to which all ChromeTableViewController EmptyViews need to conform to
@protocol ChromeEmptyTableViewBackground
// Insets of the scroll view that contains all the subviews.
@property(nonatomic, assign) UIEdgeInsets scrollViewContentInsets;
// Accessibility label describing the whole EmptyView. Defaults to the entire
// text content of the view.
@property(nonatomic, copy) NSString* viewAccessibilityLabel;
@end
#endif // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CHROME_EMPTY_TABLE_VIEW_BACKGROUND_H_
......@@ -66,9 +66,15 @@ extern const CGFloat kTableViewSeparatorInsetWithIcon;
(NSAttributedString*)attributedMessage
image:(UIImage*)image;
// Updates the accessibility label of the UILabel displaying the empty table
// view message to |newLabel|.
- (void)updateEmptyTableViewMessageAccessibilityLabel:(NSString*)newLabel;
// Adds an empty table view in the center of the ChromeTableViewController which
// displays an image, a title and a subtitle. This will remove any existing
// table view background views.
- (void)addEmptyTableViewWithImage:(UIImage*)image
title:(NSString*)title
subtitle:(NSString*)subtitle;
// Updates the accessibility label of the empty view to |newLabel|.
- (void)updateEmptyTableViewAccessibilityLabel:(NSString*)newLabel;
// Removes the empty table view, if one is present.
- (void)removeEmptyTableView;
......
......@@ -10,8 +10,10 @@
#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_header_footer_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
#import "ios/chrome/browser/ui/table_view/chrome_empty_table_view_background.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
#import "ios/chrome/browser/ui/table_view/table_view_empty_view.h"
#import "ios/chrome/browser/ui/table_view/table_view_illustrated_empty_view.h"
#import "ios/chrome/browser/ui/table_view/table_view_loading_view.h"
#import "ios/chrome/browser/ui/table_view/table_view_model.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
......@@ -27,8 +29,8 @@ const CGFloat kTableViewSeparatorInsetWithIcon = 56;
@interface ChromeTableViewController ()
// The loading displayed by [self startLoadingIndicatorWithLoadingMessage:].
@property(nonatomic, strong) TableViewLoadingView* loadingView;
// The view displayed by [self addEmptyTableViewWithMessage:].
@property(nonatomic, strong) TableViewEmptyView* emptyView;
// The view displayed by [self addEmptyTableViewWith...:].
@property(nonatomic, strong) UIView<ChromeEmptyTableViewBackground>* emptyView;
@end
@implementation ChromeTableViewController
......@@ -144,8 +146,18 @@ const CGFloat kTableViewSeparatorInsetWithIcon = 56;
self.emptyView.tintColor = [UIColor colorNamed:kPlaceholderImageTintColor];
}
- (void)updateEmptyTableViewMessageAccessibilityLabel:(NSString*)newLabel {
self.emptyView.messageAccessibilityLabel = newLabel;
- (void)addEmptyTableViewWithImage:(UIImage*)image
title:(NSString*)title
subtitle:(NSString*)subtitle {
self.emptyView =
[[TableViewIllustratedEmptyView alloc] initWithFrame:self.view.bounds
image:image
title:title
subtitle:subtitle];
}
- (void)updateEmptyTableViewAccessibilityLabel:(NSString*)newLabel {
self.emptyView.viewAccessibilityLabel = newLabel;
}
- (void)removeEmptyTableView {
......
......@@ -7,6 +7,7 @@
#import <Foundation/Foundation.h>
extern NSString* const kTableViewIllustratedEmptyViewID;
extern NSString* const kTableViewEmptyViewID;
#endif // IOS_CHROME_BROWSER_UI_TABLE_VIEW_TABLE_VIEW_CONSTANTS_H_
......@@ -8,4 +8,6 @@
#error "This file requires ARC support."
#endif
NSString* const kTableViewIllustratedEmptyViewID =
@"TableViewIllustratedEmptyView";
NSString* const kTableViewEmptyViewID = @"TableViewEmptyView";
......@@ -7,8 +7,10 @@
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/table_view/chrome_empty_table_view_background.h"
// Displays an UIImage on top of a message over a clearBackground.
@interface TableViewEmptyView : UIView
@interface TableViewEmptyView : UIView <ChromeEmptyTableViewBackground>
// Designated initializer for a view that displays |message| with default
// styling and |image| above the message.
......@@ -24,13 +26,6 @@
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
// The accessibility label to use for the message label. Default value is the
// message iteself.
@property(nonatomic, strong) NSString* messageAccessibilityLabel;
// Insets of the inner ScrollView.
@property(nonatomic, assign) UIEdgeInsets scrollViewContentInsets;
// The empty view's accessibility identifier.
+ (NSString*)accessibilityIdentifier;
......
......@@ -31,7 +31,7 @@ NSAttributedString* GetAttributedMessage(NSString* message) {
return [[NSAttributedString alloc] initWithString:message
attributes:default_attributes];
}
}
} // namespace
@interface TableViewEmptyView ()
// The message that will be displayed and the label that will display it.
......@@ -48,6 +48,9 @@ NSAttributedString* GetAttributedMessage(NSString* message) {
@implementation TableViewEmptyView
// Synthesized from the ChromeEmptyTableViewBackground protocol
@synthesize scrollViewContentInsets = _scrollViewContentInsets;
- (instancetype)initWithFrame:(CGRect)frame
message:(NSString*)message
image:(UIImage*)image {
......@@ -70,24 +73,14 @@ NSAttributedString* GetAttributedMessage(NSString* message) {
return self;
}
#pragma mark - Accessors
- (NSString*)messageAccessibilityLabel {
return self.messageLabel.accessibilityLabel;
}
- (void)setMessageAccessibilityLabel:(NSString*)label {
if ([self.messageAccessibilityLabel isEqualToString:label])
return;
self.messageLabel.accessibilityLabel = label;
}
#pragma mark - Public
+ (NSString*)accessibilityIdentifier {
return kTableViewEmptyViewID;
}
#pragma mark - ChromeEmptyTableViewBackground
- (void)setScrollViewContentInsets:(UIEdgeInsets)scrollViewContentInsets {
_scrollViewContentInsets = scrollViewContentInsets;
self.scrollView.contentInset = scrollViewContentInsets;
......@@ -95,6 +88,16 @@ NSAttributedString* GetAttributedMessage(NSString* message) {
scrollViewContentInsets.top + scrollViewContentInsets.bottom;
}
- (NSString*)viewAccessibilityLabel {
return self.messageLabel.accessibilityLabel;
}
- (void)setViewAccessibilityLabel:(NSString*)label {
if ([self.viewAccessibilityLabel isEqualToString:label])
return;
self.messageLabel.accessibilityLabel = label;
}
#pragma mark - UIView
- (void)willMoveToSuperview:(UIView*)newSuperview {
......
// 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_TABLE_VIEW_TABLE_VIEW_ILLUSTRATED_EMPTY_VIEW_H_
#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_TABLE_VIEW_ILLUSTRATED_EMPTY_VIEW_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/table_view/chrome_empty_table_view_background.h"
// Displays an UIImage on top of a message over a clearBackground.
@interface TableViewIllustratedEmptyView
: UIView <ChromeEmptyTableViewBackground>
// Designated initializer for a view that displays a large |image|, a |title|
// and a |subtitle|.
- (instancetype)initWithFrame:(CGRect)frame
image:(UIImage*)image
title:(NSString*)title
subtitle:(NSString*)subtitle NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
// The empty view's accessibility identifier.
+ (NSString*)accessibilityIdentifier;
@end
#endif // IOS_CHROME_BROWSER_UI_TABLE_VIEW_TABLE_VIEW_ILLUSTRATED_EMPTY_VIEW_H_
// 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.
#import "ios/chrome/browser/ui/table_view/table_view_illustrated_empty_view.h"
#import "ios/chrome/browser/ui/table_view/table_view_constants.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// The StackView vertical spacing between the image, the title and the subtitle.
const CGFloat kStackViewVerticalSpacingPt = 12.0;
// The StackView width.
const CGFloat kStackViewWidthPt = 310.0;
// The UIImageView height.
const CGFloat kImageHeightPt = 150.0;
} // namespace
@interface TableViewIllustratedEmptyView ()
// The image that will be displayed.
@property(nonatomic, strong) UIImage* image;
// The title that will be displayed under the image.
@property(nonatomic, copy) NSString* title;
// The subtitle that will be displayed under the title.
@property(nonatomic, copy) NSString* subtitle;
// The inner ScrollView so the whole content can be seen even if it is taller
// than the TableView.
@property(nonatomic, strong) UIScrollView* scrollView;
// The height constraint of the ScrollView.
@property(nonatomic, strong) NSLayoutConstraint* scrollViewHeight;
@end
@implementation TableViewIllustratedEmptyView
// Synthesized from the ChromeEmptyTableViewBackground protocol
@synthesize scrollViewContentInsets = _scrollViewContentInsets;
- (instancetype)initWithFrame:(CGRect)frame
image:(UIImage*)image
title:(NSString*)title
subtitle:(NSString*)subtitle {
if (self = [super initWithFrame:frame]) {
_title = title;
_subtitle = subtitle;
_image = image;
self.accessibilityIdentifier = [[self class] accessibilityIdentifier];
}
return self;
}
#pragma mark - Public
+ (NSString*)accessibilityIdentifier {
return kTableViewIllustratedEmptyViewID;
}
#pragma mark - ChromeEmptyTableViewBackground
- (void)setScrollViewContentInsets:(UIEdgeInsets)scrollViewContentInsets {
_scrollViewContentInsets = scrollViewContentInsets;
self.scrollView.contentInset = scrollViewContentInsets;
self.scrollViewHeight.constant =
scrollViewContentInsets.top + scrollViewContentInsets.bottom;
}
- (NSString*)viewAccessibilityLabel {
return self.accessibilityLabel;
}
- (void)setViewAccessibilityLabel:(NSString*)label {
if ([self.viewAccessibilityLabel isEqualToString:label])
return;
self.accessibilityLabel = label;
}
#pragma mark - UIView
- (void)willMoveToSuperview:(UIView*)newSuperview {
[super willMoveToSuperview:newSuperview];
[self createSubviews];
}
#pragma mark - Private
// Create elements to display the image, title and subtitle. Add them to a
// StackView to arrange them. Then, add the StackView to a ScrollView to make
// the empty view scrollable if the content is taller than the frame.
- (void)createSubviews {
// Return if the subviews have already been created and added.
if (!(self.subviews.count == 0))
return;
// Scroll view used to scroll the content if it is too big.
UIScrollView* scrollView = [[UIScrollView alloc] init];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.contentInset = self.scrollViewContentInsets;
self.scrollView = scrollView;
UIImageView* imageView = [[UIImageView alloc] initWithImage:self.image];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.clipsToBounds = YES;
NSMutableArray* subviewsArray = [NSMutableArray arrayWithObject:imageView];
if ([self.title length]) {
UILabel* titleLabel = [[UILabel alloc] init];
titleLabel.isAccessibilityElement = NO;
titleLabel.numberOfLines = 0;
titleLabel.text = self.title;
titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2];
titleLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
titleLabel.textAlignment = NSTextAlignmentCenter;
[subviewsArray addObject:titleLabel];
}
if ([self.subtitle length]) {
UILabel* subtitleLabel = [[UILabel alloc] init];
subtitleLabel.isAccessibilityElement = NO;
subtitleLabel.numberOfLines = 0;
subtitleLabel.text = self.subtitle;
subtitleLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
subtitleLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
subtitleLabel.textAlignment = NSTextAlignmentCenter;
[subviewsArray addObject:subtitleLabel];
}
self.isAccessibilityElement = YES;
self.accessibilityLabel = [NSString
stringWithFormat:@"%@ - %@", self.title ?: @"", self.subtitle ?: @""];
// Vertical stack view that holds the image, title and subtitle.
UIStackView* verticalStack =
[[UIStackView alloc] initWithArrangedSubviews:subviewsArray];
verticalStack.axis = UILayoutConstraintAxisVertical;
verticalStack.spacing = kStackViewVerticalSpacingPt;
verticalStack.distribution = UIStackViewDistributionFill;
verticalStack.layoutMarginsRelativeArrangement = YES;
verticalStack.layoutMargins = UIEdgeInsetsMake(
kStackViewVerticalSpacingPt, 0, kStackViewVerticalSpacingPt, 0);
verticalStack.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:verticalStack];
[self addSubview:scrollView];
// The scroll view should contains the stack view without scrolling enabled if
// it is small enough.
NSLayoutConstraint* scrollViewHeightConstraint = [scrollView.heightAnchor
constraintEqualToAnchor:verticalStack.heightAnchor
constant:(self.scrollViewContentInsets.top +
self.scrollViewContentInsets.bottom)];
scrollViewHeightConstraint.priority = UILayoutPriorityDefaultLow;
scrollViewHeightConstraint.active = YES;
self.scrollViewHeight = scrollViewHeightConstraint;
[NSLayoutConstraint activateConstraints:@[
// The vertical stack is horizontal centered.
[verticalStack.topAnchor constraintEqualToAnchor:scrollView.topAnchor],
[verticalStack.bottomAnchor
constraintEqualToAnchor:scrollView.bottomAnchor],
[verticalStack.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
[verticalStack.widthAnchor constraintEqualToConstant:kStackViewWidthPt],
[imageView.heightAnchor constraintEqualToConstant:kImageHeightPt],
// Have the scroll view taking the full width of self and be vertically
// centered, which is useful when the label isn't taking the full height.
[scrollView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
[scrollView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor],
[scrollView.bottomAnchor
constraintLessThanOrEqualToAnchor:self.bottomAnchor],
[scrollView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[scrollView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
]];
}
@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