iOS UI Component API Design

15,375 views

Published on

Published in: Technology, Business
1 Comment
9 Likes
Statistics
Notes
  • Is there a block retain cycle in slide 38? options.onPan retains self and self retains the webview, that retains options. What do you think? Thanks.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
15,375
On SlideShare
0
From Embeds
0
Number of Embeds
13,402
Actions
Shares
0
Downloads
30
Comments
1
Likes
9
Embeds 0
No embeds

No notes for slide

iOS UI Component API Design

  1. 1. iOS UI Component Design Configuration and Callbacks Using the “Parameter Object”Pattern Brian Gesiak April 9th, 2014 Research Student, The University of Tokyo @modocache
  2. 2. 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
  3. 3. Customization API Example JVFloatLabeledTextField
  4. 4. Customization API Example JVFloatLabeledTextField
  5. 5. Customization API Example JVFloatLabeledTextField Appearance API
  6. 6. Customization API Example JVFloatLabeledTextField 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
  7. 7. Customization API Example JVFloatLabeledTextField 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
  8. 8. Customization API Example JVFloatLabeledTextField 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
  9. 9. Customization API Example JVFloatLabeledTextField 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
  10. 10. But What About Categories? Favoring Composition Over Inheritance
  11. 11. But What About Categories? • JVFloatLabeledTextField is a subclass of UITextField Favoring Composition Over Inheritance
  12. 12. But What About Categories? • JVFloatLabeledTextField is a subclass of UITextField • We need to use the class for its functionality Favoring Composition Over Inheritance
  13. 13. 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
  14. 14. 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
  15. 15. 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
  16. 16. 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
  17. 17. @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
  18. 18. @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
  19. 19. @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
  20. 20. @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)
  21. 21. @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)
  22. 22. @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)
  23. 23. But What About Categories? Favoring Composition Over Inheritance
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. 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
  31. 31. Configuration Objects Encapsulate Configuration
  32. 32. Configuration Objects Encapsulate Configuration
  33. 33. Configuration Objects Encapsulate Configuration
  34. 34. Configuration Objects Encapsulate Configuration
  35. 35. Configuration Objects Encapsulate Configuration
  36. 36. Configuration Object Example MDCSwipeToChoose
  37. 37. Configuration Object Example MDCSwipeToChoose
  38. 38. Configuration Object Example MDCSwipeToChoose 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];
  39. 39. Configuration Object Example MDCSwipeToChoose 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];
  40. 40. Configuration Object Example MDCSwipeToChoose 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];
  41. 41. Configuration Object Example MDCSwipeToChoose 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];
  42. 42. Configuration Object Example MDCSwipeToChoose 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];
  43. 43. Configuration Object Example MDCSwipeToChoose 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];
  44. 44. Configuration Object Example MDCSwipeToChoose 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];
  45. 45. Configuration Object Example MDCSwipeToChoose 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];
  46. 46. Configuration Object Example MDCSwipeToChoose 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];
  47. 47. Configuration Object Example MDCSwipeToChoose 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];
  48. 48. Parameter Objects for Blocks Extensible Block Signatures
  49. 49. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures
  50. 50. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures
  51. 51. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures
  52. 52. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures
  53. 53. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
  54. 54. options.onPan = ^(UIView *view, MDCSwipeDirection direction, CGFloat thresholdRatio){ if (direction == MDCSwipeDirectionLeft) { NSLog(@"Panning to the left..."); } }; Parameter Objects for Blocks Extensible Block Signatures options.onPan = ^(MDCPanState *state){ MDCSwipeDirection direction = state.direction;
  55. 55. Extensible Block Arguments Update 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
  56. 56. Extensible Block Arguments Update 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;
  57. 57. 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
  58. 58. 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
  59. 59. 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
  60. 60. 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
  61. 61. 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

×