Getting mp3 artwork crashes on iOS 8 but works on iOS 7 - objective-c

EDIT: The culprit was iOS 8, not the simulator (which I didn't realize was already running iOS 8) I've renamed the title to reflect this.
I was happily using the code from this SO question to load album artwork from mp3 files. This was on my iPhone 5 with iOS 7.1.
But then I traced crashing in the iOS simulator to this code. Further investigation revealed that this code also crashed on my iPad. It crashed on my iPad after upgrading it to iOS 8.
It appears the dictionary containing the image is corrupted.
I created a dummy iOS project that only loads album art and got the same result. Below is the code from that viewcontroller.
- (void) viewDidAppear:(BOOL)animated
{
self.titleText = #"Overkill"; // Set song filename here
NSString *filePath = [[NSBundle mainBundle] pathForResource:self.titleText ofType:#"mp3"];
if (!filePath) {
return;
}
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSLog(#"Getting song metadata for %#", self.titleText);
AVAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
if (asset != nil) {
NSArray *keys = [NSArray arrayWithObjects:#"commonMetadata", nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
withKey:AVMetadataCommonKeyArtwork
keySpace:AVMetadataKeySpaceCommon];
UIImage *albumArtWork;
for (AVMetadataItem *item in artworks) {
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSDictionary *dict = [item.value copyWithZone:nil];
// **********
// Crashes here with SIGABRT. dict is not a valid dictionary.
// **********
if ([dict objectForKey:#"data"]) {
albumArtWork = [UIImage imageWithData:[dict objectForKey:#"data"]];
}
}
else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
// This doesn't appear to get called for images set (ironically) in iTunes
albumArtWork = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
}
if (albumArtWork != nil) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.albumArtImageView setImage:albumArtWork];
});
}
}];
}
}
I've marked the line with the crash. It expects a file Overkill.mp3 to be in the bundle. I tested with multiple mp3's and m4a's exported from iTunes and Amazon, so I know the files themselves are correctly encoded.
Tested in Xcode 6.0 and 6.1.
Any ideas why it would work on iPhone but not the simulator or iPad?
EDIT / UPDATE:
Logging the item.value reveals differences.
On iPhone 5 (works):
(lldb) po item.value
{
MIME = JPG;
data = <ffd8ffe0 .... several Kb of data ..... 2a17ffd9>;
identifier = "";
picturetype = Other;
}
On Simulator (crashes)
(lldb) po item.value
<ffd8ffe0 .... several Kb of data ..... 2a17ffd9>
So it appears that on the simulator there is no dictionary, just the raw artwork.
Changing the code to not expect a dictionary, but take item.value as a UIImage works!
for (AVMetadataItem *item in artworks) {
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSData *newImage = [item.value copyWithZone:nil];
albumArtWork = [UIImage imageWithData:newImage];
}
...
}

It seems the returned data structure has changed in iOS 8. The value of the AVMetadataItem object is no longer a dictionary, but the actual raw UIImage data.
Adding a test for the NSFoundationVersionNumber solves the problem. There is probably a cleaner solution.
- (void) viewDidAppear:(BOOL)animated
{
self.titleText = #"Overkill";
NSString *filePath = [[NSBundle mainBundle] pathForResource:self.titleText ofType:#"mp3"];
if (!filePath) {
return;
}
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSLog(#"Getting song metadata for %#", self.titleText);
AVAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
if (asset != nil) {
NSArray *keys = [NSArray arrayWithObjects:#"commonMetadata", nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
withKey:AVMetadataCommonKeyArtwork
keySpace:AVMetadataKeySpaceCommon];
UIImage *albumArtWork;
for (AVMetadataItem *item in artworks) {
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
// *** WE TEST THE IOS VERSION HERE ***
if (TARGET_OS_IPHONE && NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) {
NSData *newImage = [item.value copyWithZone:nil];
albumArtWork = [UIImage imageWithData:newImage];
}
else {
NSDictionary *dict = [item.value copyWithZone:nil];
if ([dict objectForKey:#"data"]) {
albumArtWork = [UIImage imageWithData:[dict objectForKey:#"data"]];
}
}
}
else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
// This doesn't appear to get called for images set (ironically) in iTunes
albumArtWork = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
}
if (albumArtWork != nil) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.albumArtImageView setImage:albumArtWork];
});
}
}];
}
}

