Search code examples
iosobjective-cplisticarousel

Weird plist behaviour returning null on first value and 13 results when it should be 12


I have this strange behaviour when using a plist and trying to return the data. To give you an overview the plist has 12 entries in each object so I expect 12 but instead I get 13 with the first result being null while the remaining 12 are in fact correct.

Here is my plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Name</key>
    <array>
        <string>Audit</string>
        <string>Tax</string>
        <string>Digital</string>
        <string>Consulting</string>
        <string>Risk Advisory</string>
        <string>Finance</string>
        <string>Audit</string>
        <string>Tax</string>
        <string>Digital</string>
        <string>Consulting</string>
        <string>Advisory</string>
        <string>Finance</string>
    </array>
    <key>Logo</key>
    <array>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
        <string>page.png</string>
    </array>
</dict>
</plist>

This is my ViewController.h file:

#import <UIKit/UIKit.h>
#import "iCarousel.h"


@interface PackViewController : UIViewController <iCarouselDataSource, iCarouselDelegate>
{
    NSMutableArray      *packName;
    NSMutableArray      *packLogo;
}

@property (nonatomic, retain) IBOutlet iCarousel *carousel;
@property (nonatomic, retain) NSMutableArray  *packName;
@property (nonatomic, retain) NSMutableArray  *packLogo;

@end

And then, this is my ViewController.m file:

#import "PackViewController.h"
#import "AsyncImageView.h"


@interface PackViewController () <UIActionSheetDelegate>

@property (nonatomic, assign) BOOL wrap;
@property (nonatomic, retain) NSMutableArray *items;

@end


@implementation PackViewController

@synthesize carousel, wrap, items;
@synthesize packName, packLogo;

- (void)awakeFromNib
{
    //set up data
    wrap = YES;

    NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Pack" ofType:@"plist"];
    NSArray *plistItems = [NSDictionary dictionaryWithContentsOfFile:plistPath];
    NSArray *plistData = [plistItems valueForKey:@"Name"];

    self.items = [NSMutableArray array];
    for (int i = 0; i < [plistData count]; i++)
    {
        [items addObject:[NSNumber numberWithInt:i]];
        NSLog(@"First Index Name %@",[items objectAtIndex:i]);
    }
}

- (void)dealloc
{
    carousel.delegate = nil;
    carousel.dataSource = nil;

    [carousel release];
    [super dealloc];
}

#pragma mark -
#pragma mark View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Pack.plist code
    // get paths from root direcory
    NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
    // get documents path
    NSString *documentsPath = [paths objectAtIndex:0];
    // get the path to our Pack.plist file
    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"Pack.plist"];

    // check to see if Pack.plist exists in documents
    if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
    {
        // if not in documents, get property list from main bundle
        plistPath = [[NSBundle mainBundle] pathForResource:@"Pack" ofType:@"plist"];
    }

    // read property list into memory as an NSData object
    NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
    NSString *errorDesc = nil;
    NSPropertyListFormat format;
    // convert static property list into dictionary object
    NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization
                                          propertyListFromData:plistXML
                                          mutabilityOption:NSPropertyListMutableContainersAndLeaves
                                          format:&format
                                          errorDescription:&errorDesc];
    if (!temp)
    {
        NSLog(@"Error reading plist: %@, format: %d", errorDesc, format);
    }
    // assign values
    self.packName = [NSMutableArray arrayWithArray:[temp objectForKey:@"Name"]];
    self.packLogo = [NSMutableArray arrayWithArray:[temp objectForKey:@"Logo"]];

    // configure carousel
    carousel.type = iCarouselTypeInvertedWheel;
    carousel.vertical = true;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    self.carousel = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

#pragma mark -
#pragma mark UIActionSheet methods

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex >= 0)
    {
        //map button index to carousel type
        iCarouselType type = buttonIndex;

        //carousel can smoothly animate between types
        [UIView beginAnimations:nil context:nil];
        carousel.type = type;
        [UIView commitAnimations];
    }
}

#pragma mark -
#pragma mark iCarousel methods

- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
    //NSLog(@"numberOfItemsInCarousel count: %i", [items count]);
    return [items count];
}

- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
    UIButton *button = (UIButton *)view;
    UILabel *label = nil;

    if (button == nil)
    {
        //no button available to recycle, so create new one
        UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%@", [packLogo objectAtIndex:index]]];
        //NSLog(@"companyLogo: %@", [packLogo objectAtIndex:index]);
        button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
        [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [button setBackgroundImage:image forState:UIControlStateNormal];
        button.titleLabel.font = [button.titleLabel.font fontWithSize:20];
        [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];

        label = [[[UILabel alloc] initWithFrame:view.bounds] autorelease];
        label.backgroundColor = [UIColor clearColor];
        label.textAlignment = UITextAlignmentCenter;
    }
    else
    {
        //get a reference to the label in the recycled view
        label = (UILabel *)[view viewWithTag:1];
    }
    //set button label
    [button setTitle:[self.packName objectAtIndex:index] forState:UIControlStateNormal];
    NSLog(@"companyName: %@", [items objectAtIndex:index]);

    return button;
}

#pragma mark -
#pragma mark item tap event

- (void)buttonTapped:(UIButton *)sender
{
    //get item index for button
    NSInteger index = [carousel indexOfItemViewOrSubview:sender];

    [[[[UIAlertView alloc] initWithTitle:@"Button Tapped"
                                 message:[NSString stringWithFormat:@"You tapped button number %i", index]
                                delegate:nil
                       cancelButtonTitle:@"OK"
                       otherButtonTitles:nil] autorelease] show];
}

- (CATransform3D)carousel:(iCarousel *)_carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform
{
    CGFloat count = _carousel.numberOfVisibleItems;
    CGFloat spacing = 1.0f;
    CGFloat arc = M_PI * 2.0f;
    CGFloat radius = _carousel.itemWidth * spacing * count / arc;
    CGFloat angle = arc / count * offset;

    return CATransform3DTranslate(transform, radius - radius * cos(angle), radius * sin(angle), 0.0f);
}

- (CGFloat)carousel:(iCarousel *)_carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
{
    //customise carousel display
    switch (option)
    {
        case iCarouselOptionWrap:
        {
            //normally you would hard-code this to YES or NO
            return wrap;
        }
        case iCarouselOptionSpacing:
        {
            //add a bit of spacing between the item views
            return value * 1.05f;
        }
        case iCarouselOptionFadeMax:
        {
            if (carousel.type == iCarouselTypeCustom)
            {
                //set opacity based on distance from camera
                return 0.0f;
            }
            return value;
        }
        default:
        {
            return value;
        }
    }
}

@end

You can see that I have two lines of code in my M file that outputs two NSLog items. These are:

NSLog(@"First Index Name %@",[items objectAtIndex:i]);
NSLog(@"companyName: %@", [items objectAtIndex:index]);

The output for the "First Index Name" which is contained within the for loop in awakeFromNib is:

2013-05-21 22:43:23.875 Pack[7496:c07] First Index Name 0
2013-05-21 22:43:23.877 Pack[7496:c07] First Index Name 1
2013-05-21 22:43:23.878 Pack[7496:c07] First Index Name 2
2013-05-21 22:43:23.879 Pack[7496:c07] First Index Name 3
2013-05-21 22:43:23.880 Pack[7496:c07] First Index Name 4
2013-05-21 22:43:23.880 Pack[7496:c07] First Index Name 5
2013-05-21 22:43:23.881 Pack[7496:c07] First Index Name 6
2013-05-21 22:43:23.881 Pack[7496:c07] First Index Name 7
2013-05-21 22:43:23.882 Pack[7496:c07] First Index Name 8
2013-05-21 22:43:23.882 Pack[7496:c07] First Index Name 9
2013-05-21 22:43:23.883 Pack[7496:c07] First Index Name 10
2013-05-21 22:43:23.883 Pack[7496:c07] First Index Name 11

As you can see I'm getting all 12 values in the correct ordering which is kind of what I was expecting for the second one but this is where the problem come in. For the second NSLog which is contained in -(UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view I'm outputting the items array just to see what is happening. This is what I see:

