iOS 4 bug? Image files saved to folder are overwritten after a random number of images saved - objective-c

I have an APP (SDK 4.3) which saves images being attachmemnts for a waypoint on a google map.
The file save is pretty standard (afaik) UIImagePickerController code.
Rather than saving to the camera roll I was saving the image and then the thumbnail to a subfolder. I need that.
At seemingly random points with no errors being trapped at all and logged, the images will not save to the folder but instead over-write previously saved image files!
It looks for all the world like a FIFO pop going on.
It is seriously odd and I have even built a small test APP and fired it up as soon as the spookiness appeared...saving a series of camera images to the same folders but see the same effect. The images get over-written once the random magic file number is reached!
Random in the sense that after 7 saved images, the overwriting begins...even after a reboot of the phone to ensure memory leaks is not the issue. Wipe the APP and try again...
This time it will happen after 16 oR 23 image files saved.
I have gone to all sorts of extremes and cannot find the source of the issue.
In the small test APP, in the same method I save out to the camera roll as well. It will save there but overwrite in the folder. The file names are 10 character random generated alpha-numeric.
I am now leaning to understand this as a bug. I can always reproduce the error but not predictably. It arises randomly.
I would appreciate help as I am tearing my hair out.
Here is the code...
//tester.h
#import <UIKit/UIKit.h>
#interface tester : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{
UIImagePickerController *imgPicker;
IBOutlet UIButton *pressit;
IBOutlet UIButton *seeya;
UIActivityIndicatorView *activity;
}
#property (retain )UIImagePickerController *imgPicker;
#property (nonatomic,retain)IBOutlet UIButton *pressit;
#property (nonatomic,retain)IBOutlet UIButton *seeya;
#property (nonatomic,retain)UIActivityIndicatorView *activity;
-(NSString *) genRandStringLength:(int) len ;
-(void)saveImagesFromPickerInTheBackgroundUsingImage:(UIImage *)img;
-(NSArray *)buildFilePaths;
- (IBAction)snapShots:(UIButton *)button;
-(IBAction)byebye:(id)sender;
#end
//=====================
//tester.m
#import "tester.h"
#import "MultiMediaUtilities.h"
#implementation tester
#synthesize imgPicker;
#synthesize pressit,seeya,activity;
//Image size constants
#define MAX_THUMBNAIL_RES_SIZE 103
#define MAX_IMAGE_RES_SIZE 640
- (IBAction)snapShots:(UIButton *)button
{
if (!imgPicker) imgPicker = [[UIImagePickerController alloc]init];
imgPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imgPicker.delegate = self;
[self presentModalViewController:imgPicker animated:YES];
}
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *memoImage = [[MultiMediaUtilities scaleAndRotateImage:[info objectForKey:#"UIImagePickerControllerOriginalImage"] toResolution:MAX_IMAGE_RES_SIZE ]retain];
UIImageWriteToSavedPhotosAlbum(memoImage, self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
[self saveImagesFromPickerInTheBackgroundUsingImage:memoImage];
// Dismiss the camera
[self dismissModalViewControllerAnimated:YES];
}
//builds paths to files in system with components
-(NSArray *)buildFilePaths
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *docsPath = [documentsDirectory stringByAppendingPathComponent:#"imagesfolder"];
NSString *fullDocsPath = [docsPath stringByAppendingPathComponent:#"assets"];
NSString *fullThumbsPath = [fullDocsPath stringByAppendingPathComponent:#"thumbs"];
NSArray * retArray = [NSArray arrayWithObjects:fullDocsPath,fullThumbsPath,nil];
return retArray;
}
-(void)saveImagesFromPickerInTheBackgroundUsingImage:(UIImage *)img
{
#try
{
NSFileManager *NSFm = [NSFileManager defaultManager];
NSArray *pathsArray = [NSArray arrayWithArray:[self buildFilePaths]];
NSString *fullDocsPath = [NSString stringWithFormat:#"%#", (NSString *)[pathsArray objectAtIndex:0]];
NSString *fullThumbsPath = [NSString stringWithFormat:#"%#", (NSString *)[pathsArray objectAtIndex:1]];
//Ensure Folders exist
BOOL isDir=YES;
NSError *error;
if(![NSFm fileExistsAtPath:fullDocsPath isDirectory:&isDir])
if(![NSFm createDirectoryAtPath:fullDocsPath withIntermediateDirectories:YES attributes:nil error:&error])
NSLog(#"Error: Create Images folder failed");
//create thumbs folder too
if(![NSFm fileExistsAtPath:fullThumbsPath isDirectory:&isDir])
if(![NSFm createDirectoryAtPath:fullThumbsPath withIntermediateDirectories:YES attributes:nil error:&error])
NSLog(#"Error: Create Thumbs folder failed");
//build the filenames & paths
NSString *newImageName= [NSString stringWithFormat:#"%#.png", [self genRandStringLength:10]];
NSString *imagePath = [[fullDocsPath stringByAppendingPathComponent:newImageName]retain];
NSLog(#"SavingIMage ImagePath = %#",imagePath);
NSString *thumbPath = [[fullThumbsPath stringByAppendingPathComponent:newImageName]retain];
NSLog(#"SavingIMage thumbPAth = %#",thumbPath);
//Write the files out
NSData *imgData = UIImagePNGRepresentation(img);
[imgData writeToFile:imagePath options:NSDataWritingAtomic error:&error];
if (!error) {
NSLog(#"Error writing image %#",error.description);
}
NSData *thumbData = UIImagePNGRepresentation(img);
[thumbData writeToFile:thumbPath options:NSDataWritingAtomic error:&error];
if (!error) {
NSLog(#"Error writing thumb %#",error.description);
}
}
#catch (NSException * e)
{
NSLog(#"Exception: %#", e);
}
}
-(NSString *) genRandStringLength:(int) len
{
NSString *letters = #"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
NSMutableString *randomString = [NSMutableString stringWithCapacity: len];
for (int i=0; i<len; i++)
{
[randomString appendFormat: #"%c", [letters characterAtIndex: rand()%[letters length]]];
}
return randomString;
}
- (void)image:(UIImage*)image didFinishSavingWithError:(NSError *)error contextInfo:(NSDictionary*)info {
NSString *message;
NSString *title;
if (!error)
{
title = #"Camera...";
message = #"Image saved!...Just as well.";
}
else
{
title = #"Error";
message = [error description];
}
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:title
message:message
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
if (image !=NULL){
[image release];
image=nil;
}
if(info !=NULL)
{
[info release];
info=nil;
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
-(void)dealloc
{
[imgPicker release];
[pressit release];
[seeya release];
[activity release];
[super dealloc];
}
#end

Even seeded, this is an inappropriate use of random numbers.
Three approaches:
Use an incremented sequence number. (1, 2, 3, etc.)
Use a UUID from [[NSProcessInfo processInfo] globallyUniqueString]
Use a filename constructed from the date & time.

As Mats said, if you don't initialize your random number generator with srand, rand() will behave strangely and don't expect it to generate random numbers. This can cause the same filenames you experience.

Related

Exporting all contacts in one .vcf file using Contacts.Framework in Objective - C

Using I AddressBook.framework I used to create Contacts.vcf from all contacts and save it in Documents Directory.
Here is the code I used to use :
ABAddressBookRef addressBook1 = ABAddressBookCreate();
NSArray *arrayOfAllPeople = (__bridge_transfer NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook1);
long cnt = (unsigned long)[arrayOfAllPeople count];
if (cnt==0) {
ABAddressBookRequestAccessWithCompletion(addressBook1, nil);
}
if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
{
ABAddressBookRef addressBook2 = ABAddressBookCreate();
CFArrayRef contacts = ABAddressBookCopyArrayOfAllPeople(addressBook2);
CFDataRef vcards = (CFDataRef)ABPersonCreateVCardRepresentationWithPeople(contacts);
NSString *vcardString = [[NSString alloc] initWithData:(__bridge NSData *)vcards encoding:NSUTF8StringEncoding];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *folderPath = [paths objectAtIndex:0];
NSString *filePath = [folderPath stringByAppendingPathComponent:#"Contacts.vcf"];
[vcardString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
CFRelease(addressBook2); }
How do I create a Contacts.vcf file having all device contacts using Contacts.framework and save it in documents directory ?
You can use this method to get all the contacts in .vcf file. It return the same output that you get using AddressBook.framework.
- (void)getContacts {
NSMutableArray *contactsArray=[[NSMutableArray alloc] init];
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!granted) {
dispatch_async(dispatch_get_main_queue(), ^{
});
return;
}
NSMutableArray *contacts = [NSMutableArray array];
NSError *fetchError;
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:#[[CNContactVCardSerialization descriptorForRequiredKeys], [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]]];
BOOL success = [store enumerateContactsWithFetchRequest:request error:&fetchError usingBlock:^(CNContact *contact, BOOL *stop) {
[contacts addObject:contact];
}];
if (!success) {
NSLog(#"error = %#", fetchError);
}
// you can now do something with the list of contacts, for example, to show the names
CNContactFormatter *formatter = [[CNContactFormatter alloc] init];
for (CNContact *contact in contacts) {
[contactsArray addObject:contact];
// NSString *string = [formatter stringFromContact:contact];
//NSLog(#"contact = %#", string);
}
//NSError *error;
NSData *vcardString =[CNContactVCardSerialization dataWithContacts:contactsArray error:&error];
NSString* vcardStr = [[NSString alloc] initWithData:vcardString encoding:NSUTF8StringEncoding];
NSLog(#"vcardStr = %#",vcardStr);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *folderPath = [paths objectAtIndex:0];
NSString *filePath = [folderPath stringByAppendingPathComponent:#"Contacts.vcf"];
[vcardStr writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}];
}
From iOS 9+ version AddressBookUI.framework and Addressbook.framework becomes deprecated. Apple introduced ContactUI.framework and Contact.framework with enhancements over AddressBookUI.framework and Addressbook.framework. In this blog we will talk about how to use these two new frameworks and export VCard. Let’s start picking contact from phone contacts and access basic information of that person.
Step 1. Create new Xcode project name ContactDemo and import Contacts.framework and ContactsUI.framework as shown in picture.
Step 2. In project add UIButton, UIImageView and 3 UILabels as shown in picture :
Step 3. Create outlets of button action, imageview and labels in respective view controller as :
#property (weak, nonatomic) IBOutlet UIImageView *personImage;
#property (weak, nonatomic) IBOutlet UILabel *personName;
#property (weak, nonatomic) IBOutlet UILabel *emailId;
#property (weak, nonatomic) IBOutlet UILabel *phoneNo;
- (IBAction)selectAction:(id)sender;
Step 4. Add delegate CNContactPickerDelegate to viewController.
Step 5. Add delegate method :
- (void) contactPicker:(CNContactPickerViewController *)picker
didSelectContact:(CNContact *)contact {
[self getContactDetails:contact];
}
This delegate method will return contact in the form of CNContact object which will be further processed in local method
-(void)getContactDetails:(CNContact *)contactObject {
NSLog(#"NAME PREFIX :: %#",contactObject.namePrefix);
NSLog(#"NAME SUFFIX :: %#",contactObject.nameSuffix);
NSLog(#"FAMILY NAME :: %#",contactObject.familyName);
NSLog(#"GIVEN NAME :: %#",contactObject.givenName);
NSLog(#"MIDDLE NAME :: %#",contactObject.middleName);
NSString * fullName = [NSString stringWithFormat:#"%# %#",contactObject.givenName,contactObject.familyName];
[self.personName setText:fullName];
if(contactObject.imageData) {
NSData * imageData = (NSData *)contactObject.imageData;
UIImage * contactImage = [[UIImage alloc] initWithData:imageData];
[self.personImage setImage:contactImage];
}
NSString * phone = #"";
NSString * userPHONE_NO = #"";
for(CNLabeledValue * phonelabel in contactObject.phoneNumbers) {
CNPhoneNumber * phoneNo = phonelabel.value;
phone = [phoneNo stringValue];
if (phone) {
userPHONE_NO = phone;
}}
NSString * email = #"";
NSString * userEMAIL_ID = #"";
for(CNLabeledValue * emaillabel in contactObject.emailAddresses) {
email = emaillabel.value;
if (email) {
userEMAIL_ID = email;
}}
NSLog(#"PHONE NO :: %#",userPHONE_NO);
NSLog(#"EMAIL ID :: %#",userEMAIL_ID);
[self.emailId setText:userEMAIL_ID];
[self.phoneNo setText:userPHONE_NO];
}
Step 6. Create CNContactPickerViewController class object and register its delegate in button IBAction method :
- (IBAction) selectAction:(id)sender {
CNContactPickerViewController *contactPicker = [CNContactPickerViewController new];
contactPicker.delegate = self;
[self presentViewController:contactPicker animated:YES completion:nil];
}
[self presentViewController:contactPicker animated:YES completion:nil]; will present view of contact list.
Step 7. Run project
A . Main View
B. On Tapping “Select Contact” button CNContactPickerViewController will open as shown in picture :
C. Pick one contact and view will dismiss and you will get details of that contact as shown in picture :
Earlier we have write permission code to access contacts but now it implicitly grants permission for accessing contacts. With this framework we can also generate VCard(VCF) and share among other platforms. Here is the steps to create VCard.
Step 1. Pick contact from CNContactPickerViewController and you will get CNContact Object in delegate as mention above.
Step 2. Save contact in document directory. As data is stored in NSData form so to convert contact to NSData
use CNContactVCardSerialization class that represents VCard in NSData format.
- (NSString *) saveContactToDocumentDirectory:(CNContact *)contact {
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString * VCardPath = [documentsDirectory stringByAppendingString:#"/VCard.vcf"];
NSArray *array = [[NSArray alloc] initWithObjects:contact, nil];
NSError *error;
NSData *data = [CNContactVCardSerialization dataWithContacts:array error:&error];
[data writeToFile:VCardPath atomically:YES];
return VCardPath;
}
CNContactVCardSerialization class method dataWithContacts:error: takes array of contact objects(CNContact class Object).
saveContactToDocumentDirectory method will return the file path of Vcard. With File path you can export contact anywhere you want.
Source: Contacts UI, Contacts Framework and create VCard(VCF) in Objective-C

AVAudioPCMBuffer for music files

I've been trying to play music in my SpriteKit game and used the AVAudioPlayerNode class to do so via AVAudioPCMBuffers. Every time I exported my OS X project, it would crash and give me an error regarding audio playback. After banging my head against the wall for the last 24 hours I decided to re-watch WWDC session 501 (see 54:17). My solution to this problem was what the presenter used, which is to break the frames of the buffer into smaller pieces to break up the audio file being read.
NSError *error = nil;
NSURL *someFileURL = ...
AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading: someFileURL commonFormat: AVAudioPCMFormatFloat32 interleaved: NO error:&error];
const AVAudioFrameCount kBufferFrameCapacity = 128 * 1024L;
AVAudioFramePosition fileLength = audioFile.length;
AVAudioPCMBuffer *readBuffer = [[AvAudioPCMBuffer alloc] initWithPCMFormat: audioFile.processingFormat frameCapacity: kBufferFrameCapacity];
while (audioFile.framePosition < fileLength) {
AVAudioFramePosition readPosition = audioFile.framePosition;
if (![audioFile readIntoBuffer: readBuffer error: &error])
return NO;
if (readBuffer.frameLength == 0) //end of file reached
break;
}
My current problem is that the player only plays the last frame read into the buffer. The music that I'm playing is only 2 minutes long. Apparently, this is too long to just read into the buffer outright. Is the buffer being overwritten every time the readIntoBuffer: method is called inside the loop? I'm such a noob at this stuff...how can I get the entire file played?
If I can't get this to work, what is a good way to play music (2 different files) across multiple SKScenes?
This is the solution that I came up with. It's still not perfect, but hopefully it will help someone who is in the same predicament that I've found myself in. I created a singleton class to handle this job. One improvement that can be made in the future is to only load sound effects and music files needed for a particular SKScene at the time they are needed. I had so many issues with this code that I don't want to mess with it now. Currently, I don't have too many sounds, so it's not using an excessive amount of memory.
Overview
My strategy was the following:
Store the audio file names for the game in a plist
Read from that plist and create two dictionaries (one for music and one for short sound effects)
The sound effect dictionary is composed of a AVAudioPCMBuffer and a AVAudioPlayerNode for each of the sounds
The music dictionary is compose of an array of AVAudioPCMBuffers, an array of timestamps for when those buffers should be played in queue, a AVAudioPlayerNode and the sample rate of the original audio file
The sample rate is necessary for figuring out the time at which each buffer should be played (you'll see the calculations done in code)
Create an AVAudioEngine and get the main mixer from the engine and attach all AVAudioPlayerNodes to the mixer (as per usual)
Play sound effects or music using their various methods
sound effect playing is straightforward...call method -(void) playSfxFile:(NSString*)file;
and it plays a sound
for music, I just couldn't find a good solution without invoking the help of the scene trying to play the music. The scene will call -(void) playMusicFile:(NSString*)file;and it will schedule the buffers to play in order that they were created. I couldn't find a good way to get the music to repeat once completed within my AudioEngine class so I decided to get the scene to check in its update: method whether or not the music was playing for a particular file and if not, play it again (not a very slick solution, but it works)
AudioEngine.h
#import <Foundation/Foundation.h>
#interface AudioEngine : NSObject
+(instancetype)sharedData;
-(void) playSfxFile:(NSString*)file;
-(void) playMusicFile:(NSString*)file;
-(void) pauseMusic:(NSString*)file;
-(void) unpauseMusic:(NSString*)file;
-(void) stopMusicFile:(NSString*)file;
-(void) setVolumePercentages;
-(bool) isPlayingMusic:(NSString*)file;
#end
AudioEngine.m
#import "AudioEngine.h"
#import <AVFoundation/AVFoundation.h>
#import "GameData.h" //this is a class that I use to store game data (in this case it is being used to get the user preference for volume amount)
#interface AudioEngine()
#property AVAudioEngine *engine;
#property AVAudioMixerNode *mixer;
#property NSMutableDictionary *musicDict;
#property NSMutableDictionary *sfxDict;
#property NSString *audioInfoPList;
#property float musicVolumePercent;
#property float sfxVolumePercent;
#property float fadeVolume;
#property float timerCount;
#end
#implementation AudioEngine
int const FADE_ITERATIONS = 10;
static NSString * const MUSIC_PLAYER = #"player";
static NSString * const MUSIC_BUFFERS = #"buffers";
static NSString * const MUSIC_FRAME_POSITIONS = #"framePositions";
static NSString * const MUSIC_SAMPLE_RATE = #"sampleRate";
static NSString * const SFX_BUFFER = #"buffer";
static NSString * const SFX_PLAYER = #"player";
+(instancetype) sharedData {
static AudioEngine *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
[sharedInstance startEngine];
});
return sharedInstance;
}
-(instancetype) init {
if (self = [super init]) {
_engine = [[AVAudioEngine alloc] init];
_mixer = [_engine mainMixerNode];
_audioInfoPList = [[NSBundle mainBundle] pathForResource:#"AudioInfo" ofType:#"plist"]; //open a plist called AudioInfo.plist
[self setVolumePercentages]; //this is created to set the user's preference in terms of how loud sound fx and music should be played
[self initMusic];
[self initSfx];
}
return self;
}
//opens all music files, creates multiple buffers depending on the length of the file and a player
-(void) initMusic {
_musicDict = [NSMutableDictionary dictionary];
_audioInfoPList = [[NSBundle mainBundle] pathForResource: #"AudioInfo" ofType: #"plist"];
NSDictionary *audioInfoData = [NSDictionary dictionaryWithContentsOfFile:_audioInfoPList];
for (NSString *musicFileName in audioInfoData[#"music"]) {
[self loadMusicIntoBuffer:musicFileName];
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init];
[_engine attachNode:player];
AVAudioPCMBuffer *buffer = [[_musicDict[musicFileName] objectForKey:MUSIC_BUFFERS] objectAtIndex:0];
[_engine connect:player to:_mixer format:buffer.format];
[_musicDict[musicFileName] setObject:player forKey:#"player"];
}
}
//opens a music file and creates an array of buffers
-(void) loadMusicIntoBuffer:(NSString *)filename
{
NSURL *audioFileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:#"aif"];
//NSURL *audioFileURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:filename ofType:#"aif"]];
NSAssert(audioFileURL, #"Error creating URL to audio file");
NSError *error = nil;
AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading:audioFileURL commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:&error];
NSAssert(audioFile != nil, #"Error creating audioFile, %#", error.localizedDescription);
AVAudioFramePosition fileLength = audioFile.length; //frame length of the audio file
float sampleRate = audioFile.fileFormat.sampleRate; //sample rate (in Hz) of the audio file
[_musicDict setObject:[NSMutableDictionary dictionary] forKey:filename];
[_musicDict[filename] setObject:[NSNumber numberWithDouble:sampleRate] forKey:MUSIC_SAMPLE_RATE];
NSMutableArray *buffers = [NSMutableArray array];
NSMutableArray *framePositions = [NSMutableArray array];
const AVAudioFrameCount kBufferFrameCapacity = 1024 * 1024L; //the size of my buffer...can be made bigger or smaller 512 * 1024L would be half the size
while (audioFile.framePosition < fileLength) { //each iteration reads in kBufferFrameCapacity frames of the audio file and stores it in a buffer
[framePositions addObject:[NSNumber numberWithLongLong:audioFile.framePosition]];
AVAudioPCMBuffer *readBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFile.processingFormat frameCapacity:kBufferFrameCapacity];
if (![audioFile readIntoBuffer:readBuffer error:&error]) {
NSLog(#"failed to read audio file: %#", error);
return;
}
if (readBuffer.frameLength == 0) { //if we've come to the end of the file, end the loop
break;
}
[buffers addObject:readBuffer];
}
[_musicDict[filename] setObject:buffers forKey:MUSIC_BUFFERS];
[_musicDict[filename] setObject:framePositions forKey:MUSIC_FRAME_POSITIONS];
}
-(void) initSfx {
_sfxDict = [NSMutableDictionary dictionary];
NSDictionary *audioInfoData = [NSDictionary dictionaryWithContentsOfFile:_audioInfoPList];
for (NSString *sfxFileName in audioInfoData[#"sfx"]) {
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init];
[_engine attachNode:player];
[self loadSoundIntoBuffer:sfxFileName];
AVAudioPCMBuffer *buffer = [_sfxDict[sfxFileName] objectForKey:SFX_BUFFER];
[_engine connect:player to:_mixer format:buffer.format];
[_sfxDict[sfxFileName] setObject:player forKey:SFX_PLAYER];
}
}
//WARNING: make sure that the sound fx file is small (roughly under 30 sec) otherwise the archived version of the app will crash because the buffer ran out of space
-(void) loadSoundIntoBuffer:(NSString *)filename
{
NSURL *audioFileURL = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:filename ofType:#"mp3"]];
NSAssert(audioFileURL, #"Error creating URL to audio file");
NSError *error = nil;
AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading:audioFileURL commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:&error];
NSAssert(audioFile != nil, #"Error creating audioFile, %#", error.localizedDescription);
AVAudioPCMBuffer *readBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFile.processingFormat frameCapacity:(AVAudioFrameCount)audioFile.length];
[audioFile readIntoBuffer:readBuffer error:&error];
[_sfxDict setObject:[NSMutableDictionary dictionary] forKey:filename];
[_sfxDict[filename] setObject:readBuffer forKey:SFX_BUFFER];
}
-(void)startEngine {
[_engine startAndReturnError:nil];
}
-(void) playSfxFile:(NSString*)file {
AVAudioPlayerNode *player = [_sfxDict[file] objectForKey:#"player"];
AVAudioPCMBuffer *buffer = [_sfxDict[file] objectForKey:SFX_BUFFER];
[player scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferInterrupts completionHandler:nil];
[player setVolume:1.0];
[player setVolume:_sfxVolumePercent];
[player play];
}
-(void) playMusicFile:(NSString*)file {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
if ([player isPlaying] == NO) {
NSArray *buffers = [_musicDict[file] objectForKey:MUSIC_BUFFERS];
double sampleRate = [[_musicDict[file] objectForKey:MUSIC_SAMPLE_RATE] doubleValue];
for (int i = 0; i < [buffers count]; i++) {
long long framePosition = [[[_musicDict[file] objectForKey:MUSIC_FRAME_POSITIONS] objectAtIndex:i] longLongValue];
AVAudioTime *time = [AVAudioTime timeWithSampleTime:framePosition atRate:sampleRate];
AVAudioPCMBuffer *buffer = [buffers objectAtIndex:i];
[player scheduleBuffer:buffer atTime:time options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
if (i == [buffers count] - 1) {
[player stop];
}
}];
[player setVolume:_musicVolumePercent];
[player play];
}
}
}
-(void) stopOtherMusicPlayersNotNamed:(NSString*)file {
if ([file isEqualToString:#"menuscenemusic"]) {
AVAudioPlayerNode *player = [_musicDict[#"levelscenemusic"] objectForKey:MUSIC_PLAYER];
[player stop];
}
else {
AVAudioPlayerNode *player = [_musicDict[#"menuscenemusic"] objectForKey:MUSIC_PLAYER];
[player stop];
}
}
//stops the player for a particular sound
-(void) stopMusicFile:(NSString*)file {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
if ([player isPlaying]) {
_timerCount = FADE_ITERATIONS;
_fadeVolume = _musicVolumePercent;
[self fadeOutMusicForPlayer:player]; //fade out the music
}
}
//helper method for stopMusicFile:
-(void) fadeOutMusicForPlayer:(AVAudioPlayerNode*)player {
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(handleTimer:) userInfo:player repeats:YES];
}
//helper method for stopMusicFile:
-(void) handleTimer:(NSTimer*)timer {
AVAudioPlayerNode *player = (AVAudioPlayerNode*)timer.userInfo;
if (_timerCount > 0) {
_timerCount--;
AVAudioPlayerNode *player = (AVAudioPlayerNode*)timer.userInfo;
_fadeVolume = _musicVolumePercent * (_timerCount / FADE_ITERATIONS);
[player setVolume:_fadeVolume];
}
else {
[player stop];
[player setVolume:_musicVolumePercent];
[timer invalidate];
}
}
-(void) pauseMusic:(NSString*)file {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
if ([player isPlaying]) {
[player pause];
}
}
-(void) unpauseMusic:(NSString*)file {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
[player play];
}
//sets the volume of the player based on user preferences in GameData class
-(void) setVolumePercentages {
NSString *musicVolumeString = [[GameData sharedGameData].settings objectForKey:#"musicVolume"];
_musicVolumePercent = [[[musicVolumeString componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:#""] floatValue] / 100;
NSString *sfxVolumeString = [[GameData sharedGameData].settings objectForKey:#"sfxVolume"];
_sfxVolumePercent = [[[sfxVolumeString componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:#""] floatValue] / 100;
//immediately sets music to new volume
for (NSString *file in [_musicDict allKeys]) {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
[player setVolume:_musicVolumePercent];
}
}
-(bool) isPlayingMusic:(NSString *)file {
AVAudioPlayerNode *player = [_musicDict[file] objectForKey:MUSIC_PLAYER];
if ([player isPlaying])
return YES;
return NO;
}
#end

How Can I Save This Array of Images?

I'm very new to programming, and I jumped right into a project (I know thats not the smartest thing to do, but I'm learning as I go). The app that I'm writing has 10 UIImageViews that display a picture from the users camera roll. The code I'm using needs each of the UIImageViews to have tags. I'm currently using NSData to save the array images, and it works great, but I can't use this method anymore because NSData doesn't support the use of tags. I also can't use NSUserDefaults, because I can't save images to a plist. Here is how I'm attempting to do this (using the NSData method, which works but I have to edit this so that my tags work.)
This is my current code:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[self.array addObject:imageView.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:imageView2.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
...
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
[self.array addObject:imageView.image];
[self.array addObject:imageView2.image];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[self.array objectAtIndex:0] copy];
imageView2.image = [[self.array objectAtIndex:1] copy];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
[super viewDidLoad];
}
Any help or suggestions on how to edit this code so that I can save the images, while using tags is much appreciated, thanks!
EDIT: Here is my updated code:
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
NSLog(#"Saved Button Pressed");
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
}
-(void)viewDidLoad {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL];
for (NSString *fileName in docFiles) {
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
if (!imageView.image) {
imageView.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
You need to use "Fast Enumeration" to parse the array's objects, and write each object to disk sequentially. First, you're going to need to add the UIImageView objects to the array instead of the UIImage property of the UIImageView, so you can recover the tag. So instead of writing
[self.array addObject:imageView.image];
It will be
[self.array addObject:imageView];
Try to follow along with my code. I inserted comments on each line to help.
-(void)applicationDidEnterBackground:(UIApplication *)application {
//Obtain the documents directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//begin fast enumeration
//this is special to ObjC: it will iterate over any array one object at a time
//it's easier than using for (i=0;i<array.count;i++)
for (UIImageView *imageView in self.array) {
//get the imageView's tag to append to the filename
NSInteger tag = imageView.tag;
//get the image from the imageView;
UIImage *image = imageView.image;
//create a filename, in this case "ImageTAGNUM.png"
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
//concatenate the docsDirectory and the filename
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
}
To load the images from disk, you'll have to look at your viewDidLoad method
-(void)viewDidLoad {
//get the contents of the docs directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//Get the list of files from the file manager
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL]);
//use fast enumeration to iterate the list of files searching for .png extensions and load those
for (NSString *fileName in docFiles) {
//check to see if the file is a .png file
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
//you'll have to sort out how to put these images in their proper place
if (!imageView1.image) {
imageView1.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
Hope this helps
One thing you need to be aware of is that when an app enters the background it has about 5 seconds to clean up its act before it's suspended. The UIPNGRepresentation() function takes a significant amount of time and is not instantaneous. You should be aware of this. It would probably be better to write some of this code in other places and do it earlier than at app backgrounding. FWIW
You can use the [NSbundle Mainbundel] to store that images.
To get path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
First, there's still a problem in your for loop.
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
// ...
}
Before you make any other changes, you must understand why. imageView is your for loop control variable, which changes on each iteration through the loop. self.imageView is a different thing. It is the first of the 10 imageViews attached to your viewController. Every time this loop cycles, it looks at the first imageView, and only the first.
As for why saving doesn't work, it's probably because the arrays elsewhere aren't working. Add some logging to make sure there's something in the array, and that it has as many elements as you expect.
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
// Log to make sure the views expected have previously been stored.
// If the array is empty, or shorter than expected, the problem is elsewhere.
NSLog(#"Image view array before saving = %#", self.array);
for (UIImageView *imageViewToSave in self.array) {
NSInteger tag = imageViewToSave.tag;
UIImage *image = imageViewToSave.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
// Log the image and path being saved. If either of these are nil, nothing will be written.
NSLog(#"Saving %# to %#", image, imagePath);
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:NO];
}
NSLog(#"Save Button Pressed");
}

Can't get NSData into NSMutableArray

I have an array of objects that I want to save as a file and reload back into my app. It's saving the file (with some data inside) but I can't get it to read back into a NSMutable Array.
The objects are models that conform to the NSCoding protocol:
#implementation myModel
#synthesize name;
#synthesize number;
-(void) encodeWithCoder: (NSCoder *) encoder
{
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
-(id) initWithCoder: (NSCoder *) decoder
{
name = [decoder decodeObjectForKey:#"name"];
number = [decoder decodeIntegerForKey:#"number"];
return self;
}
#end
So I create an array of these objects, then I save it...
- (void) saveMyOptions {
// Figure out where we're going to save the app's data files
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Figure out if that directory exists or not
BOOL isDir;
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager fileExistsAtPath:directoryPath isDirectory:&isDir];
// If the directory doesn't exist, create it
if (!isDir)
{
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// Assemble everything into an array of objects with options
NSMutableArray *savedPreferences = [[NSMutableArray alloc] init];
myModel *saveOptions = nil;
for (int i; i < [otherArray count]; i++)
{
saveOptions = [[myModel alloc] init];
[saveOptions setName:#"Some String"];
[saveOptions setNumber:i];
[savedPreferences addObject:saveOptions];
saveOptions = nil;
}
// Actually save those options into a file
NSData* saveData = [NSKeyedArchiver archivedDataWithRootObject:savedPreferences];
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
NSError *error = nil;
BOOL written = [saveData writeToFile:fileName options:0 error:&error];
if (!written)
{
NSLog(#"Error writing file: %#", [error localizedDescription]);
}
}
So now I try to load that data back into an array. This is where I think it's falling apart...
- (NSMutableArray *) loadOptions {
// Create file manager object
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSData *saveData = nil;
// Find user directory path
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Assign file name
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
// Create options array
NSMutableArray *myOptions = nil;
// If the file exists, fill the array with options
if ([fileManager fileExistsAtPath:fileName])
{
saveData = [NSData dataWithContentsOfFile:fileName];
myOptions = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
}
NSLog(#"%lu", [myOptions count]); // This ALWAYS reports 0!
NSLog(#"%lu", [saveData length]); // This reports a value of 236;
return myOptions;
}
Could someone point me in the direction of where I'm going wrong? I'm throughly confused :-(
Thanks in advance!
You are missing the super calls in your encodeWithCoder: and initWithCoder: methods, but that's just a guess. Why not use NSUserDefaults for saving preferences?
You might also want to make sure that your objects are retained is set using the synthesized setter.
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.number = [decoder decodeIntegerForKey:#"number"];
}
return self;
}
For your info, NSKeyedArchiver also has a method you can use directly to operate on files:
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path
and NSKeyedUnarchiver:
+ (id)unarchiveObjectWithFile:(NSString *)path

How do you save data with NSKeyedArchiver?

Hi, I am trying to save an object from a class I have created. It is called shot and Contains 5 variables I wish to save. Here is the .h file--- It cut off NSCoding and NSMutableCopying Protocals and my imports, but they are there.
#import <Foundation/Foundation.h>
#interface Shot : UIButton <NSCoding, NSMutableCopying> {
int x;
int y;
int location;
int quarter;
bool made;
int miss;
int make;
}
#property()int x;
#property()int y;
#property()int location;
#property()int quarter;
#property()bool made;
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
#end
**Here are the methods I made to save the data in my .m file---**
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:x forKey:#"x"];
[aCoder encodeInt:y forKey:#"y"];
[aCoder encodeInt:location forKey:#"location"];
[aCoder encodeInt:quarter forKey:#"quarter"];
[aCoder encodeBool:made forKey:#"made"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
x = [aDecoder decodeIntForKey:#"x"];
y = [aDecoder decodeIntForKey:#"y"];
location = [aDecoder decodeIntForKey:#"location"];
quarter = [aDecoder decodeIntForKey:#"quarter"];
made = [aDecoder decodeBoolForKey:#"made"];
}
return self;
}
-(id)mutableCopyWithZone:(NSZone *)zone {
Shot *newShot = [[Shot allocWithZone:zone]init];
[newShot set_x:x set_y:y set_quarter:quarter set_made:made set_location:location];
return newShot;
}
I can't seem to get my data to saved when I use these methods
-(NSString *)getPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolder = [paths objectAtIndex:0];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:documentFolder] == NO) {
[fm createDirectoryAtPath:documentFolder withIntermediateDirectories:YES attributes:nil error:nil];
}
return [documentFolder stringByAppendingFormat:#"iStatTrackInfo.archive"];
}
-(void)saveData {
NSString *path = [self getPath];
[NSKeyedArchiver archiveRootObject:shotArray toFile:path];
}
-(void)loadData {
NSString *path = [self getPath];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:path] == YES) {
shotArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return;
}
NSEnumerator *enumOne = [shotArray objectEnumerator];
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
shotShot.frame = CGRectMake(0, 0, 50, 50);
shotShot.backgroundColor = [UIColor whiteColor];
[self addSubview:shotShot];
}
}
I have set the save and load methods up to buttons, but my data still won't save. Please help!!!
I have finally solved the problem. Other than the above help, .archive isn't a file type. You can't save a .archive, you can only save a .arch or a .plist That was my main problem.
First, you are leaking memory like a sieve. Lots of alloc/init & mutableCopy, no releases. I'd suggest reading the memory management guide.
Next, your method names are not following the Cocoa conventions. These:
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
Should be something like:
-(void) resetMissAndMake; // or just missAndMake or activateMissAndMake
-(void) setX:(NSInteger)xValue y:(NSInteger)yValue quarter:(NSInteger)quarterValue location:(NSInteger)aLocation;
Also, getPath should just be path. Methods prefixed with get are both very rare and have a very specific meaning.
This is also nonsense:
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
There is no need for either of the [[Shot alloc]init] calls (those are both leaks).
Finally, your encoding methods are implemented incorrectly. As the documentation states, you need to call super as appropriate.
Specifically, your encodeWithCoder: must invoke super's implementation of same and initWithCoder: should call super's initWithCoder:, not init.