FLEXTableViewController.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. //
  2. // FLEXTableViewController.m
  3. // FLEX
  4. //
  5. // Created by Tanner on 7/5/19.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXTableViewController.h"
  9. #import "FLEXExplorerViewController.h"
  10. #import "FLEXBookmarksViewController.h"
  11. #import "FLEXTabsViewController.h"
  12. #import "FLEXScopeCarousel.h"
  13. #import "FLEXTableView.h"
  14. #import "FLEXUtility.h"
  15. #import "FLEXResources.h"
  16. #import "UIBarButtonItem+FLEX.h"
  17. #import <objc/runtime.h>
  18. #import "fakes.h"
  19. @interface Block : NSObject
  20. - (void)invoke;
  21. @end
  22. CGFloat const kFLEXDebounceInstant = 0.f;
  23. CGFloat const kFLEXDebounceFast = 0.05;
  24. CGFloat const kFLEXDebounceForAsyncSearch = 0.15;
  25. CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
  26. @interface FLEXTableViewController ()
  27. @property (nonatomic) NSTimer *debounceTimer;
  28. @property (nonatomic) BOOL didInitiallyRevealSearchBar;
  29. @property (nonatomic) UITableViewStyle style;
  30. @property (nonatomic) BOOL hasAppeared;
  31. @property (nonatomic, readonly) UIView *tableHeaderViewContainer;
  32. @property (nonatomic, readonly) BOOL manuallyDeactivateSearchOnDisappear;
  33. @property (nonatomic) UIBarButtonItem *middleToolbarItem;
  34. @property (nonatomic) UIBarButtonItem *middleLeftToolbarItem;
  35. @property (nonatomic) UIBarButtonItem *leftmostToolbarItem;
  36. @end
  37. @implementation FLEXTableViewController
  38. @dynamic tableView;
  39. @synthesize showsShareToolbarItem = _showsShareToolbarItem;
  40. @synthesize tableHeaderViewContainer = _tableHeaderViewContainer;
  41. @synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton;
  42. #pragma mark - Initialization
  43. - (id)init {
  44. #if FLEX_AT_LEAST_IOS13_SDK
  45. if (@available(iOS 13.0, *)) {
  46. #if !TARGET_OS_TV
  47. self = [self initWithStyle:UITableViewStyleInsetGrouped];
  48. #else
  49. self = [self initWithStyle:UITableViewStyleGrouped];
  50. #endif
  51. } else {
  52. self = [self initWithStyle:UITableViewStyleGrouped];
  53. }
  54. #else
  55. self = [self initWithStyle:UITableViewStyleGrouped];
  56. #endif
  57. return self;
  58. }
  59. - (id)initWithStyle:(UITableViewStyle)style {
  60. self = [super initWithStyle:style];
  61. if (self) {
  62. _searchBarDebounceInterval = kFLEXDebounceFast;
  63. _showSearchBarInitially = YES;
  64. _style = style;
  65. _manuallyDeactivateSearchOnDisappear = ({
  66. NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11;
  67. });
  68. // We will be our own search delegate if we implement this method
  69. if ([self respondsToSelector:@selector(updateSearchResults:)]) {
  70. self.searchDelegate = (id)self;
  71. }
  72. }
  73. return self;
  74. }
  75. #pragma mark - Public
  76. - (FLEXWindow *)window {
  77. return (id)self.view.window;
  78. }
  79. - (void)setShowsSearchBar:(BOOL)showsSearchBar {
  80. if (_showsSearchBar == showsSearchBar) return;
  81. _showsSearchBar = showsSearchBar;
  82. if (showsSearchBar) {
  83. UIViewController *results = self.searchResultsController;
  84. self.searchController = [[UISearchController alloc] initWithSearchResultsController:results];
  85. self.searchController.searchBar.placeholder = @"Filter";
  86. self.searchController.searchResultsUpdater = (id)self;
  87. self.searchController.delegate = (id)self;
  88. #if !TARGET_OS_TV
  89. self.searchController.searchBar.delegate = self;
  90. self.searchController.dimsBackgroundDuringPresentation = NO;
  91. #endif
  92. self.searchController.hidesNavigationBarDuringPresentation = NO;
  93. /// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
  94. self.automaticallyShowsSearchBarCancelButton = YES;
  95. #if FLEX_AT_LEAST_IOS13_SDK
  96. if (@available(iOS 13, *)) {
  97. self.searchController.automaticallyShowsScopeBar = NO;
  98. }
  99. #endif
  100. [self addSearchController:self.searchController];
  101. } else {
  102. // Search already shown and just set to NO, so remove it
  103. [self removeSearchController:self.searchController];
  104. }
  105. }
  106. - (void)setShowsCarousel:(BOOL)showsCarousel {
  107. if (_showsCarousel == showsCarousel) return;
  108. _showsCarousel = showsCarousel;
  109. if (showsCarousel) {
  110. _carousel = ({
  111. __weak __typeof(self) weakSelf = self;
  112. FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
  113. carousel.selectedIndexChangedAction = ^(NSInteger idx) {
  114. __typeof(self) self = weakSelf;
  115. [self.searchDelegate updateSearchResults:self.searchText];
  116. };
  117. // UITableView won't update the header size unless you reset the header view
  118. [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
  119. __typeof(self) self = weakSelf;
  120. [self layoutTableHeaderIfNeeded];
  121. }];
  122. carousel;
  123. });
  124. [self addCarousel:_carousel];
  125. } else {
  126. // Carousel already shown and just set to NO, so remove it
  127. [self removeCarousel:_carousel];
  128. }
  129. }
  130. - (NSInteger)selectedScope {
  131. if (self.searchController.searchBar.showsScopeBar) {
  132. return self.searchController.searchBar.selectedScopeButtonIndex;
  133. } else if (self.showsCarousel) {
  134. return self.carousel.selectedIndex;
  135. } else {
  136. return 0;
  137. }
  138. }
  139. - (void)setSelectedScope:(NSInteger)selectedScope {
  140. if (self.searchController.searchBar.showsScopeBar) {
  141. self.searchController.searchBar.selectedScopeButtonIndex = selectedScope;
  142. } else if (self.showsCarousel) {
  143. self.carousel.selectedIndex = selectedScope;
  144. }
  145. [self.searchDelegate updateSearchResults:self.searchText];
  146. }
  147. - (NSString *)searchText {
  148. return self.searchController.searchBar.text;
  149. }
  150. - (BOOL)automaticallyShowsSearchBarCancelButton {
  151. #if FLEX_AT_LEAST_IOS13_SDK
  152. if (@available(iOS 13, *)) {
  153. return self.searchController.automaticallyShowsCancelButton;
  154. }
  155. #endif
  156. return _automaticallyShowsSearchBarCancelButton;
  157. }
  158. - (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value {
  159. #if FLEX_AT_LEAST_IOS13_SDK
  160. if (@available(iOS 13, *)) {
  161. self.searchController.automaticallyShowsCancelButton = value;
  162. }
  163. #endif
  164. _automaticallyShowsSearchBarCancelButton = value;
  165. }
  166. - (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock {
  167. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  168. NSArray *items = backgroundBlock();
  169. dispatch_async(dispatch_get_main_queue(), ^{
  170. mainBlock(items);
  171. });
  172. });
  173. }
  174. - (void)setsShowsShareToolbarItem:(BOOL)showsShareToolbarItem {
  175. _showsShareToolbarItem = showsShareToolbarItem;
  176. if (self.isViewLoaded) {
  177. [self setupToolbarItems];
  178. }
  179. }
  180. - (void)disableToolbar {
  181. #if !TARGET_OS_TV
  182. self.navigationController.toolbarHidden = YES;
  183. self.navigationController.hidesBarsOnSwipe = NO;
  184. self.toolbarItems = nil;
  185. #endif
  186. }
  187. #pragma mark - View Controller Lifecycle
  188. - (void)loadView {
  189. self.view = [FLEXTableView style:self.style];
  190. self.tableView.dataSource = self;
  191. self.tableView.delegate = self;
  192. _shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
  193. _bookmarksToolbarItem = [UIBarButtonItem
  194. flex_itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
  195. ];
  196. _openTabsToolbarItem = [UIBarButtonItem
  197. flex_itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher)
  198. ];
  199. self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
  200. self.middleLeftToolbarItem = UIBarButtonItem.flex_fixedSpace;
  201. self.middleToolbarItem = UIBarButtonItem.flex_fixedSpace;
  202. }
  203. - (void)viewDidLoad {
  204. [super viewDidLoad];
  205. self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
  206. // Toolbar
  207. #if !TARGET_OS_TV
  208. self.navigationController.toolbarHidden = NO;
  209. self.navigationController.hidesBarsOnSwipe = YES;
  210. #endif
  211. // On iOS 13, the root view controller shows it's search bar no matter what.
  212. // Turning this off avoids some weird flash the navigation bar does when we
  213. // toggle navigationItem.hidesSearchBarWhenScrolling on and off. The flash
  214. // will still happen on subsequent view controllers, but we can at least
  215. // avoid it for the root view controller
  216. if (@available(iOS 13, *)) {
  217. if (self.navigationController.viewControllers.firstObject == self) {
  218. _showSearchBarInitially = NO;
  219. }
  220. }
  221. }
  222. - (void)viewWillAppear:(BOOL)animated {
  223. [super viewWillAppear:animated];
  224. // When going back, make the search bar reappear instead of hiding
  225. if (@available(iOS 11.0, *)) {
  226. if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
  227. #if !TARGET_OS_TV
  228. self.navigationItem.hidesSearchBarWhenScrolling = NO;
  229. #endif
  230. }
  231. }
  232. #if TARGET_OS_TV
  233. UIEdgeInsets insets = self.tableView.contentInset;
  234. insets.top+=20;
  235. self.tableView.contentInset = insets;
  236. #endif
  237. [self setupToolbarItems];
  238. }
  239. - (void)viewDidAppear:(BOOL)animated {
  240. [super viewDidAppear:animated];
  241. // Allow scrolling to collapse the search bar, only if we don't want it pinned
  242. if (@available(iOS 11.0, *)) {
  243. if (self.showSearchBarInitially && !self.pinSearchBar && !self.didInitiallyRevealSearchBar) {
  244. // All this mumbo jumbo is necessary to work around a bug in iOS 13 up to 13.2
  245. // wherein quickly toggling navigationItem.hidesSearchBarWhenScrolling to make
  246. // the search bar appear initially results in a bugged search bar that
  247. // becomes transparent and floats over the screen as you scroll
  248. [UIView animateWithDuration:0.2 animations:^{
  249. #if !TARGET_OS_TV
  250. self.navigationItem.hidesSearchBarWhenScrolling = YES;
  251. #endif
  252. [self.navigationController.view setNeedsLayout];
  253. [self.navigationController.view layoutIfNeeded];
  254. }];
  255. }
  256. }
  257. // We only want to reveal the search bar when the view controller first appears.
  258. self.didInitiallyRevealSearchBar = YES;
  259. }
  260. - (void)viewWillDisappear:(BOOL)animated {
  261. [super viewWillDisappear:animated];
  262. if (self.manuallyDeactivateSearchOnDisappear && self.searchController.isActive) {
  263. self.searchController.active = NO;
  264. }
  265. }
  266. - (void)didMoveToParentViewController:(UIViewController *)parent {
  267. [super didMoveToParentViewController:parent];
  268. // Reset this since we are re-appearing under a new
  269. // parent view controller and need to show it again
  270. self.didInitiallyRevealSearchBar = NO;
  271. }
  272. #pragma mark - Toolbar, Public
  273. - (void)setupToolbarItems {
  274. if (!self.isViewLoaded) {
  275. return;
  276. }
  277. #if !TARGET_OS_TV
  278. self.toolbarItems = @[
  279. self.leftmostToolbarItem,
  280. UIBarButtonItem.flex_flexibleSpace,
  281. self.middleLeftToolbarItem,
  282. UIBarButtonItem.flex_flexibleSpace,
  283. self.middleToolbarItem,
  284. UIBarButtonItem.flex_flexibleSpace,
  285. self.bookmarksToolbarItem,
  286. UIBarButtonItem.flex_flexibleSpace,
  287. self.openTabsToolbarItem,
  288. ];
  289. for (UIBarButtonItem *item in self.toolbarItems) {
  290. [item _setWidth:60];
  291. // This does not work for anything but fixed spaces for some reason
  292. // item.width = 60;
  293. }
  294. #endif
  295. // Disable tabs entirely when not presented by FLEXExplorerViewController
  296. UIViewController *presenter = self.navigationController.presentingViewController;
  297. if (![presenter isKindOfClass:[FLEXExplorerViewController class]]) {
  298. self.openTabsToolbarItem.enabled = NO;
  299. }
  300. }
  301. - (void)addToolbarItems:(NSArray<UIBarButtonItem *> *)items {
  302. if (self.showsShareToolbarItem) {
  303. // Share button is in the middle, skip middle button
  304. if (items.count > 0) {
  305. self.middleLeftToolbarItem = items[0];
  306. }
  307. if (items.count > 1) {
  308. self.leftmostToolbarItem = items[1];
  309. }
  310. } else {
  311. // Add buttons right-to-left
  312. if (items.count > 0) {
  313. self.middleToolbarItem = items[0];
  314. }
  315. if (items.count > 1) {
  316. self.middleLeftToolbarItem = items[1];
  317. }
  318. if (items.count > 2) {
  319. self.leftmostToolbarItem = items[2];
  320. }
  321. }
  322. [self setupToolbarItems];
  323. }
  324. - (void)setShowsShareToolbarItem:(BOOL)showShare {
  325. if (_showsShareToolbarItem != showShare) {
  326. _showsShareToolbarItem = showShare;
  327. if (showShare) {
  328. // Push out leftmost item
  329. self.leftmostToolbarItem = self.middleLeftToolbarItem;
  330. self.middleLeftToolbarItem = self.middleToolbarItem;
  331. // Use share for middle
  332. self.middleToolbarItem = self.shareToolbarItem;
  333. } else {
  334. // Remove share, shift custom items rightward
  335. self.middleToolbarItem = self.middleLeftToolbarItem;
  336. self.middleLeftToolbarItem = self.leftmostToolbarItem;
  337. self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
  338. }
  339. }
  340. [self setupToolbarItems];
  341. }
  342. - (void)shareButtonPressed:(UIBarButtonItem *)sender {
  343. }
  344. #pragma mark - Private
  345. - (void)debounce:(void(^)(void))block {
  346. [self.debounceTimer invalidate];
  347. self.debounceTimer = [NSTimer
  348. scheduledTimerWithTimeInterval:self.searchBarDebounceInterval
  349. target:block
  350. selector:@selector(invoke)
  351. userInfo:nil
  352. repeats:NO
  353. ];
  354. }
  355. - (void)layoutTableHeaderIfNeeded {
  356. if (self.showsCarousel) {
  357. self.carousel.frame = FLEXRectSetHeight(
  358. self.carousel.frame, self.carousel.intrinsicContentSize.height
  359. );
  360. }
  361. self.tableView.tableHeaderView = self.tableView.tableHeaderView;
  362. }
  363. - (void)addCarousel:(FLEXScopeCarousel *)carousel {
  364. if (@available(iOS 11.0, *)) {
  365. self.tableView.tableHeaderView = carousel;
  366. } else {
  367. carousel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
  368. CGRect frame = self.tableHeaderViewContainer.frame;
  369. CGRect subviewFrame = carousel.frame;
  370. subviewFrame.origin.y = 0;
  371. // Put the carousel below the search bar if it's already there
  372. if (self.showsSearchBar) {
  373. carousel.frame = subviewFrame = FLEXRectSetY(
  374. subviewFrame, self.searchController.searchBar.frame.size.height
  375. );
  376. frame.size.height += carousel.intrinsicContentSize.height;
  377. } else {
  378. frame.size.height = carousel.intrinsicContentSize.height;
  379. }
  380. self.tableHeaderViewContainer.frame = frame;
  381. [self.tableHeaderViewContainer addSubview:carousel];
  382. }
  383. [self layoutTableHeaderIfNeeded];
  384. }
  385. - (void)removeCarousel:(FLEXScopeCarousel *)carousel {
  386. [carousel removeFromSuperview];
  387. if (@available(iOS 11.0, *)) {
  388. self.tableView.tableHeaderView = nil;
  389. } else {
  390. if (self.showsSearchBar) {
  391. [self removeSearchController:self.searchController];
  392. [self addSearchController:self.searchController];
  393. } else {
  394. self.tableView.tableHeaderView = nil;
  395. _tableHeaderViewContainer = nil;
  396. }
  397. }
  398. }
  399. - (void)addSearchController:(UISearchController *)controller {
  400. #if TARGET_OS_TV
  401. KBSearchButton *sb = [KBSearchButton buttonWithType:UIButtonTypeSystem];
  402. sb.searchBar = self.searchController.searchBar;
  403. UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] initWithCustomView:sb];
  404. self.navigationItem.leftBarButtonItem = searchButton;
  405. #else
  406. if (@available(iOS 11.0, *)) {
  407. self.navigationItem.searchController = controller;
  408. } else {
  409. controller.searchBar.autoresizingMask |= UIViewAutoresizingFlexibleBottomMargin;
  410. [self.tableHeaderViewContainer addSubview:controller.searchBar];
  411. CGRect subviewFrame = controller.searchBar.frame;
  412. CGRect frame = self.tableHeaderViewContainer.frame;
  413. frame.size.width = MAX(frame.size.width, subviewFrame.size.width);
  414. frame.size.height = subviewFrame.size.height;
  415. // Move the carousel down if it's already there
  416. if (self.showsCarousel) {
  417. self.carousel.frame = FLEXRectSetY(
  418. self.carousel.frame, subviewFrame.size.height
  419. );
  420. frame.size.height += self.carousel.frame.size.height;
  421. }
  422. self.tableHeaderViewContainer.frame = frame;
  423. [self layoutTableHeaderIfNeeded];
  424. }
  425. #endif
  426. }
  427. - (void)removeSearchController:(UISearchController *)controller {
  428. #if TARGET_OS_TV
  429. /*
  430. [self.searchContainer willMoveToParentViewController:nil];
  431. [self.searchContainer.view removeFromSuperview];
  432. [self.searchContainer removeFromParentViewController];
  433. */
  434. self.navigationItem.leftBarButtonItem = nil;
  435. #else
  436. if (@available(iOS 11.0, *)) {
  437. self.navigationItem.searchController = nil;
  438. } else {
  439. [controller.searchBar removeFromSuperview];
  440. if (self.showsCarousel) {
  441. // self.carousel.frame = FLEXRectRemake(CGPointZero, self.carousel.frame.size);
  442. [self removeCarousel:self.carousel];
  443. [self addCarousel:self.carousel];
  444. } else {
  445. self.tableView.tableHeaderView = nil;
  446. _tableHeaderViewContainer = nil;
  447. }
  448. }
  449. #endif
  450. }
  451. - (UIView *)tableHeaderViewContainer {
  452. if (!_tableHeaderViewContainer) {
  453. _tableHeaderViewContainer = [UIView new];
  454. self.tableView.tableHeaderView = self.tableHeaderViewContainer;
  455. }
  456. return _tableHeaderViewContainer;
  457. }
  458. - (void)showBookmarks {
  459. UINavigationController *nav = [[UINavigationController alloc]
  460. initWithRootViewController:[FLEXBookmarksViewController new]
  461. ];
  462. [self presentViewController:nav animated:YES completion:nil];
  463. }
  464. - (void)showTabSwitcher {
  465. UINavigationController *nav = [[UINavigationController alloc]
  466. initWithRootViewController:[FLEXTabsViewController new]
  467. ];
  468. [self presentViewController:nav animated:YES completion:nil];
  469. }
  470. #pragma mark - Search Bar
  471. #pragma mark UISearchResultsUpdating
  472. - (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
  473. [self.debounceTimer invalidate];
  474. NSString *text = searchController.searchBar.text;
  475. void (^updateSearchResults)() = ^{
  476. if (self.searchResultsUpdater) {
  477. [self.searchResultsUpdater updateSearchResults:text];
  478. } else {
  479. [self.searchDelegate updateSearchResults:text];
  480. }
  481. };
  482. // Only debounce if we want to, and if we have a non-empty string
  483. // Empty string events are sent instantly
  484. if (text.length && self.searchBarDebounceInterval > kFLEXDebounceInstant) {
  485. [self debounce:updateSearchResults];
  486. } else {
  487. updateSearchResults();
  488. }
  489. }
  490. #if !TARGET_OS_TV
  491. #pragma mark UISearchControllerDelegate
  492. - (void)willPresentSearchController:(UISearchController *)searchController {
  493. // Manually show cancel button for < iOS 13
  494. if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
  495. [searchController.searchBar setShowsCancelButton:YES animated:YES];
  496. }
  497. }
  498. - (void)willDismissSearchController:(UISearchController *)searchController {
  499. // Manually hide cancel button for < iOS 13
  500. if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
  501. [searchController.searchBar setShowsCancelButton:NO animated:YES];
  502. }
  503. }
  504. #pragma mark UISearchBarDelegate
  505. /// Not necessary in iOS 13; remove this when iOS 13 is the deployment target
  506. - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
  507. [self updateSearchResultsForSearchController:self.searchController];
  508. }
  509. #endif
  510. #pragma mark Table View
  511. /// Not having a title in the first section looks weird with a rounded-corner table view style
  512. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  513. if (@available(iOS 13, *)) {
  514. #if !TARGET_OS_TV
  515. if (self.style == UITableViewStyleInsetGrouped) {
  516. return @" ";
  517. }
  518. #endif
  519. }
  520. return nil; // For plain/gropued style
  521. }
  522. @end