0

Чем заменить UDID?

-

В связи с тем, что Apple вчера начала отклонять приложения, которые используют уникальный идентификатор устройства (UDID), возникает вопрос - чем же можно заменить UDID? Ведь очень часто при разработке приложений, которые используют в своей работе веб-сервисы, нам необходимо знать с какого устройства пришел тот или иной запрос.

На мой взгляд наиболее эффективной заменой UDID будет один из описанных ниже способов.

(more...)

1

Quickies for NSString

-

General

  • 'join' an array of strings into a single string
    1. NSArray *chunks = ... get an array, say by splitting it;
    2. string = [chunks componentsJoinedByString: @" : | "];
    NSArray *chunks = ... get an array, say by splitting it;
    string = [chunks componentsJoinedByString: @" : | "];

    would produce something like

    oop : | ack :- | bork :- | greeble :- | ponies
  • 'split' a string into an array
    1. NSString *string = @"oop:ack:bork:greeble:ponies";
    2. NSArray *chunks = [string componentsSeparatedByString: @":"];
    NSString *string = @"oop:ack:bork:greeble:ponies";
    NSArray *chunks = [string componentsSeparatedByString: @":"];
  • Converting string to an integer
    1. NSString *string = ...;
    2. int value = [string intValue];
    NSString *string = ...;
    int value = [string intValue];

    Similarly, there is a floatValue and doubleValue NSString methods. (more...)

0

Making UITableViews look not so plain

-

As most of you probably know, UITableView’s are incredibly useful and versatile views to be using in your applications.
If you have ever tried to customize a UITableView though, you know that as soon as you start adding lots of UIViews, UILabels, and UImageViews to a cells ContentView, that these tableviews start to scroll slower and slower, and become choppier and choppier.

What we are going to explore today is how to remedy that situation.
To download the entire XCode project, you can find it at: http://github.com/elc/ICB_PrettyTableView

We are going to build a simple contact viewer, that will display the phones contacts. For each contact, if they have a first name, last name, email and phone number, they will be displayed within one cell, with different colors. The reason this is useful is because it provides the basics for customizing UITableViewCells that can really start to make your application look nice, and still scroll well.

In this example, we have a standard UITableViewController. We are going to have a couple class variables defined in the header

#import 
#import 

@interface ICBTableViewController : UITableViewController
{
    ABAddressBookRef _addressBook;
}

@property (nonatomic, retain) NSArray *contacts;

@end

ABAddressBookRef _addressBook is defined in our header, so that we don’t have to release it until we dealloc. And the contacts is so that we can hold on to the data for our tableView.
In the main table view controller file we are going to override the – (void)viewDidLoad to provide some initial configuration of the tableView, as well as loading or generating our data. (We will generate fake data for devices or the simulator that don’t have address book data)

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.tableView.backgroundColor = UIColor.blackColor;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

    ABAddressBookRef addressBook = ABAddressBookCreate();
    NSArray *tempArray = (NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
    tempArray = [tempArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSString *name1 = (NSString *)ABRecordCopyCompositeName((ABRecordRef)obj1);
        NSString *name2 = (NSString *)ABRecordCopyCompositeName((ABRecordRef)obj2);
        return [name1 compare:name2];
    }];

    if ([tempArray count] > 0) {
        self.contacts = tempArray;
    } else {
        NSMutableArray *tempMutableArray = [NSMutableArray arrayWithCapacity:100];
        for (int i = 0; i < 100; ++i) {
            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            if ((i % 9) != 0) {
                [dict setObject:[NSString stringWithFormat:@"FirstName%d", i] forKey:@"firstName"];
            }
            if ((i % 3) == 0) {
                [dict setObject:[NSString stringWithFormat:@"LastName%d", i] forKey:@"lastName"];
            }
            if ((i % 3) == 0 && (i % 2) == 0) {
                [dict setObject:[NSString stringWithFormat:@"emailTest%d@test%d.com", i, i] forKey:@"email"];
            }
            if ((i % 7) == 0) {
                NSString *string = [NSString stringWithFormat:@"%d", i];
                while ([string length] < 10) {
                    string = [string stringByAppendingFormat:@"%@", string];
                }
                [dict setObject:string forKey:@"phone"];
            }
            [tempMutableArray addObject:dict];
        }
        self.contacts = tempMutableArray;
    }
}

As you can see here we have a couple of self.tableView methods we have called to setup the background color, and also the cell separator style.
If you are running this application on a device, or simulator that has contacts, this method will also make a copy of the address book as the data to display. If there is no data in the address book, we create some fake test data just for displaying.
Also don’t forget to include our – (void)dealloc method for releasing our _addressBook variable.

