Damn custom fonts (iOS), again!

I’ve gone through a rough patch lately while working on my text editor and another very-small-app-in-a-day (which is taking me way too long, but almost done in 48 hours). The culprit: custom fonts. The loss: one hour. There’s been a change in how we manage custom fonts in Xcode, which I did not have the occasion to witness earlier. Googling it did not solve the issue, so hopefully it will save someone some time.

Adding custom Fonts to your project

Custom fonts for your UITextView or UITextField has been available for a long time, since the iPhone SDK 3.2 if I remember well. The syntax for invoking them is very simple:

[textView setFont:[UIFont fontWithName:myGreatFontStringName size:myGreatFontFloatSize];

The tricky part is actually getting the font into your project. I have used a quick tuto from TetonTechnical, which has been up for 2 years. For completeness, Here are the steps (without the images):

  • drag your font into your project tree, with the “copy” ticked. I’ve used ttf and otf without a problem,
  • declare the fonts in the app’s PList file, as “Fonts provided by application”. You are declaring them here with the full file name, eg. “Cantarell.ttf” or “Goudy Bookletter 1911.otf” in my case.
  • open Font Book, and check the name of the font as it is given there. This is the NSString you are going to pass as argument to fontWithName. So, in my case, I’ve got
@"Cantarell"

and

@"Goudy Bookletter 1911"

all very simple, though the names can be much trickier to catch, see the gist below to enumerate all the fonts as seen by iOS.

Now, here lies the rub. In previous versions of Xcode, that’s all you had to do. But in the version I’m working in today, the fonts did not appear to be loaded. The reason? That b%tch was not included in the Target Membership as being part of the package. Very simple way to change this: with the font file selected in your project tree, show the utilities panel (that’s the panel on the right of Xcode by default), scroll down to “Target Membership”, and tick your product there. It should be something like this:

Font screen shot

(I’ve blurred the name of the project, it’s still secret and a bit shameful ;-).

Bonus: gist to output all the fonts available to your console

for (id objFamilyName in [UIFont familyNames]) {
    if ([objFamilyName isKindOfClass:[NSString class]]) {
        for(id objFontName in [UIFont fontNamesForFamilyName:(NSString*)objFamilyName]) {
            if ([objFontName isKindOfClass:[NSString class]]) {
                NSLog(@"%s: %@: %@", __FUNCTION__, objFamilyName, objFontName); 
            }
        }
    }
}

Follow-Up: chat room & Parse

A big thank you to all devs who have tried the chat room project of the previous post. This was my first stab at putting together a comprehensive tutorial and sharing the code on a public git repository. I can say that I have truly been overwhelmed by the response and all the kind words. And all conversations, suggestions and questions, without any exception, have been awesome.

As discussed on Twitter, there will be at least one other chatroom tutorial to follow, with a few interesting features:

  • getting some bubbles in the tableview that makes our chatroom. Honestly, this is not difficult,
  • getting a picture of the user close to the arrow of the bubble. Now this is a bit more tricky as we would expect a user to sign-in either as a new user, as a Twitter user, or as a FB user (horror!).

That is, if Parse does not do it before me, these guys have been on fire this past two weeks. They have produced two pretty cool posts on:

Funnily enough, they do not use the former in the latter, but since the latter is one the AppStore my best guess is that it was finished much earlier than the former (I mean, you follow that reasoning?). Anyway, both are pretty cool, I’d encourage you to check them out. I’ll be honest, I was in the process of building the first one, a sign-in view, and have been a little bit annoyed to throw away that code. But this saves me from diving into the FB API again, and this one is definitely not my best friend.

iOS Tutorial: Creating a chat room using Parse.com

About Parse

Parse.com has been out of beta for a few weeks now, and I have been using this service for a few months, while they were still polishing the tool - and the API. I do regard this kind of service as the next step in app evolution: for the example that is interesting us today, if you’ve ever created your own php-Ajax-whatever enabled server and tried to bind a client to it, you certainly remember the pain. Enters Parse, which is the best invention since sliced bread. It basically takes care of all the server-side creation and management for you, and lets you deal with just interacting with your database. And, cherry on the cake, the database interaction and definition is very lazily done for you, ie. while programming it you’ll be able to add new fields on-the-go. It’s really been a pleasure to use this service, and I thought I’d share some of the steps to follow in order to create a simple chat room. Which remains a first stab at client/server development anyway.

Gimme the code

For those completely lacking patience, the code I’m describing below is available on Github at this address: https://github.com/dmendels/chatDemo.

The goals

We’re not building the next Twitter client here, quite far from that actually - though I’m convinced that you could, given a bit of effort. In a recent project, I was building a breastfeeding tracker app for iPhone, so that we can easily alleviate some of the concerns of my Wife while she was feeding our (now 2.5 months old) daughter. As with any project, I decided early on to make it available on the App Store, and started coding. The result: Baby Yummy, I’m sharing two screen shots here, you can visit the app’s website for more information.

BabyYummyBabyYummy

Don’t leave simply because you’re not a new Mom, that’s the only digression I’ll be making here. Along the way, I thought it could be cool for Moms who are breastfeeding to chat together, without leaving the app. Chat+Parse = a good demonstrator and test of the service, so there we go.

Today, we’re going to build an app similar to Baby Yummy, with a tab bar and three views, two of them being empty and the third containing our chat room. Needless to say, we’re going to use ARC and StoryBoards, because they’re really great features of iOS5.x. I’m not going to skin the app, I’ll just use some non-default backgrounds to make it look less bland. I’m using a pixel pattern I picked on the awesome subtlepatterns.com site. In the end, we should get to something that looks like this:

chatDemo_view2 chatDemo_view1

Having you own chat room inside your app can be quite interesting. First, you do control the content and administer this service. Quite simply, your timeline will not be tainted by other contents. Second, this is essentially private, and does not connect to the internet: this means that it does not get your app into the 17+ category automatically when submitting to the AppStore. And third, this can be used to deploy a simple bulletin board for internal use in enterprise apps.

Getting ready

First thing first, let’s get started with Parse. You’re going to need to sign in (or login) to Parse. It’s free (up to a large number of queries per month), so do yourself a favor and just do it. Go to Parse.com and sign in. You should get a “Your Apps” thumb in the main navigation bar. Get to it, and hit the “Create new app” button, down left. Give it a name, eg. “chatDemo”, hit the “Create App” button, and here we go. You should get to something that looks like this:

Parse_SignedIn

Parse has created your App database for you, and you ended in the Dashboard of your app. The first box on the left is what we’re after for now: the Application ID and Client Key. These are the only keys we’re going to use for now. You’ll notice that there are two other keys under, but we don’t want to discuss RESTful APIs and webservices now. You’ll notice that we don’t have any API request at the moment, and have not used Push Notifications (that’s another cool feature that we may explore in a later blog post). While we’re here, look at the DataBrowser thumb, which indicates that we’ve got no data in the app yet. There are 3 choices there, 1) go through a QuickStart guide, which I’d recommend anyway as it’s really quick and well written, 2) create a new class manually, which we’re not going to use as we’ll be doing that directly from Xcode, and 3) import existing data, which is an easy way to populate your database if need be.

