Fetching photo data for a place using Google Places API HTTP web service call URL,
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=<LOCATION>&radius=<RADIUS>&sensor=false&key=<API_KEY>
yields the expected callback. Here is the response from a request I am using for testing.
2017-09-19 11:20:32.968347-0400 <MY_APP_NAME>[7502:2809094] Places Found: (
{
...
...
photos = (
{
height = 5312;
"html_attributions" = (
"leah steen"
);
"photo_reference" = "CmRaAAAAcppPDrYGBnLqlyYQ_EMmsvgKXXpVjp0drGkJPWLBwlSzrWzuhtFFGCzQPl1pSez92QVktthtaMSsV1IlAWMIMPboycElhruKgUg-KFHWWrhLKOR6h8GGiR349rhokAFJEhApc7ghYIH0guUQBGJi1bKBGhQMcElN7-lSBzvCgAG3vVzANLkf6A";
width = 2988;
}
);
...
...
}
)
As you can see, this place has a photo dictionary containing an html_attributions value for the associated image, which I am handling appropriately throughout my application.
ISSUE:
When I fetch the photo data for the same place, by using the place_id, I get this for the photo and attribution:
2017-09-19 11:22:08.548253-0400 <MY_APP_NAME>[7502:2809094] photo data: (
"<UIImage: 0x17048b720> size {169, 300} orientation 0 scale 2.000000",
"leah steen{\n}"
)
I receive of course the same photo, but an attribution WITHOUT a link. As you can see from my other method using the web service, the photo for this place does indeed have an attribution with a link.
I am at a loss. How am I supposed to get the full attribution from Google if their iOS SDK does not return the same data?
CODE:
Using Google's other suggested method for fetching photo data for a place via the place_id you can obtain the image and the images attribution (if it has one).
I fetch the data using these two methods as Google asks:
- (void)lookUpPhotosForPlaceID:(NSString *)placeID
callback:(GMSPlacePhotoMetadataResultCallback)callback;
- (void)loadPlacePhoto:(GMSPlacePhotoMetadata *)photo
callback:(GMSPlacePhotoImageResultCallback)callback;
Here is the code I use for fetching the photo data.
// Look up photos for the place
[[GMSPlacesClient sharedClient] lookUpPhotosForPlaceID:placeID callback:^(GMSPlacePhotoMetadataList * _Nullable photos, NSError * _Nullable error) {
if (error != nil) {
response(error, NULL);
return;
}
if (photos.results.count > 0) {
GMSPlacePhotoMetadata *metaData = photos.results.firstObject; // Using the first photo
// Load the photo
[[GMSPlacesClient sharedClient] loadPlacePhoto:metaData constrainedToSize:<SOME_SIZE> scale:[UIApplication sharedApplication].keyWindow.screen.scale callback:^(UIImage * _Nullable photo, NSError * _Nullable error) {
if (error) {
response(error, NULL);
return;
}
NSArray *photoData = #[photo, metaData.attributions];
response(nil, photoData);
}];
}
else response(error, NULL);
}];
EDIT:
For reference, as Google does inform it's users that this data may be subject to change, and that multiple callbacks should return the same data. Yet multiple calls using different API's do not...
You can do couple of thing to verify correct behaviour:
You can switch back to first API and see whether it actually returns it again. Because doc says:
Multiple calls of this method will probably return the same photos
each time. However, this is not guaranteed because the underlying data
may have changed.
Second you can check the callback behaviour if you are losing some data when callback is called in line
NSArray *photoData = #[photo, metaData.attributions];
At this point it me be loosing attributed string but only retains plain text(leah steen).
Good way to check this metaData is before loadPlacePhoto:callback:
Related
I'm trying to create an app that upload video on a specified channel, without prompt a login page. I'll try to explain better what i need.
I'm using Google APIs Client Library for Objective-C for REST, with this library i can use a "standard" upload flow :
user record a video -> he press an upload button -> Safari open the login google page -> user login in his own account and give permission to the app -> Safari redirect back to the ios app -> the upload process begin -> the video will be uploaded on the personal user channel.
Instead this is the desired workflow of my ios app:
user record a video -> he press an upload button -> the video will be uploaded in the app's youtube channel.
The only help i had find is this article ,it explain a way to obtain a refreshable app token to upload video in an app channel. It is exactly what i need. Anyway this is a web example, it uploads videos that are in a server. My videos are in the phone, so i think that i have to modify the flow of this article in this way:
obtain token the first time by login as channel owner -> create a token.txt and save it in my server -> create a page called get_token.php that print the content of token.txt and refresh it if the token expire.
With this flow in my app i need this other flow:
user record a video -> press an upload button -> i made a call to get_token.php and retrive the actual token -> i made a call by library with the token retrived to upload the video on the app's youtube channel.
Here i have found some problems, this is my authentication methods :
#pragma mark - Sign In
- (void)authNoCodeExchange {
[self verifyConfig];
NSURL *issuer = [NSURL URLWithString:kIssuer];
[self logMessage:#"Fetching configuration for issuer: %#", issuer];
// discovers endpoints
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) {
if (!configuration) {
[self logMessage:#"Error retrieving discovery document: %#", [error localizedDescription]];
return;
}
[self logMessage:#"Got configuration: %#", configuration];
if (!kClientID) {
[self doClientRegistration:configuration
callback:^(OIDServiceConfiguration *configuration,
OIDRegistrationResponse *registrationResponse) {
[self doAuthWithoutCodeExchange:configuration
clientID:registrationResponse.clientID
clientSecret:registrationResponse.clientSecret];
}];
} else {
[self doAuthWithoutCodeExchangeCri:configuration clientID:kClientID clientSecret:nil];
}
}];
}
/////////////////
- (void)doAuthWithoutCodeExchangeCri:(OIDServiceConfiguration *)configuration
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret {
NSURL *redirectURI = [NSURL URLWithString:kRedirectURI];
OIDTokenRequest *tokenExchangeRequest =
[_authState.lastAuthorizationResponse tokenExchangeRequest];
[OIDAuthorizationService performTokenRequest:tokenExchangeRequest
callback:^(OIDTokenResponse *_Nullable tokenResponse,
NSError *_Nullable error) {
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
}
[_authState updateWithTokenResponse:tokenResponse error:error];
GTMAppAuthFetcherAuthorization *gtmAuthorization =
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
// Sets the authorizer on the GTLRYouTubeService object so API calls will be authenticated.
self.youTubeService.authorizer = gtmAuthorization;
// Serializes authorization to keychain in GTMAppAuth format.
[GTMAppAuthFetcherAuthorization saveAuthorization:gtmAuthorization
toKeychainForName:kGTMAppAuthKeychainItemName];
[self uploadVideoFile];
}];
}
i have also something like:
NSString * RetrivedToken = #"ya29xxxxxxx3nJxxxxxxx6qqQ-FxxxxxxxdGH";
How i can modify those methods to accept my retrivedtoken instead the one they retrive from the standard auth workflow?
This isn't really an answer but its going to be to big for a comment.
So you want to create an app that will allow others to upload to your youtube channel. Normally i would say use a service account which would allow you to do this much easier. However the YouTube api does not support service accounts.
You are going to need to authenticate the app once save your refresh token then embed this refresh token in your app.
Why this is not a good idea with a mobile app.
Refresh tokens can expire so if it does expire or break then your going to have to authenticate the app again and embed the new refresh token into your app and release a new version to your users.
An alternative would be to set up a web service with the refresh token on that and have your app access the web service for upload. Then if the refresh token breaks you will only have to fix it on your web service.
I have done this before its messy but there is really no other way of doing it.
You can see from your code
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
}
your are using OIDTokenResponse object tokenResponse in your further code.
First make an object of Class OIDTokenResponse like *oidTokenResponse and assign it your access token from your server like
oidTokenResponse.accessToken=YOUR_TOKEN_FROM_SERVER;
and then use
[_authState updateWithTokenResponse:oidTokenResponse error:error];
In other way you can also do that and you won't need to change your code much you can do it with one line of code but i'm not sure you are allow to change to access token here but you can try it.
Leave the other code and just add one more line in your else
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
tokenResponse.accessToken=YOUR_TOKEN_FROM_SERVER;
}
I have an iOS share extension that needs the URL of the opened web page. Everything works good, especially in a simulator. But on a real device I have around 20-30% cases where the extension does not receive any data i.e.:
NSExtensionItem *inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *item = inputItem.attachments.firstObject;
[item loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *item, NSError *error) {
// here the error is sometimes not nil and thus the _baseURI ends up nil
_baseURI = [item[NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:#"baseURI"];
}];
The error code is -100 with description "No item available for requested type identifier.". This happens mainly when I open the extension several times in a row without changing/refreshing the web page in the Safari.
In those situations I see a device log saying "iPhone kernel[0] : Sandbox: MobileSafari(7033) deny(1) file-read-data /private/var/containers/Bundle/Application/.../bundle.js" where the bundle.js is the javascript with the ExtensionPreprocessingJS object. The bundle.js declares the ExtensionPreprocessingJS object like this (extracted the relevant part):
ExtensionPreprocessingJS = {
run: function(arguments){
arguments.completionFunction({
"baseURI": document.baseURI
})
},
finalize: function(arguments){
}
}
In this situation, it could some time happen that when the extension is closed the next time opening the share dialog in Safari shows my extension with no icon. This happens on my testing iPhone 5s and iPhone 6 with iOS 9.3.
I think that the missing data is because of the system could not read the extension's JavaScript file, but why could this happen?
If you read the documentation for:
loadItemForTypeIdentifier(_:options:completionHandler:)
You'll see that:
The type information for the first parameter of your completionHandler
block should be set to the class of the expected type. For example,
when requesting text data, you might set the type of the first
parameter to NSString or NSAttributedString. An item provider can
perform simple type conversions of the data to the class you specify,
such as from NSURL to NSData or NSFileWrapper, or from NSData to
UIImage (in iOS) or NSImage (in OS X). If the data could not be
retrieved or coerced to the specified class, an error is passed to the
completion block’s.
Try this code to see what you recieve:
[item loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(id item, NSError *error) {
// here the error is sometimes not nil and thus the _baseURI ends up nil
_baseURI = [item[NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:#"baseURI"];
}];
Note that item is not set to NSDictionary.
I was testing this method to remove tracks from a playlist. Basically I modified the demo project "simple track playback" provided with the SDK. I wanted to remove the track form the playlist when you hit fastForward. I changed the fastForward method this way but it's not doing anything, and error is nil.
-(IBAction)fastForward:(id)sender {
if([self.player isPlaying] && self.currentPlaylistSnapshot){
SPTAuth *auth = [SPTAuth defaultInstance];
[self.currentPlaylistSnapshot removeTracksFromPlaylist:#[self.player.currentTrackURI]
withAccessToken:auth.session.accessToken
callback:^(NSError *error) {
if (error != nil) {
NSLog(#"*** Failed to remove track : %#", self.titleLabel.text);
return;
}
}];
}
[self.player skipNext:nil];
}
self.currentPlaylistSnapshot is the one I've got from the handleNewSession method.
There's also a static method apparently offering something similar which I have't tried yet.
createRequestForRemovingTracks:fromPlaylist:withAccessToken:snapshot:error:
According to the documentation both options are implemented asynchronously and will take seconds to reflect the results in the server but I'm suspecting that there's either something wrong or I'm just missing to do an actual request to push the changes on the local snapshot maybe?
Documentation:
https://developer.spotify.com/ios-sdk-docs/Documents/Classes/SPTPlaylistSnapshot.html#//api/name/removeTracksWithPositionsFromPlaylist:withAccessToken:callback:
ios sdk:
https://github.com/spotify/ios-sdk
I solved my issue by reseting simulator + adding SPTAuthPlaylistModifyPublicScope (which I fogot to do...)
auth.requestedScopes = #[SPTAuthStreamingScope, SPTAuthPlaylistModifyPublicScope];
I want to get the content of the first note in the notebook. I set a content and title. Title is ok but content attribute is null.
EDAMNoteFilter *filter = [[EDAMNoteFilter alloc] init];
[[EvernoteNoteStore noteStore] findNotesWithFilter:filter
offset:0
maxNotes:10
success:^(EDAMNoteList *result) {
if(result.totalNotes>0)
{
EDAMNote *note=result.notes[0];
NSLog(#"%#",[note title]);
NSLog(#"%#",[note content]);
}
}
failure:^(NSError *error) {
// FIXME:zxx 2012-9-26 Alert user error occurs
NSLog(#"Error occurs when retreiving notes: %#", error);
}];
If you read the documentation for the findNotesWithFilter method in the Evernote SDK you will see the following -
Discussion
Used to find a set of the notes from a user’s account based on various
criteria specified via a NoteFilter object.
The Notes (and any embedded Resources) will have empty Data bodies for contents, resource data, and resource recognition fields. These values must be retrieved individually.
You need to retrieve the content using a method such as getNoteWithGuid:withContent:withResourcesData:withResourcesRecognition:withResourcesAlternateData:success:failure:
I'm using Facebook Native Share Dialog in my iOS app. I give the initial text, but when dialog pop up, it add the post URL to my initial text. How can I solve this problem? Here the code.
BOOL displayedNativeDialog =
[FBNativeDialogs
presentShareDialogModallyFrom:self
initialText:#"Say something about this..."
image:[UIImage imageWithData:imageData]
url:[NSURL URLWithString:activityUrl]
handler:^(FBNativeDialogResult result, NSError *error) {
if (error) {
} else {
if (result == FBNativeDialogResultSucceeded) {
} else {
}
}
}];
If you're sharing a URL (link share) and the image is inside that link, then just set the image parameter to nil. Then the post URL will not be added to the initial text. If you provide an image, it's as if you're sharing a photo and adding the link (and text) as photo caption info.
So based on what you're trying to do, select the option that works for you, i.e. is it a link share or a photo share.