Daily Archives: January 24, 2012

UIStoryboard Power Drill [Jason Lust] (iDevBlogADay)


I’ve read some good reasons why Storyboards are not ready for prime time. Some of the articles like Jonathan at toxic software.com simply help me find out that I wasn’t doing something wrong, it just wasn’t meant for that. But there are some benefits from using a storyboard that I wanted to keep, so I got adamant about finding workarounds. Here’s one:

Drill down UITableViews – Standard

1. Provided you’ve started with a subclassed UITableViewController, or a TableView in a UIViewController that is already the root relationship from a UINavigationController.

2. You link the push outlet from the cell prototype back to the table view controller.
This will automatically provide the control to push-in another of the same ViewController when a cell is clicked.

3. Give the loopback segue an identifier string in the attributes inspector.

4. Instead of implementing tableView:didSelectRowAtIndexPath: in the subclassed viewcontroller you will use
prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

This method will be passed the segue object that is the “tableLoopBack” the sender object for this segue type will be the UITableViewCell.

5. Prepare what data to send to the next instance the the table.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
    NSArray* myObjectArray;
    UITableView* myTable;
    
    if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
        SubClassedTable* nextSct = segue.destinationViewController;
        UITableViewCell* myCell = (UITableViewCell*)sender;
        NSIndexPath* idx = [myTable indexPathForCell:myCell];
        nextSct.myObject = [self.myObjectArray objectAtIndex:idx.row];
    }
}

6. In the TableDataSourceDelegate have it decide how to propagate your tables data array based now on the defined and set value of “myObject”. This is very different for everyones data types.

An example, using Core Data and have say AMusicLib > Artists > Albums > Songs > Info

The myObject would ideally be of type NSManagedObject* so that you could be any of the above. When the dataDelegates are called like tableview:numberOfRowsInSection: you would want to implement something like the following to handle the many layers.

- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([self.myObject isKindOfClass:[Music class]]) {
        Music* myMusicLib = self.myObject;
        return [myMusicLib.artists count];
    } else if ([self.myObject isKindOfClass:[Artist class]]) {
        Artist* myArtist = self.myObject;
        return [myArtist.albums count];
    } else if ([self.myObject isKindOfClass:[Album class]]) {
        Album* myAlbum = self.myObject;
        return [myAlbum.songs count];
    }
}

