Monday 2 May 2016

A Category Example on UIView

If you have ever used categories, you will probably agree if I tell you they are one of the coolest features of Objective-C. They are great for making code more readable and reusable. Here's an example of a category I recently started using because it can be quite frustrating entering:

[self.view setFrame:CGRectMake(self.view.frame.origin.x,10,self.view.frame.size.width,self.view.frame.size.height)];

just to change the y value for a view's frame. I wanted to make a category where you can do something like the following:

self.view.y = 10;

The resulting category


Here is the code in the .h file for the UIView+Frame category:

@interface UIView (Frame)
@property (nonatomic) CGFloat x;
@property (nonatomic) CGFloat y;
@property (nonatomic) CGFloat height;
@property (nonatomic) CGFloat width;
@property (nonatomic) CGSize size;

@property (nonatomic,readonly) CGFloat bottom;
@property (nonatomic,readonly) CGFloat right;
@end

You might never have heard of adding properties using categories, but they're not really properties with instance variables, they're only properties in the sense that they have a setter and a getter. The setters and getters are implemented in the .m file:

@implementation UIView (Frame)
-(CGFloat)x {
    return self.frame.origin.x;
}
-(CGFloat)y {
    return self.frame.origin.y;
}
-(CGFloat)height {
    return self.frame.size.height;
}
-(CGFloat)width {
    return self.frame.size.width;
}
-(CGSize)size {
    return self.frame.size;
}
-(void)setX:(CGFloat)x {
    [self setFrame:CGRectMake(x,self.y,self.width,self.height)];
}
-(void)setY:(CGFloat)y {
    [self setFrame:CGRectMake(self.x,y,self.width,self.height)];
}
-(void)setHeight:(CGFloat)height {
    [self setFrame:CGRectMake(self.x,self.y,self.width,height)];
}
-(void)setWidth:(CGFloat)width {
    [self setFrame:CGRectMake(self.x,self.y,width,self.height)];
}
-(void)setSize:(CGSize)size {
    [self setFrame:CGRectMake(self.x,self.y,size.width,size.height)];
}

-(CGFloat)bottom {
    return self.y + self.height;
}

-(CGFloat)right {
    return self.x + self.width;
}
@end

Notice that the getter is just the name of the property, while the setter is setName where name is the name of the property. Really straightforward.  Notice also the two properties bottom and right. I added them in for convenience, because I often need to know x or y value for the right or bottom of a view. But I never want to change the bottom or the right, so I made that property readonly.

Categories can really make life a lot easier, especially when you use properties in categories. I hope I've encouraged you to try categories a bit more.

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.