- (void)dealloc
{
    CFRelease(_addressBook);
    [_contacts release];
    _contacts = nil;
}

We have to supply the tableView with our number of rows

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.contacts count];
}

The next portion we have to override is the – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath so that we can supply the tableView with our cells.

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   

    static NSString *CellIdentifier = @"ICBTableViewCellIdentifier";

    ICBTableViewCell *cell = (ICBTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[ICBTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// cell.delegate = self;
        cell.textLabel.textColor = UIColor.whiteColor;
    }
    cell.tag = indexPath.row;

    NSObject *object = [self.contacts objectAtIndex:indexPath.row];
    if ([object isKindOfClass:NSDictionary.class]) {
        [cell setDictionary:(NSDictionary *)object];
    } else {
        [cell setRecord:(ABRecordRef)object];
    }

    return cell;
}

We are checking each object coming out of our array so that we can determine if we need to call the setDictionary, or setRecord method calls.

Now the meat of this tutorial, extending a UITableViewCell.

In our header we are going to define a bunch of strings that we want to display

#import 
#import 

@interface ICBTableViewCell : UITableViewCell

@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSString *email;
@property (nonatomic, retain) NSString *phone;
//@property (nonatomic, retain) UIImage *image;
@property (nonatomic, retain) NSString *address;

- (void)setRecord:(ABRecordRef)record;
- (void)setDictionary:(NSDictionary *)dict;

@end

And then our two set methods:

- (void)setRecord:(ABRecordRef)record
{
    self.firstName = [(NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty) autorelease];
    self.lastName = [(NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty) autorelease];
    self.email = [self getFirstEmail:record];
    self.phone = [self getFirstPhone:record];
    [self setNeedsDisplay];
}

- (void)setDictionary:(NSDictionary *)dict
{
    self.firstName = [dict objectForKey:@"firstName"];
    self.lastName = [dict objectForKey:@"lastName"];
    self.email = [dict objectForKey:@"email"];
    self.phone = [dict objectForKey:@"phone"];
    [self setNeedsDisplay];
}

Some tutorials will have you put this next part into a separate UIView subclass, and add that class as the contentView of this UITableViewCell, but I prefer to override the drawRect of the UITableViewCell, and do all my drawing there.

The first thing I am doing is getting the current graphics context so that we can draw to the screen, clipping to the rect that is passed in drawRect:(CGRect)rect, and then depending on whether this cell is even, I am filling the entire rect with an almost black color, or slightly lighter.

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextClipToRect(ctx, rect);
    //If even
    if (((self.tag % 2) == 0)) {
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.1f alpha:1.f].CGColor);
    } else {
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.15f alpha:1.f].CGColor);
    }

    CGContextFillRect(ctx, rect);

The next thing I am going to figure out is whether I want this text to be centered in the cell, and I am determining this based off whether there is an email field or not.

    //Vertically center our text, if no email
    BOOL isCentered = (self.email == nil);

And now for the meat of the drawRect method. We calculate the size of firstName, draw it offset from the left by 5, and then draw lastName right after it. We also change the color we are drawing between those two.

    CGRect tempRect;
    CGFloat midY = CGRectGetMidY(rect);
    [[UIColor whiteColor] set];
    UIFont *defaultFont = [UIFont systemFontOfSize:16];
    CGSize size = [self.firstName sizeWithFont:defaultFont];
    if (isCentered == NO) {
        tempRect = CGRectMake(5, 0, size.width, size.height);
    } else {
        tempRect = CGRectMake(5, midY - size.height/2, size.width, size.height);
    }
    [self.firstName drawInRect:tempRect withFont:defaultFont];

    [[UIColor lightGrayColor] set];
    size = [self.lastName sizeWithFont:defaultFont];
    if (isCentered == NO) {
        tempRect = CGRectMake(CGRectGetMaxX(tempRect)+5, 0, size.width, size.height);
    } else {
        tempRect = CGRectMake(CGRectGetMaxX(tempRect)+5, midY - size.height/2, size.width, size.height);
    }
    [self.lastName drawInRect:tempRect withFont:defaultFont];

