[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
SlideShare a Scribd company logo
UIコンポーネントAPIデザイン
「パラメータ・オブジェクト」パターン
Brian Gesiak
2014年4月9日
Research Student, The University of Tokyo
@modocache #potatotips
内容
• 課題:カスタマイズ用のAPIをどう提供するか
• 細かいところまでカスタマイズしたい
• 継承より組み立てを好む
• 解決策:設定オブジェクト
• 設定オブジェクト
!
• 課題:コールバックのAPI
• ブロックやdelegateメソッドのパラメータが確定し
てしまうとなかなか変えられない
• 解決策:パラメータ・オブジェクト
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
カスタマイズ用のAPIの一例
JVFloatLabeledTextField
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
継承するのか?
• JVFloatLabeledTextFieldはUITextFieldのサブクラス
継承より組み立てを好む
継承するのか?
• JVFloatLabeledTextFieldはUITextFieldのサブクラス
• 機能がほしければこのクラスを使うしかない
継承より組み立てを好む
継承するのか?
• JVFloatLabeledTextFieldはUITextFieldのサブクラス
• 機能がほしければこのクラスを使うしかない
• 機能を追加したければサブクラスを新たに定義す
るしかない
継承より組み立てを好む
継承するのか?
• JVFloatLabeledTextFieldはUITextFieldのサブクラス
• 機能がほしければこのクラスを使うしかない
• 機能を追加したければサブクラスを新たに定義す
るしかない
• JVFloatLabeledTextFieldは継承ヒエラルキーに自分
をねじ込んでいる
継承より組み立てを好む
継承するのか?
• JVFloatLabeledTextFieldはUITextFieldのサブクラス
• 機能がほしければこのクラスを使うしかない
• 機能を追加したければサブクラスを新たに定義す
るしかない
• JVFloatLabeledTextFieldは継承ヒエラルキーに自分
をねじ込んでいる
継承より組み立てを好む
• カテゴリーだったら、どのUITextFieldでも使える
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
@interface UITextField (JVFloatLabeledTextField)
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
@interface UITextField (JVFloatLabeledTextField)
@interface JVFloatLabeledTextField : UITextField
!
@property (nonatomic, strong)
NSNumber *floatingLabelYPadding UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIFont *floatingLabelFont UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong)
UIColor *floatingLabelActiveTextColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, assign)
NSInteger animateEvenIfNotFirstResponder UI_APPEARANCE_SELECTOR;
!
@end
継承するのか?
継承より組み立てを好む
objc_setAssociatedObject
@interface UITextField (JVFloatLabeledTextField)
継承するのか?
継承より組み立てを好む
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
継承するのか?
継承より組み立てを好む
static void *JVFloatingLabelYPaddingKey =
&JVFloatingLabelYPaddingKey;
!
- (void)setFloatingLabelYPadding:(NSNumber *)floatingLabelYPadding {
objc_setAssociatedObject(self,
JVFloatingLabelYPaddingKey,
floatingLabelYPadding,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
!
- (NSNumber *)floatingLabelYPadding {
return objc_getAssociatedObject(self,
JVFloatingLabelYPaddingKey);
}
!
/// Add custom setters and getters for all properties
スケールしない
設定オブジェクト
カスタマイズ用のパラメータを束ねる
設定オブジェクト
カスタマイズ用のパラメータを束ねる
設定オブジェクト
カスタマイズ用のパラメータを束ねる
設定オブジェクト
カスタマイズ用のパラメータを束ねる
設定オブジェクト
カスタマイズ用のパラメータを束ねる
設定オブジェクトの一例
MDCSwipeToChoose
設定オブジェクトの一例
MDCSwipeToChoose
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
設定オブジェクトの一例
MDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new];
options.delegate = self;
options. *state){
switch (state.direction) {
case MDCSwipeDirectionLeft:
self.webView.alpha = 0.5f - state.thresholdRatio;
break;
case MDCSwipeDirectionRight:
self.webView.alpha = 0.5f + state.thresholdRatio;
break;
case MDCSwipeDirectionNone:
self.webView.alpha = 0.5f;
break;
}
};
!
[self.webView mdc_swipeToChooseSetup:options];
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *state){
MDCSwipeDirection direction = state.direction;
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
パラメータ・オブジェクト
ブロックのシグネチャの変化を回避する
options. *state){
MDCSwipeDirection direction = state.direction;
パラメータ・オブジェクト
APIの微調整、バージョニングが可能
@interface MDCPanState : NSObject
!
@property (nonatomic, strong)
UIView *view;
@property (nonatomic, assign)
MDCSwipeDirection direction;
@property (nonatomic, assign)
CGFloat thresholdRatio;
!
@end
パラメータ・オブジェクト
APIの微調整、バージョニングが可能
@interface MDCPanState : NSObject
!
@property (nonatomic, strong)
UIView *view;
@property (nonatomic, assign)
MDCSwipeDirection direction;
@property (nonatomic, assign)
CGFloat thresholdRatio;
!
@end
DEPRECATED_ATTRIBUTE;
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
options. *state){
MDCSwipeDirection direction = state.direction;
パラメータ・オブジェクト
サポートしないパラメータを少しずつ排除
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
options. *state){
MDCSwipeDirection direction = state.direction;
パラメータ・オブジェクト
サポートしないパラメータを少しずつ排除
options. *view,
MDCSwipeDirection direction,
CGFloat thresholdRatio){
if (direction == MDCSwipeDirectionLeft) {
NSLog(@"Panning to the left...");
}
};
options. *state){
MDCSwipeDirection direction = state.direction;
パラメータ・オブジェクト
サポートしないパラメータを少しずつ排除
要約
• 継承に頼らないUIコンポーネントを好む
• 設定オブジェクトで、クリーンなカスタマイズ用の
APIを提供できる
• パラメータ・オブジェクトというデザイン・パター
ンは、シグネチャの変化を未然に防ぐ
• 特にブロックのパラメータに有用
ご参考までに
• 本日のスライド
• http://modocache.io/ios-ui-component-api-design
• ぜひフォローして下さい
• Twitter: @modocache
• GitHub: https://github.com/modocache
• JVFloatLabeledTextField
• https://github.com/jverdi/JVFloatLabeledTextField
• MDCSwipeToChoose(おいらに☆を!)
• https://github.com/modocache/MDCSwipeToChoose
• 「パラメータ・オブジェクト」デザイン・パターン(英語)
• http://c2.com/cgi/wiki?ParameterObject

More Related Content

iOS UI Component API Design