Now in the implementation of the tableview:cellForRowAtIndexPath: use a similar kindOf conditional so that the cells properties can feed from the difference of the objects at each layer.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @”musicCell”;
    UITableViewCell* newCell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if ([self.myObject isKindOfClass:[Music class]]) {
        Music* myMusicLib = self.myObject;
        Artist* aArtist = [[myMusicLib.artists allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aArtist.name;
    } else if ([self.myObject isKindOfClass:[Artist class]]) {
        Artist* myArtist = self.myObject;
        Album* aAlbum = [[myArtist.albums allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aAlbum.title;
        newCell.imageView.image = [UIImage imageNamed:aAlbum.art];
    } else if ([self.myObject isKindOfClass:[Album class]]) {
        Album* myAlbum = self.myObject;
        Song* aSong = [[myAlbum.songs allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aSong.name;
        NSDate* aTime = [NSDate dateWithTimeIntervalSince1970:[aSong.length intValue]];
        NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [dateFormatter setDateFormat:@"m:ss"];
        newCell.detailTextLabel.text = [dateFormatter stringFromDate:aTime];
    }
    return newCell;
}

And finally because earlier we used a property self.myObjectArray, this property could have been a consistent type simple array, but because I walked you through handing down different managedObjects this myObjectArray getter would also need to return an NSArray from the different managedObject’s intended NSSet. Same as just above, just return [myArtist.albums allObjects] or [myAlbum.songs allSongs] and so on for the prepareForSegue can hand off the right object to the next controller.

Drill Down TableViews – Options

The above is simple but can be limiting. The segue always pushes down to another of the same table view. Now you want options, lets go this why, or that.

1. Don’t use the push outlet on the cell prototype. This is whats limiting the control of where we go.

2. Do Implement the tableView:didSelectRowAtIndexPath: this is where we will decide what segue we take, Or none.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (!sec) {
        Section* aSect = [[self.sectionFetchedController fetchedObjects] objectAtIndex:indexPath.row];
        [self performSegueWithIdentifier:@"tableLoopBack" sender:aSect];
    } else {
        Clip* aClip = [self.clipFetchedController objectAtIndexPath:indexPath];
        [[VideoPlayerController Player] setMoviePlayerForClip:aClip];
    }
}

What we are effectively doing here is getting the option to loop into another table, or stay and do something else like load a video in a modal MPMoviePlayerController. Or even call another segue that goes to detail view.

3. Continue to make use of the prepareForSegue method

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
        VideoListController* vlc = segue.destinationViewController;
        Section* mySec = (Section*)sender;
        vlc.title = mySec.title;
        vlc.sec = mySec;
    }
}

This is so data is still passed to the new instance of this controller for drill down purposes, even if not all cells drill.

4. And now the hack, if in step 1 we do not link the cell to push to the this viewController, we still need to define a segue and name it “tableLoopBack”.
Add a UIBarButtonItem to the viewController and set the push outlet on it to loopback, identify that new segue connection as “tableLoopBack” and you have access to it when calling performSegue.

5. Add another UIBarButtonItem, and link it to another ViewController like one that layout details level. Identify that segue as another name like “detailSegue” and performSegue from didSelectRowAtIndexPath: where you can pass down the object full of details right into the sender, which will also be handed through the prepareForSegue, so make sure you hand it down again to the desired property of the new destinationViewController.

Helpful? Vote me up: Stackoverflow.com

SOURCE LINK from iDevBlogADay

Unit testing for blocks based APIs [Marin Todorov] (iDevBlogADay)

Unit tests are very useful for those large projects where you might loose sight of everything that’s going on and while you are adding new code to add feature X you might be silently breaking feature Y without even suspecting. It’s super easy these days to add unit tests to your Xcode project – when you create new Xcode project just check the checkbox in the save dialogue and voila everything is setup automatically for you:

This will add unit testing to your project and when you click Product->Test from Xcode’s menu your unit test will be run. Cool and smooth.

Let’s have a look at the default unit test, which is created for you- you get a folder called “[Project name]Tests” and inside there’s one test pre-set for you. Open the .m file and find the only test inside:

- (void)testExample
{
    STFail(@"Unit tests are not implemented yet in utb_testTests");
}

It’s designed so that it’ll fail by default. Eventually if you run the tests (Cmd+U) you’ll see the tests fail (though my experience shows even this simple example test will succeed for no reason many times):

So in this article I’m not going to explain how unit tests work and so on, but I’d rather cover how to overcome some obstacles if you are writing unit tests for block based APIs.

Unlike your iPhone apps the unit test suite is being started, it runs the code of all tests and then when there’s nothing more to run it just exists. If it didn’t spit any exception, the test was successful. End of story.

So let’s see how that pairs up with using blocks. Let’s replace the content of the test method with this code:

- (void)testExample
{
    CLGeocoder* gc = [[CLGeocoder alloc] init];
    [gc geocodeAddressString:@"Hermannplatz, Berlin, Germany"
     completionHandler:^(NSArray *placemarks, NSError *error) {
 
         STFail(@"Failed inside a block");
 
     }];
}

This code creates a new geocoder and asks for matches for the given address. Since the API is asynchronous the results are fetched inside the provided block. You’ll need to also add an import at the top of the file:

#import <CoreLocation/CoreLocation.h>

and of course add the CoreLocation.framework to the project.

Hit Cmd+U and … the test passes. Inevitably and always.

So why does this happen?

The test suit doesn’t wait for and don’t know that you are waiting for geocoding results from Apple’s server. It runs all the code and then when there’s no more code to run – exits.

Problem?

You actually need to make the application hang around alive until you get the results for the geocoding server and only tell it “i finished my work you can exit now please”. But naturally if you just tell the app to sleep it won’t be able to react when the results come back from the server …

Some diggin’ through StackOverflow finds me this solution which I really like:

stackoverflow.com/a/4326754/208205

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 
[object runSomeLongOperationAndDo:^{
    STAssert…
 
    dispatch_semaphore_signal(sema);
}];
 
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);

Using Grand Central Dispatch and its C functions you can create a semaphore object and wait till the signal comes in. However there are 2 things I don’t like in this code:

  1. using C functions and
  2. I have rather complex class structure of classes, so passing reference to the semaphore is rather inconvenient.

So I quickly came up with a simple class to implement my own semaphore. It has two handy methods – waitForKey:(NSString*)key and lift:(NSString*)key … and here’s how you use it:

- (void)testExample
{
    CLGeocoder* gc = [[CLGeocoder alloc] init];
    [gc geocodeAddressString:@"Hermannplatz, Berlin, Germany"
     completionHandler:^(NSArray *placemarks, NSError *error) {
 
         STFail(@"Failed inside a block");
         [[TestSemaphore sharedInstance] lift:@”geocode1”];
     }];
 
	[[TestSemaphore sharedInstance] waitForKey:@”geocode1”];
}

Pretty straight forward – and since it’s a singleton class you can call it from wherever you want. Also it is free and available for download if you want to tingle with unit testing. And hey – it’s a semaphore, so you can use it for whatever purpose suits you :)

Sempahore code download

Marin

SOURCE LINK from iDevBlogADay

A pullable view implementation (like Notification Center) [Fábio Rodella] (iDevBlogADay)

For an app I’m designing at the moment I had the need to use a pullable view (sliding drawer) similar to how the iOS 5 Notification Center works, where you can flick or drag a handle to reveal or hide additional content. After looking for an existing open-source implementation of such feature and finding squat, I decided to create my own and share it with the interwebs.

The PullableView class tries to mimic the behaviour of the Notification Center view as closely as possible, and is very flexible. You can define and style your handle area, and the view can be pulled from any direction in the X or Y axes. To define the “open” and “closed” states you simply set the center point for the view at these states; if both points have the same X coordinate the sliding will happen in the vertical axis, or the horizontal axis if the Y coordinates are equal. You can also set a flag indicating if the view state should be toggled by simply tapping the handle area.

The sample project includes a view controller which uses 3 pullable views demonstrating many usage scenarios, but if you have additional questions or want to report a bug add a comment to this post.

You can download the source code from our Github repo. I’ve also added this component to the Cocoa Controls website. Hope you find it useful!

SOURCE LINK from iDevBlogADay

WP Like Button Plugin by Free WordPress Templates