1778 lines · 86.2 KB · Text
RawDownload
diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.ktindex 03d8d9f3a5c403c93282d53cbacf1a339e877e85..54d5eabc6c8a408e711a5be97286fdbb8ab1c461 100644--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt@@ -103,6 +103,34 @@ class ScreenStackHeaderConfigViewManager :         config.setTitle(title)     } +    // The t3 patch adds these iOS glass-chrome props to the native spec; they have+    // no Android implementation, but the codegen-generated interface requires the+    // setters to exist. No-ops keep the Android build compiling.+    override fun setSubtitle(+        config: ScreenStackHeaderConfig,+        subtitle: String?,+    ) = Unit++    override fun setLargeSubtitle(+        config: ScreenStackHeaderConfig,+        largeSubtitle: String?,+    ) = Unit++    override fun setNavigationItemStyle(+        config: ScreenStackHeaderConfig,+        navigationItemStyle: String?,+    ) = Unit++    override fun setHeaderCenterBarButtonItems(+        config: ScreenStackHeaderConfig,+        headerCenterBarButtonItems: ReadableArray?,+    ) = Unit++    override fun setHeaderToolbarItems(+        config: ScreenStackHeaderConfig,+        headerToolbarItems: ReadableArray?,+    ) = Unit+     override fun setTitleFontFamily(         config: ScreenStackHeaderConfig,         titleFontFamily: String?,diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.hindex ea5325ea8d17b1ddfa790ff8dab48ce83142d4d3..acd2fd7ceb7162ed300abc4d3c9f0c24f4d63898 100644--- a/ios/RNSBarButtonItem.h+++ b/ios/RNSBarButtonItem.h@@ -13,4 +13,8 @@ typedef void (^RNSBarButtonMenuItemAction)(NSString *menuId);                     menuAction:(RNSBarButtonMenuItemAction)menuAction                    imageLoader:(RCTImageLoader *)imageLoader; ++ (UIMenu *)initUIMenuWithDict:(NSDictionary<NSString *, id> *)dict+                    menuAction:(RNSBarButtonMenuItemAction)menuAction+                   imageLoader:(RCTImageLoader *)imageLoader;+ @enddiff --git a/ios/RNSBarButtonItem.mm b/ios/RNSBarButtonItem.mmindex 0eb1f09dee82edd99e3e614938233db5cdeaebfe..73298f10807520970f625e166d7f5dd1338206f1 100644--- a/ios/RNSBarButtonItem.mm+++ b/ios/RNSBarButtonItem.mm@@ -110,6 +110,58 @@ - (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict   if (dict[@"accessibilityHint"]) {     self.accessibilityHint = dict[@"accessibilityHint"];   }+#if !TARGET_OS_TV && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)+  NSNumber *glassEffectNum = dict[@"glassEffect"];+  if (@available(iOS 26.0, *)) {+    if ([glassEffectNum boolValue]) {+      UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];+      configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;+      UIBackgroundConfiguration *background = configuration.background;+      background.strokeWidth = 0.0;+      background.strokeColor = UIColor.clearColor;+      configuration.background = background;+      configuration.contentInsets = NSDirectionalEdgeInsetsZero;+      if (title != nil) {+        configuration.title = title;+      }++      UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];+      button.translatesAutoresizingMaskIntoConstraints = NO;+      button.enabled = self.enabled;+      button.accessibilityLabel = self.accessibilityLabel;+      button.accessibilityHint = self.accessibilityHint;++      CGFloat resolvedWidth = width != nil ? width.doubleValue : 56.0;+      [button.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;+      [button.heightAnchor constraintEqualToConstant:50.0].active = YES;++      [[self class] resolveImageFromConfig:dict+                               imageLoader:imageLoader+                           completionBlock:^(UIImage *img) {+                             UIButtonConfiguration *updatedConfiguration = button.configuration;+                             updatedConfiguration.image = img;+                             button.configuration = updatedConfiguration;+                           }];++      NSDictionary *menu = dict[@"menu"];+      if (menu) {+        button.menu = [[self class] initUIMenuWithDict:menu menuAction:menuAction imageLoader:imageLoader];+        button.showsMenuAsPrimaryAction = YES;+      } else {+        NSString *buttonId = dict[@"buttonId"];+        if (buttonId && action) {+          UIAction *pressAction = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull sender) {+            action(buttonId);+          }];+          [button addAction:pressAction forControlEvents:UIControlEventTouchUpInside];+        }+      }++      self.customView = button;+      return self;+    }+  }+#endif  #if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000   if (@available(tvOS 17.0, *)) {diff --git a/ios/RNSScreenStackHeaderConfig.h b/ios/RNSScreenStackHeaderConfig.hindex 919b984edc9f91ee9ac26faf257d8a721e26457c..5bb0cd6736ed6bc51db57e2a9326f758f22c51d8 100644--- a/ios/RNSScreenStackHeaderConfig.h+++ b/ios/RNSScreenStackHeaderConfig.h@@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN  @property (nonatomic, retain) NSString *title;+@property (nonatomic, retain) NSString *subtitle;+@property (nonatomic, retain) NSString *largeSubtitle; @property (nonatomic, retain) NSString *titleFontFamily; @property (nonatomic, retain) NSNumber *titleFontSize; @property (nonatomic, retain) NSString *titleFontWeight;@@ -45,9 +47,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL backButtonInCustomView; @property (nonatomic) UISemanticContentAttribute direction; @property (nonatomic) UINavigationItemBackButtonDisplayMode backButtonDisplayMode;+@property (nonatomic) NSInteger navigationItemStyle; @property (nonatomic) RNSBlurEffectStyle blurEffect; @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerRightBarButtonItems; @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerLeftBarButtonItems;+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerCenterBarButtonItems;+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerToolbarItems; @property (nonatomic, readwrite) BOOL synchronousShadowStateUpdatesEnabled;  NS_ASSUME_NONNULL_ENDdiff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mmindex 5970e3e3a624b9498b8dedfc16831df03a274d0c..5ebff085788d813f1139eb6f9129fb2199dcbd58 100644--- a/ios/RNSScreenStackHeaderConfig.mm+++ b/ios/RNSScreenStackHeaderConfig.mm@@ -25,11 +25,33 @@ #import "RNSSearchBar.h" #import "UINavigationBar+RNSUtility.h" +#import <objc/runtime.h>+ namespace react = facebook::react;  static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;++// Keys for the last-applied JS bar button configs, associated with the+// navigation item so unrelated header updates (title, subtitle, tint) don't+// recreate the native buttons they configure.+static char RNSAppliedHeaderBarButtonConfigsKey;+static char RNSAppliedToolbarConfigsKey; static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +static NSInteger navigationItemStyleFromCppEquivalent(+    react::RNSScreenStackHeaderConfigNavigationItemStyle navigationItemStyle)+{+  switch (navigationItemStyle) {+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Browser:+      return UINavigationItemStyleBrowser;+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Editor:+      return UINavigationItemStyleEditor;+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Navigator:+    default:+      return UINavigationItemStyleNavigator;+  }+}+ @interface RCTImageLoader (Private) - (id<RCTImageCache>)imageCache; @end@@ -47,6 +69,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string @end  @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>+#if !TARGET_OS_TV+- (NSArray<UIBarButtonItemGroup *> *)barButtonItemGroupsFromItems:(NSArray<UIBarButtonItem *> *)items;+#endif @end  @implementation RNSScreenStackHeaderConfig {@@ -81,6 +106,7 @@ - (void)initProps   self.hidden = YES;   _reactSubviews = [NSMutableArray new];   _backTitleVisible = YES;+  _navigationItemStyle = UINavigationItemStyleNavigator;   _blurEffect = RNSBlurEffectStyleNone; } @@ -496,6 +522,10 @@ + (void)updateViewController:(UIViewController *)vc    if (shouldHide) {     navitem.title = config.title;+    if (@available(iOS 26.0, *)) {+      navitem.subtitle = config.subtitle;+      navitem.largeSubtitle = config.largeSubtitle;+    }      // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.     [navctr setNavigationBarHidden:YES animated:animated];@@ -512,11 +542,19 @@ + (void)updateViewController:(UIViewController *)vc   }   navitem.largeTitleDisplayMode =       config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;++  if (@available(iOS 16.0, *)) {+    navitem.style = (UINavigationItemStyle)config.navigationItemStyle;+  } #endif    UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];   navitem.standardAppearance = appearance;   navitem.compactAppearance = appearance;+  if (@available(iOS 26.0, *)) {+    navitem.subtitle = config.subtitle;+    navitem.largeSubtitle = config.largeSubtitle;+  }  // appearance does not apply to the tvOS so we need to use lagacy customization #if TARGET_OS_TV@@ -637,10 +675,458 @@ + (void)updateViewController:(UIViewController *)vc   // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).   // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)   navitem.title = config.title;-  navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems-                                                withCurrentItems:navitem.leftBarButtonItems];-  navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems-                                                 withCurrentItems:navitem.rightBarButtonItems];+  NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];+  NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];+  NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];+  NSArray<UIBarButtonItem *> *subviewLeftItems = navitem.leftBarButtonItems ?: @[];+  NSArray<UIBarButtonItem *> *subviewRightItems = navitem.rightBarButtonItems ?: @[];+  // The key includes the config instance's identity: cached items capture+  // this config's event emitter in their press handlers, so a remounted+  // header-config view with value-equal configs must still rebuild — reusing+  // the old items would dispatch presses into the dead config's emitter.+  NSArray *headerItemsKey =+      @[ @((uintptr_t)config), headerLeftConfigs, headerRightConfigs, headerCenterConfigs ];+  // Rebuilding bar button items creates brand-new native buttons (glass+  // UIButton custom views on iOS 26). Replacing them while UIKit animates an+  // existing one (menu capsule morph, push/pop glass transitions) strands the+  // animation overlay — a stuck expanded capsule or an unmasked square back+  // button. When the JS configs are unchanged, keep the already-applied items.+  // Subview-backed items are re-derived every pass, so their presence forces+  // the rebuild path.+  BOOL reuseHeaderBarButtonItems = subviewLeftItems.count == 0 && subviewRightItems.count == 0 &&+      [objc_getAssociatedObject(navitem, &RNSAppliedHeaderBarButtonConfigsKey) isEqual:headerItemsKey];+  if (!reuseHeaderBarButtonItems) {+    NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems+                                                                      withCurrentItems:navitem.leftBarButtonItems+                                                                        navigationItem:navitem];+    NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems+                                                                       withCurrentItems:navitem.rightBarButtonItems+                                                                         navigationItem:navitem];+    NSArray<UIBarButtonItem *> *centerBarButtonItems = [config barButtonItemsFromConfigs:config.headerCenterBarButtonItems+                                                                        withCurrentItems:@[]+                                                                          navigationItem:navitem];+#if !TARGET_OS_TV+    if (@available(iOS 16.0, *)) {+      navitem.leadingItemGroups = [config barButtonItemGroupsFromItems:leftBarButtonItems];+      navitem.trailingItemGroups = [config barButtonItemGroupsFromItems:rightBarButtonItems];+      if (@available(iOS 26.0, *)) {+        navitem.centerItemGroups = [config barButtonItemGroupsFromItems:centerBarButtonItems];+      }+      navitem.leftBarButtonItems = nil;+      navitem.rightBarButtonItems = nil;+    } else {+      navitem.leftBarButtonItems = leftBarButtonItems;+      navitem.rightBarButtonItems = rightBarButtonItems;+    }+#else+    navitem.leftBarButtonItems = leftBarButtonItems;+    navitem.rightBarButtonItems = rightBarButtonItems;+#endif+    // Only dict-driven items can be reused: with subview-backed items in the+    // mix the applied state depends on view identity, so clear the key to+    // force a rebuild on the next pass.+    objc_setAssociatedObject(+        navitem,+        &RNSAppliedHeaderBarButtonConfigsKey,+        subviewLeftItems.count == 0 && subviewRightItems.count == 0 ? headerItemsKey : nil,+        OBJC_ASSOCIATION_RETAIN_NONATOMIC);+  }+  NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;+  for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {+    if (toolbarConfig[@"mailSearchToolbar"]) {+      mailSearchToolbarConfig = toolbarConfig;+      break;+    }+  }+  if (mailSearchToolbarConfig == nil) {+    for (NSDictionary<NSString *, id> *rightConfig in config.headerRightBarButtonItems) {+      if ([rightConfig[@"bottomMailSearchToolbar"] boolValue]) {+        mailSearchToolbarConfig = rightConfig;+        break;+      }+    }+  }++  static NSInteger const RNSMailSearchToolbarViewTag = 260628;+  UIView *chromeHostView = vc.view;+  UIView *existingMailSearchToolbar = [chromeHostView viewWithTag:RNSMailSearchToolbarViewTag];+  if (existingMailSearchToolbar == nil) {+    existingMailSearchToolbar = [vc.view viewWithTag:RNSMailSearchToolbarViewTag];+  }+  // Reuse the live toolbar when nothing it was built from changed: rebuilding+  // replaces the fallback UISearchTextField with a fresh empty one, dropping+  // the user's in-progress search text and first responder on every unrelated+  // header update. Keyed on the config instance (the button/search blocks+  // capture its event emitter), the toolbar config values, and the host width+  // (the width constraint constant is resolved from it at build time).+  static char RNSAppliedMailSearchToolbarConfigKey;+  NSArray *mailSearchToolbarKey = mailSearchToolbarConfig != nil+      ? @[ @((uintptr_t)config), mailSearchToolbarConfig, @(chromeHostView.bounds.size.width) ]+      : nil;+  BOOL reuseMailSearchToolbar = existingMailSearchToolbar != nil && mailSearchToolbarKey != nil &&+      [objc_getAssociatedObject(existingMailSearchToolbar, &RNSAppliedMailSearchToolbarConfigKey)+          isEqual:mailSearchToolbarKey];+  if (!reuseMailSearchToolbar) {+    [existingMailSearchToolbar removeFromSuperview];+  }++  if (mailSearchToolbarConfig != nil && !reuseMailSearchToolbar) {+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)+    if (@available(iOS 26.0, *)) {+      CGFloat horizontalInset = 18.0;+      NSNumber *width = mailSearchToolbarConfig[@"width"];+      CGFloat hostWidth = chromeHostView.bounds.size.width;+      if (hostWidth <= 0.0) {+        hostWidth = vc.view.bounds.size.width;+      }+      if (hostWidth <= 0.0) {+        hostWidth = [UIScreen mainScreen].bounds.size.width;+      }+      CGFloat fallbackWidth = MIN(560.0, MAX(300.0, hostWidth - (horizontalInset * 2.0)));+      CGFloat resolvedWidth = width != nil ? width.doubleValue : fallbackWidth;+      if (hostWidth > 0.0) {+        resolvedWidth = MIN(resolvedWidth, MAX(260.0, hostWidth - 12.0));+      }++      CGFloat toolbarHeight = 48.0;+      CGFloat toolbarRadius = toolbarHeight / 2.0;+      CGFloat buttonSize = toolbarHeight;+      CGFloat sideButtonReserve = buttonSize + 8.0;+      CGFloat searchFieldHeight = 42.0;+      CGFloat toolbarBottomSpacing = 4.0;+      UIFont *searchFont = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle3];+      NSDictionary<NSAttributedStringKey, id> *placeholderAttributes = @{+        NSForegroundColorAttributeName : UIColor.secondaryLabelColor,+        NSFontAttributeName : searchFont,+      };+      UIKeyboardLayoutGuide *keyboardLayoutGuide = chromeHostView.keyboardLayoutGuide;++      UIView *toolbarHost = [[UIView alloc] init];+      toolbarHost.tag = RNSMailSearchToolbarViewTag;+      objc_setAssociatedObject(+          toolbarHost,+          &RNSAppliedMailSearchToolbarConfigKey,+          mailSearchToolbarKey,+          OBJC_ASSOCIATION_RETAIN_NONATOMIC);+      toolbarHost.translatesAutoresizingMaskIntoConstraints = NO;+      [chromeHostView addSubview:toolbarHost];+      // The screen stays mounted beneath pushed routes, so its keyboard layout+      // guide can track a keyboard owned by another screen. Keep the toolbar at+      // rest unless its own search field is editing.+      NSLayoutConstraint *keyboardAvoidConstraint =+          [toolbarHost.bottomAnchor constraintEqualToAnchor:keyboardLayoutGuide.topAnchor+                                                   constant:-toolbarBottomSpacing];+      keyboardAvoidConstraint.priority = UILayoutPriorityDefaultLow;+      NSLayoutConstraint *restingBottomConstraint =+          [toolbarHost.bottomAnchor constraintEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor+                                                   constant:-toolbarBottomSpacing];+      restingBottomConstraint.priority = UILayoutPriorityDefaultHigh;+      [NSLayoutConstraint activateConstraints:@[+        [toolbarHost.centerXAnchor constraintEqualToAnchor:chromeHostView.centerXAnchor],+        [toolbarHost.bottomAnchor constraintLessThanOrEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor+                                                           constant:-toolbarBottomSpacing],+        keyboardAvoidConstraint,+        restingBottomConstraint,+        [toolbarHost.widthAnchor constraintEqualToConstant:resolvedWidth],+        [toolbarHost.heightAnchor constraintEqualToConstant:toolbarHeight],+      ]];+      [chromeHostView bringSubviewToFront:toolbarHost];++      UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular];+      glassEffect.interactive = YES;+      UIVisualEffectView *glassView = [[UIVisualEffectView alloc] initWithEffect:glassEffect];+      glassView.clipsToBounds = YES;+      glassView.layer.cornerRadius = toolbarRadius;+      glassView.translatesAutoresizingMaskIntoConstraints = NO;+      [toolbarHost addSubview:glassView];+      BOOL hasFilterButton =+          mailSearchToolbarConfig[@"filterButtonId"] != nil || mailSearchToolbarConfig[@"filterMenu"] != nil;+      BOOL hasComposeButton =+          mailSearchToolbarConfig[@"composeButtonId"] != nil || mailSearchToolbarConfig[@"composeMenu"] != nil;+      CGFloat glassLeadingInset = hasFilterButton ? sideButtonReserve : 0.0;+      CGFloat glassTrailingInset = hasComposeButton ? -sideButtonReserve : 0.0;+      NSLayoutConstraint *glassLeadingConstraint =+          [glassView.leadingAnchor constraintEqualToAnchor:toolbarHost.leadingAnchor constant:glassLeadingInset];+      NSLayoutConstraint *glassTrailingConstraint =+          [glassView.trailingAnchor constraintEqualToAnchor:toolbarHost.trailingAnchor constant:glassTrailingInset];+      [NSLayoutConstraint activateConstraints:@[+        glassLeadingConstraint,+        glassTrailingConstraint,+        [glassView.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],+        [glassView.heightAnchor constraintEqualToConstant:toolbarHeight],+      ]];++      void (^emitButtonPress)(NSString *) = ^(NSString *buttonId) {+        auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(+            config->_eventEmitter);+        if (eventEmitter && buttonId) {+          eventEmitter->onPressHeaderBarButtonItem(+              facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonItem{+                  .buttonId = std::string([buttonId UTF8String])});+        }+      };++      void (^emitMenuPress)(NSString *) = ^(NSString *menuId) {+        auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(+            config->_eventEmitter);+        if (eventEmitter && menuId) {+          eventEmitter->onPressHeaderBarButtonMenuItem(+              facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonMenuItem{+                  .menuId = std::string([menuId UTF8String])});+        }+      };++      UIButton *(^makeGlassButton)(NSString *, NSString *, NSDictionary *) =+          ^UIButton *(NSString *systemImageName, NSString *buttonId, NSDictionary *menuConfig) {+        UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];+        configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;+        configuration.image = [UIImage systemImageNamed:systemImageName ?: @"circle"];+        configuration.baseForegroundColor = UIColor.labelColor;+        UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];+        if (menuConfig != nil) {+          button.menu = [RNSBarButtonItem initUIMenuWithDict:menuConfig+                                                  menuAction:emitMenuPress+                                                 imageLoader:config->_imageLoader];+          button.showsMenuAsPrimaryAction = YES;+        } else if (buttonId != nil) {+          UIAction *action = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull action) {+            emitButtonPress(buttonId);+          }];+          [button addAction:action forControlEvents:UIControlEventTouchUpInside];+        }+        return button;+      };++      BOOL useFallbackSearchField = [mailSearchToolbarConfig[@"useFallbackSearchField"] boolValue];+      UISearchBar *searchBar =+          !useFallbackSearchField && navitem.searchController != nil ? navitem.searchController.searchBar : nil;+      NSString *placeholder = mailSearchToolbarConfig[@"placeholder"];+      UITextField *resolvedSearchTextField = nil;+      if (searchBar != nil) {+        if (placeholder != nil) {+          searchBar.placeholder = placeholder;+        }+        searchBar.hidden = NO;+        searchBar.alpha = 1.0;+        searchBar.userInteractionEnabled = YES;+        searchBar.searchBarStyle = UISearchBarStyleMinimal;+        searchBar.backgroundImage = [UIImage new];+        searchBar.searchTextField.hidden = NO;+        searchBar.searchTextField.alpha = 1.0;+        searchBar.searchTextField.backgroundColor = UIColor.clearColor;+        searchBar.searchTextField.font = searchFont;+        searchBar.searchTextField.adjustsFontForContentSizeCategory = YES;+        searchBar.searchTextField.textColor = UIColor.labelColor;+        searchBar.searchTextField.tintColor = UIColor.labelColor;+        resolvedSearchTextField = searchBar.searchTextField;+        if (placeholder != nil) {+          searchBar.searchTextField.attributedPlaceholder =+              [[NSAttributedString alloc] initWithString:placeholder attributes:placeholderAttributes];+        }+        searchBar.translatesAutoresizingMaskIntoConstraints = NO;+        [glassView.contentView addSubview:searchBar];+        [NSLayoutConstraint activateConstraints:@[+          [searchBar.leadingAnchor constraintEqualToAnchor:glassView.contentView.leadingAnchor constant:8.0],+          [searchBar.trailingAnchor constraintEqualToAnchor:glassView.contentView.trailingAnchor constant:-8.0],+          [searchBar.centerYAnchor constraintEqualToAnchor:glassView.contentView.centerYAnchor],+          [searchBar.heightAnchor constraintEqualToConstant:searchFieldHeight],+        ]];+      } else {+        UISearchTextField *searchField = [UISearchTextField new];+        if (placeholder != nil) {+          searchField.placeholder = placeholder;+          searchField.attributedPlaceholder =+              [[NSAttributedString alloc] initWithString:placeholder attributes:placeholderAttributes];+        }+        NSString *searchTextChangeId = mailSearchToolbarConfig[@"searchTextChangeId"];

Lines 1–500 of 1778.