Next we find out if phone actually exists, and if so set the color to red, and draw it to the right of lastName. We also have to make sure we aren’t drawing this outside our boundaries, so we check to see where the end is, and if it is outside, we crop it to 5 pixels from the end.

    if (self.phone != nil) {
        [[UIColor redColor] set];
        size = [self.phone sizeWithFont:defaultFont];
        CGFloat end = CGRectGetMaxX(tempRect) + size.width;
        if (end > rect.size.width) {
            size.width = CGRectGetMaxX(rect) - CGRectGetMaxX(tempRect) - 10; //-10 so that we get 5 from the end of last name, and 5 from the end of rect
        }
        if (isCentered == NO) {
            tempRect = CGRectMake(CGRectGetMaxX(rect) - size.width - 5, 0, size.width, size.height);
        } else {
            tempRect = CGRectMake(CGRectGetMaxX(rect) - size.width - 5, midY - size.height/2, size.width, size.height);
        }
        [self.phone drawInRect:tempRect withFont:defaultFont lineBreakMode:UILineBreakModeTailTruncation];
    }

And finally if our email actually exists draw it on the bottom left.

    if (self.email != nil) {
        [[UIColor blueColor] set];
        size = [self.email sizeWithFont:defaultFont];
        tempRect = CGRectMake(5, midY, size.width, size.height);
        [self.email drawInRect:tempRect withFont:defaultFont];
    }

I hope this helps you in configuring UITableViewCells for your own project, and hopefully will let you start to think about the possibilities.

To download the entire XCode project, you can find it at: http://github.com/elc/ICB_PrettyTableView

0

Accessing Image Properties Without Loading the Image into Memory

-

Sometimes you might want to retrieve certain properties from an image file, such as the image’s dimensions or other metadata, without actually displaying the full-size image on screen. The simplest way to do that on iOS is using the UIImage class:

UIImage *image = [UIImage imageWithContentsOfFile:...]; CGSize imageSize = image.size; ...

The problem with this approach is that the entire image gets loaded into memory. And since the pixel data is stored uncompressed in memory, even a small 512 ? 512 image (that fills less than half of an iPhone 4’s screen) will take up 1 MB of memory.

CGImageSource

As of iOS 4, the SDK includes a better solution in the form of the CGImageSource... set of functions, which have been available on the Mac since forever. These functions allow you to access certain image metadata without having to load the actual pixel data into memory. For example, getting the pixel dimensions works like this (make sure to include the ImageIO.framework in your target):

#import <ImageIO/ImageIO.h> NSURL *imageFileURL = [NSURL fileURLWithPath:...]; CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL); if (imageSource == NULL) { // Error loading image ... return; } NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], (NSString *)kCGImageSourceShouldCache, nil]; CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (CFDictionaryRef)options); if (imageProperties) { NSNumber *width = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth); NSNumber *height = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight); NSLog(@"Image dimensions: %@ x %@ px", width, height); CFRelease(imageProperties); }

All the Metadata You Dreamed Of

The dictionary returned by CGImageSourceCopyPropertiesAtIndex() contains a lot more than just the image dimensions. If present, it includes the complete EXIF and IPTC metadata and also various file-format-specific information for TIFF, GIF, JPEG, PNG and Raw files (among others).

As an example, let’s read out the date a photo was taken, the model name of the camera and the GPS coordinates stored in the metadata:

CFDictionaryRef exif = CFDictionaryGetValue(imageProperties, kCGImagePropertyExifDictionary); if (exif) { NSString *dateTakenString = (NSString *)CFDictionaryGetValue(exif, kCGImagePropertyExifDateTimeOriginal); NSLog(@"Date Taken: %@", dateTakenString); } CFDictionaryRef tiff = CFDictionaryGetValue(imageProperties, kCGImagePropertyTIFFDictionary); if (tiff) { NSString *cameraModel = (NSString *)CFDictionaryGetValue(tiff, kCGImagePropertyTIFFModel); NSLog(@"Camera Model: %@", cameraModel); } CFDictionaryRef gps = CFDictionaryGetValue(imageProperties, kCGImagePropertyGPSDictionary); if (gps) { NSString *latitudeString = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitude); NSString *latitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLatitudeRef); NSString *longitudeString = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitude); NSString *longitudeRef = (NSString *)CFDictionaryGetValue(gps, kCGImagePropertyGPSLongitudeRef); NSLog(@"GPS Coordinates: %@ %@ / %@ %@", longitudeString, longitudeRef, latitudeString, latitudeRef); }

This produces the following output on my sample image:

Date Taken: 2011:03:27 11:30:30
Camera Model: Canon EOS 20D
GPS Coordinates: 8.374788 E / 54.89472 N

An NSLog(@"Image properties: %@", (NSDictionary *)imageProperties); will quickly show you all the available metadata for a particular image. It’s fun to experiment with different files. The documentation lists all available metadata keys at CGImageProperties Reference.