I have a MasterDetail app that I am working on that will be used to show players (as in sports) and detail stats. The list of players is called from a Postgres Database and parsed from JSON using the JSONModel. So far, I am able to get all of the data that I need from the Postgres DB, and display it perfectly in the MasterView. I am using the NSNotificationCenter to pass the data from the Master to the Detail view (I fetch the data using a function in the MasterView). I am able to pass the data accurately to the Detail view, but for some reason, my didSelectRowAtIndexPath is not working right. I obviously did something wrong, but I have no idea what it is. Here are the important parts of the code:
In the MasterViewController.m viewDidAppear:
-(void)viewDidAppear:(BOOL)animated
{
//fetch the feed from the Postgres Database
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil userInfo:json];
//reload the table view after data collected
[self.tableView reloadData];
}];
}
and I collect that info in my DetailViewController.m like so:
- (void)handleNotification:(NSNotification *) notification
{
NSLog(#"%#", notification.userInfo);
NSArray *playerData = [notification.userInfo objectForKey:#"player"];
NSDictionary *firstElement = [playerData objectAtIndex:0];
nameLabel.text = [firstElement objectForKey:#"name"];
}
and then my didSelectRowAtIndexPath in my MasterViewController
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
Player *selectedPlayer = [_players objectAtIndex:indexPath.row];
if (_delegate) {
[_delegate selectedPlayer:slectedPlayer];
}
}
There you have it. If you want me to post more or if you want code from the DetailViewController.m, MasterViewController.h, or PlayerSelectionDelegate.h, just let me know.
As a note, I originally created this based off of the Ray Wenderlich iPad SplitView app tutorial a while back. And yes, I am new to this all.
You need to place the NSNotification in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//Here you have the NSDictionary from the json
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc]initWithDictionary:json error:&error];
//Print the data fethced to NSLog in JSON format
NSLog(#"Players: %#", _feed.players);
//Assuming that you have the players in the same order as your list
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil userInfo:[[json objectForKey:#"players"]objectAtIndex:indexPath.row]];
}];
Player *selectedPlayer = [_players objectAtIndex:indexPath.row];
if (_delegate) {
[_delegate selectedPlayer:slectedPlayer];
}
}
And in your DetailViewController :
- (void)handleNotification:(NSNotification *) notification
{
NSLog(#"%#", notification.userInfo);
nameLabel.text = [notification.userInfo objectForKey:#"name"];
}
Related
I wrote some code to add text to the Messages.app input field in my iMessage extension.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"didSelect called");
NSLog(#"%d", 1);
[[self activeConversation] insertText:#"https://google.com" completionHandler:^(NSError * error) {
NSLog(#"Error happened");
NSLog(#"Error: %#", error);
}];
NSLog(#"%d", 2);
}
The strange part is that all of the normal logs are happening. The app will log "didSelect called", "1" and "2". However, the message - the Google url - isn't being inserted, and the error logs aren't being shown. So I don't really have a clue as to what's going wrong. Any idea's what I'm doing wrong?
Solution #1
Send correct reference from MessagesViewController to your view controller.
Check activeConversation value for nil:
if ([self activeConversation] != nil) {
[[self activeConversation] insertText:#"Some text" completionHandler:^(NSError * _Nullable error) {
NSLog(#"error: %#", error.localizedDescription);
}];
} else {
NSLog(#"Conversation is nil");
}
Solution #2
Create Singleton in iMessage extension name space.
In MessagesViewController in - (void)viewDidLoad setup reference to
your MSConversation: [[Conversation shared] activeConversation] = [self activeConversation];
Use [[Conversation shared] activeConversation] insertText: .... ];
for sending messages from any controllers.
I'm trying to implement background fetch as well as refresh in iOS 10.
I'm using XML parsing to parse the data and then storing it in a file in the document's directory. For parsing XML I'm using a custom class (XMLParser) that confirms the NSXMLParserDelegate protocol.
The background fetch works fine. But I'm having problems in displaying the refreshed data, both when I click on the refresh button as well as in viewDidLoad.
I'm calling the refreshData method in viewDidLoad.
Here's how far I've gotten.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//--Set background fetch--//
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
...
#pragma mark Background data fetch methods
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSDate *fetchStart = [NSDate date];
ArtsViewController *artsViewController = (ArtsViewController *)self.window.rootViewController;
[artsViewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
ArtsViewController.h
#interface ArtsViewController : UIViewController <UIPageViewControllerDataSource>
#property BOOL newsAvailable;
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; // No problems here
#end
ArtsViewcontroller.m
#interface ArtsViewController ()
#property (nonatomic, strong) NSArray *arrNewsData;
-(void)refreshData;
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;
#end
...
#implementation ArtsViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshData];
//--Load the file that saves news--//
[self loadNews];
if (_newsAvailable == YES)
{
[self setupPageViewController];
}
else
{
[self showNoNewsMessage];
}
}
...
#pragma mark Data Fetch methods
-(void)refreshData{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
[self performNewFetchedDataActionsWithDataArray:dataArray];
}
else{
NSLog(#"%#", [error localizedDescription]);
}
}];
}
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
// 1. Initialize the arrNewsData array with the parsed data array.
if (self.arrNewsData != nil) {
self.arrNewsData = nil;
}
self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];
// 2. Write the file and reload the view.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDirectory = [paths objectAtIndex:0];
NSString * newsFilePath = [NSString stringWithFormat:#"%#",[docDirectory stringByAppendingPathComponent:#"arts2"]]; // NewsFile
if (![self.arrNewsData writeToFile:newsFilePath atomically:YES]) {
_newsAvailable = NO;
NSLog(#"Couldn't save data.");
}
else
{
_newsAvailable = YES;
NSLog(#"Saved data.");
[self viewWillAppear:YES];
}
}
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
NSString *latestTitle = [latestDataDict objectForKey:#"title"];
NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
NSString *existingTitle = [existingDataDict objectForKey:#"title"];
if ([latestTitle isEqualToString:existingTitle]) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No new data found.");
}
else{
[self performNewFetchedDataActionsWithDataArray:dataArray];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New data was fetched.");
}
}
else{
completionHandler(UIBackgroundFetchResultFailed);
NSLog(#"Failed to fetch new data.");
}
}];
}
...
#pragma mark IBActions
- (IBAction)reloadNews:(UIBarButtonItem *)sender
{
[self viewDidLoad];
}
I've debugged the application and found that after viewDidLoad
completes execution, the data file is written but the view isn't
updated. I've also tried calling the refreshData method in the main
thread, but there's no change.
after viewDidLoad is complete the showNoNewNews method is called.
I'm suspecting that my logic isn't wrong but implementation is. Threads at play here..
Any help would be appreciated.
Update:
Hope this helps those with similar problems...
I moved the logic of viewDidLoad to a different method, called the method for the first time in viewDidLoad and again in refreshData, after
[self performNewFetchedDataActionsWithDataArray:dataArray];
i try to get some data from Azure, all works fine.
My problem is to populate the Data in a table view.
Here is my Implementaion of the TableViewController (only the important):
#interface tableview ()
#property (strong, nonatomic) AzureService *ClientService;
#end
#implementation overview
#synthesize tableViewObject,tabledata,ClientService;
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableViewObject setDelegate:self];
[self.tableViewObject setDataSource:self];
self.ClientService = [[AzureService alloc]init];
[self.ClientService DatamyWay:^
{
[self.tableViewObject reloadData];
NSLog(#"Reload Table after complete Request");
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.ClientService.loadedItems count];
}
#end
Here´s the Implementation of the method form the Service
- (void) DatamyWay:(completionBlock)completion
{
[self.table readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) { // error is nil if no error occured
NSLog(#"ERROR %#", error);
} else {
[self.loadedItems addObjectsFromArray:result.items];
for(NSDictionary *item in result.items) { // items is NSArray of records that match query
NSLog(#"Location Name: %#", [item objectForKey:#"name"]);
}
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
Here the debugger output
2015-06-17 22:24:02.848 type2[3041:301640] Reload Table after complete Request
2015-06-17 22:24:03.601 type2[3041:301640] Location Name: abc
2015-06-17 22:24:03.601 type2[3041:301640] Location Name: def
2015-06-17 22:24:03.602 type2[3041:301640] Location Name: ghj
2015-06-17 22:24:03.602 type2[3041:301640] Location Name: klm
so i´ve leraned the call is asynchron. i want to check when the Completion code is called. So i decided to write the "Reload" NS Log Message. As you can see in the Debugger output, the NSlog message from the Completion block writes before the NSlog message in the Service Method. so the numbersinRowSection can´t count and nothing will happen. I hope the problem is described clearly.
Regards
i know, i shouldn´t post my own answer. I hope someone can help this one.
Put the Completion block in the Request.
code that works in my Service Implementation
- (void) DatamyWay:(completionBlock)completion
{
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[self.table readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) { // error is nil if no error occured
NSLog(#"ERROR %#", error);
} else {
[delegate.loadedItems addObjectsFromArray:result.items];
for(NSDictionary *item in result.items) { // items is NSArray of records that match query
NSLog(#"Location Name: %#", [item objectForKey:#"name"]);
}
completion();
}
}];
}
I am currently developing an iOS app. I am trying to fetch data from a table just after insert the data on table. This is the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Notification *notification = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([notification.isRead isEqualToNumber:#NO]) {
[self sendNotificationData:notification];
notification.isRead = #YES;
}
[self insertPost:notification.notificatableId];
Post *post = [self fetchObjectfrom:#"Post"
withPredicate:[NSPredicate predicateWithFormat:#"objectId == %#", notification.notificatableId]];
}
- (void)insertPost:(NSString *)postId
{
NSDictionary *parameters = [[NSDictionary alloc] initWithObjectsAndKeys:postId, #"notification[post_id]", nil];
[[AkdemiaAPIClient sharedClient] POST:[NSString stringWithFormat:#"/notifications/%#/get_post",postId] parameters:parameters
success:^(NSURLSessionDataTask *task, id JSON) {
[[NSUserDefaults standardUserDefaults] setBool:YES
forKey:#"PostNotificationsDetailsNotificationName"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PostNotificationsDetailsSucceedNotificationName"
object:nil];
AkdemiaSyncEngine *syncEngine = [[AkdemiaSyncEngine alloc] init];
[syncEngine processJSONDataRecordsIntoCoreData:JSON forComponent:kFeed];
[[AkdemiaCoreDataController sharedInstance] saveBackgroundContext];
[[AkdemiaCoreDataController sharedInstance] saveMasterContext];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"PostNotificationsDetailsErrorNotificationName"
object:nil];
}];
}
The problem is when I select a notification and try to fetch the post immediately after to insert, because it seems like isn't on this table, but if I refresh the controller and select the same notification then the post exists and everything is ok.
I was trying to find something like:
[tableView reloadData]
but that works on a request instead of the table.
You should implement the NSFetchedResultsController delegate methods. In the callback from your API call you can insert the retrieved items in Core Data and save. The delegate methods should then be called and insert / update / delete table view cells based on the indexPath. There is a reference implementation in the Xcode template (Master-Detail, check Core Data).
I have an iPhone app connects to a server using OAuth. On success, it fetches the a user from the server. Again, upon success, it adds an item to the array of objects that populates the table view. Here is the code that does this:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
if (editing) {
[super setEditing:YES animated:YES];
self.backButton = self.navigationItem.leftBarButtonItem;
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(signInWithCatapult)];
self.navigationItem.leftBarButtonItem = leftButton;
} else {
[super setEditing:NO animated:YES];
self.navigationItem.leftBarButtonItem = self.backButton;
}
}
- (void)signInWithCatapult
{
[self signOut];
GTMOAuth2Authentication *auth = [self catapultAuthenticaiton];
NSURL *authURL = [NSURL URLWithString:#"https://oauth.lvh.me:3000/oauth/authorize"];
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithAuthentication:auth
authorizationURL:authURL
keychainItemName:kCatapultKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[[self navigationController] pushViewController:viewController animated:YES];
}
- (GTMOAuth2Authentication *)catapultAuthenticaiton
{
NSURL *tokenURL = [NSURL URLWithString:kDoorkeeperTokenURL];
NSString *redirectURI = #"https://catapultcentral.com/iOSClientCallback";
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2Authentication authenticationWithServiceProvider:#"Catapult Central"
tokenURL:tokenURL
redirectURI:redirectURI
clientID:kDoorkeeperClientID
clientSecret:kDoorkeeperClientSecret];
return auth;
}
- (void)signOut
{
}
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error
{
if (error != nil) {
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
} else {
NSURL *url = [NSURL URLWithString:#"https://api.lvh.me:3000/api/users/me"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
[fetcher setAuthorizer:auth];
[fetcher beginFetchWithDelegate:self didFinishSelector:#selector(currentUserFetcher:finishedWithData:error:)];
}
}
- (void)currentUserFetcher:(GTMHTTPFetcher *)fetcher
finishedWithData:(NSData *)data
error:(NSError *)error
{
if (error != nil) {
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
} else {
NSLog(#"Before: %#", self.accounts);
[self.tableView beginUpdates];
[self.accounts addObject:#"Success!!!"];
[self.tableView endUpdates];
// [self.tableView reloadData];
NSLog(#"After %#", self.accounts);
}
}
It's in the currentUserFetcher:finishedWithData:error: method that I add the object to the self.accounts mutable array. Now if I use this code it doesn't work:
[self.tableView beginUpdates];
[self.accounts addObject:#"Success!!!"];
[self.tableView endUpdates];
It fails at the line [self.tableView endUpdates]; with the following error message:
2013-03-28 08:56:21.040 Catapult for iOS[55012:c07] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1054
And on the endUpdates line, XCode is complaining saying Thread 1: breakpoint 1.3. Now, if I use this code, it works normally:
[self.accounts addObject:#"Success!!!"];
[self.tableView reloadData];
Now I suspect that it is failing because I add an object to the self.accounts instance variable but I don't actually add the cell. So my question is: How do I add a cell to the tableView from the currentUserFetcher:finishedWithData:error: method?
If you just override this method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
Calling [UITableView reloadData] should just work itself out. The UITableViewController will just ask the amount of data (cells) that are there (using "tableView:numberOfRowsInSection:") and is requesting the Cell for every indexPath using the first mentioned method.