2013-05-21 22:43:23.916 Pack[7496:c07] companyName: 0
2013-05-21 22:43:23.918 Pack[7496:c07] companyName: 0
2013-05-21 22:43:23.919 Pack[7496:c07] companyName: 11
2013-05-21 22:43:23.920 Pack[7496:c07] companyName: 7
2013-05-21 22:43:23.921 Pack[7496:c07] companyName: 6
2013-05-21 22:43:23.922 Pack[7496:c07] companyName: 2
2013-05-21 22:43:23.922 Pack[7496:c07] companyName: 1
2013-05-21 22:43:23.923 Pack[7496:c07] companyName: 10
2013-05-21 22:43:23.924 Pack[7496:c07] companyName: 9
2013-05-21 22:43:23.925 Pack[7496:c07] companyName: 8
2013-05-21 22:43:23.925 Pack[7496:c07] companyName: 5
2013-05-21 22:43:23.926 Pack[7496:c07] companyName: 4
2013-05-21 22:43:23.927 Pack[7496:c07] companyName: 3

Again the first two results are both 0 and the ordering is quite messed up unlike the first iteration. I found a thread on SO that mentions that plists don't necessarily output in the order as they are just key objects so I get that but the core issue is the first two results.

If I change my NSLog to output the results of self.packName (like this: NSLog(@"companyName: %@", [self.packName objectAtIndex:index]);) here is what is returned:

2013-05-21 22:49:43.663 Pack[7543:c07] companyName: (null)
2013-05-21 22:49:43.665 Pack[7543:c07] companyName: Audit
2013-05-21 22:49:43.666 Pack[7543:c07] companyName: Finance
2013-05-21 22:49:43.667 Pack[7543:c07] companyName: Tax
2013-05-21 22:49:43.668 Pack[7543:c07] companyName: Audit
2013-05-21 22:49:43.669 Pack[7543:c07] companyName: Digital
2013-05-21 22:49:43.669 Pack[7543:c07] companyName: Tax
2013-05-21 22:49:43.670 Pack[7543:c07] companyName: Risk Advisory
2013-05-21 22:49:43.671 Pack[7543:c07] companyName: Consulting
2013-05-21 22:49:43.672 Pack[7543:c07] companyName: Digital
2013-05-21 22:49:43.672 Pack[7543:c07] companyName: Finance
2013-05-21 22:49:43.673 Pack[7543:c07] companyName: Risk Advisory
2013-05-21 22:49:43.673 Pack[7543:c07] companyName: Consulting

As you can see again, the first result is (null) and I'm stumped as to why this is happening.

To further debug this I ran this NSLog: NSLog(@"companyName: %@", packName); to output the raw results as an array and these are the results:

2013-05-21 22:52:59.284 Pack[7581:c07] companyName: (
    "Audit",
    "Tax",
    "Digital",
    "Consulting",
    "Risk Advisory",
    "Finance",
    "Audit",
    "Tax",
    "Digital",
    "Consulting",
    "Advisory",
    "Finance"
)

There's no (null) result in the array so I don't have a clue where this rogue null value is coming from.

Any help would be immensely appreciated!!!!


Solution

  • For the second NSLog which is contained in carousel:viewForItemAtIndex:reusingView: I'm outputting the items array just to see what is happening.

    No, you are outputting exactly one element of items: the one with the index passed in the iCarousel delegate callback. You have no control over when iCarousel sends this message and the order of the indices is not specified. That explains the following output

    2013-05-21 22:43:23.916 Pack[7496:c07] companyName: 0
    2013-05-21 22:43:23.918 Pack[7496:c07] companyName: 0
    2013-05-21 22:43:23.919 Pack[7496:c07] companyName: 11
    ...
    

    Again the first two results are both 0 and the ordering is quite messed up unlike the first iteration. I found a thread on SO that mentions that plists don't necessarily output in the order as they are just key objects so I get that but the core issue is the first two results.

    As explained the order depends on the iCarousel implementation. It has nothing to do with plists. You misread the linked post, it's probably about order in dictionaries. Of course, plists preserve array element order.

    If I change my NSLog to output the results of self.packName (like this: NSLog(@"companyName: %@", [self.packName objectAtIndex:index]);) here is what is returned:

    2013-05-21 22:49:43.663 Pack[7543:c07] companyName: (null)
    2013-05-21 22:49:43.665 Pack[7543:c07] companyName: Audit
    ...
    

    The line with the (null) result can only mean one thing: At the first callback the items ivar is not yet initialized. objectAtIndex: can never return nil, so this is the only explanation. You could check that using the debugger, setting breakpoints in the callback and awakeFromNib.