FLEXTableViewController.m 21 KB

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