Right, back to our keys. The Application ID and Client Key are going to get us on Parse with one line of code. Yup, that’s cool.

Now, fire up Xcode, and create a new project. Choose the “Tabbed Application” in iOS Application drop. Let’s call that “chatDemo”, use ARC and StoryBoards, and we’re only going to build the iPhone version (only).

XcodeCreateProject

Hit “Next” and create. I’m using the DM prefix on all ViewControllers, that’s not an obligation. Now that we’ve got our project up and ready, we’ll first check that it’s OK. Compile and run, you’ll get a simple app with two view controllers.

Now, we’re going to run the QuickStart guide from Parse. Go to this address or simply hit the third thumb from the main navigation bar on the site “Quick Start”, and you should see something similar to this:

ParseQuickStart

Just leave the first radio button as it is (“Existing iOS Project”), and proceed through the steps, ie. download the SDK first, install it, and add the libraries needed as requested. Follow through until step 10, this should take you about 3 minutes. The critical point is to copy/paste the line:

[Parse setApplicationId:@”QIH9NZnfICel96LMKDVwkkoHxYyHjv3ZJBxCe9kS” clientKey:@”AEGfslh18prPvZysXmjn61hagygBSfcYTJdSISw7”];

just as it is given to you, in your AppDelegate, inside the application:didFinishLaunchingWithOptions: function:.

Great, compile and run, if you’re not getting any errors/warnings from Xcode you’re done. You can go and test the SDK, (section 3), by creating a PFObject. Given our app structure, I’d recommend to paste that code in the viewDidLoad method of the first viewController. Got past point 3? Great! You have successfully uploaded data to the Cloud, and it was effortless. Now let’s build something cool with that service.

Building a chat view

We’re going to add our chat to a UITableView. Since iOS5.0, setting up these beasts has become much easier, and there are plenty of blog posts out there that explain how to set them up and skin them if need be. We’ll be using three fields for each cell: a userLabel, a timeLabel, and a textString. Let’s create a UITableViewCell class first, and get this out of the way. Go on and create a New file, use the Objective-C class from the Cocoa Touch templates, name it “chatCell” as a subclass of UITableViewCell. Bang. Like so:

chatCellCreate

Let’s do our property declarations in chatCell.h while we’re at it:

@interface ChatCell : UITableViewCell
{
    IBOutlet UILabel *userLabel;
    IBOutlet UITextView *textString;
    IBOutlet UILabel *timeLabel;
}
@property (nonatomic,retain) IBOutlet UILabel *userLabel;
@property (nonatomic,retain) IBOutlet UITextView *textString;
@property (nonatomic,retain) IBOutlet UILabel *timeLabel;

@end

and let’s synthesize our properties in chatCell.m:

@synthesize userLabel, timeLabel, textString;

Now let’s get into the Storyboard. There’s only one, open it and you’ll see our two view controllers linked to a TabBar controller. Now we’re going to create our Table, but unfortunately we cannot use a UITAbleViewController directly, as there will be more than a simple table in our view. We’re building something that will look like this:

StoryBoard

Let’s get started: we drag in a new view controller, and put a table view in it. Next, because we’re generating the table view from scratch, drag into your table a TableViewCell. Then connect the new view to the TabBarController: Control-Drag from the TabBarController to your view controller, select the “Relationship - ViewController” segue, and we’re done. Change the Bar Item to “Chat Room” and the icon (I’m using the “chat” icon from the awesome Icon Sweet here), compile and run, and you should have something like this, after getting to the Chat Room thumb:

SBRunEmpty

We’ve got one warning though: “Prototype table cells must have reuse identifiers”. Right, let’s silence this one, and give our cells an identifier. Select the cell in your Storyboard, and give it chatCellIdentifier as the identifier. Compile and run, issue gone. We’re going to do a little more while we’re on the cell: change its class to a custom class, which is, you guessed, chatCell. Go on and change the class name in the Identity Inspector. This should be like shown below, and then check in your Connection Inspector that you have access to all the goodness of the chatCell class, ie. our outlets.

chatCellAssign ChatCellConnections

Now, let’s add our two UILabels and a UITextView. Just drag and drop them inside the cell, and make the UITextView not editable, and while we’re at it, un-tick the Detection methods (Links, addresses, etc…). We’re building a table cell that looks like this:

ChatRoomViewController

Now, in your Connections Inspector, drag your textString outlet to the TextView, and your timeLabel and userLabel outlets to their respective labels. Everything is connected, we’re good. There’s one more thing left, though: what good would a chat room be if we couldn’t enter text? Let’s get out of the Storyboard for a minute, create our ViewController for the chat room, we’ll be back soon.

Chat room View Controller

Text entry

Create a new file, DMChatRoomViewController using the Objective-C class from the Cocoa Touch templates, as a subclass of UIViewController. We’re going to need a UITextField tfEntry that we create in the interface and give it an outlet as well. Next thing, we go back to our StoryBoard and add that field to our chat room view. Let’s change the class of our View Controller first, with the vc selected, go into the Identity Inspector and change the class to DMChatRoomViewController. All good, now let’s add a background to our textField (I’ve got a textField_background.png image with height of 49 pix here), and we’re dragging a text field on top. Give it a width of 280 pix. From the inspector, connect the tfEntry to the textField. You should get something that looks like the previous picture, now.

We’re going to deal with the keyboard in code, so we’re done here and are left with populating our table. If you build and run now, you’ll see your keyboard going up when typing into the textField, but there’s no way to get it down. The methods to show/hide the keyboard are pretty much classic, I’m not going to discuss them here, you can check them inside the #pragma mark - Chat textfield in the DMChatRoomViewController.m file on github. The four methods are:

-(void) registerForKeyboardNotifications;
-(void) freeKeyboardNotifications;
-(void) keyboardWasShown:(NSNotification*)aNotification;
-(void) keyboardWillHide:(NSNotification*)aNotification;

