Apple Music for iOS developers (hint: it doesn’t work)

Back in June Apple released its own music streaming service, Apple Music, and to this date there is no API that allows developers to easily take advantage of the user’s Apple Music library. Sure, MPMediaPickerController and MPMusicPlayerController work great with very few lines of code, but the player doesn’t work in the background and that’s a very big limitation.
AVFoundation’s AVPlayer and AVAudioPlayer are two very powerful options when it comes to audio and video playback and they work in the background, but they both have a problem: they can’t play directly the MPMediaItems that the MPMediaPickerController returns.
Before Apple Music (and iTunes Match), every MPMediaItem used to represent a single song that was physically stored on the user’s device, so the quickest option was to access its MPMediaItemPropertyAssetURL and use it for playback. Today, there is a range of different situations and the AssetURL isn’t always available.

Song origin/location AssetURL
iTunes (Win/Mac OSX transfer) YES
iTunes (downloaded via iOS app) YES
iTunes Match (stored on device) YES
iTunes Match (stored on iCloud) NO
Apple Music (stored on device) NO
Apple Music (stored on iCloud) NO

As expected, an iTunes Match or Apple Music song that is only in the cloud doesn’t have a local AssetURL, but Apple Music songs aren’t available even if the user decided to save them for offline use. Since there is no support for Apple Music, if we want to let the user pick a song to be played with AVAudioPlayer, we need to be able to hide the ones that don’t have a valid AssetURL. By default MPMediaPickerController shows every song in the library (including cloud items), so the only option is to create our own song picker using MPMediaQuery. This class doesn’t allow to filter directly the songs based on their AssetURL, but once we have the complete list we can cycle through the array and remove the ones who don’t have a valid URL:

// Get all the songs from the library
MPMediaQuery *query = [MPMediaQuery songsQuery];

// Filter out cloud items
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:[NSNumber numberWithBool:NO] forProperty:MPMediaItemPropertyIsCloudItem]];

NSArray *items = [query items];
NSMutableArray *itemsWithAssetURL = [NSMutableArray array];

// For every song stored locally
for (MPMediaItem *item in items)
{
    // If the song has an assetURL
    if ([item valueForProperty:MPMediaItemPropertyAssetURL]) {
        // Add it to the array of valid songs
        [itemsWithAssetURL addObject:item];
    }
}

This does the trick, but if the user’s library contains thousands of songs (as it does most of the times) it’s probably best to perform this away from the main thread and/or to limit the number of items to check in the first place.

Creating a two-dimensional scroll interface for iOS using UIPageView and UICollectionView

Recently I’ve been looking into ways to create an interface that allows the user to navigate through full-page views using both vertical and horizontal scrolling. Each view provides a data chart (replaced by a simple label in this example) for a specific day, week or month. By swiping left/right the user can navigate between days, while swiping up/down they can switch to the weekly (and further down, monthly) view and back up. The navigation works in a very similar way to Sony PS3’s and PS4’s menu interface.

iOS provides different ways to display scrolling and paged content, namely UIPageView, UIScrollView and UICollectionView. The last of the three was the last to be introduced and it is the most versatile thanks to the ability to use a custom UICollectionViewLayout to create a grid interface.
At first, a grid interface with only one view at a time on-screen seemed like the way to go, but then the horizontal and vertical navigation would have been tied together: if the user navigates 3 days back in time (horizontally) and then swipes down to the week view, they would be provided with the data from 3 weeks back, and this would be counterintuitive and confusing.

The solution was to have three different UICollectionView to handle the horizontal navigation independently and a UIPageView to navigate vertically between the three.

I added a UIPageViewController to the storyboard, and in the Attribute Inspector I set the navigation direction to vertical and the transition style to scroll. This will be the initial view controller.

Then I added a UICollectionViewController and set its storyboard ID to “collectionViewController” in the Identity Inspector. I navigated the view hierarchy down one level to the UICollectionView and configured it as follows.

The UIPageView is handled by VerticalPageViewController:

@implementation VerticalPageViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Set yourself as the datasource and delegate of the page view
    self.dataSource = self;
    self.delegate = self;

    // Get the first of the three UICollectionViewControllers and load it on screen
    HorizontalCollectionViewController *startingViewController = [self viewControllerAtIndex:0];
    NSArray *viewControllers = @[startingViewController];
    [self setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
}

