PageRenderTime 75ms CodeModel.GetById 24ms RepoModel.GetById 2ms app.codeStats 1ms

/Classes/UVBaseViewController.m

https://gitlab.com/lisit1003/uservoice-ios-sdk
Objective C | 544 lines | 457 code | 74 blank | 13 comment | 89 complexity | 7b1cd70a5e5346ba4efc1f6150992db0 MD5 | raw file
  1. //
  2. // UVBaseViewController.m
  3. // UserVoice
  4. //
  5. // Created by UserVoice on 10/19/09.
  6. // Copyright 2009 UserVoice Inc. All rights reserved.
  7. //
  8. #import <QuartzCore/QuartzCore.h>
  9. #import "UVBaseViewController.h"
  10. #import "UVSession.h"
  11. #import "UVClientConfig.h"
  12. #import "UVSuggestion.h"
  13. #import "UVUser.h"
  14. #import "UVStyleSheet.h"
  15. #import "UVImageCache.h"
  16. #import "UVAccessToken.h"
  17. #import "UVSigninManager.h"
  18. #import "UVKeyboardUtils.h"
  19. #import "UVUtils.h"
  20. #import "UVTruncatingLabel.h"
  21. @implementation UVBaseViewController
  22. - (id)init {
  23. self = [super init];
  24. if (self) {
  25. _signinManager = [UVSigninManager manager];
  26. _signinManager.delegate = self;
  27. _templateCells = [NSMutableDictionary dictionary];
  28. }
  29. return self;
  30. }
  31. - (void)dismiss {
  32. if (_firstController) {
  33. [[UVImageCache sharedInstance] flush];
  34. [[UVSession currentSession] clear];
  35. }
  36. if (_firstController && [[UserVoice delegate] respondsToSelector:@selector(userVoiceRequestsDismissal)]) {
  37. [[UserVoice delegate] userVoiceRequestsDismissal];
  38. } else {
  39. __weak UVBaseViewController *_weakSelf = self;
  40. [self dismissViewControllerAnimated:YES
  41. completion:^{
  42. if (_weakSelf.firstController && [[UserVoice delegate] respondsToSelector:@selector(userVoiceWasDismissed)]) {
  43. [[UserVoice delegate] userVoiceWasDismissed];
  44. }
  45. }];
  46. }
  47. }
  48. - (CGRect)contentFrame {
  49. CGRect barFrame = CGRectZero;
  50. barFrame = self.navigationController.navigationBar.frame;
  51. CGRect appFrame = [UIScreen mainScreen].applicationFrame;
  52. CGFloat yStart = barFrame.origin.y + barFrame.size.height;
  53. return CGRectMake(0, yStart, appFrame.size.width, appFrame.size.height - barFrame.size.height);
  54. }
  55. - (void)showActivityIndicator {
  56. if (!_shade) {
  57. _shade = [[UIView alloc] initWithFrame:self.view.bounds];
  58. _shade.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
  59. _shade.backgroundColor = [UIColor blackColor];
  60. _shade.alpha = 0.5;
  61. [self.view addSubview:_shade];
  62. }
  63. if (!_activityIndicatorView) {
  64. _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  65. _activityIndicatorView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/4);
  66. _activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleTopMargin;
  67. [self.view addSubview:_activityIndicatorView];
  68. }
  69. _shade.hidden = NO;
  70. _activityIndicatorView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/([UVKeyboardUtils visible] ? 4 : 2));
  71. _activityIndicatorView.hidden = NO;
  72. [_activityIndicatorView startAnimating];
  73. }
  74. - (void)hideActivityIndicator {
  75. [self enableSubmitButton];
  76. [_activityIndicatorView stopAnimating];
  77. _activityIndicatorView.hidden = YES;
  78. _shade.hidden = YES;
  79. }
  80. - (void)setSubmitButtonEnabled:(BOOL)enabled {
  81. if (!self.navigationItem) {
  82. return;
  83. }
  84. if (self.navigationItem.rightBarButtonItem) {
  85. self.navigationItem.rightBarButtonItem.enabled = enabled;
  86. }
  87. }
  88. - (void)disableSubmitButton {
  89. [self setSubmitButtonEnabled:NO];
  90. }
  91. - (void)enableSubmitButton {
  92. [self enableSubmitButtonForce:NO];
  93. }
  94. - (void)enableSubmitButtonForce:(BOOL)force {
  95. BOOL shouldEnableButton = [self shouldEnableSubmitButton];
  96. if (shouldEnableButton || force) {
  97. [self setSubmitButtonEnabled:YES];
  98. }
  99. }
  100. - (BOOL)shouldEnableSubmitButton {
  101. return YES;
  102. }
  103. - (void)alertError:(NSString *)message {
  104. [[[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Error", @"UserVoice", [UserVoice bundle], nil)
  105. message:message
  106. delegate:nil
  107. cancelButtonTitle:NSLocalizedStringFromTableInBundle(@"OK", @"UserVoice", [UserVoice bundle], nil)
  108. otherButtonTitles:nil] show];
  109. }
  110. - (void)didReceiveError:(NSError *)error {
  111. NSString *msg = nil;
  112. [self hideActivityIndicator];
  113. if ([UVUtils isConnectionError:error]) {
  114. msg = NSLocalizedStringFromTableInBundle(@"There appears to be a problem with your network connection, please check your connectivity and try again.", @"UserVoice", [UserVoice bundle], nil);
  115. } else {
  116. NSDictionary *userInfo = [error userInfo];
  117. for (NSString *key in [userInfo allKeys]) {
  118. if ([key isEqualToString:@"message"] || [key isEqualToString:@"type"])
  119. continue;
  120. NSString *displayKey = nil;
  121. if ([key isEqualToString:@"display_name"])
  122. displayKey = NSLocalizedStringFromTableInBundle(@"User name", @"UserVoice", [UserVoice bundle], nil);
  123. else
  124. displayKey = [[key stringByReplacingOccurrencesOfString:@"_" withString:@" "] capitalizedString];
  125. // Suggestion title has custom messages
  126. if ([key isEqualToString:@"title"])
  127. msg = [userInfo valueForKey:key];
  128. else
  129. msg = [NSString stringWithFormat:@"%@ %@", displayKey, [userInfo valueForKey:key]];
  130. }
  131. if (!msg)
  132. msg = NSLocalizedStringFromTableInBundle(@"Sorry, there was an error in the application.", @"UserVoice", [UserVoice bundle], nil);
  133. }
  134. [self alertError:msg];
  135. }
  136. - (void)initNavigationItem {
  137. self.navigationItem.title = NSLocalizedStringFromTableInBundle(@"Feedback", @"UserVoice", [UserVoice bundle], nil);
  138. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Back", @"UserVoice", [UserVoice bundle], nil)
  139. style:UIBarButtonItemStylePlain
  140. target:nil
  141. action:nil];
  142. _exitButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Cancel", @"UserVoice", [UserVoice bundle], nil)
  143. style:UIBarButtonItemStylePlain
  144. target:self
  145. action:@selector(dismiss)];
  146. if ([UVSession currentSession].isModal && _firstController) {
  147. self.navigationItem.leftBarButtonItem = _exitButton;
  148. }
  149. }
  150. - (UIView *)poweredByView {
  151. UIView *power = [UIView new];
  152. power.frame = CGRectMake(0, 0, 0, 80);
  153. UILabel *uv = [UILabel new];
  154. uv.text = NSLocalizedStringFromTableInBundle(@"Powered by UserVoice", @"UserVoice", [UserVoice bundle], nil);
  155. uv.font = [UIFont systemFontOfSize:13];
  156. uv.textColor = [UIColor grayColor];
  157. uv.backgroundColor = [UIColor clearColor];
  158. uv.textAlignment = NSTextAlignmentCenter;
  159. UILabel *version = [UILabel new];
  160. version.text = [NSString stringWithFormat:@"iOS SDK v%@", [UserVoice version]];
  161. version.font = [UIFont systemFontOfSize:13];
  162. version.textColor = [UIColor lightGrayColor];
  163. version.textAlignment = NSTextAlignmentCenter;
  164. version.backgroundColor = [UIColor clearColor];
  165. [self configureView:power
  166. subviews:NSDictionaryOfVariableBindings(uv, version)
  167. constraints:@[@"V:|-[uv]-[version]", @"|[uv]|", @"|[version]|"]];
  168. return power;
  169. }
  170. - (BOOL)needNestedModalHack {
  171. return [UIDevice currentDevice].systemVersion.floatValue >= 6;
  172. }
  173. - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
  174. duration:(NSTimeInterval)duration {
  175. // We are the top modal, make to sure that parent modals use our size
  176. if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
  177. for (UIViewController* parent = self.presentingViewController;
  178. parent.presentingViewController;
  179. parent = parent.presentingViewController) {
  180. parent.view.superview.frame = parent.presentedViewController.view.superview.frame;
  181. }
  182. }
  183. [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
  184. }
  185. - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
  186. duration:(NSTimeInterval)duration {
  187. // We are the top modal, make to sure that parent modals are hidden during transition
  188. if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
  189. for (UIViewController* parent = self.presentingViewController;
  190. parent.presentingViewController;
  191. parent = parent.presentingViewController) {
  192. parent.view.superview.hidden = YES;
  193. }
  194. }
  195. [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  196. }
  197. - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
  198. // We are the top modal, make to sure that parent modals are shown after animation
  199. if (self.needNestedModalHack && self.presentedViewController == nil && self.presentingViewController) {
  200. for (UIViewController* parent = self.presentingViewController;
  201. parent.presentingViewController;
  202. parent = parent.presentingViewController) {
  203. parent.view.superview.hidden = NO;
  204. }
  205. }
  206. if (!IOS7 && _tableView) {
  207. [_tableView reloadData];
  208. }
  209. [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
  210. }
  211. #pragma mark ===== helper methods for table views =====
  212. #pragma clang diagnostic push
  213. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  214. - (UITableViewCell *)createCellForIdentifier:(NSString *)identifier
  215. tableView:(UITableView *)theTableView
  216. indexPath:(NSIndexPath *)indexPath
  217. style:(UITableViewCellStyle)style
  218. selectable:(BOOL)selectable {
  219. UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:identifier];
  220. if (cell == nil) {
  221. cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:identifier];
  222. cell.selectionStyle = selectable ? UITableViewCellSelectionStyleBlue : UITableViewCellSelectionStyleNone;
  223. SEL initCellSelector = NSSelectorFromString([NSString stringWithFormat:@"initCellFor%@:indexPath:", identifier]);
  224. if ([self respondsToSelector:initCellSelector]) {
  225. [self performSelector:initCellSelector withObject:cell withObject:indexPath];
  226. }
  227. }
  228. SEL customizeCellSelector = NSSelectorFromString([NSString stringWithFormat:@"customizeCellFor%@:indexPath:", identifier]);
  229. if ([self respondsToSelector:customizeCellSelector]) {
  230. [self performSelector:customizeCellSelector withObject:cell withObject:indexPath];
  231. }
  232. if (!IOS7) {
  233. cell.contentView.frame = CGRectMake(0, 0, [self cellWidthForStyle:_tableView.style accessoryType:cell.accessoryType], 0);
  234. [cell.contentView setNeedsLayout];
  235. [cell.contentView layoutIfNeeded];
  236. for (UIView *view in cell.contentView.subviews) {
  237. if ([view isKindOfClass:[UILabel class]]) {
  238. UILabel *label = (UILabel *)view;
  239. if (label.numberOfLines != 1) {
  240. [label setPreferredMaxLayoutWidth:label.frame.size.width];
  241. }
  242. [label setBackgroundColor:[UIColor clearColor]];
  243. } else if ([view isKindOfClass:[UVTruncatingLabel class]]) {
  244. UVTruncatingLabel *label = (UVTruncatingLabel *)view;
  245. [label setPreferredMaxLayoutWidth:label.frame.size.width];
  246. }
  247. }
  248. }
  249. return cell;
  250. }
  251. #pragma clang diagnostic pop
  252. #pragma mark ===== Keyboard Notifications =====
  253. - (void)registerForKeyboardNotifications {
  254. [[NSNotificationCenter defaultCenter] addObserver:self
  255. selector:@selector(keyboardWillShow:)
  256. name:UIKeyboardWillShowNotification object:nil];
  257. [[NSNotificationCenter defaultCenter] addObserver:self
  258. selector:@selector(keyboardDidShow:)
  259. name:UIKeyboardDidShowNotification object:nil];
  260. [[NSNotificationCenter defaultCenter] addObserver:self
  261. selector:@selector(keyboardWillHide:)
  262. name:UIKeyboardWillHideNotification object:nil];
  263. [[NSNotificationCenter defaultCenter] addObserver:self
  264. selector:@selector(keyboardDidHide:)
  265. name:UIKeyboardDidHideNotification object:nil];
  266. }
  267. - (void)keyboardWillShow:(NSNotification*)notification {
  268. if (IPAD) {
  269. NSInteger formSheetHeight = 576;
  270. if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
  271. _kbHeight = formSheetHeight - 352;
  272. } else {
  273. _kbHeight = formSheetHeight - 504;
  274. }
  275. } else {
  276. NSDictionary* info = [notification userInfo];
  277. CGRect rect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  278. // Convert from window space to view space to account for orientation
  279. _kbHeight = (NSInteger)[self.view convertRect:rect fromView:nil].size.height;
  280. }
  281. }
  282. - (UIScrollView *)scrollView {
  283. return _tableView;
  284. }
  285. - (void)keyboardDidShow:(NSNotification*)notification {
  286. UIEdgeInsets contentInsets = UIEdgeInsetsMake([self scrollView].contentInset.top, 0.0, _kbHeight, 0.0);
  287. [self scrollView].contentInset = contentInsets;
  288. [self scrollView].scrollIndicatorInsets = contentInsets;
  289. }
  290. - (void)keyboardWillHide:(NSNotification*)notification {
  291. }
  292. - (void)keyboardDidHide:(NSNotification*)notification {
  293. UIEdgeInsets contentInsets = UIEdgeInsetsMake([self scrollView].contentInset.top, 0.0, 0.0, 0.0);
  294. [self scrollView].contentInset = contentInsets;
  295. [self scrollView].scrollIndicatorInsets = contentInsets;
  296. }
  297. - (void)presentModalViewController:(UIViewController *)viewController {
  298. UINavigationController *navigationController = [UINavigationController new];
  299. [UVUtils applyStylesheetToNavigationController:navigationController];
  300. navigationController.viewControllers = @[viewController];
  301. if (FORMSHEET)
  302. navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
  303. [self presentViewController:navigationController animated:YES completion:nil];
  304. }
  305. - (void)setupGroupedTableView {
  306. _tableView = [[UITableView alloc] initWithFrame:[self contentFrame] style:UITableViewStyleGrouped];
  307. _tableView.delegate = (id<UITableViewDelegate>)self;
  308. _tableView.dataSource = (id<UITableViewDataSource>)self;
  309. if ([UVStyleSheet instance].tableViewBackgroundColor) {
  310. if (!IOS7) {
  311. UIView *bg = [UIView new];
  312. bg.backgroundColor = [UVStyleSheet instance].tableViewBackgroundColor;
  313. _tableView.backgroundView = bg;
  314. }
  315. _tableView.backgroundColor = [UVStyleSheet instance].tableViewBackgroundColor;
  316. }
  317. self.view = _tableView;
  318. }
  319. - (void)setView:(UIView *)view {
  320. [super setView:view];
  321. if (IOS7) {
  322. view.tintColor = [UVStyleSheet instance].tintColor;
  323. }
  324. }
  325. - (void)requireUserSignedIn:(UVCallback *)callback {
  326. [_signinManager signInWithCallback:callback];
  327. }
  328. - (void)requireUserAuthenticated:(NSString *)email name:(NSString *)name callback:(UVCallback *)callback {
  329. [_signinManager signInWithEmail:email name:name callback:callback];
  330. }
  331. - (void)setUserName:(NSString *)theName {
  332. _userName = theName;
  333. NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  334. [prefs setObject:_userName forKey:@"uv-user-name"];
  335. [prefs synchronize];
  336. }
  337. - (NSString *)userName {
  338. if ([UVSession currentSession].user)
  339. return [UVSession currentSession].user.name;
  340. if (_userName)
  341. return _userName;
  342. NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  343. _userName = [prefs stringForKey:@"uv-user-name"];
  344. return _userName;
  345. }
  346. - (void)setUserEmail:(NSString *)theEmail {
  347. _userEmail = theEmail;
  348. NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  349. [prefs setObject:_userEmail forKey:@"uv-user-email"];
  350. [prefs synchronize];
  351. }
  352. - (NSString *)userEmail {
  353. if ([UVSession currentSession].user)
  354. return [UVSession currentSession].user.email;
  355. if (_userEmail)
  356. return _userEmail;
  357. NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
  358. _userEmail = [prefs stringForKey:@"uv-user-email"];
  359. return _userEmail;
  360. }
  361. #pragma clang diagnostic push
  362. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  363. - (CGFloat)heightForDynamicRowWithReuseIdentifier:(NSString *)reuseIdentifier indexPath:(NSIndexPath *)indexPath {
  364. NSString *cacheKey = [NSString stringWithFormat:@"%@-%d", reuseIdentifier, (int)self.view.frame.size.width];
  365. UITableViewCell *cell = [_templateCells objectForKey:cacheKey];
  366. if (!cell) {
  367. cell = [[UITableViewCell alloc] initWithStyle:0 reuseIdentifier:reuseIdentifier];
  368. SEL initCellSelector = NSSelectorFromString([NSString stringWithFormat:@"initCellFor%@:indexPath:", reuseIdentifier]);
  369. if ([self respondsToSelector:initCellSelector]) {
  370. [self performSelector:initCellSelector withObject:cell withObject:nil];
  371. }
  372. [_templateCells setObject:cell forKey:cacheKey];
  373. }
  374. SEL customizeCellSelector = NSSelectorFromString([NSString stringWithFormat:@"customizeCellFor%@:indexPath:", reuseIdentifier]);
  375. if ([self respondsToSelector:customizeCellSelector]) {
  376. [self performSelector:customizeCellSelector withObject:cell withObject:indexPath];
  377. }
  378. cell.contentView.frame = CGRectMake(0, 0, [self cellWidthForStyle:_tableView.style accessoryType:cell.accessoryType], 10000);
  379. [cell.contentView setNeedsLayout];
  380. [cell.contentView layoutIfNeeded];
  381. // cells are usually flat so I don't bother to iterate recursively
  382. for (UIView *view in cell.contentView.subviews) {
  383. if ([view isKindOfClass:[UILabel class]]) {
  384. UILabel *label = (UILabel *)view;
  385. if (label.numberOfLines != 1) {
  386. [label setPreferredMaxLayoutWidth:label.frame.size.width];
  387. }
  388. }
  389. }
  390. [cell.contentView setNeedsLayout];
  391. [cell.contentView layoutIfNeeded];
  392. return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1;
  393. }
  394. #pragma clang diagnostic pop
  395. - (CGFloat)cellWidthForStyle:(UITableViewStyle)style accessoryType:(UITableViewCellAccessoryType)accessoryType {
  396. CGFloat width = self.view.frame.size.width;
  397. CGFloat accessoryWidth = 0;
  398. CGFloat margin = 0;
  399. if (IOS7) {
  400. if (accessoryType == UITableViewCellAccessoryDisclosureIndicator) {
  401. accessoryWidth = 33;
  402. } else if (accessoryType == UITableViewCellAccessoryCheckmark) {
  403. accessoryWidth = 38.5;
  404. }
  405. } else {
  406. if (accessoryType == UITableViewCellAccessoryDisclosureIndicator || accessoryType == UITableViewCellAccessoryCheckmark) {
  407. accessoryWidth = 20;
  408. }
  409. if (width > 20) {
  410. if (width < 400) {
  411. margin = 10;
  412. } else {
  413. margin = MAX(31, MIN(45, width*0.06f));
  414. }
  415. } else {
  416. margin = width - 10;
  417. }
  418. }
  419. return width - (style == UITableViewStyleGrouped ? margin * 2 : 0) - accessoryWidth;
  420. }
  421. - (void)configureView:(UIView *)superview subviews:(NSDictionary *)viewsDict constraints:(NSArray *)constraintStrings {
  422. [self configureView:superview subviews:viewsDict constraints:constraintStrings finalCondition:NO finalConstraint:nil];
  423. }
  424. - (void)configureView:(UIView *)superview subviews:(NSDictionary *)viewsDict constraints:(NSArray *)constraintStrings finalCondition:(BOOL)includeFinalConstraint finalConstraint:(NSString *)finalConstraint {
  425. [UVUtils configureView:superview subviews:viewsDict constraints:constraintStrings finalCondition:includeFinalConstraint finalConstraint:finalConstraint];
  426. }
  427. - (UITextField *)configureView:(UIView *)view label:(NSString *)labelText placeholder:(NSString *)placeholderText {
  428. UITextField *field = [UITextField new];
  429. field.placeholder = placeholderText;
  430. [field setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
  431. UILabel *label = [UILabel new];
  432. label.text = [NSString stringWithFormat:@"%@:", labelText];
  433. label.backgroundColor = [UIColor clearColor];
  434. label.textColor = [UIColor grayColor];
  435. [label setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
  436. [self configureView:view
  437. subviews:NSDictionaryOfVariableBindings(field, label)
  438. constraints:@[@"|-16-[label]-[field]-|", @"V:|-12-[label]", @"V:|-12-[field]"]];
  439. return field;
  440. }
  441. - (UIStatusBarStyle)preferredStatusBarStyle {
  442. return [UVStyleSheet instance].preferredStatusBarStyle;
  443. }
  444. #pragma mark - UVSigninManagerDelegate
  445. - (void)signinManagerDidSignIn:(UVUser *)user {
  446. [self hideActivityIndicator];
  447. }
  448. - (void)signinManagerDidFail {
  449. [self hideActivityIndicator];
  450. }
  451. #pragma mark ===== Basic View Methods =====
  452. - (void)loadView {
  453. [self initNavigationItem];
  454. [self registerForKeyboardNotifications];
  455. }
  456. - (void) viewWillDisappear:(BOOL)animated {
  457. [self.tableView setContentOffset:self.tableView.contentOffset animated:NO];
  458. [super viewWillDisappear:animated];
  459. }
  460. - (void)dealloc {
  461. if (self.tableView) {
  462. self.tableView.dataSource = nil;
  463. self.tableView.delegate = nil;
  464. }
  465. if (_signinManager) {
  466. _signinManager.delegate = nil;
  467. }
  468. [[NSNotificationCenter defaultCenter] removeObserver:self];
  469. }
  470. @end