There isn’t much to it, in short we just create the keyboard notifications (willShow and willHide), and use our keyboardWasShown and keyboardWillHide in order to sync the movement of the rest of the view with the keyboard going up and down. Don’t forget to hook up your textfield in the Storyboard with the textFieldDoneEditing IBAction. The code at the moment is:

*** DMChatRoomViewController.h ***

#import <UIKit/UIKit.h>
@interface DMChatRoomViewController : UIViewController <UITextFieldDelegate>
{
    UITextField             *tfEntry;
}
@property(nonatomic, strong) IBOutlet UITextField *tfEntry;
-(void) registerForKeyboardNotifications;
-(void) freeKeyboardNotifications;
-(void) keyboardWasShown:(NSNotification*)aNotification;
-(void) keyboardWillHide:(NSNotification*)aNotification;
@end

*** DMChatRoomViewController.m ***

#import "DMChatRoomViewController.h"

#define TABBAR_HEIGHT 49.0f
#define TEXTFIELD_HEIGHT 70.0f

@interface DMChatRoomViewController ()

@end

@implementation DMChatRoomViewController
@synthesize tfEntry;

- (void)viewDidLoad
{
    [super viewDidLoad];
    tfEntry.delegate = self;
    tfEntry.clearButtonMode = UITextFieldViewModeWhileEditing; 
    [self registerForKeyboardNotifications];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    [self freeKeyboardNotifications];
}

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

#pragma mark - Chat textfield

-(IBAction) textFieldDoneEditing : (id) sender
{    
    NSLog(@"the text content%@",tfEntry.text);
    [sender resignFirstResponder];
    [tfEntry resignFirstResponder];
}

-(IBAction) backgroundTap:(id) sender
{
    [self.tfEntry resignFirstResponder];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSLog(@"the text content%@",tfEntry.text);
    [textField resignFirstResponder];
    
    if (tfEntry.text.length>0) {
    }
    return NO;
}


-(void) registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}


-(void) freeKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}


-(void) keyboardWasShown:(NSNotification*)aNotification
{
    NSLog(@"Keyboard was shown");
    NSDictionary* info = [aNotification userInfo];
    
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardFrame;
    [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
    [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
    
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];
    [self.view setFrame:CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y- keyboardFrame.size.height+TABBAR_HEIGHT, self.view.frame.size.width, self.view.frame.size.height)];

    [UIView commitAnimations];
    
}

-(void) keyboardWillHide:(NSNotification*)aNotification
{
    NSLog(@"Keyboard will hide");
    NSDictionary* info = [aNotification userInfo];
    
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardFrame;
    [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
    [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
    
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];
    [self.view setFrame:CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y + keyboardFrame.size.height-TABBAR_HEIGHT, self.view.frame.size.width, self.view.frame.size.height)];
    
    [UIView commitAnimations];
}

@end

Table source

Now that we’ve done all that leg work to setup our view, (phew!), we can finally get to the main matter: populating our Table cells, and hooking its source to the Parse database we have opened. Fortunately, this is rather simple. As a - very neat - bonus, Parse includes in its framework a version of the EGORefreshTableHeader class, which enables the pull-to-refresh functionality on the table. We’re going to use it, of course. Notice that if you simply wanted a table without the entry field, you could have created a simple UITableViewController and populated it with your data in a few lines of code: there is a very simple, 5 minutes tutorial, on Parse website.

We’re going to add three delegates to our class: UITableViewDelegate, UITableViewDataSource,PF_EGORefreshTableHeaderDelegate. In order to use the last one, we need to import the Parse framework in our class: #import goes at the top of our DMChatRoomViewController.h.

Since we created our table manually, we are going to create an outlet for our table, and a mutable array for its data:

IBOutlet UITableView    *chatTable;
NSMutableArray          *chatData;
PF_EGORefreshTableHeaderView *_refreshHeaderView;

Also create the properties:

@property (nonatomic, retain) UITableView *chatTable;
@property (nonatomic, retain) NSArray *chatData;

and synthesize them in DMChatRoomViewController.m.

Now, in the StoryBoard, Control-drag from the Chat Room View Controller to the Table View, and select the chatData outlet. Then Control-drag from the Table View, and select the Chat Room View Controller for both the dataSource and the delegate. If you build and run now, you’ll get a nice crash that tells you that the numberOfRowsInSection: method is not being implemented. In other words, the table we are trying to display does not have any methods defining how to display and populate it. Let’s just do it, then.

We’re going to need an indicator that signals whether the table is reloading, so add:

BOOL _reloading;

to your interface in DMChatRoomViewController.h. We’re also going to need a loading method for our data, call it loadLocalChat and add it after our keyboard methods in _DMChatRoomViewController.h:

- (void)loadLocalChat;

Let’s get done with the EGORefreshTableHeaderDelegate methods now. In DMChatRoomViewController.m, we add the following:

#pragma mark -
#pragma mark Data Source Loading / Reloading Methods

- (void)reloadTableViewDataSource{
    
    //  should be calling your tableviews data source model to reload
    //  put here just for demo
    _reloading = YES;
    [self loadLocalChat];
    [chatTable reloadData];
}

- (void)doneLoadingTableViewData{
    
    //  model should call this when its done loading
    _reloading = NO;
    [_refreshHeaderView egoRefreshScrollViewDataSourceDidFinishedLoading:chatTable];
    
}


#pragma mark -
#pragma mark UIScrollViewDelegate Methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 
    
    [_refreshHeaderView egoRefreshScrollViewDidScroll:scrollView];
    
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    
    [_refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView];
    
}


#pragma mark -
#pragma mark EGORefreshTableHeaderDelegate Methods

- (void)egoRefreshTableHeaderDidTriggerRefresh:(PF_EGORefreshTableHeaderView*)view{
    
    [self reloadTableViewDataSource];
    [self performSelector:@selector(doneLoadingTableViewData) withObject:nil afterDelay:3.0];
    
}

