diff --git a/components/Snackbar/examples/SnackbarSimpleExample.m b/components/Snackbar/examples/SnackbarSimpleExample.m index 03c2c44917b..226d9229849 100644 --- a/components/Snackbar/examples/SnackbarSimpleExample.m +++ b/components/Snackbar/examples/SnackbarSimpleExample.m @@ -35,15 +35,10 @@ - (void)viewDidLoad { [[MDCTypographyScheme alloc] initWithDefaults:MDCTypographySchemeDefaultsMaterial201804]; } [self setupExampleViews:@[ - @"Simple Snackbar", - @"Snackbar with Action Button", - @"Snackbar with Long Text", - @"Attributed Text Example", - @"Color Themed Snackbar", - @"Customize Font Example", - @"De-Customize Example", - @"Customized Message Using Block", - @"Non Transient Snackbar", + @"Simple Snackbar", @"Snackbar with Action Button", @"Snackbar with Long Text", + @"Attributed Text Example", @"Color Themed Snackbar", @"Customize Font Example", + @"De-Customize Example", @"Customized Message Using Block", @"Non Transient Snackbar", + @"Snackbar Presented On Custom View" ]]; self.title = @"Snackbar"; _legacyMode = YES; @@ -204,6 +199,14 @@ - (void)showNonTransientSnackbar:(id)sender { [MDCSnackbarManager showMessage:message]; } +- (void)showSimpleSnackbarOnCustomPresentationHostView:(id)sender { + MDCSnackbarMessage *message = [[MDCSnackbarMessage alloc] init]; + message.text = @"Snackbar Message"; + message.focusOnShow = YES; + message.presentationHostViewOverride = self.collectionView; + [MDCSnackbarManager showMessage:message]; +} + #pragma mark - UICollectionView - (void)collectionView:(UICollectionView *)collectionView @@ -237,6 +240,9 @@ - (void)collectionView:(UICollectionView *)collectionView case 8: [self showNonTransientSnackbar:nil]; break; + case 9: + [self showSimpleSnackbarOnCustomPresentationHostView:nil]; + break; default: break; } diff --git a/components/Snackbar/src/MDCSnackbarManager.m b/components/Snackbar/src/MDCSnackbarManager.m index 80b781cee7e..57dd43341f3 100644 --- a/components/Snackbar/src/MDCSnackbarManager.m +++ b/components/Snackbar/src/MDCSnackbarManager.m @@ -237,7 +237,7 @@ - (void)displaySnackbarViewForMessage:(MDCSnackbarMessage *)message { self.currentSnackbar = snackbarView; self.overlayView.accessibilityViewIsModal = snackbarView.accessibilityViewIsModal; self.overlayView.hidden = NO; - [self activateOverlay:self.overlayView]; + [self activateOverlay:self.overlayView forMessage:message]; // Once the Snackbar has finished animating on screen, start the automatic dismiss timeout, but // only if the user isn't running VoiceOver. @@ -349,11 +349,13 @@ - (BOOL)isSnackbarTransient:(MDCSnackbarMessageView *)snackbarView { #pragma mark - Overlay Activation -- (void)activateOverlay:(UIView *)overlay { +- (void)activateOverlay:(UIView *)overlay forMessage:(MDCSnackbarMessage *)message { UIWindow *window = [self bestGuessWindow]; UIView *targetView = nil; - if (self.presentationHostView) { + if (message.presentationHostViewOverride) { + targetView = message.presentationHostViewOverride; + } else if (self.presentationHostView) { targetView = self.presentationHostView; } else if ([window isKindOfClass:[MDCOverlayWindow class]]) { // If the application's window is an overlay window, take advantage of it. Otherwise, just add diff --git a/components/Snackbar/src/MDCSnackbarMessage.h b/components/Snackbar/src/MDCSnackbarMessage.h index b0ea3d4acf8..6f8f613a2d5 100644 --- a/components/Snackbar/src/MDCSnackbarMessage.h +++ b/components/Snackbar/src/MDCSnackbarMessage.h @@ -198,6 +198,18 @@ extern NSString *__nonnull const MDCSnackbarMessageBoldAttributeName; */ @property(nonatomic) BOOL automaticallyDismisses; +/** + MDCSnackbarManager will display the snackbar message in this view. + + Call this method to choose where in the view hierarchy this specific snackbar message will be + presented. This method should be called only in cases where the default behavior is unable to find + the correct view to place the snackbar message in. Furthermore, please set this property only if + this message is a special case and needs to be presented on a view different than the other + messages of this specific snackbar manager. Otherwise, please set the presentation host view by + using MDCSnackbarManager's @c setPresentationHostView. + */ +@property(nonatomic, weak, nullable) UIView *presentationHostViewOverride; + @end /** diff --git a/components/Snackbar/src/MDCSnackbarMessage.m b/components/Snackbar/src/MDCSnackbarMessage.m index 78dce603691..0e9b849d907 100644 --- a/components/Snackbar/src/MDCSnackbarMessage.m +++ b/components/Snackbar/src/MDCSnackbarMessage.m @@ -74,6 +74,7 @@ - (instancetype)copyWithZone:(__unused NSZone *)zone { copy.focusOnShow = self.focusOnShow; copy.elementToFocusOnDismiss = self.elementToFocusOnDismiss; copy.automaticallyDismisses = self.automaticallyDismisses; + copy.presentationHostViewOverride = self.presentationHostViewOverride; // Unfortunately there's not really a concept of 'copying' a block (in the same way you would copy // a string, for example). A block's pointer is immutable once it is created and copied to the diff --git a/components/Snackbar/tests/unit/MDCSnackbarMessageViewTests.m b/components/Snackbar/tests/unit/MDCSnackbarMessageViewTests.m index ebb790ab692..8b26fca24c3 100644 --- a/components/Snackbar/tests/unit/MDCSnackbarMessageViewTests.m +++ b/components/Snackbar/tests/unit/MDCSnackbarMessageViewTests.m @@ -547,4 +547,24 @@ - (void)testSnackbarDidDisappearDelegateCalled { [self waitForExpectationsWithTimeout:3 handler:nil]; } +- (void)testSettingPresentationHostViewOverrideDisplaysSnackbarInCorrectView { + // Given + UIView *customView = [[UIView alloc] init]; + self.message.presentationHostViewOverride = customView; + + // When + [self.manager showMessage:self.message]; + XCTestExpectation *expectation = [self expectationWithDescription:@"completed"]; + dispatch_time_t popTime = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)((CGFloat)0.1 * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); + [self waitForExpectationsWithTimeout:3 handler:nil]; + + // Then + XCTAssertTrue([customView.subviews count] > 0); + XCTAssertEqual([customView.subviews.firstObject class], [MDCSnackbarOverlayView class]); +} + @end