Related

Is there an example code for corespotlight search feature - iOS 9 API?

Is there an example code for corespotlight search feature - iOS 9 API? Really appreciate if can look at sample code to implement/test.
Create a new iOS project and add CoreSpotlight and MobileCoreServices framework to your project.
Create the actual CSSearchableItem and associating the uniqueIdentifier, domainIdentifier and the attributeSet. Finally index the CSSearchableItem using [[CSSearchableIndex defaultSearchableIndex]...] as show below.
OK!Test the index!
CSSearchableItemAttributeSet *attributeSet;
attributeSet = [[CSSearchableItemAttributeSet alloc]
initWithItemContentType:(NSString *)kUTTypeImage];
attributeSet.title = #"My First Spotlight Search";
attributeSet.contentDescription = #"This is my first spotlight Search";
attributeSet.keywords = #[#"Hello", #"Welcome",#"Spotlight"];
UIImage *image = [UIImage imageNamed:#"searchIcon.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc]
initWithUniqueIdentifier:#"com.deeplink"
domainIdentifier:#"spotlight.sample"
attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item]
completionHandler: ^(NSError * __nullable error) {
if (!error)
NSLog(#"Search item indexed");
}];
Note: kUTTypeImage requires that you import the MobileCoreServices framework.
To complete the spotlight search functionality, once you have implemented mayqiyue's answer, you'll be able to see the results in the search but on selection of the result simply your app would open not the related view with related content.
In order to do so, go to your AppDelegate.m and add the following method.
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
//check if your activity has type search action(i.e. coming from spotlight search)
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType ] == YES) {
//the identifier you'll use to open specific views and the content in those views.
NSString * identifierPath = [NSString stringWithFormat:#"%#",[userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier]];
if (identifierPath != nil) {
// go to YOUR VIEWCONTROLLER
// use notifications or whatever you want to do so
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
MyViewController *myViewController = [storyboard instantiateViewControllerWithIdentifier:#"MyViewController"];
// this notification must be registered in MyViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"OpenMyViewController" object: myViewController userInfo:nil];
return YES;
}
}
return NO;
}
Make sure to import in AppDelegate.m :
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreSpotlight/CoreSpotlight.h>
UPDATE for Swift 2.1
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if #available(iOS 9.0, *) {
if userActivity.activityType == CSSearchableItemActionType {
//the identifier you'll use to open specific views and the content in those views.
let dict = userActivity.userInfo! as NSDictionary
let identifierPath = dict.objectForKey(CSSearchableItemActivityIdentifier) as! String
if identifierPath.characters.count > 0 {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mvc: MyViewController = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController
NSNotificationCenter.defaultCenter().postNotificationName("OpenMyViewController", object: mvc, userInfo: nil)
}
return true
}
} else {
// Fallback on earlier versions
return false
}
return false
}
Make sure to import in AppDelegate.swift :
import CoreSpotlight
import MobileCoreServices
I am using similar implementation as mentioned by #mayqiyue but I am also checking the existence of the item variable for backwards compatibility with iOS 8.
- (void)setupCoreSpotlightSearch
{
CSSearchableItemAttributeSet *attibuteSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeImage];
attibuteSet.title = NSLocalizedString(#"Be happy!", #"Be happy!");
attibuteSet.contentDescription = #"Just like that";
attibuteSet.keywords = #[#"example", #"stackoverflow", #"beer"];
UIImage *image = [UIImage imageNamed:#"Image"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attibuteSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"1"
domainIdentifier:#"album-1"
attributeSet:attibuteSet];
if (item) {
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Search item indexed");
}
}];
}
}
To handle the tap on the search item from Spotlight you need to implement following method in your AppDelegate:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:CSSearchableItemActionType]) {
NSString *uniqueIdentifier = userActivity.userInfo[CSSearchableItemActivityIdentifier];
// Handle 'uniqueIdentifier'
NSLog(#"uniqueIdentifier: %#", uniqueIdentifier);
}
return YES;
}
Write in your main controller Class
-(void)storeValueForSpotligtSearch {
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
// **Your Model Array that Contain Data Like attributes Make, Model, Variant and Year and Images**
for (MyCatalogeModel *myCatalogeModelObj in yourDataContainer) {
NSMutableArray *arrKeywords = [[NSMutableArray alloc] initWithObjects: myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant, nil];
NSString *strIdentifier = [NSString stringWithFormat:#"%#.%#",bundleIdentifier, myCatalogeModelObj.carId];
self.userActivity = [[NSUserActivity alloc]initWithActivityType:strIdentifier];
self.userActivity.title = myCatalogeModelObj.year;
self.userActivity.title = myCatalogeModelObj.make;
self.userActivity.title = myCatalogeModelObj.model;
self.userActivity.title = myCatalogeModelObj.variant;
self.userActivity.eligibleForSearch = YES;
self.userActivity.eligibleForPublicIndexing = YES;
self.userActivity.eligibleForHandoff = YES;
CSSearchableItemAttributeSet * attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeJSON];
attributeSet.title = myCatalogeModelObj.make;
attributeSet.thumbnailData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[myCatalogeModelObj.imageArray objectAtIndex:0]]];
attributeSet.contentDescription = [NSString stringWithFormat:#"%# %# %# %#", myCatalogeModelObj.year, myCatalogeModelObj.make, myCatalogeModelObj.model, myCatalogeModelObj.variant];
attributeSet.keywords = arrKeywords;
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:strIdentifier domainIdentifier:#"spotlight.CARS24ChannelPartnerapp" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
}];
self.userActivity.contentAttributeSet = attributeSet;
[self.userActivity becomeCurrent];
[self updateUserActivityState:self.userActivity];
}
}
Write in App Delegate
-(BOOL)application:(nonnull UIApplication *) application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * __nullable))restorationHandler {
#try {
NSString *strIdentifier;
NSNumber *numScreenId;
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
NSLog(#"Activity = %#",userActivity.userInfo);
if (userActivity.userInfo[#"vc"]) {
numScreenId = userActivity.userInfo[#"vc"];
}
else{
strIdentifier = [userActivity.userInfo objectForKey:#"kCSSearchableItemActivityIdentifier"];
NSLog(#"strIdentifier : %#",strIdentifier);
NSArray *arr = [strIdentifier componentsSeparatedByString:#"."];
NSString *strScreenId = [arr objectAtIndex:3];
NSLog(#"ID -= %#",strScreenId);
**// On Click in Spotlight search item move your particular view.**
[self moveToParticular:[strScreenId intValue]];
numScreenId = [numFormatter numberFromString:strScreenId];
}
}
#catch (NSException *exception) {}
return YES;
}
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeImage as String)
attributeSet.title = "Searchable Item"
attributeSet.contentDescription = "Code for creating searchable item"
attributeSet.keywords = ["Item","Searchable","Imagine"]
attributeSet.thumbnailURL = NSURL(string: "https://blog.imagine.com/")
let searchableItem = CSSearchableItem(uniqueIdentifier: "com.imagine.objectA", domainIdentifier: "spotlight.search", attributeSet: attributeSet)
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems([searchableItem]) {_ in}

uiWebView printing a pdf

Inside of uiwebview, what is a good way print a pdf document?
The pdf is accessible via a url or it can be loaded inside of an iframe.
Using the standard javascript widnow.print() functions will not work.
I am considering using a javascript bridge such as:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"native"]) {
NSString *urlString = [[request URL] absoluteString];
NSArray *urlParts = [urlString componentsSeparatedByString:#":"];
NSString *cmd = [urlParts objectAtIndex:1];
if ( [cmd isEqualToString:#"printPdf"] ) {
// [self dosomething];
}
}
return YES;
}
At this point I need some sort of xcode function which accept a path to the pdf and send it the airPrinter.
Is this a good approach? I am searching examples of how to print a pdf inside a uiWebView.
As I've earned the tumbleweed badge for no up votes and no responses, I'll post my solution.
This fetches the pdf doc and opens the airPrint dialog--all within a uiWebView.
So if IOS would simply allow the javascript window.print() to function inside of uiWebView, my app would not be setting in the app store waiting for approval and re-release.
Anyway, here's a working solution:
- (void)printInit:(NSString *)parm {
UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
if(!controller){
NSLog(#"Couldn't get shared UIPrintInteractionController!");
return;
}
NSString *base = #"https://someurl.com/";
NSString *ustr = [base stringByAppendingString:parm];
//NSURL *url = [NSURL fileURLWithPath:ustr];
NSURL *url = [NSURL URLWithString:ustr];
NSData *thePdf = [NSData dataWithContentsOfURL:url];
controller.printingItem = thePdf;
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputGeneral;
printInfo.jobName = #"PDFDoc";
controller.printInfo = printInfo;
void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) =
^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
if (!completed && error) {
NSLog(#"FAILED! error = %#",[error localizedDescription]);
}
};
CGRect rect = CGRectMake(310, 5, 100, 5);
[controller presentFromRect:rect inView:self.webView animated:YES completionHandler:completionHandler];
}

iOS AVFoundation: How do I fetch artwork from an mp3 file?

My code:
- (void)metadata {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:self.fileURL options:nil];
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtwork keySpace:AVMetadataKeySpaceCommon];
NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
NSArray *artists = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
NSArray *albumNames = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyAlbumName keySpace:AVMetadataKeySpaceCommon];
AVMetadataItem *artwork = [artworks objectAtIndex:0];
AVMetadataItem *title = [titles objectAtIndex:0];
AVMetadataItem *artist = [artists objectAtIndex:0];
AVMetadataItem *albumName = [albumNames objectAtIndex:0];
if ([artwork.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSDictionary *dictionary = [artwork.value copyWithZone:nil];
self.currentSongArtwork = [UIImage imageWithData:[dictionary objectForKey:#"data"]];
}
else if ([artwork.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
self.currentSongArtwork = [UIImage imageWithData:[artwork.value copyWithZone:nil]];
}
self.currentSongTitle = [title.value copyWithZone:nil];
self.currentSongArtist = [artist.value copyWithZone:nil];
self.currentSongAlbumName = [albumName.value copyWithZone:nil];
self.currentSongDuration = self.audioPlayer.duration;
}
This works for fetching artwork from m4a files, but doesn’t work for mp3 files. If the asset points to an mp3 file, artworks is empty. What am I doing wrong and how do I fix it?
I found that the artworks were being loaded asynchronously while the image was being set synchronously. I solved it this way:
- (void)metadata {
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:self.fileURL options:nil];
NSArray *titles = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
NSArray *artists = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtist keySpace:AVMetadataKeySpaceCommon];
NSArray *albumNames = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyAlbumName keySpace:AVMetadataKeySpaceCommon];
AVMetadataItem *title = [titles objectAtIndex:0];
AVMetadataItem *artist = [artists objectAtIndex:0];
AVMetadataItem *albumName = [albumNames objectAtIndex:0];
NSArray *keys = [NSArray arrayWithObjects:#"commonMetadata", nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata
withKey:AVMetadataCommonKeyArtwork
keySpace:AVMetadataKeySpaceCommon];
for (AVMetadataItem *item in artworks) {
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSDictionary *d = [item.value copyWithZone:nil];
self.currentSongArtwork = [UIImage imageWithData:[d objectForKey:#"data"]];
} else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
self.currentSongArtwork = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
}
}];
self.currentSongTitle = [title.value copyWithZone:nil];
self.currentSongArtist = [artist.value copyWithZone:nil];
self.currentSongAlbumName = [albumName.value copyWithZone:nil];
self.currentSongDuration = self.audioPlayer.duration;
}
I just found the answer to that here: How can I extract metadata from mp3 file in ios development and was now looking to see why that code doesn't work on an m4a file.
I took a little bit from your code and present the following which seems to work for both m4a and mp3. Note that I left the code which works for mp3 as an open else clause without the qualifier - if anybody gains more experience with this they are welcome to refine!
- (void)getMetaDataForSong:(MusicItem *)musicItem {
AVAsset *assest;
// filePath looks something like this: /var/mobile/Applications/741647B1-1341-4203-8CFA-9D0C555D670A/Library/Caches/All Summer Long.m4a
NSURL *fileURL = [NSURL fileURLWithPath:musicItem.filePath];
NSLog(#"%#", fileURL);
assest = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSLog(#"%#", assest);
for (NSString *format in [assest availableMetadataFormats]) {
for (AVMetadataItem *item in [assest metadataForFormat:format]) {
if ([[item commonKey] isEqualToString:#"title"]) {
musicItem.strSongTitle = (NSString *)[item value];
}
if ([[item commonKey] isEqualToString:#"artist"]) {
musicItem.strArtistName = (NSString *)[item value];
}
if ([[item commonKey] isEqualToString:#"albumName"]) {
musicItem.strAlbumName = (NSString *)[item value];
}
if ([[item commonKey] isEqualToString:#"artwork"]) {
UIImage *img = nil;
if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes]) {
img = [UIImage imageWithData:[item.value copyWithZone:nil]];
}
else { // if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
NSData *data = [(NSDictionary *)[item value] objectForKey:#"data"];
img = [UIImage imageWithData:data] ;
}
musicItem.imgArtwork = img;
}
}
}
}
As Ryan Heitner pointed out in a comment to Andy Weinstein, the way to retrieve the binary artwork data from the AVMetadataKeySpaceID3 has changed somewhere from IOS7 to IOS8. Casting blindly item to NSDictionary will crash in the ID3 case nowadays. To make code bullet proof just check dataValue, otherwise the Classes. In fact today item.value points to item.dataValue and has the NSData class.
- (UIImage*)imageFromItem:(AVMetadataItem*)item
{
NSData* data=nil;
if (item.dataValue!=nil) {
data=item.dataValue;
} else if ([item.value isKindOfClass:NSData.class]) { //never arrive here nowadays
data=(NSData*)item.value;
} else if ([item.value isKindOfClass:NSDictionary.class]) { //never arrive here nowadays...
NSDictionary* dict=(NSDictionary*)item.value;
data=dict[#"data"];
}
if (data==nil) {
return nil;
}
return [UIImage imageWithData:data];
}

NSFetchedResultController won't show data in IOS 5

I am new to IOS development. At the moment I'm building an iPad app, what gets data back from the webservice, saves it in a core database and shows it in a tableview using NSFetchedResultController.
What is the problem. In IOS6 it al works like a charm, in IOS 5.0 it saves the data but not showing it in the tableview. It gives the following error.
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. The NIB data is invalid. with userInfo (null)
And when I test in IOS 5.1 it does not do anything. It does not shows data in a tableview and data is not being stored in core database.
Here is what I do. In my view did appear I check if there is a document I can use.
if (!self.genkDatabase) { // we'll create a default database if none is set
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Default appGenk Database"];
self.genkDatabase = [[UIManagedDocument alloc] initWithFileURL:url]; // setter will create this for us on disk
}
Then it goes to my setter. And next in usedocument gonna check in which state my document is.
- (void)setGenkDatabase:(UIManagedDocument *)genkDatabase
{
if (_genkDatabase != genkDatabase) {
_genkDatabase = genkDatabase;
[self useDocument];
}
NSLog(#"Comes in the setdatabase methode.");
}
- (void)useDocument
{
NSLog(#"Comses in the use document");
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.genkDatabase.fileURL path]]) {
// does not exist on disk, so create it
[self.genkDatabase saveToURL:self.genkDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"create");
[self setupFetchedResultsController];
NSLog(#"create 2");
[self fetchNewsIntoDocument:self.genkDatabase];
NSLog(#"create 3");
}];
} else if (self.genkDatabase.documentState == UIDocumentStateClosed) {
NSLog(#"closed news");
// exists on disk, but we need to open it
[self.genkDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.genkDatabase.documentState == UIDocumentStateNormal) {
NSLog(#"normal");
// already open and ready to use
[self setupFetchedResultsController];
}
}
Finally I fetch my data in my document using the following function.
- (void)fetchNewsIntoDocument:(UIManagedDocument *)document
{
dispatch_queue_t fetchNews = dispatch_queue_create("news fetcher", NULL);
dispatch_async(fetchNews, ^{
NSArray *news = [GenkData getNews];
[document.managedObjectContext performBlock:^{
int newsId = 0;
for (NSDictionary *genkInfo in news) {
newsId++;
[News newsWithGenkInfo:genkInfo inManagedObjectContext:document.managedObjectContext withNewsId:newsId];
NSLog(#"news inserted");
}
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
});
dispatch_release(fetchNews);
}
For the problem in IOS 5.0 is this my NSFetchResultController method.
- (void)setupFetchedResultsController // attaches an NSFetchRequest to this UITableViewController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"News"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES],nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.genkDatabase.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
I am doing this like the example from Paul Hegarty in stanford university. You can find the example Photomania over here.
Hope anyone can help me!
Kind regards

Cacheing UIImage as NSData and getting it back

I'm trying to cache images I load from Flickr. If I load the same image, I'm hoping to use the cached version instead. For some reason, when I download an image from the internet, it works, but if I load it from my cache, it displays a blank image. I checked cacheData, and it has the same amount of bits as the image I put in, so it appears loading the file is working.
Here is how I cache images:
+ (void)cachePhoto:(NSData *)photo withKey:(NSString *)key {
if (photo) {
NSArray * urlArray = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask];
NSURL * targetDirectory = (NSURL *)[urlArray objectAtIndex:0];
targetDirectory = [targetDirectory URLByAppendingPathComponent:key];
[photo writeToURL:targetDirectory atomically:YES];
[cachedPhotos addObject:key];
NSLog(#"target url %#", targetDirectory);
}
}
+ (NSData *)photoInCache:(NSString *)key {
if ([cachedPhotos containsObject:key]) {
NSString * path = [[cacheDirectory URLByAppendingPathComponent:key] path];
NSLog(#"path: %#", path);
return [fileManager contentsAtPath:path];
} else {
return nil;
}
}
And my code to get it back:
NSData * cacheData = [PhotoCache photoInCache:key];
if (cacheData) {
self.imageView.image = [UIImage imageWithData:cacheData];
[spinner stopAnimating];
NSLog(#"used cached image");
} else {
dispatch_queue_t downloadQueue = dispatch_queue_create("get photo from flickr", NULL);
dispatch_async(downloadQueue, ^{
NSData * imageData = [[NSData alloc] initWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
self.imageView.image = [UIImage imageWithData:imageData];
[PhotoCache cachePhoto:imageData
withKey:key];
});
});
}
I figured it out - I was loading the image into the imageView in my prepareForSegue, where the ImageView was not yet loaded. I loaded the image in viewDidLoad and it worked. I also had to use UIImagePNGRepresentation, like suggested in the comments.