I would like to display images that were saved locally from the app to the iPhones Documents directory in a gallery layout, similar to Photos.app with the thumbnails (4 columns, as many rows necessary). I have tried some open sourced code, including CHGridView, but most of them are several years old and not working with iOS 7. I would prefer to not use open sourced code if possible anyway.
I am able to store my files in an NSMutableArray using the following method:
- (NSMutableArray *)arrayOfImages
{
int count;
NSMutableArray *array = [NSMutableArray array];
for (count = 0; count < 500; count++) //assuming no more than 500 items saved.
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fileName = [NSString stringWithFormat:#"img%d", count]; //images are called img0.png, img1.png, etc
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
UIImage *checkImage = [[UIImage alloc] initWithContentsOfFile:filePath];
if (checkImage != nil) //if there is an image there, add it to array
{
[array addObject:checkImage];
}
}
return array;
}
UICollectionView was added in iOS6 and is designed to do exactly what you are describing. The code needed is similar to the code for a UITableView, but instead of managing a 1D collection like a table view, the collection view manages a 2D collection of objects.
Related
I need to retrive the full list (as comma-separated NSString) of file paths to images and videos stored in an iPhone.
I wrote the following code, currently it gets only images. Can you show me how to improve that code to get also video? Thank you
Please also check if there is something wrong: I have the impression to get less photo then the ones actually stored in the device.
#import <Photos/Photos.h>
-(NSString*)listItems{
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
// NSLog(#"Total: %d",(int)result.count);
NSMutableArray *images = [NSMutableArray arrayWithCapacity:[result count]];
for (PHAsset *asset in result) {
[asset requestContentEditingInputWithOptions:[PHContentEditingInputRequestOptions new] completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
[images addObject : imageURL.absoluteString];
}];
}
NSMutableString * listFiles = [[NSMutableString alloc] init];
for (NSObject * obj in images)
{
[listFiles appendString:[obj description]];
[listFiles appendString:#","];
}
return listFiles;
}
#end
In my data controller I have book objects and each object contains these properties (among others):
novel.title = #"Book One";
novel.imageArray = [[NSMutableArray alloc] initWithObjects: [UIImage imageNamed: #"book1image1"], nil];
In the app, users can add a book image using UIImagePickerController like this:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
_addedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
_addedImage = [self scaleImage:_addedImage toSize:CGSizeMake(120, 168)];
[_book.imageArray addObject:_addedImage];
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];//folder name
NSError *error = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath])
[[NSFileManager defaultManager] createDirectoryAtPath:stringPath withIntermediateDirectories:NO attributes:nil error:&error];
int i = 0;
for(_addedImage in _book.imageArray)
{
_book.bookAddedToArray = YES;
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];//image name
NSData *data = UIImageJPEGRepresentation(_addedImage, 1.0);
[data writeToFile:fileName atomically:YES];
}
[self.collectionView reloadData];
}
This works perfectly fine. All images are saved in the right place as expected.
As long as the app stays open, the new images can be viewed in the collectionView. You can navigate anywhere in the app, come back, and view the collectionView some more. When the app is completely exited out of and then reopened, the collectionView is reset and only shows the initial image that was set in the dataController (no matter what code I've implemented so far. It just always resets). All the user generated images are still in their respective folders in the documents directory but I cannot seem to update the cellForItemAtIndexPath: with the stored images presumably because the images are not being saved to the _book.imageArray. Right now I have the cell being populated by the default imagearray so, of course, that is what will show up. How do I update the array and pull from documents directory to show user images?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if(_book.bookAddedToArray)
{
//*** HOW TO UPDATE _BOOK.IMAGEARRAY ????
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
else
{
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
return cell;
}
I've read that you can store an array of directory paths in nsuserdefaults and retrieve it to populate an imagearray but I have yet to find a solution that works for me. None of the answers seem to address loading into a collectionview or tableview. Can anyone point me in the right direction? Please? Anyone? Lol. Let me know if you need to see more code. All suggestions are much appreciated!
Eventually the app will need to be rewritten as it has grown beyond the scope of my original code, but at this time, this is what I'm working with. I'll probably eventually use CoreData but I've even seen where people on S.O. recommended against storing images in CoreData and to use the documents directory. That still leaves me in this same situation then.
Yes! I got it figured out and it works like a charm. It seems that I could not actually update my custom NSMutableArray. I could make it read another array but could not actually change the array itself. (I'm referring to the _book.imageArray.)
Everything stayed the same in the didFinishPickingMediaWithInfo.
I didn't need to add anything to the cellForItemAtIndexPath. It is just simply:
if(_book.bookAddedToArray)
{
cell.bookImageView.image = [_book.imageArray objectAtIndex: indexPath.row];
}
I created a property for my new array:
#property (nonatomic, strong) NSMutableArray *imagePathArray;
All the magic happens in ViewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
//initialize new array here
_imagePathArray = [[NSMutableArray alloc] init];
if (_book.bookAddedToArray)
{
int i = 0;
//this is the path to the folder.
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];
int z;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:stringPath error:NULL];
for (z = 0; z < (int)[directoryContent count]; z++)
{
//this is the path to each image file
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];
NSLog(#"imagefile = %#", fileName);
UIImage *fileNameImage = [UIImage imageWithContentsOfFile:fileName];
//add each image to a new array
[_imagePathArray addObject:fileNameImage];
}
//set my array to the new array
_book.imageArray = _imagePathArray;
}
}
I can thank this post for helping to read the images within each folder:
go to link.
//these are all properties in my Data Class.
_book.imageArrayID
_book.bookAddedToArray
_book.imageArray
I hope this helps someone out!
I have a video game app that I have created in Xcode, SpriteKit with a "Game" template application. My game has a score label that keeps track of the score and is updated when the player scores a point. Below is the code I have created for the two labels which are the current score and the high score. There is a lot more code in my app but I just wanted to simplify it and just show the code that is relevant to this situation. When i close the app down and reopen the app the score resets back to number zero. But what i want is to save the high score number even if you close the app down? And also if the current score is higher than the high score then I want the high score to be updated to the new score if that makes sense.
I'm open to suggestions.
GameScene.h
#import <SpriteKit/SpriteKit.h>
#interface GameScene : SKScene <SKPhysicsContactDelegate>
{
NSTimer *timerOne;
NSTimer *timerTwo;
NSInteger HighScore;
NSInteger score;
}
#end
GameScene.m
-(void)didMoveToView:(SKView *)view {
//SCORELABEL
SKLabelNode *scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"ChalkboardSE-Bold"];
scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height-50);
scoreLabel.fontSize = 30;
scoreLabel.text = #"0";
scoreLabel.fontColor = [SKColor blueColor];
scoreLabel.name = #"scoreLabel";
[self addChild:scoreLabel];
}
-(void) didBeginContact:(SKPhysicsContact *)contact {
//When redblock comes in contact with redball
if (contact.bodyA.categoryBitMask == CollisionCategoryRedBlock &&
contact.bodyB.categoryBitMask == CollisionCategoryRedBall) {
//Update the score label and add 1 to the current score
[self addPoint:1];
}
//if anything else happens it's gameover
else {
[self performGameOver];
}
}
//Method to add a point to the scorelabel
-(void) addPoint:(NSInteger)points {
score += points;
SKLabelNode *scoreLabel = (SKLabelNode*)[self childNodeWithName:#"scoreLabel"];
scoreLabel.text =[NSString stringWithFormat:#"%ld", (long)score];
}
//Gameover method which will show the high score label
-(void) performGameOver {
//HIGHSCORELABEL
SKLabelNode *highScoreLabel = [SKLabelNode labelNodeWithFontNamed:#"ChalkboardSE-Bold"];
highScoreLabel.position = CGPointMake(CGRectGetMidX(self.frame), 100);
highScoreLabel.fontSize = 20;
highScoreLabel.text = [NSString stringWithFormat:#"High Score: %ld", (long)score];
highScoreLabel.fontColor = [SKColor blueColor];
[self addChild:highScoreLabel];
}
Suppose we have an array of high scores, i.e: Multiple users
Userdefaults, Core Data and Plists can all be read/write but if you use a plist you need to pay attention in what dir you put it. See the plist part down below.
NSUserDefaults:
These are kind of global variables you can say, which don't remove their state until they are destroyed. but can be synchronized
To write them to the userdefaults:
NSArray *stringsArray = [[NSArray alloc] arrayWithObjects: string1, string2, string3, nil];
[[NSUserDefaults standardUserDefaults] setObject:stringsArray forKey:#"MyStrings"];
[[NSUserDefaults standardUserDefaults] synchronize]
To read the from the userdefaults:
NSArray *stringsArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"MyStrings"];
Plist:
If your strings are going to be modified you will need to write and read a plist but you can't write into your app's resources.
To have a read/write plist first find the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Strings.plist"];
Create the array (I am assuming the strings are string1, ...)
NSArray *stringsArray = [[NSArray alloc] arrayWithObjects: string1, string2, string3, nil];
write it to file
[stringsArray writeToFile:stringsPlistPath atomically:YES];
To read the plist:
find the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Strings.plist"];
read it in:
NSArray *stringsArray = [NSArray arrayWithContentsOfFile:stringsPlistPath];
You can either use NSUserDefaults, or if you intend to use device sync and to store bigger ammounts of data, NSCoding.
The easiest and quickest is NSUserDefaults. NSCoding is slightly harder but there are some guided through the net.
As for now, NSUSer Defaults would go like this:
Create at implementation a NSUserDefaults:
#implementation FKMyScene{
NSUserDefaults *prefs;
/* Any other you use below*/
}
Then, at init start it like this:
prefs = [[NSUserDefaults alloc] init];
if(!prefs)
[prefs setInteger:0 forKey:#"YourAppHighScore"];
Now it should be working and will store value for desired keys. (We setInteger to 0, so initial value is 0.
Next, assuming you want to store high scores, you will use a string to name the value (Like "YourAppHighScore") and summon Integer with this name.
/*This should fetch the value whenever you need*/
[prefs integerForKey:#"YourAppHighScore"];
/*This should update the value whenever you need*/
/*Note that I use setInteger to insert the new value. This can be used anywhere.*/
if(currentScore > [prefs integerForKey:#"YourAppHighScore"])
[prefs setInteger:currentScore forKey:#"YourAppHighScore"];
If you intend to save anything else, you can do it with the same method, but verify whether you need a integerForKey, objectForKey, etc etc.
This seems to be a fairly common problem, but the solutions that I have looked at do not solve the error. I am trying to read an NSMutableArray from a JSON file. Many of the suggestions I have seen involve using mutableCopy or [NSMutableArray arrayWithArray:] but both of these solutions do not fix the problem when using the call replaceObjectAtIndex:withObject: seen below. Please let me know if you have any advice on how to solve this problem.
EDIT: I would also like to add that the inventory list is an NSMutableArray of NSMutableArray objects.
The exact error reads:
Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: '-[__NSCFArray replaceObjectAtIndex:withObject:]:
mutating method sent to immutable object'
I have the property defined as follows at the top of my implementation file:
NSMutableArray *inventoryData;
I am trying to read it from a JSON file as follows:
- (void)readJSON
{
//Code to get dictionary full of saves from JSON file (overworld.json) - includes the file path on the ipad as well as
//the dictionary itself
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localPath = [[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"savedPaintGameData.json"]];
NSString *filePath = [localPath mutableCopy];
NSError *e = nil;
// Read data from file saved previously - read the raw data from the path, then parse it into a dictionary using JSONObjectWithData
NSData *RawJSON = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&e];
if (RawJSON == nil) {
[self saveGameInitialize];
} else {
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:NSJSONReadingAllowFragments error:&e]];
NSMutableDictionary *savedDataDictionary = [localDictionary mutableCopy];
//inventoryData = [[savedDataDictionary objectForKey:#"inventory"] mutableCopy];
inventoryData = [NSMutableArray arrayWithArray:[savedDataDictionary objectForKey:#"inventory"]];
}
}
I am then trying to replace an object at the given index of the NSMutableArray as seen here:
- (void)setInventoryData: (NSString *) colorKey: (int) change
{
// Check if inventory already contains the paint and change the amount
bool foundPaint = false;
int newAmount = 100; // Magic number prevents crashing # removal check
for (int i = 0; i < [inventoryData count]; i++) {
NSMutableArray *object = [inventoryData objectAtIndex:i];
if ([[object objectAtIndex:0] isEqualToString:colorKey]) {
newAmount = [[object objectAtIndex:1] integerValue] + change;
[[inventoryData objectAtIndex:i] replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:newAmount]];
foundPaint = true;
break;
}
}
if (newAmount == 0) {
[self removeInventoryColor:colorKey];
}
}
The issue appears to be surround the depth at which you are working... the mutable versions of containers you are creating only apply to that "level". You are later indexing into that level (i.e. accessing a container one level deeper) which is still immutable. Try passing the NSJSONReadingMutableContainers option when you first unserialize the JSON:
NSUInteger jsonReadingOptions = NSJSONReadingAllowFragments | NSJSONReadingMutableContainers;
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:jsonReadinOptions error:&e]];
Im creating a application that writes strings to a plist file, but the problem im having is every time its writing to the plist file, it deletes the previous one, im trying to figure out either how to write to the existing one without deleting its original contents, or replace the plist file and keep the original contents and then re write them on to it..
Heres what my code looks like to save the file
- (NSString *) saveFilePath
{
NSArray *pathArray =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"scores.plist"];
}
-(void)alertView:(UIAlertView *)alert_view didDismissWithButtonIndex:
(NSInteger)button_index{
if(button_index == 0){
NSLog(#"1");
score = 0;
}
if(button_index == 1){
NSLog(#"2");
NSString *scoreString = [NSString stringWithFormat:#"%i by %#", score, name.text];
NSLog(#"%#", scoreString);
NSArray *values = [[NSArray alloc] initWithObjects:scoreString, nil];
[values writeToFile:[self saveFilePath] atomically:YES];
[values release];
score = 0;
}
}
Any ideas? Thanks!
You can read the plist and write it to the NSMutableArray. Then append it with your data and write it back to the file overwriting the existing one.
The same thing with NSMutableDictionary.