davwillev
davwillev

Reputation: 83

Create an array from images in a folder and present a given number of them at random (Objective-C)

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

Answers (2)

davwillev
davwillev

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

CRD
CRD

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:

  1. Never use a global variable just to return a value from a method
  2. This declares a variable, 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

Related Questions