#pragma mark - UIPageViewController Delegate & Datasource

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSInteger index = ((HorizontalCollectionViewController *) viewController).pageIndex;

    if (index == NSNotFound) {
        return nil;
    }

    index--;

    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    NSInteger index = ((HorizontalCollectionViewController *) viewController).pageIndex;

    if (index == NSNotFound) {
        return nil;
    }

    index++;

    return [self viewControllerAtIndex:index];
}

- (HorizontalCollectionViewController *)viewControllerAtIndex:(NSInteger)index
{
    if ((index > 0) || (index < -2)) {
        return nil;
    } else {

        // Get a new UICollectionViewController (with all the settings and subviews) from the storyboard
        HorizontalCollectionViewController *collectionViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"collectionViewController"];
        // Give the controller a page index so it is aware of which data to display (day/week/month)
        collectionViewController.pageIndex = index;

        return collectionViewController;
    }
}       

@end

The three UICollectionView are handled by a single controller, HorizontalCollectionViewController.m.

@interface HorizontalCollectionViewController ()

@property IBOutlet UILabel *label;

@property NSMutableArray *dailyContent;
@property NSMutableArray *weeklyContent;
@property NSMutableArray *monthlyContent;

@end

@implementation HorizontalCollectionViewController

static NSString * const reuseIdentifier = @"Cell";

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Register cell classes
    // [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];

    // Do any additional setup after loading the view.
    _dailyContent = [[NSMutableArray alloc] initWithArray:@[@"DAY: One", @"DAY: Two", @"DAY: Three", @"DAY: Four", @"DAY: Five"]];
    _weeklyContent = [[NSMutableArray alloc] initWithArray:@[@"WEEK: One", @"WEEK: Two", @"WEEK: Three", @"WEEK: Four", @"WEEK: Five"]];
    _monthlyContent = [[NSMutableArray alloc] initWithArray:@[@"MONTH: One", @"MONTH: Two", @"MONTH: Three", @"MONTH: Four", @"MONTH: Five"]];

    [self scrollToEnd];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

#pragma mark <UICollectionViewDataSource>

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    // Depending on the page index (0 to -2) figure out which data to display
    switch (_pageIndex)
    {
        case 0: {
            return [_dailyContent count];
            break;
        }

        case -1: {
            return [_weeklyContent count];
            break;
        }


        case -2: {
            return [_monthlyContent count];
            break;
        }

        default: {
            return 1;
            break;
        }
    }
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // Don't forget to give the UICollectionViewCell a class and a storyboard ID in the Interface Builder
    // HorizontalCollectionViewCell.h declared a IBOutlet UILabel *label
    // This way the compiler knows that each cell has a label and doesn't complain (wire the outlet to the placeholder cell in the Interface Builder)
    HorizontalCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MovesCell" forIndexPath:indexPath];

    // Get the content from the correct array and update the label
    switch (_pageIndex)
    {
        case 0: {
            cell.label.text = [_dailyContent objectAtIndex:indexPath.row];
            break;
        }

        case -1: {
            cell.label.text = [_weeklyContent objectAtIndex:indexPath.row];
            break;
        }


        case -2: {
            cell.label.text = [_monthlyContent objectAtIndex:indexPath.row];
            break;
        }

        default: {
            break;
        }
    }

    return cell;
}

#pragma mark UICollectionViewDelegate

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // Each cell must be the same as the screen size minus the height of the status bar (otherwise it will brake the UICollectionViewFlowLayout)
    CGFloat width = self.view.frame.size.width;
    CGFloat height = self.view.frame.size.height - [UIApplication sharedApplication].statusBarFrame.size.height;

    return CGSizeMake(width, height);
}

@end

The header files for these two classes just declare the public properties and the protocols the two classes conform to. Using a custom subclass of UICollectionViewCell allows to declare its outlets and to design its interface easily in the storyboard.

If everything has been wired and set correctly in the storyboard, these few lines of code will allow you to create a pretty neat and complex UI!

iOS 9 new privacy and security features and how to comply to (or work around) them

With the release of iOS 9 last week, Apple has introduced new under-the-hood features meant to protect the end-user and their privacy. While this is a welcome addition, said features may break your app if not addressed properly.

App Transport Security

According to Apple, App Transport Security is a feature that improves the security of connections between an app and web services. The feature consists of default connection requirements that conform to best practices for secure connections.

These are the App Transport Security requirements:

  • The server must support at least Transport Layer Security (TLS) protocol version 1.2.
  • Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
  • Certificates must be signed using a SHA256 or greater signature hash algorithm, with either a 2048-bit or greater RSA key or a 256-bit or greater Elliptic-Curve (ECC) key.
    Invalid certificates result in a hard failure and no connection.

