DBDebugToolkit.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. // The MIT License
  2. //
  3. // Copyright (c) 2016 Dariusz Bukowski
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. #import "DBDebugToolkit.h"
  23. #import "DBShakeTrigger.h"
  24. #import "DBMenuTableViewController.h"
  25. #import "NSBundle+DBDebugToolkit.h"
  26. #import "DBPerformanceWidgetView.h"
  27. #import "DBPerformanceToolkit.h"
  28. #import "DBPerformanceTableViewController.h"
  29. #import "DBConsoleOutputCaptor.h"
  30. #import "DBNetworkToolkit.h"
  31. #import "DBUserInterfaceToolkit.h"
  32. #import "DBLocationToolkit.h"
  33. #import "DBKeychainToolkit.h"
  34. #import "DBUserDefaultsToolkit.h"
  35. #import "DBCoreDataToolkit.h"
  36. #import "DBCrashReportsToolkit.h"
  37. #import "DBTopLevelViewsWrapper.h"
  38. #import "UIApplication+DBDebugToolkit.h"
  39. static NSString *const DBDebugToolkitObserverPresentationControllerPropertyKeyPath = @"containerView";
  40. @interface DBDebugToolkit () <DBDebugToolkitTriggerDelegate, DBMenuTableViewControllerDelegate, DBPerformanceWidgetViewDelegate>
  41. @property (nonatomic, copy) NSArray <id <DBDebugToolkitTrigger>> *triggers;
  42. @property (nonatomic, strong) DBMenuTableViewController *menuViewController;
  43. @property (nonatomic, assign) BOOL showsMenu;
  44. @property (nonatomic, strong) DBPerformanceToolkit *performanceToolkit;
  45. @property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
  46. @property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
  47. @property (nonatomic, strong) DBUserInterfaceToolkit *userInterfaceToolkit;
  48. @property (nonatomic, strong) DBLocationToolkit *locationToolkit;
  49. @property (nonatomic, strong) DBCoreDataToolkit *coreDataToolkit;
  50. @property (nonatomic, strong) DBCrashReportsToolkit *crashReportsToolkit;
  51. @property (nonatomic, strong) NSMutableArray <DBCustomAction *> *customActions;
  52. @property (nonatomic, strong) NSMutableDictionary <NSString *, DBCustomVariable *> *customVariables;
  53. @property (nonatomic, strong) DBTopLevelViewsWrapper *topLevelViewsWrapper;
  54. @end
  55. @implementation DBDebugToolkit
  56. #pragma mark - Setup
  57. + (void)setup {
  58. NSArray <id <DBDebugToolkitTrigger>> *defaultTriggers = [self defaultTriggers];
  59. [self setupWithTriggers:defaultTriggers];
  60. }
  61. + (void)setupWithTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
  62. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  63. toolkit.triggers = triggers;
  64. }
  65. + (NSArray <id <DBDebugToolkitTrigger>> *)defaultTriggers {
  66. return [NSArray arrayWithObject:[DBShakeTrigger trigger]];
  67. }
  68. + (instancetype)sharedInstance {
  69. static DBDebugToolkit *sharedInstance = nil;
  70. static dispatch_once_t onceToken;
  71. dispatch_once(&onceToken, ^{
  72. sharedInstance = [[DBDebugToolkit alloc] init];
  73. [sharedInstance registerForNotifications];
  74. [sharedInstance setupTopLevelViewsWrapper];
  75. [sharedInstance setupPerformanceToolkit];
  76. [sharedInstance setupConsoleOutputCaptor];
  77. [sharedInstance setupNetworkToolkit];
  78. [sharedInstance setupUserInterfaceToolkit];
  79. [sharedInstance setupLocationToolkit];
  80. [sharedInstance setupCoreDataToolkit];
  81. [sharedInstance setupCustomActions];
  82. [sharedInstance setupCustomVariables];
  83. [sharedInstance setupCrashReportsToolkit];
  84. });
  85. return sharedInstance;
  86. }
  87. - (void)dealloc {
  88. [[NSNotificationCenter defaultCenter] removeObserver:self];
  89. }
  90. #pragma mark - Setting triggers
  91. - (void)setTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
  92. _triggers = [triggers copy];
  93. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
  94. [self addTriggersToWindow:keyWindow];
  95. for (id <DBDebugToolkitTrigger> trigger in triggers) {
  96. trigger.delegate = self;
  97. }
  98. }
  99. - (void)addTriggersToWindow:(UIWindow *)window {
  100. for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
  101. [trigger addToWindow:window];
  102. }
  103. }
  104. - (void)removeTriggersFromWindow:(UIWindow *)window {
  105. for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
  106. [trigger removeFromWindow:window];
  107. }
  108. }
  109. #pragma mark - Window notifications
  110. - (void)registerForNotifications {
  111. [[NSNotificationCenter defaultCenter] addObserver:self
  112. selector:@selector(newKeyWindowNotification:)
  113. name:UIWindowDidBecomeKeyNotification
  114. object:nil];
  115. [[NSNotificationCenter defaultCenter] addObserver:self
  116. selector:@selector(windowDidResignKeyNotification:)
  117. name:UIWindowDidResignKeyNotification
  118. object:nil];
  119. }
  120. - (void)newKeyWindowNotification:(NSNotification *)notification {
  121. UIWindow *newKeyWindow = notification.object;
  122. [self addTriggersToWindow:newKeyWindow];
  123. [self addTopLevelViewsWrapperToWindow:newKeyWindow];
  124. }
  125. - (void)windowDidResignKeyNotification:(NSNotification *)notification {
  126. UIWindow *windowResigningKey = notification.object;
  127. [self removeTriggersFromWindow:windowResigningKey];
  128. }
  129. #pragma mark - Performance toolkit
  130. - (void)setupPerformanceToolkit {
  131. self.performanceToolkit = [[DBPerformanceToolkit alloc] initWithWidgetDelegate:self];
  132. [self.topLevelViewsWrapper addTopLevelView:self.performanceToolkit.widget];
  133. }
  134. #pragma mark - Console output captor
  135. - (void)setupConsoleOutputCaptor {
  136. self.consoleOutputCaptor = [DBConsoleOutputCaptor sharedInstance];
  137. self.consoleOutputCaptor.enabled = YES;
  138. }
  139. + (void)setCapturingConsoleOutputEnabled:(BOOL)enabled {
  140. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  141. toolkit.consoleOutputCaptor.enabled = enabled;
  142. }
  143. #pragma mark - Network toolkit
  144. - (void)setupNetworkToolkit {
  145. self.networkToolkit = [DBNetworkToolkit sharedInstance];
  146. self.networkToolkit.loggingEnabled = YES;
  147. }
  148. + (void)setNetworkRequestsLoggingEnabled:(BOOL)enabled {
  149. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  150. toolkit.networkToolkit.loggingEnabled = enabled;
  151. }
  152. #pragma mark - User interface toolkit
  153. - (void)setupUserInterfaceToolkit {
  154. self.userInterfaceToolkit = [DBUserInterfaceToolkit sharedInstance];
  155. self.userInterfaceToolkit.colorizedViewBordersEnabled = NO;
  156. self.userInterfaceToolkit.slowAnimationsEnabled = NO;
  157. self.userInterfaceToolkit.showingTouchesEnabled = NO;
  158. [self.userInterfaceToolkit setupDebuggingInformationOverlay];
  159. [self.userInterfaceToolkit setupGridOverlay];
  160. [self.topLevelViewsWrapper addTopLevelView:self.userInterfaceToolkit.gridOverlay];
  161. }
  162. #pragma mark - Location toolkit
  163. - (void)setupLocationToolkit {
  164. self.locationToolkit = [DBLocationToolkit sharedInstance];
  165. }
  166. #pragma mark - Core Data toolkit
  167. - (void)setupCoreDataToolkit {
  168. self.coreDataToolkit = [DBCoreDataToolkit sharedInstance];
  169. }
  170. #pragma mark - Custom actions
  171. - (void)setupCustomActions {
  172. self.customActions = [NSMutableArray array];
  173. }
  174. + (void)addCustomAction:(DBCustomAction *)customAction {
  175. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  176. [toolkit.customActions addObject:customAction];
  177. }
  178. + (void)addCustomActions:(NSArray <DBCustomAction *> *)customActions {
  179. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  180. [toolkit.customActions addObjectsFromArray:customActions];
  181. }
  182. + (void)removeCustomAction:(DBCustomAction *)customAction {
  183. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  184. [toolkit.customActions removeObject:customAction];
  185. }
  186. + (void)removeCustomActions:(NSArray <DBCustomAction *> *)customActions {
  187. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  188. [toolkit.customActions removeObjectsInArray:customActions];
  189. }
  190. #pragma mark - Custom variables
  191. - (void)setupCustomVariables {
  192. self.customVariables = [NSMutableDictionary dictionary];
  193. }
  194. + (void)addCustomVariable:(DBCustomVariable *)customVariable {
  195. [self removeCustomVariableWithName:customVariable.name];
  196. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  197. toolkit.customVariables[customVariable.name] = customVariable;
  198. }
  199. + (void)addCustomVariables:(NSArray <DBCustomVariable *> *)customVariables {
  200. for (DBCustomVariable *customVariable in customVariables) {
  201. [self addCustomVariable:customVariable];
  202. }
  203. }
  204. + (void)removeCustomVariableWithName:(NSString *)variableName {
  205. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  206. DBCustomVariable *customVariable = toolkit.customVariables[variableName];
  207. [customVariable removeTarget:nil action:nil];
  208. toolkit.customVariables[variableName] = nil;
  209. }
  210. + (void)removeCustomVariablesWithNames:(NSArray <NSString *> *)variableNames {
  211. for (NSString *variableName in variableNames) {
  212. [self removeCustomVariableWithName:variableName];
  213. }
  214. }
  215. + (DBCustomVariable *)customVariableWithName:(NSString *)variableName {
  216. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  217. return toolkit.customVariables[variableName];
  218. }
  219. #pragma mark - Crash reports toolkit
  220. - (void)setupCrashReportsToolkit {
  221. self.crashReportsToolkit = [DBCrashReportsToolkit sharedInstance];
  222. self.crashReportsToolkit.consoleOutputCaptor = self.consoleOutputCaptor;
  223. self.crashReportsToolkit.buildInfoProvider = [DBBuildInfoProvider new];
  224. self.crashReportsToolkit.deviceInfoProvider = [DBDeviceInfoProvider new];
  225. }
  226. + (void)setupCrashReporting {
  227. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  228. [toolkit.crashReportsToolkit setupCrashReporting];
  229. }
  230. #pragma mark - Resources
  231. + (void)clearKeychain {
  232. DBKeychainToolkit *keychainToolkit = [DBKeychainToolkit new];
  233. [keychainToolkit handleClearAction];
  234. }
  235. + (void)clearUserDefaults {
  236. DBUserDefaultsToolkit *userDefaultsToolkit = [DBUserDefaultsToolkit new];
  237. [userDefaultsToolkit handleClearAction];
  238. }
  239. + (void)clearDocuments {
  240. NSFileManager *fileManager = [NSFileManager defaultManager];
  241. NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  242. NSArray <NSString *> *documentsContents = [fileManager contentsOfDirectoryAtPath:documentsPath error:nil];
  243. for (NSString *file in documentsContents) {
  244. NSString *filePath = [documentsPath stringByAppendingPathComponent:file];
  245. [fileManager removeItemAtPath:filePath error:nil];
  246. }
  247. }
  248. + (void)clearCookies {
  249. NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  250. for (NSHTTPCookie *cookie in cookieStorage.cookies) {
  251. [cookieStorage deleteCookie:cookie];
  252. }
  253. [[NSUserDefaults standardUserDefaults] synchronize];
  254. }
  255. #pragma mark - Shortcut items
  256. + (void)addClearDataShortcutItem {
  257. if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
  258. // Shortcut items are not supported on the running iOS version.
  259. return;
  260. }
  261. NSArray <UIApplicationShortcutItem *> *shortcutItems = UIApplication.sharedApplication.shortcutItems;
  262. for (UIApplicationShortcutItem *item in shortcutItems) {
  263. if ([item.type isEqualToString:DBClearDataShortcutItemType]) {
  264. // We already added `Clear data` shortcut item.
  265. return;
  266. }
  267. }
  268. UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeCompose];
  269. UIApplicationShortcutItem *clearDataShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:DBClearDataShortcutItemType
  270. localizedTitle:@"Clear data"
  271. localizedSubtitle:@"DBDebugToolkit"
  272. icon:shortcutIcon
  273. userInfo:nil];
  274. NSMutableArray *newShortcutItems = shortcutItems == nil ? [NSMutableArray array] : [NSMutableArray arrayWithArray:shortcutItems];
  275. [newShortcutItems insertObject:clearDataShortcutItem atIndex:0];
  276. UIApplication.sharedApplication.shortcutItems = [newShortcutItems copy];
  277. }
  278. + (void)handleClearDataShortcutItemAction {
  279. [self clearUserDefaults];
  280. [self clearKeychain];
  281. [self clearDocuments];
  282. [self clearCookies];
  283. }
  284. #pragma mark - Top level views
  285. - (void)setupTopLevelViewsWrapper {
  286. self.topLevelViewsWrapper = [DBTopLevelViewsWrapper new];
  287. UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
  288. [self addTopLevelViewsWrapperToWindow:keyWindow];
  289. }
  290. - (void)addTopLevelViewsWrapperToWindow:(UIWindow *)window {
  291. [self.topLevelViewsWrapper.superview removeObserver:self forKeyPath:@"layer.sublayers"];
  292. [window addSubview:self.topLevelViewsWrapper];
  293. // We observe the "layer.sublayers" property of the window to keep the widget on top.
  294. [window addObserver:self
  295. forKeyPath:@"layer.sublayers"
  296. options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
  297. context:nil];
  298. }
  299. #pragma mark - Convenience methods
  300. + (void)showMenu {
  301. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  302. if (!toolkit.showsMenu) {
  303. [toolkit showMenu];
  304. }
  305. }
  306. + (void)showPerformanceWidget {
  307. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  308. DBPerformanceToolkit *performanceToolkit = toolkit.performanceToolkit;
  309. performanceToolkit.isWidgetShown = YES;
  310. }
  311. + (void)showDebuggingInformationOverlay {
  312. DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
  313. DBUserInterfaceToolkit *userInterfaceToolkit = toolkit.userInterfaceToolkit;
  314. if (userInterfaceToolkit.isDebuggingInformationOverlayAvailable) {
  315. [userInterfaceToolkit showDebuggingInformationOverlay];
  316. }
  317. }
  318. #pragma mark - Showing menu
  319. - (void)showMenu {
  320. self.showsMenu = YES;
  321. UIViewController *presentingViewController = [self topmostViewController];
  322. UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.menuViewController];
  323. navigationController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
  324. navigationController.modalPresentationStyle = UIModalPresentationOverFullScreen;
  325. navigationController.modalPresentationCapturesStatusBarAppearance = YES;
  326. [presentingViewController presentViewController:navigationController animated:YES completion:^{
  327. // We need this to properly handle a case of menu being dismissed because of dismissing of the view controller that presents it.
  328. [navigationController.presentationController addObserver:self
  329. forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath
  330. options:0
  331. context:nil];
  332. }];
  333. }
  334. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  335. if ([object isKindOfClass:[UIPresentationController class]]) {
  336. UIPresentationController *presentationController = (UIPresentationController *)object;
  337. if (presentationController.containerView == nil) {
  338. // The menu was dismissed.
  339. self.showsMenu = NO;
  340. [presentationController removeObserver:self forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath];
  341. }
  342. } else if ([object isKindOfClass:[UIWindow class]]) {
  343. [self.topLevelViewsWrapper.superview bringSubviewToFront:self.topLevelViewsWrapper];
  344. }
  345. }
  346. - (DBMenuTableViewController *)menuViewController {
  347. if (!_menuViewController) {
  348. NSBundle *bundle = [NSBundle debugToolkitBundle];
  349. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBMenuTableViewController" bundle:bundle];
  350. _menuViewController = [storyboard instantiateInitialViewController];
  351. _menuViewController.performanceToolkit = self.performanceToolkit;
  352. _menuViewController.consoleOutputCaptor = self.consoleOutputCaptor;
  353. _menuViewController.networkToolkit = self.networkToolkit;
  354. _menuViewController.userInterfaceToolkit = self.userInterfaceToolkit;
  355. _menuViewController.locationToolkit = self.locationToolkit;
  356. _menuViewController.coreDataToolkit = self.coreDataToolkit;
  357. _menuViewController.crashReportsToolkit = self.crashReportsToolkit;
  358. _menuViewController.buildInfoProvider = [DBBuildInfoProvider new];
  359. _menuViewController.deviceInfoProvider = [DBDeviceInfoProvider new];
  360. _menuViewController.delegate = self;
  361. }
  362. _menuViewController.customVariables = self.customVariables.allValues;
  363. _menuViewController.customActions = self.customActions;
  364. return _menuViewController;
  365. }
  366. - (UIViewController *)topmostViewController {
  367. UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  368. return [self topmostViewControllerWithRootViewController:rootViewController];
  369. }
  370. - (UIViewController *)topmostViewControllerWithRootViewController:(UIViewController *)rootViewController {
  371. if ([rootViewController isKindOfClass:[UITabBarController class]]) {
  372. UITabBarController *tabBarController = (UITabBarController *)rootViewController;
  373. return [self topmostViewControllerWithRootViewController:tabBarController.selectedViewController];
  374. } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
  375. UINavigationController *navigationController = (UINavigationController *)rootViewController;
  376. if (navigationController.visibleViewController == nil) {
  377. return navigationController;
  378. }
  379. return [self topmostViewControllerWithRootViewController:navigationController.visibleViewController];
  380. } else if (rootViewController.presentedViewController) {
  381. UIViewController* presentedViewController = rootViewController.presentedViewController;
  382. return [self topmostViewControllerWithRootViewController:presentedViewController];
  383. }
  384. return rootViewController;
  385. }
  386. #pragma mark - DBDebugToolkitTriggerDelegate
  387. - (void)debugToolkitTriggered:(id<DBDebugToolkitTrigger>)trigger {
  388. if (!self.showsMenu) {
  389. [self showMenu];
  390. }
  391. }
  392. #pragma mark - DBMenuTableViewControllerDelegate
  393. - (void)menuTableViewControllerDidTapClose:(DBMenuTableViewController *)menuTableViewController {
  394. UIViewController *presentingViewController = self.menuViewController.navigationController.presentingViewController;
  395. [presentingViewController dismissViewControllerAnimated:YES completion:^{
  396. self.showsMenu = NO;
  397. }];
  398. }
  399. #pragma mark - DBPerformanceWidgetViewDelegate
  400. - (void)performanceWidgetView:(DBPerformanceWidgetView *)performanceWidgetView didTapOnSection:(DBPerformanceSection)section {
  401. BOOL shouldAnimateShowingPerformance = YES;
  402. if (!self.showsMenu) {
  403. [self showMenu];
  404. shouldAnimateShowingPerformance = NO;
  405. }
  406. UINavigationController *navigationController = self.menuViewController.navigationController;
  407. if (navigationController.viewControllers.count > 1 && [navigationController.viewControllers[1] isKindOfClass:[DBPerformanceTableViewController class]]) {
  408. // Only update the presented DBPerformanceTableViewController instance.
  409. DBPerformanceTableViewController *performanceTableViewController = (DBPerformanceTableViewController *)navigationController.viewControllers[1];
  410. performanceTableViewController.selectedSection = section;
  411. } else {
  412. // Update navigation controller's view controllers.
  413. [self.menuViewController openPerformanceMenuWithSection:section
  414. animated:shouldAnimateShowingPerformance];
  415. }
  416. }
  417. @end