ViewController.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "ViewController.h"
  15. #import <FirebaseAnalytics/FirebaseAnalytics.h>
  16. #import <FirebaseCore/FIROptions.h>
  17. #import <FirebaseCore/FirebaseCore.h>
  18. #import <FirebaseInstallations/FirebaseInstallations.h>
  19. #import <FirebaseRemoteConfig/FIRRemoteConfig_Private.h>
  20. #import <FirebaseRemoteConfig/FirebaseRemoteConfig.h>
  21. #import "FRCLog.h"
  22. static NSString *const FIRPerfNamespace = @"fireperf";
  23. static NSString *const FIRDefaultFIRAppName = @"__FIRAPP_DEFAULT";
  24. static NSString *const FIRSecondFIRAppName = @"secondFIRApp";
  25. @interface ViewController ()
  26. @property(nonatomic, strong) IBOutlet UIButton *fetchButton;
  27. @property(nonatomic, strong) IBOutlet UIButton *applyButton;
  28. @property(nonatomic, strong) IBOutlet UIButton *refreshButton;
  29. @property(nonatomic, strong) IBOutlet UIButton *clearLogsButton;
  30. @property(nonatomic, strong) IBOutlet UITextView *mainTextView;
  31. /// Key of custom variable to be added by user.
  32. @property(nonatomic, weak) IBOutlet UITextField *keyLabel;
  33. /// Value of custom variable to be added by user.
  34. @property(nonatomic, weak) IBOutlet UITextField *valueLabel;
  35. /// Expiration in seconds to be set by user.
  36. @property(nonatomic, weak) IBOutlet UITextField *expirationLabel;
  37. /// Config Defaults.
  38. @property(nonatomic, strong) NSMutableDictionary *configDefaults;
  39. /// developer mode switch.
  40. @property(strong, nonatomic) IBOutlet UISwitch *developerModeEnabled;
  41. /// Current selected namespace.
  42. @property(nonatomic, copy) NSString *currentNamespace;
  43. /// Curret selected FIRApp instance name.
  44. @property(nonatomic, copy) NSString *FIRAppName;
  45. /// Selected namespace picker control view.
  46. @property(nonatomic, strong) IBOutlet UIPickerView *namespacePicker;
  47. /// Selected app picker control view.
  48. @property(nonatomic, strong) IBOutlet UIPickerView *appPicker;
  49. /// Array of prepopulated namespaces supported by this app.
  50. @property(nonatomic, strong) NSArray<NSString *> *namespacePickerData;
  51. /// Array of prepopulated FIRApp names supported by this app.
  52. @property(nonatomic, strong) NSArray<NSString *> *appPickerData;
  53. /// Array of Remote Config instances.
  54. @property(nonatomic, strong) NSMutableDictionary *RCInstances;
  55. @end
  56. @implementation ViewController
  57. - (void)viewDidLoad {
  58. [super viewDidLoad];
  59. [[FRCLog sharedInstance] setLogView:self.mainTextView];
  60. [[FRCLog sharedInstance] logToConsole:@"Viewcontroller loaded"];
  61. [[FRCLog sharedInstance] logToConsole:[[NSBundle mainBundle] bundleIdentifier]];
  62. // Setup UI
  63. self.expirationLabel.text = [NSString stringWithFormat:@"0"];
  64. self.configDefaults = [[NSMutableDictionary alloc] init];
  65. self.keyLabel.delegate = self;
  66. self.valueLabel.delegate = self;
  67. self.expirationLabel.delegate = self;
  68. self.mainTextView.editable = NO;
  69. // TODO(mandard): Add support for deleting and adding namespaces in the app.
  70. self.namespacePickerData =
  71. [[NSArray alloc] initWithObjects:FIRNamespaceGoogleMobilePlatform, FIRPerfNamespace, nil];
  72. self.appPickerData =
  73. [[NSArray alloc] initWithObjects:FIRDefaultFIRAppName, FIRSecondFIRAppName, nil];
  74. self.RCInstances = [[NSMutableDictionary alloc] init];
  75. for (NSString *namespaceString in self.namespacePickerData) {
  76. for (NSString *appString in self.appPickerData) {
  77. // Check for the default instance.
  78. if (!self.RCInstances[namespaceString]) {
  79. self.RCInstances[namespaceString] = [[NSMutableDictionary alloc] init];
  80. }
  81. if ([namespaceString isEqualToString:FIRNamespaceGoogleMobilePlatform] &&
  82. [appString isEqualToString:FIRDefaultFIRAppName]) {
  83. self.RCInstances[namespaceString][appString] = [FIRRemoteConfig remoteConfig];
  84. } else {
  85. FIRApp *firebaseApp = ([appString isEqualToString:FIRDefaultFIRAppName])
  86. ? [FIRApp defaultApp]
  87. : [FIRApp appNamed:appString];
  88. self.RCInstances[namespaceString][appString] =
  89. [FIRRemoteConfig remoteConfigWithFIRNamespace:namespaceString app:firebaseApp];
  90. }
  91. FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
  92. settings.fetchTimeout = 300;
  93. settings.minimumFetchInterval = 0;
  94. ((FIRRemoteConfig *)(self.RCInstances[namespaceString][appString])).configSettings = settings;
  95. }
  96. }
  97. [[FRCLog sharedInstance] logToConsole:@"RC instances inited"];
  98. self.namespacePicker.dataSource = self;
  99. self.namespacePicker.delegate = self;
  100. self.appPicker.dataSource = self;
  101. self.appPicker.delegate = self;
  102. [self.developerModeEnabled setOn:true animated:false];
  103. }
  104. - (IBAction)fetchButtonPressed:(id)sender {
  105. [[FRCLog sharedInstance] logToConsole:@"Fetch button pressed"];
  106. // fetchConfig api callback, this is triggered when client receives response from server
  107. ViewController *__weak weakSelf = self;
  108. FIRRemoteConfigFetchCompletion completion = ^(FIRRemoteConfigFetchStatus status, NSError *error) {
  109. ViewController *strongSelf = weakSelf;
  110. if (!strongSelf) {
  111. return;
  112. }
  113. [[FRCLog sharedInstance]
  114. logToConsole:[NSString stringWithFormat:@"Fetch completed. Status=%@",
  115. [strongSelf statusString:status]]];
  116. if (error) {
  117. [[FRCLog sharedInstance] logToConsole:[NSString stringWithFormat:@"Fetch Error=%@", error]];
  118. }
  119. NSMutableString *output = [NSMutableString
  120. stringWithFormat:@"Fetch status : %@.\n\n", [strongSelf statusString:status]];
  121. if (error) {
  122. [output appendFormat:@"%@\n", error];
  123. }
  124. if (status == FIRRemoteConfigFetchStatusFailure) {
  125. [output appendString:[NSString stringWithFormat:@"Fetch Error :%@.\n",
  126. [strongSelf errorString:error.code]]];
  127. if (error.code == FIRRemoteConfigErrorThrottled) {
  128. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  129. NSNumber *throttledTime =
  130. (NSNumber *)error.userInfo[FIRRemoteConfigThrottledEndTimeInSecondsKey];
  131. NSDate *date = [NSDate dateWithTimeIntervalSince1970:[throttledTime doubleValue]];
  132. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  133. NSString *timeString = [dateFormatter stringFromDate:date];
  134. [output appendString:[NSString stringWithFormat:@"Throttled end at: %@ \n", timeString]];
  135. }
  136. }
  137. [[FRCLog sharedInstance] logToConsole:output];
  138. };
  139. // fetchConfig api call
  140. [[FRCLog sharedInstance] logToConsole:@"Calling fetchWithExpirationDuration.."];
  141. [self.RCInstances[self.currentNamespace][self.FIRAppName]
  142. fetchWithExpirationDuration:self.expirationLabel.text.integerValue
  143. completionHandler:completion];
  144. }
  145. - (IBAction)fetchAndActivateButtonPressed:(id)sender {
  146. // fetchConfig api callback, this is triggered when client receives response from server
  147. ViewController *__weak weakSelf = self;
  148. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion = ^(
  149. FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  150. ViewController *strongSelf = weakSelf;
  151. if (!strongSelf) {
  152. return;
  153. }
  154. NSMutableString *output = [@"Fetch and activate status :" mutableCopy];
  155. if (status == FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote) {
  156. [output appendString:@"Success from remote fetch."];
  157. } else if (status == FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData) {
  158. [output appendString:@"Success using pre-fetched data."];
  159. } else if (status == FIRRemoteConfigFetchAndActivateStatusError) {
  160. [output
  161. appendString:[NSString stringWithFormat:@"Failure: %@", [error localizedDescription]]];
  162. }
  163. if (error) {
  164. [output appendFormat:@"%@\n", error];
  165. }
  166. if (status == FIRRemoteConfigFetchAndActivateStatusError) {
  167. [output appendString:[NSString stringWithFormat:@"Fetch And Activate Error :%@.\n",
  168. [strongSelf errorString:error.code]]];
  169. if (error.code == FIRRemoteConfigErrorThrottled) {
  170. [output appendString:[NSString stringWithFormat:@"Throttled.\n"]];
  171. }
  172. }
  173. // activate status
  174. [[FRCLog sharedInstance] logToConsole:output];
  175. if (status == FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote ||
  176. status == FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData) {
  177. [strongSelf printResult:[[NSMutableString alloc] init]];
  178. }
  179. };
  180. // fetchConfig api call
  181. [self.RCInstances[self.currentNamespace][self.FIRAppName]
  182. fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  183. }
  184. - (IBAction)activateButtonPressed:(id)sender {
  185. [self apply];
  186. }
  187. - (IBAction)refreshButtonPressed:(id)sender {
  188. NSMutableString *output = [[NSMutableString alloc] init];
  189. [self printResult:output];
  190. }
  191. - (IBAction)setDefaultFromPlistButtonPressed:(id)sender {
  192. [self.RCInstances[self.currentNamespace][self.FIRAppName]
  193. setDefaultsFromPlistFileName:@"Defaults"];
  194. [self printDefaultConfigs];
  195. }
  196. - (IBAction)setDefaultButtonPressed:(id)sender {
  197. if (self.configDefaults.count) {
  198. [self.RCInstances[self.currentNamespace][self.FIRAppName] setDefaults:self.configDefaults];
  199. [self.configDefaults removeAllObjects];
  200. [self printDefaultConfigs];
  201. } else {
  202. [[FRCLog sharedInstance] logToConsole:@"Nothing to set for defaults."];
  203. }
  204. }
  205. - (IBAction)onClearLogsButtonPressed:(id)sender {
  206. self.mainTextView.text = @"";
  207. }
  208. - (void)printDefaultConfigs {
  209. NSMutableString *output = [[NSMutableString alloc] init];
  210. [output appendString:@"\n-------Default config------\n"];
  211. NSArray<NSString *> *result = [self.RCInstances[self.currentNamespace][self.FIRAppName]
  212. allKeysFromSource:FIRRemoteConfigSourceDefault];
  213. if (result) {
  214. NSString *stringPerNs = @"";
  215. for (NSString *key in result) {
  216. FIRRemoteConfigValue *value =
  217. [self.RCInstances[self.currentNamespace][self.FIRAppName] defaultValueForKey:key];
  218. stringPerNs = [NSString stringWithFormat:@"%@%@ : %@ : %@\n", stringPerNs,
  219. self.currentNamespace, key, value.stringValue];
  220. }
  221. [output appendString:stringPerNs];
  222. }
  223. [[FRCLog sharedInstance] logToConsole:output];
  224. }
  225. - (IBAction)getValueButtonPressed:(id)sender {
  226. [[FRCLog sharedInstance] logToConsole:[self.RCInstances[self.currentNamespace][self.FIRAppName]
  227. configValueForKey:self.keyLabel.text]
  228. .debugDescription];
  229. }
  230. - (IBAction)logValueButtonPressed:(id)sender {
  231. [[FRCLog sharedInstance]
  232. logToConsole:[NSString stringWithFormat:@"key: %@ logged", self.keyLabel.text]];
  233. }
  234. // add default variable button pressed
  235. - (IBAction)addButtonPressed:(id)sender {
  236. [self addNewEntryToVariables:self.configDefaults isDefaults:YES];
  237. }
  238. - (IBAction)developerModeSwitched:(id)sender {
  239. FIRRemoteConfigSettings *configSettings =
  240. [[FIRRemoteConfigSettings alloc] initWithDeveloperModeEnabled:self.developerModeEnabled.isOn];
  241. ((FIRRemoteConfig *)(self.RCInstances[self.currentNamespace][self.FIRAppName])).configSettings =
  242. configSettings;
  243. [[FRCLog sharedInstance]
  244. logToConsole:[NSString
  245. stringWithFormat:@"Developer Mode Enabled: %d\n",
  246. ((FIRRemoteConfig *)(self.RCInstances[self.currentNamespace]
  247. [self.FIRAppName]))
  248. .configSettings.isDeveloperModeEnabled]];
  249. }
  250. - (void)addNewEntryToVariables:(NSMutableDictionary *)variables isDefaults:(BOOL)isDefaults {
  251. if ([self.keyLabel.text length]) {
  252. variables[self.keyLabel.text] = self.valueLabel.text;
  253. NSString *showText = @"custom variables ";
  254. if (isDefaults) {
  255. showText = @"config defaults";
  256. }
  257. [[FRCLog sharedInstance]
  258. logToConsole:[NSString stringWithFormat:@"New %@ added %@ : %@\n", showText,
  259. self.keyLabel.text, self.valueLabel.text]];
  260. self.keyLabel.text = @"";
  261. self.valueLabel.text = @"";
  262. }
  263. }
  264. - (void)apply {
  265. [self.RCInstances[self.currentNamespace][self.FIRAppName]
  266. activateWithCompletionHandler:^(NSError *_Nullable error) {
  267. NSMutableString *output = [[NSMutableString alloc] init];
  268. [output appendString:[NSString stringWithFormat:@"ActivateFetched = %@\n",
  269. error ? @"NO" : @"YES"]];
  270. [[FRCLog sharedInstance] logToConsole:output];
  271. if (!error) {
  272. [self printResult:output];
  273. } else {
  274. [self printResult:[[NSString stringWithFormat:@"Activate failed. Error: %@",
  275. error.localizedDescription] mutableCopy]];
  276. }
  277. }];
  278. }
  279. // print out fetch result
  280. - (void)printResult:(NSMutableString *)output {
  281. FIRRemoteConfig *currentRCInstance = self.RCInstances[self.currentNamespace][self.FIRAppName];
  282. NSString *namespace_p = self.currentNamespace;
  283. [output appendString:@"-------Active config------\n"];
  284. NSArray<NSString *> *result = [self.RCInstances[self.currentNamespace][self.FIRAppName]
  285. allKeysFromSource:FIRRemoteConfigSourceRemote];
  286. if (result) {
  287. NSString *stringPerNs = @"";
  288. for (NSString *key in result) {
  289. FIRRemoteConfigValue *value = [currentRCInstance configValueForKey:key];
  290. stringPerNs = [NSString
  291. stringWithFormat:@"%@%@ : %@ : %@\n", stringPerNs, namespace_p, key, value.stringValue];
  292. }
  293. [output appendString:stringPerNs];
  294. }
  295. [output appendString:@"\n-------Default config------\n"];
  296. result = [currentRCInstance allKeysFromSource:FIRRemoteConfigSourceDefault];
  297. if (result) {
  298. NSString *stringPerNs = @"";
  299. for (NSString *key in result) {
  300. FIRRemoteConfigValue *value = [currentRCInstance defaultValueForKey:key];
  301. stringPerNs = [NSString
  302. stringWithFormat:@"%@%@ : %@ : %@\n", stringPerNs, namespace_p, key, value.stringValue];
  303. }
  304. [output appendString:stringPerNs];
  305. }
  306. [output appendString:@"\n--------Custom Variables--------\n"];
  307. [output
  308. appendString:[NSString
  309. stringWithFormat:@"Developer Mode Enabled: %d\n",
  310. currentRCInstance.configSettings.isDeveloperModeEnabled]];
  311. [output appendString:@"\n----------Last fetch time----------------\n"];
  312. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  313. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  314. [output
  315. appendString:[NSString stringWithFormat:@"%@\n",
  316. [dateFormatter
  317. stringFromDate:currentRCInstance.lastFetchTime]]];
  318. [output appendString:@"\n-----------Last fetch status------------\n"];
  319. [output appendString:[NSString
  320. stringWithFormat:@"%@\n",
  321. [self statusString:currentRCInstance.lastFetchStatus]]];
  322. FIRInstallations *installations = [FIRInstallations installations];
  323. [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
  324. NSError *_Nullable error) {
  325. [output appendString:@"\n-----------Installation ID------------------\n"];
  326. [output appendString:[NSString stringWithFormat:@"%@\n", identifier]];
  327. [output appendString:@"\n-----------Installation ID token------------\n"];
  328. [installations authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
  329. NSError *_Nullable error) {
  330. [output appendString:[NSString stringWithFormat:@"%@\n", tokenResult.authToken]];
  331. [[FRCLog sharedInstance] logToConsole:output];
  332. }];
  333. }];
  334. }
  335. - (BOOL)textFieldShouldReturn:(UITextField *)textField {
  336. [textField resignFirstResponder];
  337. return YES;
  338. }
  339. - (NSString *)statusString:(FIRRemoteConfigFetchStatus)status {
  340. switch (status) {
  341. case FIRRemoteConfigFetchStatusSuccess:
  342. return @"Success";
  343. case FIRRemoteConfigFetchStatusNoFetchYet:
  344. return @"NotFetchYet";
  345. case FIRRemoteConfigFetchStatusFailure:
  346. return @"Failure";
  347. case FIRRemoteConfigFetchStatusThrottled:
  348. return @"Throttled";
  349. default:
  350. return @"Unknown";
  351. }
  352. return @"";
  353. }
  354. - (NSString *)errorString:(FIRRemoteConfigError)error {
  355. switch (error) {
  356. case FIRRemoteConfigErrorInternalError:
  357. return @"Internal Error";
  358. case FIRRemoteConfigErrorUnknown:
  359. return @"Unknown Error";
  360. case FIRRemoteConfigErrorThrottled:
  361. return @"Throttled";
  362. default:
  363. return @"unknown";
  364. }
  365. return @"";
  366. }
  367. - (IBAction)fetchIIDButtonClicked:(id)sender {
  368. FIRInstallations *installations =
  369. [FIRInstallations installationsWithApp:[FIRApp appNamed:self.FIRAppName]];
  370. [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
  371. NSError *_Nullable error) {
  372. if (error) {
  373. [[FRCLog sharedInstance] logToConsole:[NSString stringWithFormat:@"%@", error]];
  374. } else {
  375. [installations authTokenWithCompletion:^(
  376. FIRInstallationsAuthTokenResult *_Nullable tokenResult,
  377. NSError *_Nullable error) {
  378. if (tokenResult.authToken) {
  379. ((FIRRemoteConfig *)self.RCInstances[self.currentNamespace][self.FIRAppName])
  380. .settings.configInstallationsToken = tokenResult.authToken;
  381. [[FRCLog sharedInstance]
  382. logToConsole:[NSString
  383. stringWithFormat:
  384. @"Successfully got installation ID : \n\n%@\n\nToken : \n\n%@\n",
  385. identifier, tokenResult.authToken]];
  386. }
  387. }];
  388. }
  389. }];
  390. }
  391. - (IBAction)searchButtonClicked:(id)sender {
  392. NSString *output = @"-------Active Config------\n";
  393. for (NSString *key in [self.RCInstances[self.currentNamespace][self.FIRAppName]
  394. keysWithPrefix:self.keyLabel.text]) {
  395. FIRRemoteConfigValue *value =
  396. ((FIRRemoteConfig *)(self.RCInstances[self.currentNamespace][self.FIRAppName]))[key];
  397. output = [NSString stringWithFormat:@"%@%@ : %@ : %@\n", output, self.currentNamespace, key,
  398. value.stringValue];
  399. }
  400. [[FRCLog sharedInstance] logToConsole:output];
  401. }
  402. - (IBAction)userPropertyButtonClicked:(id)sender {
  403. [FIRAnalytics setUserPropertyString:self.valueLabel.text forName:self.keyLabel.text];
  404. NSString *output = [NSString
  405. stringWithFormat:@"Set User Property => %@ : %@\n", self.keyLabel.text, self.valueLabel.text];
  406. [[FRCLog sharedInstance] logToConsole:output];
  407. }
  408. #pragma mark - picker
  409. // The number of columns of data
  410. - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
  411. // App and currentNamespace pickers.
  412. return 2;
  413. }
  414. // The number of rows of data
  415. - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
  416. NSInteger rowCount = (component == 0) ? self.namespacePickerData.count : self.appPickerData.count;
  417. return rowCount;
  418. }
  419. // The data to return for the row and component (column) that's being passed in
  420. - (NSString *)pickerView:(UIPickerView *)pickerView
  421. titleForRow:(NSInteger)row
  422. forComponent:(NSInteger)component {
  423. if (component == 0) {
  424. self.currentNamespace = self.namespacePickerData[row];
  425. return self.namespacePickerData[row];
  426. } else {
  427. self.FIRAppName = self.appPickerData[row];
  428. return self.appPickerData[row];
  429. }
  430. }
  431. - (UIView *)pickerView:(UIPickerView *)pickerView
  432. viewForRow:(NSInteger)row
  433. forComponent:(NSInteger)component
  434. reusingView:(UIView *)view {
  435. UILabel *tView = (UILabel *)view;
  436. if (!tView) {
  437. tView = [[UILabel alloc] init];
  438. [tView setFont:[UIFont fontWithName:@"Helvetica" size:15]];
  439. tView.numberOfLines = 3;
  440. }
  441. if (component == 0) {
  442. self.currentNamespace = self.namespacePickerData[row];
  443. tView.text = self.namespacePickerData[row];
  444. } else {
  445. self.FIRAppName = self.appPickerData[row];
  446. tView.text = self.appPickerData[row];
  447. }
  448. return tView;
  449. }
  450. @end