The default behaviour causes all connections using NSURLConnection, CFURL, or NSURLSession to fail if they don’t follow these requirements.
Luckily, this layer of security can be disabled pretty easily by adding an exception into your Info.plist file. Just add a NSAppTransportSecurity dictionary to the .plist and inside of that a NSExceptionDomains dictionary with the desired exceptions. You can also disable the transport security altogether by adding a NSAllowsArbitraryLoads boolean to the first dictionary. For more options just check the relative Apple technote.

Privacy and URL Scheme Changes

URL schemes are used by many apps for a variety of purposes. A common use is to deep link to a particular piece of content within an app – seen as an “Open in app” link from a web page.

Before trying to open a URL handled by another app (using NSApplication’s openURL: method) it’s good practice to check if that app is actually installed on the device. NSApplication’s canOpenURL: method used to return a YES or NO answer after checking if there were any apps installed on the device that know how to handle the given URL.

With iOS 9, canOpenURL: can’t be used on arbitrary URLs anymore. Apple noticed that some apps were using this method to collect information about what apps were or were not installed on the user’s device for marketing and other purposes the method was not intended for.

From now on canOpenURL: will return NO even if there is an app able to handle the given URL, unless said URL has been whitelisted in the app Info.plist before submission to the App Store. These limits will be retroactively enforced on apps built and submitted to the store prior to iOS 9 by the system, which will build it’s own whitelist of up to 50 URL schemes allowed for an app based on the actual calls made by the app.

To whitelist the URL schemes your app needs to function properly, just a LSApplicationQueriesSchemes array into your Info.plist and individual allowed schemes inside of that array.

This new limitations should not take long to adjust to for apps that use canOpenURL: sparingly, but it can have a very big impact on launcher-style apps that heavily rely on them. For more information about this, check out Apple Privacy and Your App video and Awkard Hare’s Quick Take on iOS 9 URL Scheme Changes.

Storyboards With Custom Container View Controllers

Storyboards With Custom Container View Controllers

How to setup your Core Data OSX application

Core Data is an object graph and persistence framework provided by Apple in the Mac OS X and iOS operating systems. It’s been around for quite a while (since iPhone SDK 3.0) and it’s probably one of the most misunderstood Frameworks on OS X and iOS. After my first experience with it I can totally understand the love/hate relationship most developers have with it.
There are a lot of good examples on how to setup your app to start using Core Data quickly and painfully, and I absolutely recommend lessons 12 and 13 from Developing iOS 7 Apps by Stanford University, but the problem is that most of them are specific to iOS and even the ones that are not (e.g the one at raywenderlich.com) don’t go very deep in detail as they just show basic code-free Cocoa Bindings stuff (see the link above).
The problem is that while Core Data for OS X and Core Data for iOS are basically the same thing 90% of the time, the differences that lie in that remaing 10% will make you want to smash your beloved Macbook on the floor, multiple times.

On iOS all you have to do to start enjoying all the cool stuff about Core Data is create an instance of UIManagedDocument. This will automatically generate the Persistent Store Coordinator, the Persistent Object Store and, most importantly, the Managed Object Context (MOC from here on).

Because if you’re in a hurry and you just want to jump in and skip to the good stuff, all you need to know is where your MOC’s at.

I’m not going to explain from scratch how Core Data works (objc.io does a great job doing that in its Core Data Overview) but basically you define entities (and their attributes and relationships) in your Data Model, then you insert instances of those entities into your MOC and set their attributes. Upon calling [moc save:&error] you save those instances and the changes you’ve made to them in your Persistent Store (a wrapper for the actual database file in the filesystem). This means that before that call you have full undo/redo support for the changes you (and your app’s user) made on the moc. On iOS, you don’t even have to call that save method because UIManagedDocument takes care of everything for you. Cool, right?

image

The problem is that there’s no such thing as UIManagedDocument (not even a close relative) on OS X, so you have to take care of generating all that stuff I was telling you about on your own.

The best way you can do that is by starting a new Xcode project and choose “Use Core Data” option. This will generate the necessary code in you Application Delegate. If you can’t start a new project, just copy all the Core Data related methods from a new project’s AppDelegate in your own.

Now all you need to know to use Core Data is, as I previously said, where your Managed Object Context’s at. The MOC has been created by the code you just copied in your AppDelegate so just get a pointer to that from whenever you are in your application by doing

AppDelegate *delegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];

and then access its moc property like this

NSManagedObjectContext *moc = delegate.managedObjectContext;

That’s it, you’re all ready to go. Happy coding!

