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.
Related
I have a 'Terms and Conditions' controller I represent in the first launch of the app, but when opening the app in the first time from the App Store page the Terms and Conditions' controller not shown- only after I close the app and reopen it from the device itself (not the App Store page) then the controller is shown.
this code is from the launched screen controller:
- (void)viewDidLoad {
[self agreedToServerTerms];
}
- (void)agreedToServerTerms {
[[HttpUtils instance] httpRequest:TERMS_AGREEMENT_URL :params completionHandler:^(NSData *data, NSError *error) {
#try {
bool acceptTerms = false;
if (error) {
[Utils log:[NSString stringWithFormat:#"agreedToServerTerms error=%#", error]];
} else {
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!error) {
acceptTerms = [jsonData.allValues[0] boolValue];
if (!acceptTerms) {
TermsAndConditionController *tac = [[TermsAndConditionController alloc] initWithNibName:#"TermsAndConditionController" bundle:nil];
tac.delegate = self;
[[SlideNavigationController sharedInstance] pushViewController:tac animated:false];
}
else {
[self performSelectorInBackground:#selector(initialApp) withObject:nil];
}
} else {
[Utils log:[NSString stringWithFormat:#"parsing jsonData error = %#" ,error]];
}
}
} #catch (NSException *e) {
[Utils log:[NSString stringWithFormat:#"Exception occurred: %#, %#", e, [e userInfo]]];
}
}];
}
from App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
LaunchScreenController *bbp = [[LaunchScreenController alloc] initWithNibName:#"LaunchScreenController" bundle:nil];
[[SlideNavigationController sharedInstance] pushViewController:bbp animated:YES];
if (_launchedURL) {
bbp.launchedURL = _launchedURL;
}
[self.window addSubview:bbp.view];
}
herewith how I do almost the exact same thing. I think you need to rework yours, it could be a bit different, but this may help.
In the first VC that my app displays, in its viewWillAppear message, I have the following code
// Terms of use
if ( [NSUserDefaults.standardUserDefaults integerForKey:#"tou"] != 20170614 )
{
[self performSegueWithIdentifier:#"tou" sender:nil];
[NSUserDefaults.standardUserDefaults setInteger:20170614 forKey:#"tou"];
}
This uses user defaults to note if the terms of use have ever been presented and, if so, it marks #"tou" with some arbitrary value. In your case you can set the logic in your controller and only mark it once the user accepts.
This requires a segue in the storyboard called #"tou" that will present your T&C controller and you may need to prevent exit if the user does not agree, but the idea is to segue away from your first VC if the user did not agree yet to present the user with the T&C rather than injecting it into the app delegate as you do at present.
I've implemented the Places autocomplete API in my iOS app, using a UISearchBar and UITableView to house the autocomplete predictions.
The implementation works, but sometimes -[GMSAutocompleteTableDataSourceDelegate didUpdateAutocompletePredictionsForTableDataSource:] simply doesn't get called.
The -[GMSAutocompleteTableDataSourceDelegate didRequestAutocompletePredictionsForTableDataSource:] is always called when the GMSAutocompleteTableDataSource instance is passed the -[GMSAutocompleteTableDataSource sourceTextHasChanged:] method.
I would expect either the above update delegate method or the -[GMSAutocompleteTableDataSourceDelegate tableDataSource: didFailAutocompleteWithError:] to be called at some point in response to a request, but again, this doesn't seem to always be the case.
Every now and then I just simply stop receiving updates regardless of what gets typed in the search bar, and no delegate method other than the request one gets called. The implementations for the delegate methods:
#pragma mark - Autocomplete Table Data Source Delegate
- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource
didAutocompleteWithPlace:(GMSPlace *)place {
NSString *state;
NSString *stateAbbreviation;
NSArray *addressComponents = place.addressComponents;
if (addressComponents) {
// Type of addressComponent is <GMSAddressComponent *> (absent from public header UPDATE: Is present in 1.12.)
for (GMSAddressComponent *addressComponent in addressComponents) {
if ([addressComponent.type isEqualToString:#"administrative_area_level_1"]) {
state = addressComponent.name;
break;
}
}
}
if (state && [[_stateAbbreviations allKeys] containsObject:state]) {
stateAbbreviation = _stateAbbreviations[state];
} else {
stateAbbreviation = #"";
}
NSLog(#"City: <%#> State: <%#> Abbr: <%#>", place.name, state, stateAbbreviation);
}
- (void)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource
didFailAutocompleteWithError:(NSError *)error {
NSLog(#"Error: %#", [error description]);
}
- (BOOL)tableDataSource:(GMSAutocompleteTableDataSource *)tableDataSource
didSelectPrediction:(GMSAutocompletePrediction *)prediction {
NSLog(#"Selected Prediction: <%#>", prediction);
return YES;
}
- (void)didUpdateAutocompletePredictionsForTableDataSource:
(GMSAutocompleteTableDataSource *)tableDataSource {
NSLog(#"-[%# %#] called.", self.class, NSStringFromSelector(_cmd));
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// Reload table data.
[_searchResultsTableView reloadData];
}
- (void)didRequestAutocompletePredictionsForTableDataSource:
(GMSAutocompleteTableDataSource *)tableDataSource {
NSLog(#"-[%# %#] called.", self.class, NSStringFromSelector(_cmd));
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// Reload table data.
[_searchResultsTableView reloadData];
}
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've been struggling on this for several days so any would be appreciated. I'm trying to save the players array below and display it in a UITableView. I'd like to save it so I can display the local player's friends. I've tried several different things but something that looks it's working for others is this.
__block NSArray *friends;
- (void) loadPlayerData: (NSArray *) identifiers {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
friends = players;
NSLog(#"Inside: %#", friends); //Properly shows the array
}
}];
NSLog(#"Outside: %#", friends): //Doesn't work, shows nil
}
But friends is still nil/null afterwards. Am I doing something wrong? Is there any way to save players and use it in a UITableView? Thanks.
***EDIT***
So here's the solution I put together.
typedef void(^CallbackBlock)(id object);
+ (void) retrieveFriends: (CallbackBlock)callback {
GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
if (lp.authenticated) {
[lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil) {
[self loadPlayerDataWithIdentifiers:friends callback:^(NSArray *playersInfo) {
if (callback) callback(playersInfo);
}];
}
}];
}
}
+ (void) loadPlayerDataWithIdentifiers: (NSArray *) identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
if (callback) callback(players);
}
}];
}
The only thing is, my UITableView is in another class so I tried doing this and making the two methods above public. info isn't printing out anything. Any ideas?
[GameCenterHelper retrieveFriends:^(NSArray *info) {
NSLog(#"Friends Info: %#", info);
}];
Use callback blocks.
typedef void(^CallbackBlock)(id object);
- (void)loadPlayerDataWithIdentifiers:(NSArray *)identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (callback) callback(players);
}];
}
You imply in your question that this is for a table view. If so, you need to reload your table after the data has been loaded.
[self loadPlayerDataWithIdentifiers:identifiers callback:^(NSArray *players) {
self.players = players;
[self.tableView reloadData];
}];
Crimson Chris is correct.
The other option is use GCD to wait for the response to comeback.
Or change this to synchronous call as you want to get the results immediately.
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"];
}