blob: 2ddb2a781acc3c728c753483dbeef6b104d7e739 [file] [log] [blame] [edit]
// Copyright 2015-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MDCFeatureHighlightViewController.h"
#import "private/MDCFeatureHighlightAnimationController.h"
#import "private/MDCFeatureHighlightView+Private.h"
#import "MDCFeatureHighlightView.h"
static const CGFloat kMDCFeatureHighlightLineSpacing = 1;
static const CGFloat kMDCFeatureHighlightPulseAnimationInterval = (CGFloat)1.5;
@interface MDCFeatureHighlightViewController () <UIViewControllerTransitioningDelegate>
@property(nonatomic, nullable, weak) MDCFeatureHighlightView *featureHighlightView;
@end
@implementation MDCFeatureHighlightViewController {
MDCFeatureHighlightAnimationController *_animationController;
MDCFeatureHighlightCompletion _completion;
NSString *_viewAccessibilityHint;
NSTimer *_pulseTimer;
UIView *_displayedView;
UIView *_highlightedView;
}
@synthesize adjustsFontForContentSizeCategory = _adjustsFontForContentSizeCategory;
- (nonnull instancetype)initWithHighlightedView:(nonnull UIView *)highlightedView
andShowView:(nonnull UIView *)displayedView
completion:(nullable MDCFeatureHighlightCompletion)completion {
if (self = [super initWithNibName:nil bundle:nil]) {
_highlightedView = highlightedView;
_displayedView = displayedView;
_completion = completion;
_animationController = [[MDCFeatureHighlightAnimationController alloc] init];
_animationController.presenting = YES;
_outerHighlightColor =
[[UIColor blueColor] colorWithAlphaComponent:kMDCFeatureHighlightOuterHighlightAlpha];
_innerHighlightColor = [UIColor whiteColor];
_displayedView.accessibilityTraits = UIAccessibilityTraitButton;
super.transitioningDelegate = self;
super.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.featureHighlightView.displayedView = _displayedView;
self.featureHighlightView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.featureHighlightView.mdc_adjustsFontForContentSizeCategory =
_mdc_adjustsFontForContentSizeCategory;
self.featureHighlightView.adjustsFontForContentSizeCategory = _adjustsFontForContentSizeCategory;
self.featureHighlightView.mdc_legacyFontScaling = _mdc_legacyFontScaling;
__weak MDCFeatureHighlightViewController *weakSelf = self;
self.featureHighlightView.interactionBlock = ^(BOOL accepted) {
MDCFeatureHighlightViewController *strongSelf = weakSelf;
[strongSelf dismiss:accepted];
};
UIGestureRecognizer *tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(acceptFeature)];
[_displayedView addGestureRecognizer:tapGestureRecognizer];
[self.featureHighlightView.accessibilityDismissView addTarget:self
action:@selector(rejectFeature)
forControlEvents:UIControlEventTouchUpInside];
self.featureHighlightView.outerHighlightColor = _outerHighlightColor;
self.featureHighlightView.innerHighlightColor = _innerHighlightColor;
self.featureHighlightView.titleColor = _titleColor;
self.featureHighlightView.bodyColor = _bodyColor;
self.featureHighlightView.titleFont = _titleFont;
self.featureHighlightView.bodyFont = _bodyFont;
self.featureHighlightView.accessibilityHint = _viewAccessibilityHint;
}
/* Disable setter. Always use internal transition controller */
- (void)setTransitioningDelegate:
(__unused id<UIViewControllerTransitioningDelegate>)transitioningDelegate {
NSAssert(NO, @"MDCAlertController.transitioningDelegate cannot be changed.");
return;
}
/* Disable setter. Always use custom presentation style */
- (void)setModalPresentationStyle:(__unused UIModalPresentationStyle)modalPresentationStyle {
NSAssert(NO, @"MDCAlertController.modalPresentationStyle cannot be changed.");
return;
}
- (nonnull instancetype)initWithHighlightedView:(nonnull UIView *)highlightedView
completion:(nullable MDCFeatureHighlightCompletion)completion {
UIView *snapshotView = [highlightedView snapshotViewAfterScreenUpdates:YES];
// We have to wrap the snapshoted view because _UIReplicantViews can't be accessibility elements.
UIView *displayedView = [[UIView alloc] initWithFrame:snapshotView.bounds];
[displayedView addSubview:snapshotView];
// Copy the accessibility values from the view being highlighted.
displayedView.isAccessibilityElement = YES;
displayedView.accessibilityTraits = UIAccessibilityTraitButton;
displayedView.accessibilityLabel = highlightedView.accessibilityLabel;
displayedView.accessibilityValue = highlightedView.accessibilityValue;
displayedView.accessibilityHint = highlightedView.accessibilityHint;
return [self initWithHighlightedView:highlightedView
andShowView:displayedView
completion:completion];
}
- (void)loadView {
self.view = [[MDCFeatureHighlightView alloc] initWithFrame:CGRectZero];
self.featureHighlightView = (MDCFeatureHighlightView *)self.view;
}
- (void)viewWillLayoutSubviews {
self.featureHighlightView.titleLabel.attributedText =
[self attributedStringForString:self.titleText
lineSpacing:kMDCFeatureHighlightLineSpacing
font:_titleFont];
self.featureHighlightView.bodyLabel.attributedText =
[self attributedStringForString:self.bodyText
lineSpacing:kMDCFeatureHighlightLineSpacing
font:_bodyFont];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (self.traitCollectionDidChangeBlock) {
self.traitCollectionDidChangeBlock(self, previousTraitCollection);
}
}
- (void)dealloc {
[_pulseTimer invalidate];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CGPoint point = [_highlightedView.superview convertPoint:_highlightedView.center
toCoordinateSpace:self.featureHighlightView];
self.featureHighlightView.highlightPoint = point;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_pulseTimer = [NSTimer scheduledTimerWithTimeInterval:kMDCFeatureHighlightPulseAnimationInterval
target:self.featureHighlightView
selector:@selector(animatePulse)
userInfo:NULL
repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_pulseTimer invalidate];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator
animateAlongsideTransition:^(
__unused id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self resetHighlightPoint];
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self resetHighlightPoint];
}];
}
- (void)resetHighlightPoint {
CGPoint point = [_highlightedView.superview convertPoint:_highlightedView.center
toView:self.featureHighlightView];
self.featureHighlightView.highlightPoint = point;
[self.featureHighlightView layoutIfNeeded];
[self.featureHighlightView updateOuterHighlight];
}
- (void)setOuterHighlightColor:(UIColor *)outerHighlightColor {
_outerHighlightColor = outerHighlightColor;
if (self.isViewLoaded) {
self.featureHighlightView.outerHighlightColor = outerHighlightColor;
}
}
- (void)setInnerHighlightColor:(UIColor *)innerHighlightColor {
_innerHighlightColor = innerHighlightColor;
if (self.isViewLoaded) {
self.featureHighlightView.innerHighlightColor = innerHighlightColor;
}
}
- (void)setTitleColor:(UIColor *)titleColor {
_titleColor = titleColor;
if (self.isViewLoaded) {
self.featureHighlightView.titleColor = titleColor;
}
}
- (void)setBodyColor:(UIColor *)bodyColor {
_bodyColor = bodyColor;
if (self.isViewLoaded) {
self.featureHighlightView.bodyColor = bodyColor;
}
}
- (void)setTitleFont:(UIFont *)titleFont {
_titleFont = titleFont;
if (self.isViewLoaded) {
self.featureHighlightView.titleFont = titleFont;
}
}
- (void)setBodyFont:(UIFont *)bodyFont {
_bodyFont = bodyFont;
if (self.isViewLoaded) {
self.featureHighlightView.bodyFont = bodyFont;
}
}
- (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory {
_adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
if (self.isViewLoaded) {
self.featureHighlightView.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
}
}
- (void)acceptFeature {
[self dismiss:YES];
}
- (void)rejectFeature {
[self dismiss:NO];
}
- (void)dismiss:(BOOL)accepted {
_animationController.presenting = NO;
if (accepted) {
_animationController.dismissStyle = MDCFeatureHighlightDismissAccepted;
} else {
_animationController.dismissStyle = MDCFeatureHighlightDismissRejected;
}
[self dismissViewControllerAnimated:YES
completion:^() {
if (self->_completion) {
self->_completion(accepted);
}
}];
}
#pragma mark - Dynamic Type
- (void)mdc_setAdjustsFontForContentSizeCategory:(BOOL)adjusts {
_mdc_adjustsFontForContentSizeCategory = adjusts;
if (_mdc_adjustsFontForContentSizeCategory) {
[self updateFontsForDynamicType];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentSizeCategoryDidChange:)
name:UIContentSizeCategoryDidChangeNotification
object:nil];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIContentSizeCategoryDidChangeNotification
object:nil];
}
}
- (void)mdc_setLegacyFontScaling:(BOOL)legacyScaling {
_mdc_legacyFontScaling = legacyScaling;
if (self.isViewLoaded) {
self.featureHighlightView.mdc_legacyFontScaling = legacyScaling;
}
}
- (void)contentSizeCategoryDidChange:(__unused NSNotification *)notification {
[self updateFontsForDynamicType];
}
- (void)updateFontsForDynamicType {
[self.featureHighlightView updateTitleFont];
[self.featureHighlightView updateBodyFont];
[self.featureHighlightView layoutIfNeeded];
}
#pragma mark - Accessibility
- (BOOL)accessibilityPerformEscape {
[self rejectFeature];
return YES;
}
#pragma mark - UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)
animationControllerForPresentedController:(UIViewController *)presented
presentingController:(__unused UIViewController *)presenting
sourceController:(__unused UIViewController *)source {
if (presented == self) {
return _animationController;
}
return nil;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:
(UIViewController *)dismissed {
if (dismissed == self) {
return _animationController;
}
return nil;
}
#pragma mark - UIAccessibility
- (void)setAccessibilityHint:(NSString *)accessibilityHint {
_viewAccessibilityHint = accessibilityHint;
if (self.isViewLoaded) {
self.featureHighlightView.accessibilityHint = accessibilityHint;
}
}
- (NSString *)accessibilityHint {
return _viewAccessibilityHint;
}
#pragma mark - Private
- (NSAttributedString *)attributedStringForString:(NSString *)string
lineSpacing:(CGFloat)lineSpacing
font:(UIFont *)font {
if (!string) {
return nil;
}
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = lineSpacing;
NSMutableDictionary *attrs = [[NSMutableDictionary alloc]
initWithDictionary:@{NSParagraphStyleAttributeName : paragraphStyle}];
if (font) {
[attrs setObject:font forKey:NSFontAttributeName];
}
return [[NSAttributedString alloc] initWithString:string attributes:attrs];
}
@end