Reputation: 83
I am trying to display a predesignated number of .PNG images (e.g. 20) at random from a folder containing a much larger number of images (120).
Here is my code based on the several examples I could find; I am calling this method within viewDidLoad.
-(void)displayImagesInRandomOrder {
// Obtain set number of attempts
imageArrayLength = 20; // arbitrary choice in this example
// Build array from all (120) .PNG images in folder
NSArray *_imageArray = [[NSBundle mainBundle] pathsForResourcesOfType:@".png" inDirectory:@"(Folder directory)/."];
// Create a random integer for each of the (120) images in the array
int randomIndex = arc4random_uniform((uint32_t) _imageArray.count);
// Select an image from the array using the random number
UIImage *randomImage = [_imageArray objectAtIndex:randomIndex];
// Add the UIImage to a UIImageView and display it on the screen
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:randomImage];
// Set position of the image
tempImageView.contentMode = UIViewContentModeScaleAspectFit;
}
All help/advice graciously received, since I have been staring at this for far too long. Many thanks in advance :)
EDIT ///
Here is the first of my revised methods to build an array of 20 images selected at random from the folder:
NSMutableArray *imageQueue;
- (void)buildArrayOfRandomImages {
// Build array of pathnames to images in folder
NSArray *pathArray = [[NSBundle bundleForClass:[self class]] pathsForResourcesOfType:@"png" inDirectory:@"(Folder directory)"];
// Obtain preset number of attempts
imageQueueLength = 20;
// Create image queue (new array) of predetermined length
// use a loop to add correct number of images to image queue
for(int i = 1; i <= imageQueueLength; i++) {
// Select an integer at random for each image in the array of pathnames
int randomIndex = arc4random_uniform((uint32_t) [pathArray count]);
// Add images to the image queue
[imageQueue arrayByAddingObject:[UIImage imageWithContentsOfFile:[pathArray objectAtIndex:randomIndex]]];
}
}
Here is the second method:
- (void)displayImageFromArray {
// Add the UIImage to a UIImageView and display it on screen
UIImageView *imageView = [[UIImageView alloc] initWithImage:[imageQueue objectAtIndex:imageCount]];
// Set screen position of image
imageView.contentMode = UIViewContentModeScaleAspectFit;
// increment count every time method is called
imageCount++;
}
I felt confident that this would work, but the imageQueue array remains empty from the buildArrayOfRandomImages
method.
Upvotes: 0
Views: 532
Reputation: 83
@CRD, as requested I didn't update the question any more, but wanted to show you my progress. Many thanks again for getting me this far!!
[As you sensibly suggested, I will limit this thread to the creation of the array of randomly selected images, since displaying them is quite a different issue.]
Solution 1 (seems to work but inelegant):
- (NSArray *) buildArrayOfRandomImagesWithLength:(NSUInteger)imageQueueLength {
// Build array of pathnames to images in folder (nil directory was the only one that actually worked for me)
NSArray *pathArray = [[NSBundle bundleForClass:[self class]] pathsForResourcesOfType:@"png" inDirectory:nil];
// Create a mutable array to hold the images
NSMutableArray *imageQueue = [NSMutableArray arrayWithCapacity:imageQueueLength];
// Fill the imageQueue array using pathnames
for(int i = 1; i <= imageQueueLength; i++) {
// Select a random integer
int randomIndex = arc4random_uniform((uint32_t) [pathArray count]);
// Use the random integer to select a pathname and create an image object
UIImage *image = [UIImage imageWithContentsOfFile:[pathArray objectAtIndex:randomIndex]];
// Ensure all elements are unique
if (![imageQueue containsObject:image]) {
// Add image to the image queue
[imageQueue addObject:image];
} else {
i--; // ensure the array is the correct length
}
}
// Return the final array, by convention immutable (NSArray) so copy
return [imageQueue copy];
}
Solution 2: (much more elegant but requires an import of the GameplayKit
Framework, which also must be linked to the project - see https://stackoverflow.com/a/47404462/11137617)
- (NSArray *) buildArrayOfRandomImagesWithLength:(NSUInteger)imageQueueLength {
// Build array of pathnames to images in folder
NSArray *pathArray = [[NSBundle bundleForClass:[self class]] pathsForResourcesOfType:@"png" inDirectory:nil];
//Create a shuffled copy of pathArray
NSArray *shuffledPaths;
shuffledPaths = [pathArray shuffledArray];
// Create a mutable array to hold the images
NSMutableArray *imageQueue = [NSMutableArray arrayWithCapacity:imageQueueLength];
// Fill the image queue array using pathnames
for(NSUInteger i = 1; i <= imageQueueLength; i++) {
UIImage *image = [UIImage imageWithContentsOfFile:[shuffledPaths objectAtIndex:(i - 1)]];
[imageQueue addObject:image];
}
// Return the final array, by convention immutable (NSArray) so copy
return [imageQueue copy];
}
Solution 3: (avoids importing GameplayKit
by using a separate array shuffling method, based largely on this: https://stackoverflow.com/a/56656/11137617, which itself is based on the Fisher–Yates shuffle that @CRD recommended)
- (NSArray *) buildArrayOfRandomImagesWithLength:(NSUInteger)imageQueueLength {
// Build array of pathnames to images in folder
NSString *directory = nil;
NSArray *pathArray = [[NSBundle bundleForClass:[self class]] pathsForResourcesOfType:@"png" inDirectory:directory];
//Create a shuffled copy of pathArray
NSArray *shuffledPaths;
shuffledPaths = [self shuffleArray:pathArray];
// qCreate a mutable array to hold the images
NSMutableArray *imageQueue = [NSMutableArray arrayWithCapacity:imageQueueLength];
// Fill the image queue array using pathnames
for(NSUInteger i = 1; i <= imageQueueLength; i++) {
UIImage *image = [UIImage imageWithContentsOfFile:[shuffledPaths objectAtIndex:(i - 1)]];
[imageQueue addObject:image];
}
// Return the final array, by convention immutable (NSArray) so copy
return [imageQueue copy];
}
Array shuffling method:
- (NSArray *) shuffleArray:(NSArray*)array {
NSMutableArray *shuffledArray = [NSMutableArray arrayWithArray:array];
for (NSUInteger i = 0; i < [shuffledArray count] - 1; ++i) {
NSInteger remainingCount = [shuffledArray count] - i;
NSInteger exchangeIndex = i + arc4random_uniform((u_int32_t )remainingCount);
[shuffledArray exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
}
return [shuffledArray copy];
}
Upvotes: 0
Reputation: 53000
Let's see if we can help you along:
-(void)displayImagesInRandomOrder { // Obtain set number of attempts imageArrayLength = 20; // arbitrary choice in this example // Build array from all (120) .PNG images in folder NSArray *_imageArray = [[NSBundle mainBundle] pathsForResourcesOfType:@".png" inDirectory:@"(Folder directory)/."];
This produces an array of paths (see the documentation). What is the value of this array when you run your code? Are any paths found?
// Create a random integer for each of the (120) images in the array int randomIndex = arc4random_uniform((uint32_t) _imageArray.count); // Select an image from the array using the random number UIImage *randomImage = [_imageArray objectAtIndex:randomIndex];
You have an array of paths, indexing it won't produce a UIImage
. Read Creating Image Objects in the UIImage
documentation.
// Add the UIImage to a UIImageView and display it on the screen UIImageView *tempImageView = [[UIImageView alloc] initWithImage:randomImage]; // Set position of the image tempImageView.contentMode = UIViewContentModeScaleAspectFit; }
There is no code here to select 20 elements. When you want to select N different cards at random from a deck of playing cards how do you do it? Now read the NSArray
documentation... HTH
Addendum After Your Comment & Question Edit
You've made some good progress, but as you say it still doesn't work. Let's take a look at your new code:
Here is the first of my revised methods to build an array of 20 images selected at random from the folder:
NSMutableArray *imageQueue;
There are a couple of problems here, one design, one programming:
imageQueue
, which is capable of storing a reference to an NSMutableArray
. All objects need to be created, you don't create an array here, and this variable will have the value nil
– i.e. it references nothing.Try a design along the lines of:
- (NSArray *) buildArrayOfRandomImagesWithLength:(NSUInteger)imageQueueLength
{
// create a mutable array to hold the images
NSMutableArray *imageQueue = [NSMutableArray arrayWithCapacity:imageQueueLength];
// your code to fill the array
...
// return the final array, by convention immutable (NSArray) so copy
return [imageQueue copy];
}
Back to your code:
- (void)buildArrayOfRandomImages { // Build array of pathnames to images in folder NSArray *pathArray = [[NSBundle bundleForClass:[self class]] pathsForResourcesOfType:@"png" inDirectory:@"(Folder directory)"];
There is nothing wrong with this per se, but have you checked (using the debugger or logging statements) that pathArray
contains any elements? While (Folder Directory)
is a valid name for a folder did you actually call your folder this name?
// Obtain preset number of attempts imageQueueLength = 20; // Create image queue (new array) of predetermined length // use a loop to add correct number of images to image queue for(int i = 1; i <= imageQueueLength; i++) { // Select an integer at random for each image in the array of pathnames int randomIndex = arc4random_uniform((uint32_t) [pathArray count]);
Again there is nothing wrong with this per se, it produces a random index for you. However it could produce the same index more than once, do you not want 20 different images?
My playing card analogy obviously was detailed enough: How do you typically select N random cards from a deck? You shuffle the deck and then deal the required N cards off the top. Take another look at NSArray
...
// Add images to the image queue [imageQueue arrayByAddingObject:[UIImage imageWithContentsOfFile:[pathArray objectAtIndex:randomIndex]]]; } }
This doesn't do what you expect. The method arrayByAddingObject:
is from NSArray
and it creates a new NSArray
from an existing one and the new element, the method then returns this new array. This code ignores the return value, thus discarding the new array...
However you don't have an NSArray
but an NSMutableArray
, so you don't need to create a new array each time you need to add an object to your mutable array. Take a look at NSMutableArray
again...
Here is the second method:
- (void)displayImageFromArray { // Add the UIImage to a UIImageView and display it on screen UIImageView *imageView = [[UIImageView alloc] initWithImage:[imageQueue objectAtIndex:imageCount]]; // Set screen position of image imageView.contentMode = UIViewContentModeScaleAspectFit; // increment count every time method is called imageCount++; }
First this method should have a declaration like:
- (void) displayImage:(NSUInteger)imageCount fromArray:(NSArray *)imageQueue
i.e. pass in the required data, don't use global variables. Or better yet just:
- (void) displayImage:(UIImage)theImage
as there is no need to pass the array & index and if defined this way the image doesn't need to come from an array at all.
However the above declarations are probably insufficient – your method as written won't display any images and the additional code to do that may require a further argument as well.
Why won't it display any images? An UIImageView
will only display its image on screen if it is part of the current view hierarchy. Now that is another whole topic in itself, so unless you've omitted code for brevity you've some more reading to do.
Postscript
Don't try to extend this question further, that is not how SO works. If in researching and designing your new solution you hit an issue ask a new question on that new issue. You can include a reference to this question so people assisting you can trace back the history.
Upvotes: 1