- (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(PF_EGORefreshTableHeaderView*)view{
    
    return _reloading; // should return if data source model is reloading
    
}

- (NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:(PF_EGORefreshTableHeaderView*)view{
    
    return [NSDate date]; // should return date data source was last changed
    
}

We’re going to deal with the table delegate now. Add this code to DMChatRoomViewController.m (you’ll need to #import “chatCell.h” as well):

#pragma mark - Table view delegate

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    chatCell *cell = (chatCell *)[tableView dequeueReusableCellWithIdentifier: @"chatCellIdentifier"];
    NSUInteger row = [chatData count]-[indexPath row]-1;
    
    if (row < chatData.count){
        NSString *chatText = [[chatData objectAtIndex:row] objectForKey:@"text"];
        cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
        UIFont *font = [UIFont systemFontOfSize:14];
        CGSize size = [chatText sizeWithFont:font constrainedToSize:CGSizeMake(225.0f, 1000.0f) lineBreakMode:UILineBreakModeCharacterWrap];
        cell.textString.frame = CGRectMake(75, 14, size.width +20, size.height + 20);
        cell.textString.font = [UIFont fontWithName:@"Helvetica" size:14.0];
        cell.textString.text = chatText;
        [cell.textString sizeToFit];
        
        NSDate *theDate = [[chatData objectAtIndex:row] objectForKey:@"date"];
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"HH:mm a"];
        NSString *timeString = [formatter stringFromDate:theDate];
        cell.timeLabel.text = timeString;
        
        cell.userLabel.text = [[chatData objectAtIndex:row] objectForKey:@"userName"];
    }    
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellText = [[chatData objectAtIndex:chatData.count-indexPath.row-1] objectForKey:@"text"];
    UIFont *cellFont = [UIFont fontWithName:@"Helvetica" size:14.0];
    CGSize constraintSize = CGSizeMake(225.0f, MAXFLOAT);
    CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
    
    return labelSize.height + 40;
}

Now if you compile and run, you should be without error. It’s just a matter of connecting to Parse now, and loading our data… We check first that our reload method is working when pull-to-refresh is activated. Just add the following lines to the end of the viewDidLoad method:

    if (_refreshHeaderView == nil) {
        
        PF_EGORefreshTableHeaderView *view = [[PF_EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - chatTable.bounds.size.height, self.view.frame.size.width, chatTable.bounds.size.height)];
        view.delegate = self;
        [chatTable addSubview:view];
        _refreshHeaderView = view;      
    }
    //  update the last update date
    [_refreshHeaderView refreshLastUpdatedDate];

If we build and run, we’ll see that the well-known pull-to-refresh messages comes up, before a crash: we have not entered the loadChatCata method yet. Let’s do that, add these lines to your DMChatRoomViewController.m.

#pragma mark - Parse

- (void)loadLocalChat
{
    PFQuery *query = [PFQuery queryWithClassName:className];
    
    
    // If no objects are loaded in memory, we look to the cache first to fill the table
    // and then subsequently do a query against the network.
    if ([chatData count] == 0) {
        query.cachePolicy = kPFCachePolicyCacheThenNetwork;
        [query orderByAscending:@"createdAt"];
        NSLog(@"Trying to retrieve from cache");
        [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
            if (!error) {
                // The find succeeded.
                NSLog(@"Successfully retrieved %d chats from cache.", objects.count);
                [chatData removeAllObjects];
                [chatData addObjectsFromArray:objects];
                [chatTable reloadData];
            } else {
                // Log details of the failure
                NSLog(@"Error: %@ %@", error, [error userInfo]);
            }
        }];
    }
    __block int totalNumberOfEntries = 0;
    [query orderByAscending:@"createdAt"];
    [query countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
        if (!error) {
            // The count request succeeded. Log the count
            NSLog(@"There are currently %d entries", number);
            totalNumberOfEntries = number;
            if (totalNumberOfEntries > [chatData count]) {
                NSLog(@"Retrieving data");
                int theLimit;
                if (totalNumberOfEntries-[chatData count]>MAX_ENTRIES_LOADED) {
                    theLimit = MAX_ENTRIES_LOADED;
                }
                else {
                    theLimit = totalNumberOfEntries-[chatData count];
                }
                query.limit = [NSNumber numberWithInt:theLimit];
                [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
                    if (!error) {
                        // The find succeeded.
                        NSLog(@"Successfully retrieved %d chats.", objects.count);
                        [chatData addObjectsFromArray:objects];
                        NSMutableArray *insertIndexPaths = [[NSMutableArray alloc] init];
                        for (int ind = 0; ind < objects.count; ind++) {
                            NSIndexPath *newPath = [NSIndexPath indexPathForRow:ind inSection:0];
                            [insertIndexPaths addObject:newPath];
                        } 
                        [chatTable beginUpdates];
                        [chatTable insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationTop];
                        [chatTable endUpdates];
                        [chatTable reloadData];
                        [chatTable scrollsToTop];
                    } else {
                        // Log details of the failure
                        NSLog(@"Error: %@ %@", error, [error userInfo]);
                    }
                }];
            }

        } else {
            // The request failed, we'll keep the chatData count?
            number = [chatData count];
        }
    }];
}

We’ve got two errors that pop up: a missing parameter, for which you can add #define MAX_ENTRIES_LOADED 25 to the top, and the className. This is the name of the class you are using on Parse, that references your database. It’s just a NSString we’re going to declare in our .h file interface, as:

NSString                *className;

and initialize in our viewWillAppear as:

className = @"chatroom";

While we’re at it, add a userName variable to your .h:

NSString                *userName;

and initialize it in viewWillAppear:

userName = @"John Appleseed";

Now we should be able to compile without errors. Let’s look at our LoadLocalChat method now. It all begins with a PFQuery, which is our query to the Parse server. Here we’ll query only one class, that is _@”chatRoom”, but we could have more than one in our app, of course. And structured differently as well. There are two ways to query Parse: synchronously or asynchronously. The former should be used only for testing purposes. Querying your database synchronously means it is performed on your main thread, and therefore hangs your interface. Not good at all. In addition, we want our user to be presented with some content when the database loads, and therefore the first thing we do is loading the cache. The Parse framework handles the caching for us, which is simply great.