iOS/OS X network testing with XCTest

I recently worked on a OS X app which had to keep in sync with a database running on a remote server, to interact with a hardware device via serial port and to allow the user to edit said database with information coming from the hardware device. Since reinventing the wheel is never a good idea, I decided to use various frameworks for those tasks: ORSSerialPort to handle the serial communication, AFNetworking for the networking, Apple’s own Core Data for the local database and XCTest together with OHHTTPStubs for the testing. The networking part was by far the easiest to develop but also the hardest to test, due to its asynchronicity.

This is basically how XCTest works: you create subclasses of XCTestCase, and then define instance methods with their name starting with “test”. Inside these methods you can check for whatever condition you need to test. Here’s a very simple example:

- (void)testReverseString{ 

    NSString *originalString = @"test";
    NSString *reversedString = [self.controllerToTest reverseString:originalString];
    NSString *expectedReversedString = @"tset";

    XCTAssertEqualObjects(expectedReversedString, reversedString);
}

XCTAssert allows to easily compare variables and to let tests fail/succeed based on the result, but it is not enough when it comes to testing the networking code. For example, I needed to test a GET request that fetched the user database from the remote server and tried to do so this way:

- (void)testSuccessfulUserFetch
{

    // Perform fetch
    [self.networkManager fetchUsers];

    // Test if the number of users created in the local database is equal to the one in the remote database
    NSArray *localUserDatabase = [User usersInManagedObjectContext:self.moc];
    XCTAssertEqual([localUserDatabase count], 10);

}

The test failed even if the GET was performed with success, because the local database was tested right after the network request was started and not after its completion.

Luckily Xcode 6 introduced a new feature in XCTest that allows to test asynchronous code as well: XCTestExpectation. It’s as easy as declaring an expectation and setting a timeout for that expectation. The only other thing that’s needed is of course to check for whatever condition has to be met and to fulfill the expectation if that happens.

The obvious place to check and fulfill my network expectations was inside the success/failure blocks that my network manager used, but since that code was written way before I decided to write tests for it, there was no easy way to do so without refactoring. At first I felt like it was not a very good idea to refactor the network manager just to be able to test it, but I’m glad I did: while the existing code was working fine, refactoring it and being able to test it afterwards helped a lot while further developing it.

This is how I changed on of my NetworkManger.m methods to use optional success/failure blocks:

typedef void(^SuccessBlock)(AFHTTPRequestOperation *operation, id responseObject);
typedef void(^FailureBlock)(AFHTTPRequestOperation *operation, NSError *error);

// Perform network request for user database
- (void)fetchUsersWithSuccess:(SuccessBlock)optionalSuccessBlock
         withFailure:(FailureBlock)optionalFailureBlock
{
    // Create success block
    SuccessBlock successBlock = ^(AFHTTPRequestOperation *operation, id responseObject){   

        // Load users into the database
        NSArray *usersArray = (NSArray *)responseObject;
        [User loadUsersFromArray:usersArray inManagedObjectContext:self.managedObjectContext];

        // Optional success block, if given
        if (optionalSuccessBlock) optionalSuccessBlock(operation, responseObject);
    };  

    // Create failure block
    FailureBlock failureBlock = ^(AFHTTPRequestOperation *operation, NSError *error){

        // Optional failure block, if given
        if (optionalFailureBlock) optionalFailureBlock(operation, error);
    };  

    // Perform request to server
    [self.manager GET:self.userSyncURL parameters:nil success:successBlock failure:failureBlock];
}

And here’s what the test method looks like:

- (void)testSuccessfulUserFetch{
    // Create expectation
    XCTestExpectation *countExpectation = [self expectationWithDescription:@"User count expectation"];

    // Create custom success block
    SuccessBlock successBlock = ^(AFHTTPRequestOperation *operation, id responseObject){
        // Check and fulfill expectation
        [self checkUsersAndFullfillCountExpectation:countExpectation];
    };

  // Wait for expectations to be fulfilled. If timeout is met, test fails.
  [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
    if (error) {
      NSLog(@"Timeout Error: %@", error);
    }
  }];
}

// Check expectations for successful user fetch
- (void)checkUsersAndFullfillExpectation:(XCTestExpectation *)countExpectation{

  // Check the users count in the database
  NSArray *users = [User usersInManagedObjectContext:self.moc];
  if ([users count] == 10){
    [countExpectation fulfill];
  }
}

Bringing Asynchronous Core Data documents to OS X | Making the Switch

Bringing Asynchronous Core Data documents to OS X | Making the Switch