I am using this code for in-app purchases, took it from RaywernderLich's tutorial.
// Encode the receiptData for the itms receipt verification POST request.
NSString *jsonObjectString = [self encodeBase64:(uint8_t *)transaction.transactionReceipt.bytes
length:transaction.transactionReceipt.length];
Now Xcode is saying
'transactionReceipt' is deprecated: first deprecated in iOS 7.0
How to fix it?
Regarding Deprecation
Since this question is technically asking how one should go about addressing the deprecated attribute its fair to assume that the OP is still deploying on an iOS version less than 7. Therefore you need to check for the availability of the newer API rather than calling it blindly:
Objective-C
Edit
As pointed out in the comments you can't use the respondsToSelector on NSBundle since that API was private in previous iOS versions
NSData *receiptData;
if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_7_0) {
receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
} else {
receiptData = transaction.transactionReceipt;
}
//now you can convert receiptData into string using whichever encoding:)
Swift
Since Swift can only be deployed on iOS 7 and above we can use the appStoreReceiptURL safely
if let receiptData = NSData(contentsOfURL: NSBundle.mainBundle().appStoreReceiptURL!) {
//we have a receipt
}
Regarding Receipt Validation
The newer API the receipt now contains the list of all transactions performed by the user. The documentation clearly outlines what a receipt looks like:
Meaning that if you really, really wanted to, you can iterate through all the items contained in the receipt to validate against each transaction.
For more on receipt validation you can read obc.io
Replace with something like:
[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
Convert NSData to NSString after that.....
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if(!receipt) {
/* No local receipt -- handle the error. */
}
NSString *jsonObjectString = [receipt base64EncodedString];
Related
I have searched and read the docs, but I cannot seem to find a solution to this (seemingly-simple) issue I've run into. I have songs exporting working fine from the user's iTunes library, and it downloads into the user's documents folder with no issues every time, but videos just don't seem to work.
I have it showing an MPMediaPickerController (allowsPickingMultipleItems = YES) to allow the user to select either videos or songs from their downloaded library. When done, here is the relavent code I'm using:
- (void)mediaPicker:(MPMediaPickerController*)mediaPicker didPickMediaItems:(MPMediaItemCollection*)mediaItemCollection {
AVAssetExportSession *exportSession;
for (MPMediaItem *item in mediaItemCollection.items) {
NSURL *assetUrl = [item valueForProperty:MPMediaItemPropertyAssetURL];
MPMediaType type = [[item valueForProperty:MPMediaItemPropertyMediaType] intValue];
if (type >= MPMediaTypeMovie) {
exportSession = [[AVAssetExportSession alloc] initWithAsset:[AVAsset assetWithURL:assetUrl] presetName:AVAssetExportPreset640x480];
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
filePath = [title stringByAppendingString:#".mov"];
exportSession.outputURL = [NSURL fileURLWithPath:[[NSFileManager documentDirectory] stringByAppendingPathComponent:filePath]];
} // .. check for song-types here and set session up appropriately
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// never gets into AVAssetExportSessionStatusCompleted here for videos
}
}
}
The error I get every time is the following:
Error Domain=AVFoundationErrorDomain Code=-11820 "Cannot Complete Export" UserInfo=0x1e1a2180 {NSLocalizedRecoverySuggestion=Try exporting again., NSLocalizedDescription=Cannot Complete Export}
Not very helpful. :( I feel like I may be potentially missing something obvious here. Am I going about this the correct way? Is it potentially a problem with me trying to "force" it to MOV-format? Or perhaps needing a different way of setting up the export session?
For reference, I'm using iOS 6.0.1 on my iPhone 5 for testing, with a baseSDK of 6.0. Thanks in advance for any guidance that can be offered on this!
Additional Info #1: something that's odd. It seems to crash immediately with a "SIGTRAP" if I set the outputFileType to "AVFileTypeAppleM4V".. I wanted to try M4V, because when I do a log output of the assetURL, I see something like: ipod-library://item/item.m4v?id=12345. Don't know if that makes a difference or not, but odd that it just crashes like that if I try m4v format. Probably because it's not in the supported filetypes list (see next info point).
Additional Info #2: The supported file types I get (from calling the "supportedFileTypes" method are: "com.apple.quicktime-movie" and "public.mpeg-4". The "exportPresetsCompatibleWithAsset" include all of the video ones, including m4a, low/med/high quality, and the specific dimensions ones. I have tried EVERY combination of all these, such as AVFileTypeQuickTimeMovie and AVFileTypeMPEG4 for fileTypes, and all of the presets, including the low/med/high, and all of the dimension ones. It never fails that I get the "Cannot Complete Export" error.
Additional Info #3: I am also using a Deployment Target of 5.1. But yes, I have tried 6.0, and it gives the same error. :(
Additional Info #4: If needed to know, the movie I'm testing with is a "Pilot" TV show, one video, the first one I saw in iTunes that was free. So I downloaded it for use in this app.
Additional Info #5: Not sure if this is important, but the "hasProtectedContent" method returns YES for the AVAsset (and AVURLAsset if I convert). May not make a difference, but thought I'd throw it out there.
After trying to replicate the issue and doing some testing, I strongly suspect the protected content is an issue. Here's why:
I copied your code, and tested it on my iPod Touch (5th gen, iOS 6.0.1), though instead of coming from a media picker, I just let it loop through all the videos I have on the device (7 of them.) It worked great, and called the completion handler and made proper .mov files in the documents directory of the app sandbox. I moved the .mov files to my Mac and they all played.
These video files had the hasProtectedContent as NO.
So I placed a video file I got from the iTunes store, and confirmed it had the hasProtectedContent as YES. Interestingly, when I try to get the URL from MPMediaItemPropertyAssetURL, I get nil for the protected/iTunes obtained video.
I strongly suspect the media protection is the problem.
Here's the variation of code that I used. I didn't change your conversion code at all, just how the URLs are supplied:
// select all the video files
MPMediaPropertyPredicate *predicate = [MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithInteger:MPMediaTypeMovie] forProperty:MPMediaItemPropertyMediaType];
MPMediaQuery *query = [[MPMediaQuery alloc] init];
[query addFilterPredicate:predicate];
NSArray *items = [query items];
// now go through them all to export them
NSString* title;
NSURL * url;
AVAssetExportSession *exportSession;
NSString *storePath;
AVAsset *theAsset;
// we fill put the output at this path
NSString *applicationDocumentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
// loop through the items and export
for (MPMediaItem* item in items)
{
title = [item valueForProperty:MPMediaItemPropertyTitle];
url = [item valueForProperty:MPMediaItemPropertyAssetURL];
NSLog(#"Title: %#, URL: %#",title,url);
theAsset = [AVAsset assetWithURL:url];
if ([theAsset hasProtectedContent]) {
NSLog(#"%# is protected.",title);
} else {
NSLog(#"%# is NOT protected.",title);
}
exportSession = [[AVAssetExportSession alloc] initWithAsset:theAsset presetName:AVAssetExportPreset640x480];
storePath = [applicationDocumentsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.mov",title]];
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
exportSession.outputURL = [NSURL fileURLWithPath:storePath];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog(#"done!");
}];
}
Out of curiosity, are you checking the AVAsset exportable flag?
I've been modifying some code to work between Mac OS X and iPhone OS.
I came across some code that was using NSURL's URLByAppendingPathComponent: (added in 10.6), which as some may know, isn't available in the iPhone SDK.
My solution to make this code work between OS's is to use
NSString *urlString = [myURL absoluteString];
urlString = [urlString stringByAppendingPathComponent:#"helloworld"];
myURL = [NSURL urlWithString:urlString];
The problem with this is that NSString's stringByAppendingPathComponent: seems to remove one of the /'s from the http:// part of the URL.
Is this intended behaviour or a bug?
Edit
Ok, So I was a bit too quick in asking the question above. I re-read the documentation and it does say:
Note that this method only works with file paths (not, for example, string representations of URLs)
However, it doesn't give any pointers in the right direction for what to do if you need to append a path component to a URL on the iPhone...
I could always just do it manually, adding a /if necessary and the extra string, but I was looking to keep it as close to the original Mac OS X code as possible...
I would implement a myURLByAppendingPathComponent: method on NSURL that does the same thing. The reason to give it a different name is so it doesn't override the Apple-provided method when Apple gets around to porting the 10.6 API to the iPhone (so the "my" is just an example — the point is that it's unlikely somebody else would write a method with that name).
It seems to me you just want to mess with the path rather than the whole URL. Here's an untested example:
- (NSURL *)myURLByAppendingPathComponent:(NSString *)component {
NSString *newPath = [[self path] stringByAppendingPathComponent:component];
return [[[NSURL alloc] initWithScheme: [self scheme]
host: [self host]
path: newPath]
autorelease];
}
It would only work correctly with URLs that have file-like paths, but I'm pretty sure the Apple method works the same way. At any rate, hopefully it helps you in the right direction.
URLByAppendingPathComponent
Since iOS 4, URLByAppendingPathComponent is available on iOS and handles the two slashes correctly. (OS X had it since 10.6., as Chuck points out)
myURL = [myURL URLByAppendingPathComponent:#"hello world"]
// http://foo/bar/hello%20world
Note that unlike stringByAppendingPathComponent, this method escapes the argument.
URLWithString:relativeToURL:
Alternatively, there is URLWithString:relativeToURL:, which does not escape. So if the url component is already escaped, use:
myURL = [NSURL URLWithString:#"hello%20world" relativeToURL:myURL]
// http://foo/bar/hello%20world
Note that myURL needs to end with a slash here and the added segment must not have a leading slash.
The NSString Reference says this about stringByAppendingPathComponent:
Note that this method only works with
file paths (not, for example, string
representations of URLs).
So, I think it's a case of "Don't Do That".
Use -stringByAppendingString: instead?
There is a simple work around.
Using [NSString stringWithFormat:#"%#/%#", server, file] works fine.
E.g. server : ftp://www.server.com and file : file.txt
+ (NSString *)urlStringPathWithComponents:(NSArray *)paths
{
NSString *url = #"";
for( NSString *item in paths)
{
if([item isEqualToString:paths.firstObject])
{
url = [url stringByAppendingString:[NSString stringWithFormat:#"%#", item]];
}else{
url = [url stringByAppendingString:[NSString stringWithFormat:#"/%#", item]];
}
}
return url;
}
As you see the code above is a class method and this permit use in anywhere without a instance of an object.
Note: Always the first object of the array must be a base url.
I'm using cocoahttpserver in an iphone app, but when I try to download it (by clicking a link in the standard demo app), my sqlite file (myDatabase.sqlite) arrives on my Mac Desktop as "Unknown" with no suffix at all. However, when I "Save As..." it provides the name fine. I would prefer it to save the file with the sqlite suffix.
So, it must be the suffix causing the problems????
If this is the problem, I cannot seem to find a way in the classes to download the correct file name BUT then change it when presenting it to the browser (with a suffix like .backup, or .db, or something that works).
Anyone know where in the classes to change the download file name so the browser (Safari) will not call it "unknown"? Thanks.
The proper way to do this is to use the httpHeaders override in your async file class:
- (NSDictionary *)httpHeaders
{
NSString *key = #"Content-Disposition";
NSString *value = [NSString stringWithFormat:#"attachment; filename=\"%#\"", [filePath lastPathComponent]];
return [NSDictionary dictionaryWithObjectsAndKeys:value, key, nil];
}
I found someone else's code (MyAppSales) and in replyToHTTPRequest, I added the Content-Disposition header as below (in one section of the method), and now it works!
if(!isRangeRequest)
{
// Status Code 200 - OK
response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
NSString *contentLengthStr = [NSString stringWithFormat:#"%qu", contentLength];
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Length"), (CFStringRef)contentLengthStr);
// ************* added this from MyAppSales
if ([httpResponse isKindOfClass:[HTTPFileResponse class]])
{
NSString *baseName = [(NSString *)[(HTTPFileResponse *)httpResponse filePath] lastPathComponent];
NSString *contentDispoStr = [NSString stringWithFormat:#"Content-Disposition: attachment; filename=\"%#\"", baseName];
CFHTTPMessageSetHeaderFieldValue(response, CFSTR("Content-Disposition"), (CFStringRef)contentDispoStr);
}
}
I had a same problem and solved it by changing client side code. At the client side I add download attribute to tags.
Download
For Example:
Download
I am reading string data from a PLIST which I am using to create a JSON string (incidentally for use within Facebook Connect).
NSString *eventLink = [eventDictionary objectForKey:EVENT_FIND_OUT_MORE_KEY];
NSString *eventLinkEscaped = [eventLink stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *eventName = [eventDictionary objectForKey:EVENT_NAME_KEY];
NSString *eventDescription = [eventDictionary objectForKey:#"Description"];
NSString *eventImageAddress = [eventDictionary valueForKey:#"Image URL"];
if ([eventImageAddress length] == 0)
{
eventImageAddress = NO_EVENT_IMAGE_URL;
}
// Publish a story to the feed using the feed dialog
FBStreamDialog *facebookStreamDialog = [[[FBStreamDialog alloc] init] autorelease];
facebookStreamDialog.delegate = self;
facebookStreamDialog.userMessagePrompt = #"Publish to Facebook";
facebookStreamDialog.attachment =[NSString stringWithFormat: #"{\"name\":\"%#\",\"href\":\"%#\",\"description\":\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"%#\",\"href\":\"%#\"}]}", eventName, eventLinkEscaped, eventDescription, eventImageAddress, eventLinkEscaped];
[facebookStreamDialog show];
All this works well, but certain event descriptions (4 out of approx. 150) the text that appears in the dialog is blank. I have found the obvious candidates, i.e., the description contains the " character for instance or the copyright symbol. My question is, is there an easy method call, such as stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding that will ensure that any dodgy characters are escaped or ignored?
Thanks in advance,
Dave
I don't think there is an easy way to escape the problem strings. If you need JSON support anywhere else in your code, consider using one of the existing JSON parsing/generator frameworks such as yajl-objc or SBJSON. Either of these will let you build your response as Foundation objects (NSArray/NSDictionary) and then call a single method to generate the appropriate JSON. Your code will be cleaner and you have the benefit that both of these frameworks are well-tested.
If just need to generate this one bit of JSON, your best bet is probably to manually walk over the input strings, replacing potential problem characters with the appropriately escaped versions. Is is not as bad as you might think. Take a look at the source for SBJsonWriter
I'm writing an iPhone app. It's already been published, but I would like to add a feature where its version number is displayed.
I'd rather not have to do this manually with each version I release...
Is there a way in objective-C to find out what the version is of my app?
As I describe here, I use a script to rewrite a header file with my current Subversion revision number. That revision number is stored in the kRevisionNumber constant. I can then access the version and revision number using something similar to the following:
[NSString stringWithFormat:#"Version %# (%#)", [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"], kRevisionNumber]
which will create a string of the format "Version 1.0 (51)".
Building on Brad Larson's answer, if you have major and minor version info stored in the info plist (as I did on a particular project), this worked well for me:
- (NSString *)appNameAndVersionNumberDisplayString {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appDisplayName = [infoDictionary objectForKey:#"CFBundleDisplayName"];
NSString *majorVersion = [infoDictionary objectForKey:#"CFBundleShortVersionString"];
NSString *minorVersion = [infoDictionary objectForKey:#"CFBundleVersion"];
return [NSString stringWithFormat:#"%#, Version %# (%#)",
appDisplayName, majorVersion, minorVersion];
}
Now revving a minor version manually can be a pain, and so using a source repository revision number trick is ideal. If you've not tied that in (as I hadn't), the above snippet can be useful. It also pulls out the app's display name.
Swift version for both separately:
Swift 3
let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
Swift 2
let versionNumber = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String
let buildNumber = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleVersion") as! String
Its included in this repo, check it out:
https://github.com/goktugyil/EZSwiftExtensions
This is what I did in my application
NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
Hopefully this simple answer will help somebody...
You can specify the CFBundleShortVersionString string in your plist.info and read that programmatically using the provided API.
There are two things - build version and app version.
To get App version:
NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
To get Build version:
NSString *buildVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
A succinct way to obtain a version string in X.Y.Z format is:
[NSBundle mainBundle].infoDictionary[#"CFBundleVersion"]
Or, for just X.Y:
[NSBundle mainBundle].infoDictionary[#"CFBundleShortVersionString"]
Both of these snippets returns strings that you would assign to your label object's text property, e.g.
myLabel.text = [NSBundle mainBundle].infoDictionary[#"CFBundleVersion"];
// Syncs with App Store and Xcode Project Settings Input
NSString *appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
You can try using dictionary as:-
NSDictionary *infoDictionary = [[NSBundle mainBundle]infoDictionary];
NSString *buildVersion = infoDictionary[(NSString*)kCFBundleVersionKey];
NSString *bundleName = infoDictionary[(NSString *)kCFBundleNameKey]
Swift 5:
There are two things - App version and build version
To get App version:
if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
// present appVersion
}
To get Build version:
if let buildVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
// present buildVersion
}
Thanks #Brad Larson♦ a lot
Read the info.plist file of your app and get the value for key CFBundleShortVersionString. Reading info.plist will give you an NSDictionary object
You can use the infoDictionary which gets the version details from info.plist of you app. This code works for swift 3. Just call this method and display the version in any preferred UI element.
Swift-3
func getVersion() -> String {
let dictionary = Bundle.main.infoDictionary!
let version = dictionary["CFBundleShortVersionString"] as! String
let build = dictionary["CFBundleVersion"] as! String
return "v\(version).\(build)"
}
If you need a combination of both version and build num, here's a short way using Swift 3:
let appVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"]!
let buildNum = Bundle.main.infoDictionary!["CFBundleVersion"]!
let versionInfo = "\(appVersion) (build \(buildNum))"
// versionInfo is now something like "2.3.0 (build 17)"
Add an as! String to the end of either the appVersion or buildNum line to get only that portion as a String object. No need for that though if you're looking for the full versionInfo.
I hope this helps!
func getAppVersion() -> String {
let dictionary = Bundle.main.infoDictionary!
let versionValue = dictionary["CFBundleShortVersionString"] ?? "0"
let buildValue = dictionary["CFBundleVersion"] ?? "0"
return "\(versionValue) (build \(buildValue))"
}
Based on #rajat chauhan answer without forced cast to String.
This is a good thing to handle with a revision control system. That way when you get a bug report from a user, you can check out that revision of code and (hopefully) reproduce the bug running the exact same code as the user.
The idea is that every time you do a build, you will run a script that gets the current revision number of your code and updates a file within your project (usually with some form of token replacement). You can then write an error handling routine that always includes the revision number in the error output, or you can display it on an "About" page.
You can try this method:
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];