あるUILabelのサブクラスをSwiftで書きなおした
iPhone6の発表イベントが9/9に決まって、iOS8 + Xcode6も同発表と共に解禁になるはず。 Swiftで書いてもリリースできなかったんでイマイチやる気起きなかったけど、 ようやくぼちぼちSwiftやる気が出てきた。ということで、 試しにひとつ既存の簡単なObjective-CクラスをSwiftで書きなおしてみた。
もともとのクラスはこんな感じ。
UILabel
のサブクラス。- 数字を表示するためのカスタムビュー。バッジ的な表現用。
(id)init
がひとつ、(void)layoutToView
でセレクタが2種類の3つのメソッド。- CocoaPodsで入れたFrameAccessorを利用。
コードはこう。座標指定が思いっきりリテラルなのは...まあ見なかったことにしてくれ。
// BadgeView.m
#import "BadgeView.h"
#import "Constants.h"
#import <FrameAccessor.h>
@implementation BadgeView
- (id)init
{
self = [super init];
UILabel *badge = self;
badge.font = [badge.font fontWithSize:kSubFontPointSize];
badge.textColor = UIColor.whiteColor;
badge.textAlignment = NSTextAlignmentCenter;
badge.layer.backgroundColor = [UIColor.mainTintColor colorWithAlphaComponent:0.77].CGColor;
badge.layer.cornerRadius = 3;
return self;
}
- (void)layoutToView:(UIView *)parent withValue:(NSString *)value
{
UILabel *badge = self;
badge.text = value;
[badge sizeToFit];
badge.width = MIN(22, badge.width + 8 + 8);
badge.x = 11.0;
badge.y = -18 + (9.25) + 4;
}
- (void)layoutToView:(UIView *)parent withUInteger:(NSUInteger)value
{
NSString *val = [NSString stringWithFormat:@"%lu", value];
[self layoutToView:parent withValue:val];
}
@end
それが、こうなった。
// BadgeView.swift
class BadgeView : UILabel {
required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}
override init(frame: CGRect) {
super.init(frame:frame)
}
override init() {
super.init()
var badge = self;
badge.font = badge.font.fontWithSize(kSubFontPointSize)
badge.textColor = UIColor.whiteColor()
badge.textAlignment = NSTextAlignment.Center
badge.layer.backgroundColor = UIColor.mainTintColor().colorWithAlphaComponent(0.77).CGColor
badge.layer.cornerRadius = 3
}
func layoutToView(parent: UIView, withValue text: String) {
var badge = self
badge.text = text
badge.sizeToFit()
badge.width = min(22, badge.width + 8 + 8)
badge.x = 11.0;
badge.y = -18 + (9.25) + 4;
}
func layoutToView(parent: UIView, withUInteger val: Int) {
var v = NSString(format:"%lu", val);
layoutToView(parent, withValue:v);
}
}
特徴的なところをひとつひとつ見てみる。
ちなみに以下の説明で、Objective-Cとかの用語間違ってるかもしれないので、そこはひとつおおらかな気持ちで見つけたらやさしく教えて欲しい。
import
#import "BadgeView.h"
#import "Constants.h"
#import <FrameAccessor.h>
BadgeView.h
ヘッダファイルはSwiftなのでもちろん必要なし。
"Constants.h"
と<FrameAccessor.h>
はObjective-C Swiftブリッジのヘッダ
{プロジェクト名}-Bridging-Header.h
へ移動。
ということで、importはSwiftからはなくなった。
Required & Designated initializers
指定イニシャライザとかそういうやつ。 もとのObjective-Cのコードにはないコードの追加が必要だった。
required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}
// - (id)initWithCoder:(NSCoder *)aDecoder; に対応している
.../BadgeView.swift:8:7: Class 'BadgeView' does not implement its superclass's required members
定義しないと上のコンパイルエラーが発生した。
override init(frame: CGRect) {
super.init(frame:frame)
}
// - (instancetype)initWithFrame:(CGRect)frame; に対応している
.../BadgeView.swift: 8: 7: fatal error: use of unimplemented initializer 'init(frame:)' for class '{プロジェクト名}.BadgeView'
定義しないと上のランタイムエラーが発生した。
Method definition, invokation & static field
- (void)layoutToView:(UIView *)parent withValue:(NSString *)value
- (void)layoutToView:(UIView *)parent withUInteger:(NSUInteger)value
func layoutToView(parent: UIView, withValue text: String)
func layoutToView(parent: UIView, withUInteger val: Int)
Swiftでもセレクタ的な表現にする。
text
とかval
を付けない場合はwithValue
、withUIInteger
がメソッド内でのパラメータ名になる。
Objective-Cのコードから呼ばれてもいいような名前をつけておいたほうが無難。
layoutToView(parent, withValue:v);
ちなみに呼び出しはこう。ひとつめのキーワードは書かずに、ふたつめから書く。
badge.font = [badge.font fontWithSize:kSubFontPointSize];
badge.font = badge.font.fontWithSize(kSubFontPointSize)
この辺は分かりやすい。.font
はプロパティ。
badge.textColor = UIColor.whiteColor;
badge.textColor = UIColor.whiteColor()
Objective-Cでのクラス定数は、Swiftではクラスメソッド呼び出しになる。
badge.layer.backgroundColor = [UIColor.mainTintColor colorWithAlphaComponent:0.77].CGColor;
badge.layer.backgroundColor = UIColor.mainTintColor().colorWithAlphaComponent(0.77).CGColor
カテゴリで定義したクラスメソッドmainTintColor
も素直に呼べた。
@interface UIColor (TextColor)
+ (UIColor *)mainTintColor;
...
もともとの定義はこんな感じ。
Constant, enum & etc
extern CGFloat const kSubFontPointSize;
badge.font = badge.font.fontWithSize(kSubFontPointSize)
Objective-Cで定義した定数は、上記のように普通に参照できた。
badge.textAlignment = NSTextAlignmentCenter;
badge.textAlignment = NSTextAlignment.Center
Cocoaフレームワークの定数は大半がenumで定義してあるようだ。
badge.textAlignment = .Center
右辺の型名は省略可能。ただXcode6+beta5上ではドットをタイプして補完(Ctrl + space)しても補完候補は出てこなかった。 Xcode6 GM版では表示してくれるといいな。
また、Objective-CのMIN
は、Swiftではmin
という関数が定義されていた。
func min<T : Comparable>(x: T, y: T) -> T
定義はこう。
@property
プロパティは変更なしでそのまま使える。
badge.x = 11.0;
badge.x = 11.0
@property (nonatomic) CGFloat x;
ちなみにこのx
はFrameAccessorで定義されているプロパティ。
Objective-Cで書かれたFrameworkもほぼ透過的に使える。
雑感
.h
,.m
じゃなくて.swift
ひとつで書けるのやっぱり嬉しい。- UIViewのサブクラスは指定イニシャライザの定義を避けられないのかな?ちょっとめんどくさい。
- やっぱりだけど、全部Swiftで書き直したくなってくる。