MESUIT, from iOS to Android and back

A smartphone with a sophisticated design like an iPhone but with inside all the flexibility that Android OS allows.

This is the dream of many users, and many manufacturers have tried (with poor results, editor’s note).

From China comes the first solution to meet this need, it is called MESUIT and is a cover for the iPhone that allows the smartphone of Cupertino to use the Android operating system.

image

Continue reading “MESUIT, from iOS to Android and back”

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

Learn Apple’s Swift Language: free online resources

During a meeting last week, I was discussing with a colleague about Swift courses, in his view it’s too early to attend a Swift course because nobody has mastered it completely yet. I think it’s better, in the beginning, to build your knowledge with online resources. But besides the iBook and the official documentation released by Apple*, what are some good resources to start learning Swift?  Continue reading “Learn Apple’s Swift Language: free online resources”

Playing with POP

A couple of weeks ago Facebook shared a video with some behind the curtains details about the realisation of Paper, their iOS new fancy client.

Among the talks a bomb was released: they were going to open source their animation framework POP, and they finally did!

POP it has been released for iOS and OSX, it can animate everything, not only CGLayers, you can even animate volume or other object’s properties.

And to whet your appetite before heading to the repo check Codeplease’s posts on POP