Tuesday 26 April 2016

Music from terminal with afplay on Mac

So you want to play music on your Mac but you don't want to use iTunes. Open the terminal app (Launchpad => Other => Terminal) cd to the folder of music you want to play, and copy and paste the following:

find * | grep "mp3$" | while read; do echo "$REPLY"; afplay "$REPLY"; wait; done

This will play all mp3s in the folder (and subfolders) it is run in. find lists all the files in the folder and subfolders, grep selects those files ending in mp3, the last piece of code plays them one after the other using afplay.

Shuffling


If you don't want to play them in order, you'll need something to shuffle the lines of output from grep. There's a stackoverflow question on this, but I decided to uses shuf. To get shuf using homebrew it's brew install coreutils. On mac by default it's run as gshuf:

find * | grep "mp3$" | gshuf | while read; do echo "$REPLY"; afplay "$REPLY"; wait; done

And there you have it. A simple way to play your mp3s using the terminal. Now I just need a way to pause and play again ...

Saturday 23 April 2016

Superclass with a Category Factory method

tl;dr If you want to make a factory method in Objective-C using a category and still get it to work for subclasses, you can do something like this:

@implementation UIButton (ColorButton)
+(instancetype)buttonWithColor:(UIColor*)color {
    UIButton* button = [[self.class alloc] initWithFrame:CGRectZero];
    ...
    return button;
}


The Problem

This situation came up recently when I had a category class method that made a button with a certain background colour, but I wanted to customise the button to change background colour when it was highlighted or in the touch down state.

The category looked like this:

@implementation UIButton (ColorButton)
+(UIButton)buttonWithColor:(UIColor*)color {
    UIButton* button = [[UIButton allocinitWithFrame:CGRectMake(0,0,UIScreen.mainScreen.bounds.size.width,50)];
    button.backgroundColor = color;
    return button;
}
@end

This method takes a UIColor and returns a UIButton with a size and colour.

In the subclass, I needed to override the setHighlighted: method:


@implementation ColorButton
- (void) setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    if (highlighted) {
        self.backgroundColor = self.colour_selected;
    }
    else {
        self.backgroundColor = self.colour;
    }
}
@end

This allows me to pick two colours colour_selected and colour to be the tap down and tap up colours respectively. I tried using my new subclass with:

ColorButton* button_new = [ColorButton buttonWithColor:[UIColor whiteColor]];
button_new.colour = [UIColor whiteColor];
button_new.colour_selected = [UIColor grayColor];

However when I tried to compile this code and I got the following warning:


incompatible pointer types assigning to 'ColorButton *' from 'UIButton *'


The first fix

Okay. I tried changing +(UIButton)buttonWithColor:(UIColor*)color to
+(instancetype)buttonWithColor:(UIColor*)color. Now the code compiled and ran, but it crashed when it tried to set the colour property on button_new.


The second fix

It crashed because button_new was actually a UIButton and not a ColorButton. To fix this I needed to look at the code in the buttonWithColor: method a little closer.

@implementation UIButton (ColorButton)
+(instancetype)buttonWithColor:(UIColor*)color {
    UIButton* button = [[UIButton allocinitWithFrame:CGRectMake(0,0,UIScreen.mainScreen.bounds.size.width,50)];
    button.backgroundColor = color;
    return button;
}
@end

Notice the call to alloc is for a UIButton. How did I make it work for the current class just like I did with instancetype? I replaced the UIButton with self.class:

@implementation UIButton (ColorButton)
+(instancetype)buttonWithColor:(UIColor*)color {
    UIButton* button = [[self.class allocinitWithFrame:CGRectMake(0,0,UIScreen.mainScreen.bounds.size.width,50)];
    button.backgroundColor = color;
    return button;
}
@end

And that's it. The code runs and the button changes colour when it's tapped. But what is self in this case and what does self.class do? I guess that question could be a topic for another blog post.