We then proceed to loading our data if necessary, ie. if there are new entries. Parse lets us use blocks, and we take full advantage of that feature while using the method [query countObjectsInBackgroundWithBlock:^(int number, NSError *error) {…}. If we run the app now, we should be getting a console resembling that:

 chatDemo[94443:f803] Trying to retrieve from cache
    chatDemo[94443:f803] Error: result not cached (Code: 120, Version: 0.4.40)
    chatDemo[94443:f803] Error: Error Domain=Parse Code=120 "The operation couldn’t be completed. (Parse error 120.)" UserInfo=0x68d4e10 {code=120, error=result not cached} {
    code = 120;
    error = "result not cached";
}
    chatDemo[94443:f803] There are currently 0 entries
    chatDemo[94443:f803] Successfully retrieved 0 chats from cache.

In other words, we successfully connected to Parse, and there is nothing to show, not any cached data. Let’s assign the chatDat first in our viewWillAppear:

chatData  = [[NSMutableArray alloc] init];
[self loadLocalChat];

And now we’re just going to modify our textFieldShouldReturn method, and we’re done. Change it to the following:

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSLog(@"the text content%@",tfEntry.text);
    [textField resignFirstResponder];
    
    if (tfEntry.text.length>0) {
        // updating the table immediately
        NSArray *keys = [NSArray arrayWithObjects:@"text", @"userName", @"date", nil];    
        NSArray *objects = [NSArray arrayWithObjects:tfEntry.text, userName, [NSDate date], nil];
        NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
        [chatData addObject:dictionary];
        
        NSMutableArray *insertIndexPaths = [[NSMutableArray alloc] init];
        NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:0];
        [insertIndexPaths addObject:newPath];
        [chatTable beginUpdates];
        [chatTable insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationTop];
        [chatTable endUpdates];
        [chatTable reloadData];

        // going for the parsing
        PFObject *newMessage = [PFObject objectWithClassName:@"chatroom"];
        [newMessage setObject:tfEntry.text forKey:@"text"];
        [newMessage setObject:userName forKey:@"userName"];
        [newMessage setObject:[NSDate date] forKey:@"date"];
        [newMessage saveInBackground];
        tfEntry.text = @"";
    }

    // reload the data
    [self loadLocalChat];
    return NO;
}

Compile and run, and you should be able to send a message to your chatroom, say “Hello World!”, like so:

HelloWorld HelloWorldResult

Something really interesting happened. Remember I told you earlier about Parse’s ability to Lazy parse your data? We did not declare anything on the server side, yet, if we now compare our “chatRoom” class in the web browser before and after we messaged, we get the following picture:

*** BEFORE ***

chatRoomParse_Before

*** AFTER ***

chatRoomParse_After

This means quite simply that Parse has created the database fields upon our parsing of the message object. This is done through the creation of the PFObject for which we set three keys: userName, text and date. When we send the command saveInBackground, the framework sends the object to the server, where the fields are created as they did not exist. One pretty cool feature is that you can add fields on-the-go as well, ie. if you decide to add say a gender key, you’ll just need to issue a new key to your object, and it will add it to all the previous and future entries. Neat, eh?

Conclusion

So, there you have it, your own private chat room. That was a long one, and I hope that you could follow. It’s honestly not that complicated once you know what you’re doing, just takes a bit of time to get into it. The full project is available on github, and there’s a bit of a bonus in there:

  • to be good Apple/iPhone citizens, I’ve added the check that the connection is alive (check on the network status, quite standard if you want to submit to the AppStore);

  • as it’s become really easy to do in iOS5, I’ve also added a pop-up alert to set the userName. John Appleseed has enough work on his plate, let’s not bother him with infinite chat testing as well.

Hope it was useful, and don’t hesitate to fork the project on Github and submit your corrections/comments etc. I’m looking forward to hearing from you, and can be reached on twitter @davmendels.

Bash script to convert eps files to png format

Just a quickie post, as I’ve been looking for that trick a little while, before solving it myself. Here, I’m programming an app for toddlers, and the material is extracted from a book (more about that at a later date). So, the book’s publisher gave us the source files, which were, of course, encapsulated postscripts, in the range of 10-20Mo (Man, that fills a dropbox quickly). That’s completely unusable, and I needed them in png.

Now, there are a lot of “Tools” available on the web to do that kind of conversion, and if you’re lucky enough to own a Mac, the Preview app does convert from eps to png in a breeze. Just open the file, save as PNG and boom, you’re done. Unfortunately, I’ve got 56 files and I’m too lazy to run them one by one, particularly as there is a scaling operation involved after. In comes ImageMagick, which is a pretty handy tool. If you haven’t done so, install the thing and come back here in a minute. Next thing, open the terminal, and the bash code is:

> cd myepsimagefolder
> for i in $(find . -maxdepth 1 type f -iname "*.eps";
> do
> convert ${i}/$(basename ${i} .eps).png;
> done

OK, that’s it, the entire *.eps content of your myepsimagefolder has been converted to png (a copy has been created, to be exact). Needless to say, it would work for pretty much any input or output format (just substitute the extensions for what you want in the second and fourth lines above).

Next up, rescale. Rather than going through a similar ImageMagick process, we can make use of OS X’s excellent Automator. Unfortunately, there aren’t any options that let you use automator to convert from eps, but after that any image manipulation is pretty easy. Here’s a view of the Automator script we run:

That’s all, save and hit “run” and all your png images have been scaled to 256x256pix, maintaining resolution, and placed in a subfolder (pngscaled in my case). Pretty cool, uh?

Enjoy yourselves!

Microsoft vs. Star Trek /via @wulfettenoire

Picard: “Mr. LaForge, have you had any success with your attempts at finding a weakness in the Borg? And Mr. Data, have you been able to access their command pathways?”

Geordi: “Yes, Captain. In fact, we found the answer by searching through our archives on late Twentieth-century computing technology.”

Geordi presses a key, and a logo appears on the computer screen.

Riker: (looks puzzled). “What the hell is ‘Microsoft’?”

Data: (turns to answer). “Allow me to explain. We will send this program, for some reason called ‘Windows’, through the Borg command pathways. Once inside their root command unit, it will begin consuming system resources at an unstoppable rate.”

Picard: “But the Borg have the ability to adapt. Won’t they alter their processing systems to increase their storage capacity?”

Data: “Yes, Captain. But when ‘Windows’ detects this, it creates a new version of itself known as an ‘upgrade’. The use of resources increases exponentially with each iteration. The Borg will not be able to adapt quickly enough. Eventually all of their processing ability will be taken over and none will be available for their normal operational functions.”

Picard: “Excellent work. This is even better than that ‘unsolvable geometric shape’ idea.”

… 15 Minutes Later …

Data: “Captain, We have successfully installed the ‘Windows’ in the command unit and as expected it immediately consumed 85% of all resources. We however have not received any confirmation of the expected ‘upgrade’.”

Geordi: “Our scanners have picked up an increase in Borg storage and CPU capacity to compensate, but we still have no indication of an ‘upgrade’ to compensate for their increase.”

Picard: “Data, scan the history banks again and determine if their is something we have missed.”

Data: “Sir, I believe their is a reason for the failure in the ‘upgrade’. Apparently the Borg have circumvented that part of the plan by not sending in their registration cards.

Riker: “Captain we have no choice. Requesting permission to begin emergency escape sequence 3F …”

Geordi: (excited) “Wait, Captain I just detected their CPU capacity has suddenly dropped to 0% !”

Picard: “Data, what does your scanners show?”

Data: “Apparently the Borg have found the internal ‘Windows’ module named ‘Solitaire’ and it has used up all the CPU capacity.”

Picard: “Lets wait and see how long this ‘solitaire’ can reduce their functionality.”

… Two Hours Pass …

Riker: “Geordi what’s the status on the Borg?”

Geordi: “As expected the Borg are attempting to re-engineer to compensate for increased CPU and storage demands, but each time they successfully increase resources I have setup our closest deep space monitor beacon to transmit more ‘windows’ modules from something called the ‘Microsoft fun-pack’.

Picard: “How much time will that buy us ?”

Data: “Current Borg solution rates allow me to predicate an interest time span of 6 more hours.”

Geordi: “Captain, another vessel has entered our sector.”

Picard: “Identify.”

Data: “It appears to have markings very similar to the ‘Microsoft’ logo”

Over the speakers…
“THIS IS ADMIRAL BILL GATES OF THE MICROSOFT FLAGSHIP MONOPOLY. WE HAVE POSITIVE CONFIRMATION OF UNREGISTERED SOFTWARE IN THIS SECTOR. SURRENDER ALL ASSETS AND WE CAN AVOID ANY TROUBLE. YOU HAVE 10 SECONDS”

Data: “The alien ship has just opened its forward hatches and released thousands of humanoid shaped objects.”

Picard: “Magnify forward viewer on the alien craft”

Riker: “Good God captain! Those are humans floating straight toward the Borg ship with no life support suits! How can they survive the tortures of deep space?!”

Data: “I don’t believe that those are humans sir, if you will look closer I believe you will see that they are carrying something recognized by twenty-first century man as doe skin leather briefcases, and wearing Armani suits” Riker and Picard together horrified: “Lawyers !!”

Geordi: “It can’t be. All the Lawyers were rounded up and sent hurtling into the sun in 2017 during the Great Awakening.”

Data: “True, but apparently some must have survived.”

Riker: “They have surrounded the Borg ship and are covering it with all types of papers.”

Data: “I believe that is known in ancient vernacular as ‘red tape’ it often proves fatal.”

Riker: “They’re tearing the Borg to pieces !”

Picard: “Turn off the monitors. I can’t stand to watch, not even the Borg deserve that.”

Keeping it straight - Patrick Rhone

I’m definitely more used to peer-reviewing scientific articles than books or novels, and wouldn’t know where to start, for once. Yet, I really wanted to do more for this one than a simple Tweet, so here we go. In no particular order.

I’ve had the pleasure and honor to read Patrick’s first book over the last week-end —More details and availability at MinimalMac and Lulu. And I had a good time. Obviously. I’ve been following Patrick’s work since he started MinimalMac, the successful blog that lets you in on the most unguarded secrets of your Mac and OS X: they’re a beautiful machine and you can do more or less anything with the tools installed right out of the box. While he’s become a minimalism guru for many, he’s been wise enough to not reduce himself to just that role, and has kept a steady production in his personal blog. This book is a curated collection of his personal blog and MinimalMac, with some edited content. Having read most of Patrick’s writings, I have to have an opinion. For instance, I was really happy to see some of my favorite pieces in the book, and felt that some others should have been included. That they were not simply reflects that although I can connect really well with Patrick, we are simply two different human beings… more on that later.

Let me put it in a simple way: you owe it to yourself to read it.

It will take you a couple of hours, and will make you feel better about yourself. And about others. Why? Patrick’s level of introspection is likely to get you into places where you haven’t been consciously, and it is a good thing to get there from time to time. It makes you realize what you have, what you want, what you really have and what you really want. The book is organized in 3 parts: You, Me and Everything Else. To be honest, I really loved the first two, and was less passionate about the third. That’s probably due to the fact that I’m not a big fan of the GTD method although I apply it daily, and do not wish to read about it that much. But the first two parts are worth every penny, as they take you through what it is to being human, and being at peace with yourself. Read and re-read Doing and Being, Value, Time is Precious, Thrive in the Present, Don’t Worry, and it will make you feel better, or it will make you a better person. There’s one deep piece I also really enjoyed: Fourty Years. Strangely enough, it was not followed by one of my favorites from Patrick’s work (The Story of My Name, I think it was called), which together with Rodney’s story was one of the most moving reads I had these past two years. I happened to read those when I was on a business trip to a conference in Florida, and flew through Atlanta, where I saw MLK’s memorial. And it really stuck to me. That’s how you know you read something great: you remember where, when, and how and why it stuck to you.

Ayway, before being mellow, here are a few quotes I love:

  • “I think the more we fill our lives with more and more things we have to do, the less and less time we are spending on who we have to be.”
  • “The past serves us only in having taught us the lessons needed to thrive in the present and strive towards the future”
  • “we are all in complete control of our own suffering. Therefore, we are in complete control of ending it”
  • “And, if you must give up everything in order to see a cause through, never give up this… Hope.”
  • ” The Three Most Important Productivity Tools The trash can, the delete key, and the word No”

One more thing: I’m 40 years old, Patrick is apparently 41 at the time of publication, and it may be why this book falls so well on me. If you’re turing 40 soon, you will realize that this is a time for introspection. And questions. Many, too many questions. In our circle of Friends, it’s funny to see that a non-negligible portion have changed their attitude towards life, or their life altogether, to do what they really wanted, or try something new. I believe that this is the time of your life when you feel the need to look out for yourself, your real self. Patrick did it, and was kind enough to deliver his results to us. That’s his way of being, and I truly thank him for that. More than everything we say, anything we do, or the way we behave, our writing reveals who we truly are - and where we stand at the time of writing. It takes a lot of courage, abnegation, and effort to produce this continuous an effort. And there’s one big takeaway: hope. I’m the kind of guy who’s always got the memoirs of Marcus Aurelius in his backpack, trying to muster again and again what all of this - life - is about. The pocket book may change, the questions do not. Patrick’s is another take on this, but it’s much closer to me, being a mid-life introspection, than Marcus Aurelius’, who -allegedly- produced that at the end of his life.

(One other thing that comes to mind while I look at the book cover again: it’s beautiful, and I think Patrick and his designer should consult for Merlin Mann, who seems to have problems with putting a cover on his upcoming book).

So, again, Patrick, many thanks for letting us readers go through it. I wish this book (and Patrick) all the best, it’s become rare in this world of pulp to read things well written. So, go buy it!

And here’s what comes next:
- Jameson
- 12 years of age
- two fingers
It’s never been so well deserved.

On Accessibility in iOs Development, Implementation in TipToe

It’s a wide world

Let’s start with the obvious: it’s a World Wide Web, and we tend to forget about it while being entrenched in one way or another in our lives and work activities. It sounds like I’m going to be apologetic, which is not frequent, and I am. I’ve recently developed and published through the AppStore an app called TipToe, see it here and follow the link to the store — $1, cheap, indispensable (it sure is ;-). So, that’s a cute app that lets you search Bing and Blekko, and aggregates the search results in a nice table. And I’ve put in a minimal in-app browser (minimal, yet complete) which is lean and fast.
The app has been featured on [MinimalMac](http://minimalmac.com], we proudly sponsored the RSS feed the week of the release of TipToe (entry on the 4th of April), and it was picked recently by LifeHacker. Life couldn’t be better (well, actually, we could always sell more, but that’s another story). Couldn’t? Sure it could, and should.

The glitch

A few hours after we got featured on LifeHacker, I received a moving email from a user who purchased the app, regarding the lack of accessibility of TipToe. This user is visually impaired, and complained that VoiceOver in TipToe was inexistent… Well, actually, being a really sweet person, he sent an email noticing that some of the buttons were not customized for VoiceOver access. Indeed, that’s one of those emails that hit you pretty strong in the face, and for two reasons. One, I remember reading a very good blog post by someone visually impaired explaining how the iPhone gave him back a sense of sight by letting him know which colors were in front of him. This post moved me (note to self: gotta find it in my archives). And two, there was an excellent post from Matt Gemmel here that describes how easy and important it is to make your app accessible. So, shame on me for forgetting about it, I read that piece, enjoyed it and bookmarked it for using it later. Only problem is I skipped using it. Now, pretty much like anything I do, I just considered that shortcoming as an engineering problem, ie. a mistake that should be fixed. Off we go, let’s add Accessibility to TipToe. Matt having indicated that it’s easy, I shouldn’t have many problems to implement that. Quite wrong, but here’s what I did in the end.

The elements to be made accessible

Accessibility_Post

I’ve made a quick capture of some screens from TipToe, and here we’re going to address two kinds of tips: 1- making the table cells more readable, and 2- accessing some elements that do not have their Accessibility property reachable from Interface Builder (IB). The latter is probably the most interesting case. In TipToe, I had to trade the Navigation bar at the top of the view for a standard toolbar. Basically, I wanted to define an empty button in the center of the toolbar so that I can make the web address bar disappear on touch. That’s a neat trick to maximize the screen earl estate, and it saves me from a scroll view, which I don’t like when browsing (always need to scroll back up to get the actions, and I wanted the actions button to be always visible, as they open the share dialog). There’s certainly other ways to achieve that, but it was the easiest and quickest to implement. You’ll notice as well that I haven’t circled all the elements on the left hand side of the picture. Basically, the UITextField for search entry and the information button at the bottom right are already addressable in IB, so no need to do a fuss. This case is well documented in both Apple’s Guidelines and Matt Gemmel’s post.

The table

There’s a good hint in Apple’s Accessibility Guidelines, which I shamelessly use here. It consists in concatenating some fields displayed in your cell in order to make it more easy for the robot to read.
Here is the snippet for the table:

1
2
3
4
5
6
7
8
UILabel * titleLabel = (UILabel *) [cell.contentView viewWithTag:TitleTag];
UILabel * urlLabel = (UILabel *) [cell.contentView viewWithTag:UrlTag];
UILabel * descriptionLabel = (UILabel *) [cell.contentView viewWithTag:DescriptionTag];
titleLabel.text = [displayTitle objectAtIndex:indexPath.row];
urlLabel.text = [displayURL objectAtIndex:indexPath.row];
descriptionLabel.text = [displayDescription objectAtIndex:indexPath.row];
cell.accessoryType =  UITableViewCellAccessoryDisclosureIndicator;
cell.accessibilityLabel = [NSString stringWithFormat:@"%@, %@", titleLabel.text, descriptionLabel.text];

So, quite simply, I’ve got a custom cell which displays the title (re-formatted to make it homogeneous in my app, the url (ditto) and the abstracted content (that’s the description label). This is done in a most classic way using three UILabel (Lines 1-3), for which I’m assigning the .text property after, while extracting the search results from the main table of the class (Lines 4-6). Now, the catch is that VoiceOver reads everything in a cell when it is tapped. And clearly, you do not want the url to be read, it will literally read “h t t p column slash slash etc…”. Not really pleasant. Instead, we’re just merging the titleLabel.text and descriptionLabel.text, and assigning the result to the cell.accessibilityLabel (Line 8). This is now the text that will be read when the cell is tapped. That was pretty easy, let’s get to the hairy section now.

Bar buttons

I’m getting away in the Display view with only changing the viewWillAppear method, such that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(void)viewWillAppear:(BOOL)animated
{
    UIToolbar *topbar =(UIToolbar*)[self.view viewWithTag:200];
    [topbar setAccessibilityLabel:@"Top bar"];
    [topbar setAccessibilityHint:@"Navigate search results"];
// 
    UIView *topbarbutton1 = (UIView*)[topbar.items objectAtIndex:1];
    [topbarbutton1 setAccessibilityLabel:@"List"];
    [topbarbutton1 setAccessibilityHint:@"Goes back to search results"];
    UIView *topbarbutton2 = (UIView*)[topbar.items objectAtIndex:2];
    [topbarbutton2 setAccessibilityLabel:@"Share"];
    [topbarbutton2 setAccessibilityHint:@"Opens share options"];
//
    UISegmentedControl *segmentControl = (UISegmentedControl*)[self.view viewWithTag:202];
    NSUInteger buttonCount =  0;
    for (UIView* thisView in [segmentControl subviews])
    {
        switch (buttonCount) {
            case  0:
                thisView.accessibilityLabel = NSLocalizedString(@"Down",nil);
                thisView.accessibilityHint = NSLocalizedString(@"Goes down one entry in results list", nil);
                break;
            case 1:
                thisView.accessibilityLabel = NSLocalizedString(@"Up",nil);
                thisView.accessibilityHint = NSLocalizedString(@"Goes up one entry in results list", nil);
                break;
        }
        buttonCount++;
    }
}

Now, there’s plenty to discuss here. The main point is that all those elements do not have their accessibility property accessible from Interface Builder, so we’ve got to be a bit smart. We’re going to use the same trick for all the elements, which consists in accessing the accessibility properties of the view that contains the element.

First up: the top bar. That’s a UIToolbar, I’m accessing it through a tag. Nothing much to say about this… except that we need that UIToolbar to be defined for the next step.

Second: let’s get into the bar buttons. Once you know how to do it, it’s evident. We’re just picking up the UIView that contains the button (Lines 7 & 10), and doing so from the UIToolbar list of items. These items are numbered in the order they are displayed in the IB, starting with 0. In my view I’ve got a space before the first button so my button1 is at index 1 (Line 7). The all shebang is then over, as I can set the accessibility label and hint of my newly defined UIView (Lines 8-9 and 11-12). Simple enough! Unfortunately the UIBarButtonItem accessibility cannot be accessed directly from IB, but this does the trick in a few lines.

Third: well we’re got this damned UISegmentedControl. Let’s face it, life would have been much easier if I had kept the first version of the app (alpha), which had two separate buttons. But no, I had to get it with the look and feel of Apple’s Mail and Safari, which use this UISegmentedControl. Now, the same trick is used, with a twist. We start by picking up the UISegmentedControl (Line 14), and then we’ve got to navigate through its subviews (Lines 15-29). We’re doing so using a for loop (Lines 16-29) over the [segmentControl subviews], and together with a simple switch statement (Line 18) we get our subview accessible. In our case, we’ve just got two segments, but this could be used for more. That’s all there is to it…

Summary

That was a quick rundown, and hopefully this post will be obsolete in a future release of iOS. Quite unfortunately, the doc on this subject is very light, so I hope it may help someone. The take-home message is simple:
- if the element has its accessibility property available in IB, just use that;
- if it does not, I’ve either covered the case here, or you’ll have to dig through your elements inheritance and find the view for which you can set-up the accessibility.

Questions and comments are always welcome, though I’m afraid I don’t have much time to check your code these days… So, please don’t hold it against me if I can’t respond fast enough.

HTML to Markdown conversion

Just a quickie, Brett Terpstra (@ttscoff), the author of the sensational fork of Notational Velocity NVAlt has been active these past two days, and has put together an excellent (or, let’s say, Awesome, really it is) HTML to Markdown converter. This is still in the experimental stage, but will certainly delight all Edito users.

The tool is located here: FuckYeahMarkdown.com or at the more “politically” correct HeckYesMarkdown.com. I’m really longing to see where this goes, the first shots are really worth a look.

merlin:

What the FUCK, Apple?

What the fuck is wrong with this fucking cocksucker piece-of-shit computer?

I ask you:

What. The. FUCK. Apple.

Jesus fuck, you fuckstained fanboy retards—what the fuck is your DAMAGE?

Seriously. How can you do this to a fella?

I innocently go out and invest my hard-earned fake internet money in the cheapest Mac Pro available—in 2006; I leave it hobbled and way under-powered with its original couple gigs of RAM; I constantly cram its four (4) internal and three (3) external (commodity-class/69-dollar/5200RPM) drives to within a precious few gigs of capacity; I never bother to do a friendly fsck -fy (or run a precautionary Onyx.app session) unless something’s totally blown up; I have one (1) FireWire 800 device, two (2) FireWire 400 devices, and fourteen (14) USB 2 devices attached or mounted; I’m running two fucktastically giant Dell monitors; I’m running programs, prefpanes, kexts, StartupItems and who knows what other shit cobbled together in various mixes of Cocoa, Carbon, Classic, Java, Python, Perl, PHP, and more; my bash profile looks like a capuchin monkey vomited some colons and equal signs into a 10-year-old Guatemalan boy’s ESL dictionary; I’ve added a metric shit-ton of homemade “scripts” to my startup folder, my launchd agents, and only Jesus, Daddy, or the Spook knows where else; I have Hazel, Launchbar, Automator, MobileMe, BusyCal, Dropbox, Time Machine, and who knows what else performing an impossible, ongoing, and fiendishly-automated Stravinsky concert in the background ALL THE TIME…

Plus, I have only thirty applications open.

Is that literally all it takes for me to notice my computer slowing down just a little, tiny bit? Really? That is IT? That?

Again.

What. The. FUCK. Apple.

I’m done with this shit.

Here I come, Ubuntu 11.01 (Naughty Nurse). You’ll be impossible to break.

Right?

merlin:

What the FUCK, Apple?

What the fuck is wrong with this fucking cocksucker piece-of-shit computer?

I ask you:

What. The. FUCK. Apple.

Jesus fuck, you fuckstained fanboy retards—what the fuck is your DAMAGE?

Seriously. How can you do this to a fella?

I innocently go out and invest my hard-earned fake internet money in the cheapest Mac Pro available—in 2006; I leave it hobbled and way under-powered with its original couple gigs of RAM; I constantly cram its four (4) internal and three (3) external (commodity-class/69-dollar/5200RPM) drives to within a precious few gigs of capacity; I never bother to do a friendly fsck -fy (or run a precautionary Onyx.app session) unless something’s totally blown up; I have one (1) FireWire 800 device, two (2) FireWire 400 devices, and fourteen (14) USB 2 devices attached or mounted; I’m running two fucktastically giant Dell monitors; I’m running programs, prefpanes, kexts, StartupItems and who knows what other shit cobbled together in various mixes of Cocoa, Carbon, Classic, Java, Python, Perl, PHP, and more; my bash profile looks like a capuchin monkey vomited some colons and equal signs into a 10-year-old Guatemalan boy’s ESL dictionary; I’ve added a metric shit-ton of homemade “scripts” to my startup folder, my launchd agents, and only Jesus, Daddy, or the Spook knows where else; I have Hazel, Launchbar, Automator, MobileMe, BusyCal, Dropbox, Time Machine, and who knows what else performing an impossible, ongoing, and fiendishly-automated Stravinsky concert in the background ALL THE TIME…

Plus, I have only thirty applications open.

Is that literally all it takes for me to notice my computer slowing down just a little, tiny bit? Really? That is IT? That?

Again.

What. The. FUCK. Apple.

I’m done with this shit.

Here I come, Ubuntu 11.01 (Naughty Nurse). You’ll be impossible to break.

Right?