Upload
brian-gesiak
View
10.588
Download
1
Tags:
Embed Size (px)
Citation preview
iOS UI Component DesignConfiguration and Callbacks Using the “Parameter Object” Pattern
Brian Gesiak
April 9th, 2014
Research Student, The University of Tokyo
@modocache
Today
• Problem: How do we allow users to customize UI elements?
• Appearance, animations, and behavior should be customizable
• Composition over inheritance • Solution: Configuration objects !
• Problem: How do we define an API for callbacks? • Public delegate and block callbacks are hard to deprecate or change
• Solution: Parameter objects
Customization API ExampleJVFloatLabeledTextField
Customization API ExampleJVFloatLabeledTextField
Customization API ExampleJVFloatLabeledTextField Appearance API
Customization API ExampleJVFloatLabeledTextField Appearance API
@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
Customization API ExampleJVFloatLabeledTextField Appearance API
@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
Customization API ExampleJVFloatLabeledTextField Appearance API
@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
Customization API ExampleJVFloatLabeledTextField Appearance API
@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
But What About Categories?Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField
Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField• We need to use the class for its functionality
Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField• We need to use the class for its functionality • We’re forced to choose it or other useful subclasses
Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField• We need to use the class for its functionality • We’re forced to choose it or other useful subclasses• We need to subclass it to add functionality
Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField• We need to use the class for its functionality • We’re forced to choose it or other useful subclasses• We need to subclass it to add functionality• It forces itself upon our inheritance hierarchy
Favoring Composition Over Inheritance
But What About Categories?
• JVFloatLabeledTextField is a subclass of UITextField• We need to use the class for its functionality • We’re forced to choose it or other useful subclasses• We need to subclass it to add functionality• It forces itself upon our inheritance hierarchy
Favoring Composition Over Inheritance
• If it were a category, we’d be able to use it with any 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
But What About Categories?Favoring Composition Over Inheritance
@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
But What About Categories?Favoring Composition Over Inheritance
@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
But What About Categories?Favoring Composition Over Inheritance
@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
But What About Categories?Favoring Composition Over Inheritance
@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
But What About Categories?Favoring Composition Over Inheritance
@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
But What About Categories?Favoring Composition Over Inheritance
objc_setAssociatedObject@interface UITextField (JVFloatLabeledTextField)
But What About Categories?Favoring Composition Over Inheritance
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
But What About Categories?Favoring Composition Over Inheritance
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
Doesn’t scale
Configuration ObjectsEncapsulate Configuration
Configuration ObjectsEncapsulate Configuration
Configuration ObjectsEncapsulate Configuration
Configuration ObjectsEncapsulate Configuration
Configuration ObjectsEncapsulate Configuration
Configuration Object ExampleMDCSwipeToChoose
Configuration Object ExampleMDCSwipeToChoose
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Configuration Object ExampleMDCSwipeToChoose
MDCSwipeOptions *options = [MDCSwipeOptions new]; options.delegate = self; options.onPan = ^(MDCPanState *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];
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
Parameter Objects for BlocksExtensible Block Signatures
options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
Extensible Block ArgumentsUpdate the API without Breaking Old Versions
@interface MDCPanState : NSObject !@property (nonatomic, strong) UIView *view; @property (nonatomic, assign) MDCSwipeDirection direction; @property (nonatomic, assign) CGFloat thresholdRatio; !@end
Extensible Block ArgumentsUpdate the API without Breaking Old Versions
@interface MDCPanState : NSObject !@property (nonatomic, strong) UIView *view; @property (nonatomic, assign) MDCSwipeDirection direction; @property (nonatomic, assign) CGFloat thresholdRatio; !@end
DEPRECATED_ATTRIBUTE;
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
Extensible Block Arguments Slowly Phase Out Deprecated Parameters
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
Extensible Block Arguments Slowly Phase Out Deprecated Parameters
options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } };
options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
Extensible Block Arguments Slowly Phase Out Deprecated Parameters
Takeaways
• Favor composition over inheritance when building UI components
• Build extensible, future-proof customization APIs using parameter objects
• Parameter objects are especially useful as block arguments
Additional Resources
• Follow me on Twitter and GitHub at @modocache • Today’s slides
• http://modocache.io/ios-ui-component-api-design • JVFloatLabeledTextField
• https://github.com/jverdi/JVFloatLabeledTextField • MDCSwipeToChoose
• https://github.com/modocache/MDCSwipeToChoose • The Parameter Object Design Pattern
• http://c2.com/cgi/wiki?ParameterObject