I can't find anything online about threading loading an image from a device and scrolling smoothly through a tableview. There is one on ray wen about this, but it doesn't really help me for my situation.
Does anybody have any advice or code which would help to allow a tableview to scroll smoothly and load images from the device's temporary directory?
i did exactly as mentioned at tutorial, but with modification for nsoperation subclass
this is methods for fetch
-(void) updateData
{
[self.pendingOperations.downloadQueue addOperationWithBlock:^{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *filePathes = [self recursiveRecordsForResourcesOfType:#[#"png", #"jpeg", #"jpg",#"pdf"] inDirectory:documentsDirectory];
#synchronized (self) {
self.documents = filePathes;
NSLog(#"documents count %#", #([self.documents count]));
}
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self.delegate modelDidUpdate:self];
});
}];
}
- (NSArray *)recursiveRecordsForResourcesOfType:(NSArray *)types inDirectory:(NSString *)directoryPath{
NSMutableArray *filePaths = [[NSMutableArray alloc] init];
NSMutableDictionary *typesDic = [NSMutableDictionary dictionary];
for (NSString *type in types)
[typesDic setObject:type forKey:type];
// Enumerators are recursive
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:directoryPath];
NSString *filePath;
while ((filePath = [enumerator nextObject]) != nil){
// If we have the right type of file, add it to the list
// Make sure to prepend the directory path
if([typesDic objectForKey:[filePath pathExtension]]){
//[filePaths addObject:[directoryPath stringByAppendingPathComponent:filePath]];
CURFileRecord *record = [CURFileRecord new];
record.filePath =[directoryPath stringByAppendingPathComponent:filePath];
record.fileName = filePath;
[filePaths addObject:record];
}
}
return filePaths;
}
this is .m for subclass
- (void)main {
// 4
#autoreleasepool {
if (self.isCancelled)
return;
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:self.fileRecord.filePath];
// self.fileRecord.fileData = fileData;
if (self.isCancelled) {
fileData = nil;
return;
}
if (fileData) {
UIImage *newImage;
if ([[self.fileRecord.filePath pathExtension] isEqualToString:#"pdf"])
{
CGPDFDocumentRef doc = [CURDocumentViewerUtilities MyGetPDFDocumentRef:fileData];
newImage = [CURDocumentViewerUtilities buildThumbnailImage:doc withSize:CGSizeMake(64, 96)];
}
else
{
newImage = [CURDocumentViewerUtilities makePreviewImageFromData:fileData];
}
self.fileRecord.previewImage = newImage;
}
else {
self.fileRecord.failed = YES;
}
fileData = nil;
if (self.isCancelled)
return;
// 5
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];
}
}
With update func i've fetched pathes to proccess, and nsoperation subclass loads images. Works fine with 2000 images in fullhd - smoothly and without any lugs
Related
I’m building an article reading app. I’m fetching data from JSON link like article image and title in uitableview.
I’m unable to implement pagination in uitableview, let say my JSON link is www.example.com&page=1 contain 10 articles at a time which is 1-10.
When I concatenate in the JSON link like www.example.com&page=2 to get 11-20 article list.
I’m unable to implement how I can load the data in uitableview on scrolling and increase no.of rows with data.
Here is my code:
int *x=1;
int *inc=10;
#interface ysTableViewController ()
{
Reachability *internetReachable;
}
#end
#implementation ysTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self checkInternetConnection];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(20,10,0,20)];
titleLabel.textColor = [UIColor blackColor];
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.text = #"Story";
[self.navigationItem setTitleView:titleLabel];
}
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance) {
NSLog(#"load more rows");
inc=inc+10;
BOOL myBool = [self isNetworkAvailable];
if (myBool)
{
#try {
// for table cell seperator line color
self.tableView.separatorColor = [UIColor colorWithRed:190/255.0 green:190/255.0 blue:190/255.0 alpha:1.0];
// for displaying the previous screen lable with back button in details view controller
UIBarButtonItem *backbutton1 = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStyleBordered target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backbutton1];
_Title1 = [[NSMutableArray alloc] init];
_Author1 = [[NSMutableArray alloc] init];
_Images1 = [[NSMutableArray alloc] init];
_Details1 = [[NSMutableArray alloc] init];
_link1 = [[NSMutableArray alloc] init];
_Date1 = [[NSMutableArray alloc] init];
NSString *urlString=[NSString stringWithFormat:#“www.example.com&page=%d",x];
NSLog(#"xxxxx===%d",x);
NSURL *newUrl=[NSURL URLWithString:urlString];
NSData* data = [NSData dataWithContentsOfURL:newUrl];
NSArray *ys_avatars = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
x++;
if(ys_avatars){
for (int j=0;j<ys_avatars.count;j++)
{
[_Title1 addObject:ys_avatars[j][#"title"]];
[_Author1 addObject: ys_avatars[j][#"author"]];
[_Images1 addObject: ys_avatars[j][#"featured_img"]];
[_Details1 addObject:ys_avatars[j][#"content"]];
[_link1 addObject:ys_avatars[j][#"permalink"]];
NSString *newStr=[ys_avatars[j][#"date"] substringToIndex:[ys_avatars[j][#"date"] length]-3];
[_Date1 addObject:newStr];
} }
else
{
NSLog(#"asd");
} }
#catch (NSException *exception) {
}
}
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return inc;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier1 = #"ysTableViewCell";
ysTableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
// Configure the cell...
long row = [indexPath row];
cell1.TitleLabel1.text = _Title1[row];
cell1.AuthorLabel1.text = _Author1[row];
NSString *yourStoryUrl = [_Images1[indexPath.row] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if(yourStoryUrl) {
NSArray *subStringsUrl = [yourStoryUrl componentsSeparatedByString:#"/"];
NSString *stripedName = [subStringsUrl lastObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//Local stored image file path
NSString* filePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#",stripedName]];
if(filePath) {
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
if(image) {
// Now the image will have been loaded and decoded and is ready to rock for the main thread
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=image;
cell1.ThumbImage1.image=image;
} else {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
NSURL *Imageurl = [NSURL URLWithString:yourStoryUrl];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *images1 = [[UIImage alloc] initWithData:data];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSData *imageData = UIImagePNGRepresentation(images1);
//_imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png",stripedName]];
// NSLog((#"pre writing to file"));
if (![imageData writeToFile:filePath atomically:NO])
{
NSLog((#"Failed to cache image data to disk"));
}
else
{
NSLog((#"the cachedImagedPath is %#",filePath));
}
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=images1;
cell1.ThumbImage1.image=images1;
});
});
}
} else {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
NSURL *Imageurl = [NSURL URLWithString:yourStoryUrl];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *images1 = [[UIImage alloc] initWithData:data];
// NSString *myString = [Imageurl absoluteString];
// NSLog(#"%#",myString);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSData *imageData = UIImagePNGRepresentation(images1);
_imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png",stripedName]];
// NSLog((#"pre writing to file"));
if (![imageData writeToFile:_imagePath atomically:NO])
{
NSLog((#"Failed to cache image data to disk"));
}
else
{
// NSLog((#"the cachedImagedPath is %#",_imagePath));
}
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=images1;
cell1.ThumbImage1.image=images1;
});
});
}
}
return cell1;
}
This is by no means easy. IN GENERAL TERMS you need code that looks like this..
Note the four very distinct parts of this fundamental routine.
I have never found a working "general" package solution for this problem.
Again, look to the "four sections" in this: they give the logic you're looking for!
-(void)forTerm:(NSString *)term doPageAfter:(int)doingThisPage
{
doingThisPage = doingThisPage + 1;
if ( doingThisPage > 20 ) return; // never, ever, ever forget that!! heh.
[CLOUD search:term page:doingThisPage then:^(NSArray *thoseTenResults)
{
self.searchSpinner.hidden = YES;
// (step 1) IF IT IS "PAGE 1", we need to re-kick-off the array...
if ( doingThisPage == 1 )
CLOUD.searchResultsRA = [[NSMutableArray alloc] init];
// (step 2) go ahead and add on these results
if ( doingThisPage == 1 )
{
[CLOUD.searchResultsRA addObjectsFromArray:thoseTenResults];
[self.searchDisplay safelyReloadBouncyTable];
}
else
{
[self.searchDisplay.collectionView performBatchUpdates:^
{
NSUInteger oldSize = CLOUD.searchResultsRA.count;
[CLOUD.searchResultsRA addObjectsFromArray:thoseTenResults];
NSUInteger newSize = CLOUD.searchResultsRA.count;
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (NSUInteger i = oldSize; i < newSize; i++)
[arrayWithIndexPaths
addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[self.searchDisplay justSignal];
[self.searchDisplay.collectionView
insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
}
// (step 3) indeed if it's the first page, do a drop-in for fun
if ( doingThisPage == 1 ) [self.searchDisplay.view dropIn:nil];
// (for a "new search" which is now being displayed, in your UX
// there would be some sort of indication of that fact - do it here)
// (step 4) IF there WERE results .. try another page!
if ( thoseTenResults.count > 0 )
[self forTerm:term doPageAfter:doingThisPage];
// note we are calling this same routine, again!!!
}
];
}
I am filling a TableView from a text file. I want to enable the user to download an updated text file and replace the existing content of the TableView with the content of the downloaded file. I am able to download the file and replace the original file. If I close the application and open it again, it loads the updated file.
But the TableView doesn't change while the app is running. When I execute the method to load data from the file into the TableView, I can see, using NSLog, that the method is getting the original data from the file.
What am I doing incorrectly? How can I get the method to see the updated text file instead of the original text file?
Thanks.
#interface
#property (strong, nonatomic) NSArray *tableViewData;
#end
#implementation
/*
When user presses button, IBAction method
- downloads text file
- saves the downloaded file, replacing the original text file
- loads the text file into the TableView data (this is what doesn't work)
- sends a reload message to the TableView
*/
- (IBAction)buttonUpdateTextFile:(UIBarButtonItem *)sender
{
NSString *contentsOfTextFile = [self downloadTextFileFromURL:#"http://www.apple.com/index.html"];
[self saveContentsOfTextFile:contentsOfTextFile toFile:#"tableViewData.txt"];
[self loadDataFromFileWithFileName:#"tableViewData" fileExtension:#"txt"];
[self.tableView reloadData];
}
- (NSString *)downloadTextFileFromURL:(NSString *)textFileURLstring
{
NSURL *textFileURL = [NSURL URLWithString:textFileURLstring];
NSError *error = nil;
NSString *contentsOfTextFile = [NSString stringWithContentsOfURL:textFileURL encoding:NSUTF8StringEncoding error:&error];
return contentsOfTextFile;
}
- (void)saveContentsOfTextFile:(NSString *)contentsOfTextFile toFile:(NSString *)fileName
{
NSString *pathName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileNameWithPath = [pathName stringByAppendingPathComponent:fileName];
if (![[NSFileManager defaultManager] fileExistsAtPath:fileNameWithPath]) {
[[NSFileManager defaultManager] createFileAtPath:fileNameWithPath contents:nil attributes:nil];
[[contentsOfTextFile dataUsingEncoding:NSUTF8StringEncoding] writeToFile:fileNameWithPath atomically:NO];
}
- (void)loadDataFromFileWithFileName:(NSString *)fileName fileExtension:(NSString *)fileExtension
{
NSString *path = [[NSBundle mainBundle] pathForResource:fileName
ofType:fileExtension];
NSString *content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
NSString *remainingText = [content mutableCopy];
NSMutableArray *data = [[NSMutableArray alloc] init];
NSRange *substringRange;
while (![remainingText isEqualToString:#""]) {
substringRange = [remainingText rangeOfString:#"/n"];
if (substringRange.location == NSNotFound)
{
currentLine = remainingText;
remainingText = #"";
} else {
substringRange.length = substringRange.location;
substringRange.location = 0;
currentLine = [[remainingText substringWithRange:substringRange] mutableCopy];
// - strip line from remainingText
substringRange.location = substringRange.length + 1;
substringRange.length = remainingText.length - substringRange.length - 1;
remainingText = [[remainingText substringWithRange:substringRange] mutableCopy];
}
[data addObject:currentLine];
}
self.tableViewData = [data copy];
}
I think
self.tableViewData = [data copy];
may be the problem.
I would make data a "private" property of the class. Only init once and then manually add and remove objects to it. Don't use copy.
I have a method that progressively migrates a core data sqlite store though multiple NSManagedObjectModel versions until the store is at a the current version. The method is inspired by the code in Marcus Zarra's Core Data book.
The app is in production but my method is failing in about 0.5% of cases. When it fails it returns NO and an error is logged using Crashlytics:
NSSQLiteErrorDomain = 14; NSUnderlyingException = "I/O error for database at <store url>. SQLite error code:14, 'unable to open database file'"
The sqlite store use's write-ahead logging (WAL) and I am able to sometimes get the same error if I call +metadataForPersistentStoreOfType:URL:error: after first manually deleting the sqlite-WAL file.
Progressive Migration Code:
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
toModel:(NSManagedObjectModel*)finalModel
error:(NSError**)error
{
NSURL *storeDirectoryURL = [sourceStoreURL URLByDeletingLastPathComponent];
NSString *storeExtension = [sourceStoreURL pathExtension];
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
while (![finalModel isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata]) {
NSManagedObjectModel *sourceModel =
[self managedObjectModelForMetadata:sourceMetadata];
if (!sourceModel) return NO;
NSString *modelName = nil;
NSManagedObjectModel *targetModel =
[self suitableTargetModelForMigrationFromSourceModel:sourceModel
modelName:&modelName];
if (!targetModel) return NO;
NSMigrationManager *manager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:targetModel];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
NSURL *destinationStoreURL =
[[storeDirectoryURL URLByAppendingPathComponent:modelName]
URLByAppendingPathExtension:storeExtension];
BOOL migrated = [manager migrateStoreFromURL:sourceStoreURL
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:error];
if (!migrated) return NO;
NSString *sourceModelName =
[self versionStringForManagedObjectModel:sourceModel];
NSURL *backUpURL = [self backupURLWithDirectoryURL:storeDirectoryURL
pathExtension:storeExtension
modelName:sourceModelName];
BOOL replaced = [self replaceStoreAtURL:sourceStoreURL
withStoreAtURL:destinationStoreURL
backupURL:backUpURL
error:error];
if (replaced == NO) return NO;
sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
}
return YES;
}
- (NSManagedObjectModel *)managedObjectModelForMetadata:(NSDictionary *)metadata
{
for (NSURL *URL in [self modelURLs]) {
NSManagedObjectModel *model =
[[NSManagedObjectModel alloc] initWithContentsOfURL:URL];
if ([model isConfiguration:nil compatibleWithStoreMetadata:metadata]) {
return model;
}
}
return nil;
}
- (NSManagedObjectModel *)suitableTargetModelForMigrationFromSourceModel:(NSManagedObjectModel *)sourceModel
modelName:(NSString **)modelName
{
for (NSURL *modelURL in [self modelURLs]) {
NSManagedObjectModel *targetModel =
[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
if (mappingModel) {
*modelName = [[modelURL lastPathComponent] stringByDeletingPathExtension];
return targetModel;
}
}
return nil;
}
- (NSArray *)modelURLs
{
NSMutableArray *modelURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:#"mom"
subdirectory:nil] mutableCopy];
NSArray *momdURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:#"momd"
subdirectory:nil] mutableCopy];
for (NSURL *momdURL in momdURLs) {
NSString *directory = [momdURL lastPathComponent];
NSArray *array =
[[NSBundle mainBundle] URLsForResourcesWithExtension:#"mom"
subdirectory:directory];
[modelURLs addObjectsFromArray:array];
}
return [modelURLs copy];
}
- (NSURL *)backupURLWithDirectoryURL:(NSURL *)URL
pathExtension:(NSString *)extension
modelName:(NSString *)name
{
NSString *GUID = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *pathComponant = [NSString stringWithFormat:#"%#-%#", GUID, name];
return [[URL URLByAppendingPathComponent:pathComponant]
URLByAppendingPathExtension:extension];
}
- (BOOL)replaceStoreAtURL:(NSURL *)originalStoreURL
withStoreAtURL:(NSURL *)newStoreURL
backupURL:(NSURL *)backupURL
error:(NSError **)error
{
BOOL storeMoved = [self moveStoreAtURL:originalStoreURL
toURL:backupURL
error:error];
if (!storeMoved) return NO;
storeMoved = [self moveStoreAtURL:newStoreURL
toURL:originalStoreURL
error:error];
if (!storeMoved) return NO;
return YES;
}
- (BOOL)moveStoreAtURL:(NSURL *)sourceURL
toURL:(NSURL *)targetURL
error:(NSError **)error
{
NSMutableArray *sourceURLs = [#[sourceURL] mutableCopy];
NSMutableArray *targetURLs = [#[targetURL] mutableCopy];
NSString *walExtension = #"sqlite-wal";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:walExtension]) {
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:walExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:walExtension]];
}
NSString *shmExtension = #"sqlite-shm";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:shmExtension]) {
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:shmExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:shmExtension]];
}
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [sourceURLs count]; i++) {
BOOL fileMoved = [fileManager moveItemAtURL:sourceURLs[i]
toURL:targetURLs[i]
error:error];
if (!fileMoved) return NO;
}
return YES;
}
- (BOOL)storeAtURL:(NSURL *)URL hasAccessoryFileWithExtension:(NSString *)extension
{
NSURL *accessoryURL = [self URLByReplacingExtensionOfURL:URL
withExtension:extension];
return [[NSFileManager defaultManager] fileExistsAtPath:[accessoryURL path]];
}
- (NSURL *)URLByReplacingExtensionOfURL:(NSURL *)URL withExtension:(NSString *)extension
{
return [[URL URLByDeletingPathExtension] URLByAppendingPathExtension:extension];
}
- (NSString *)versionStringForManagedObjectModel:(NSManagedObjectModel *)model
{
NSString *string = #"";
for (NSString *identifier in model.versionIdentifiers) {
string = [string stringByAppendingString:identifier];
}
return string;
}
Sorry for the super long code.
The likely cause is your moveStoreAtURL:toURL:error: method. The error you're getting is mentioned in Apple's docs as being the result of failing to copy all of a persistent store's files. It looks like you're trying to hit all of them, but either (a) there's a bug in the copy code that I can't find right now or (b) the store is "live" in your app, being used by a persistent store coordinator, and so you're not getting a consistent state from the copy.
You might be able to fix it with some debugging, and if you ensured that the store was not in use. It would be better and probably more reliable to change the journal mode so that you don't have wal and shm files (which that link also describes). Even better, if your store files aren't too huge, use migratePersistentStore:toURL:options:withType:error to have Core Data make the copy. That should be pretty much guaranteed to work, though in some cases it can use too much memory.
I'm using NSMigrationManager, so I can't use NSPersistentStoreCoordinator's - migratePersistentStore..., so my solution was to force a checkpoint operation:
- (void)performCheckpointStoreWithSourceModel:(NSManagedObjectModel *)sourceModel sourceStoreURL:(NSURL *)sourceStoreURL {
NSPersistentStoreCoordinator *tempPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:sourceModel];
[tempPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:#{NSSQLitePragmasOption: #{#"journal_mode": #"DELETE"}} error:nil];
[tempPSC removePersistentStore:[tempPSC persistentStoreForURL:sourceStoreURL] error:nil];
}
...before performing migration with NSMigrationManager:
if (![manager migrateStoreFromURL:sourceStoreURL
type:type
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:type
destinationOptions:nil
error:error]) {
return NO;
}
I have an array of Objects (containing an item name and a creation date) used to display table data. I want to save this array using NSCoding protocol. I call my saveDataToDisk method every time a "submit" button is pressed to submit a new table entry. However, my app crashes upon hitting submit. This is my first time learning how to save app data and I looked a several tutorials and my form seems to be the same. I also looked for several days on the internet and couldn't find a solution. What is wrong?
Here are my methods:
Decoder Method:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.itemArray = [decoder decodeObjectForKey:#"itemArray"];
return self;
}
Encoder Method:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.itemArray forKey:#"itemArray"];
}
Path Finder Method:
- (NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"appData"];
if([fileManager fileExistsAtPath:filePath] == NO)
{
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *fileName = #"Budgetlist.appData";
return [filePath stringByAppendingPathComponent: fileName];
}
Save Data Method:
- (void) saveDataToDisk
{
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: [self itemArray] forKey:#"itemArray"];
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];
}
Load Data Method:
- (void) loadDataFromDisk
{
NSString * path = [self pathForDataFile];
NSDictionary * rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
[self setItemArray: [rootObject valueForKey:#"itemArray"]];
}
My Button:
- (IBAction)submitButton:(id)sender {
if (self.textField.text.length > 0 && [self.textField.text floatValue]>0) {
self.item = [[Data alloc] init];
self.item.itemName = self.textField.text;
[self.itemArray addObject:self.item];
[self.tableView reloadData];
[self saveDataToDisk];
}
}
You are trying to save an NSArray with custom objects.
Your objects must implement NSCoding protocol as well.
Implement those:
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
in your "Data" class.
NSArray is going through all objects and call those methods for each object inisde an array
Once more I come to the Internet, hat in hand. :)
I'm attempting to use a class method to return a populated array containing other arrays as elements:
.h:
#interface NetworkData : NSObject {
}
+(NSString*) getCachePath:(NSString*) filename;
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filePath;
+(NSString*) readFromFile:(NSString*) filePath;
+(void) loadParkData:(NSString*) filename;
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename;
#end
.m:
#import "NetworkData.h"
#import "JSON.h"
#import "Utility.h"
#implementation NetworkData
+(NSString*) getCachePath:(NSString*) filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *cachePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0], filename];
[paths release];
return cachePath;
}
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filename {
NSMutableArray *array = [[NSArray alloc] init];
[array addObject:text];
[array writeToFile:filename atomically:YES];
[array release];
}
+(NSString*) readFromFile:(NSString*) filename {
NSFileManager* filemgr = [[NSFileManager alloc] init];
NSData* buffer = [filemgr contentsAtPath:filename];
NSString* data = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
[buffer release];
[filemgr release];
return data;
}
+(void) loadParkData:(NSString*) filename {
NSString *filePath = [self getCachePath:filename];
NSURL *url = [NSURL URLWithString:#"http://my.appserver.com"];
NSData *urlData = [NSData dataWithContentsOfURL:url];
[urlData writeToFile:filePath atomically:YES];
}
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename {
// NSLog(#"generateColumnArray called: %u %# %#", column, type, filename);
// productArray = [[NSMutableArray alloc] init];
// NSString *filePath = [self getCachePath:filename];
// NSString *fileContent = [self readFromFile:filePath];
// NSString *jsonString = [[NSString alloc] initWithString:fileContent];
// NSDictionary *results = [jsonString JSONValue];
// NSArray *eventsArray = [results objectForKey:type];
// NSInteger* eventsArrayCount = [eventsArray count];
// NSInteger* a;
// for (a = 0; a < eventsArrayCount; a++) {
// NSArray *eventsColSrc = [eventsArray objectAtIndex:a];
// NSArray *blockArray = [eventsColSrc objectAtIndex:column];
// [productArray addObject:blockArray];
// [blockArray release];
// }
// [eventsArray release];
// [results release];
// [jsonString release];
// [fileContent release];
// [filePath release];
// [a release];
// [eventsArrayCount release];
// return productArray;
}
-(void)dealloc {
[super dealloc];
}
#end
.. and the call:
NSArray* dataColumn = [NetworkData generateColumnArray:0 type:#"eventtype_a" filename:#"data.json"];
The code within the method works (isn't pretty, I know - noob at work). It's essentially moot because just calling it (with no active code, as shown) causes the app to quit before the splash screen reveals anything else.
I'm betting this is a headslapper - many thanks for any knowledge you can drop.
If your app crashes, there's very likely a message in the console that tells you why. It's always helpful to include that message when seeking help.
One obvious problem is that your +generateColumnArray... method is supposed to return a pointer to an NSArray, but with all the code in the method commented out, it's not returning anything, and who-knows-what is being assigned to dataColumn. Try just adding a return nil; to the end of the method and see if that fixes the crash. Again, though, look at the error message to see specifically why the code is crashing, and that will lead you to the solution.
Well, you're not returning a valid value from your commented out code. What do you use 'dataColumn' for next? Running under the debugger should point you right to the issue, no?