Slide 1

Slide 1 text

The ART OF CUSTOM UI CONTROLS

Slide 2

Slide 2 text

@iwantmyrealname /sammyd iwantmyreal.name

Slide 3

Slide 3 text

@ShinobiControls /ShinobiControls shinobicontrols.com

Slide 4

Slide 4 text

shinobicontrols shinobicontrols.com/360idev

Slide 5

Slide 5 text

flickr/charliematters where shall we go?

Slide 6

Slide 6 text

flickr/charliematters what are how to create a good design considerations designing other ui controls API software user experience concerns ? where shall we go?

Slide 7

Slide 7 text

What are UI Controls?

Slide 8

Slide 8 text

What are UI Controls? Reusable components to aid

Slide 9

Slide 9 text

What are UI Controls? Reusable components to aid designers

Slide 10

Slide 10 text

What are UI Controls? Reusable components to aid designers developers

Slide 11

Slide 11 text

What are UI Controls? Reusable components to aid designers developers users

Slide 12

Slide 12 text

Why go custom?

Slide 13

Slide 13 text

Why go custom? same flexibility

Slide 14

Slide 14 text

Why go custom? same flexibility new Functionality

Slide 15

Slide 15 text

Why go custom? same flexibility new Functionality

Slide 16

Slide 16 text

Example Shinobi Knob Control

Slide 17

Slide 17 text

Let me at the code...

Slide 18

Slide 18 text

Let me at the code... hold ON

Slide 19

Slide 19 text

UX Design visual design principles collect use cases flickr/charliematters

Slide 20

Slide 20 text

collect use cases flickr/charliematters What do they want? whom for how will they use it? are we doing it? will we do? why do they want it?

Slide 21

Slide 21 text

collect use cases flickr/charliematters iOS control circular version of UISlider play nice with iOS7 customizable appearance

Slide 22

Slide 22 text

visual design principles Intensity HUE Orientation SIZE POSITION flickr/charliematters

Slide 23

Slide 23 text

visual design principles flickr/charliematters

Slide 24

Slide 24 text

visual design principles flickr/charliematters 34

Slide 25

Slide 25 text

visual design principles flickr/charliematters 34 have to read text no idea of the range

Slide 26

Slide 26 text

visual design principles flickr/charliematters 34 34

Slide 27

Slide 27 text

visual design principles flickr/charliematters 34 34 still not sure of range

Slide 28

Slide 28 text

visual design principles flickr/charliematters 34 34 34

Slide 29

Slide 29 text

visual design principles flickr/charliematters 34 34 34 hue difference

Slide 30

Slide 30 text

visual design principles flickr/charliematters 34 34 34 uses orientation difference

Slide 31

Slide 31 text

Your API is hard to change world visible testable documentation

Slide 32

Slide 32 text

Your API is hard to change world visible testable documentation

Slide 33

Slide 33 text

hard to change world visible /** Current volume level. In decibels */ @property (nonatomic, assign) CGFloat volume; /** Minimum volume level. In decibels */ @property (nonatomic, assign) CGFloat minVolume; /** Maximum volume level. In decibels */ @property (nonatomic, assign) CGFloat maxVolume;

Slide 34

Slide 34 text

hard to change world visible /** Current volume level. In decibels */ @property (nonatomic, assign) CGFloat volume; /** Minimum volume level. In decibels */ @property (nonatomic, assign) CGFloat minVolume; /** Maximum volume level. In decibels */ @property (nonatomic, assign) CGFloat maxVolume; /** Current opacity level. */ @property (nonatomic, assign) CGFloat opacity; /** Minimum opacity level. */ @property (nonatomic, assign) CGFloat minOpacity; /** Maximum opacity level. */ @property (nonatomic, assign) CGFloat maxOpacity;

Slide 35

Slide 35 text

Your API is hard to change world visible testable documentation

Slide 36

Slide 36 text

Your API is hard to change world visible testable documentation - (void)test_valueShouldBeClippedToBounds { ... } - (void)test_valueShouldBeClippedWhenNewExtremaProvided { ... }

Slide 37

Slide 37 text

Your API is hard to change world visible testable documentation

Slide 38

Slide 38 text

testable documentation #pragma mark - Knob Appearance /** Specifies the angle of the start of the knob control track. Defaults to -11π/8. */ @property (nonatomic, assign) CGFloat startAngle; /** Specifies the end angle of the knob control track. Defaults to 3π/8. */ @property (nonatomic, assign) CGFloat endAngle; /** Specifies the width in points of the knob control track. Defaults to 2.0. */ @property (nonatomic, assign) CGFloat lineWidth; /** Specifies the length in points of the pointer on the knob. Defaults to 6.0. */ @property (nonatomic, assign) CGFloat pointerLength; @property (nonatomic, assign) CGFloat startAngle; @property (nonatomic, assign) CGFloat endAngle; @property (nonatomic, assign) CGFloat lineWidth; @property (nonatomic, assign) CGFloat pointerLength;

Slide 39

Slide 39 text

hard to change world visible testable documentation /** Contains the current value Setting this value will redraw the knob with the correct specified value. To animate to the new value use `setValue:animated:` method instead. If you set the value outside of the allowed range then it will be clipped to the appropriate extremum. */ @property (nonatomic, assign) CGFloat value;

Slide 40

Slide 40 text

Your API should be discussed & iterated created early minimal platform friendly

Slide 41

Slide 41 text

Your API should be discussed & iterated @property (nonatomic, assign) CGFloat startAngle; @property (nonatomic, assign) CGFloat endAngle; @property (nonatomic, assign) CGFloat lineWidth; @property (nonatomic, assign) CGFloat pointerLength; @property (nonatomic, assign) CGPoint startPosition; @property (nonatomic, assign) CGPoint endPosition; @property (nonatomic, assign) CGFloat innerRadius; @property (nonatomic, assign) CGFloat outerRadius; @property (nonatomic, assign) CGFloat pointerLength; created early minimal

Slide 42

Slide 42 text

Your API should be discussed & iterated created early minimal platform friendly

Slide 43

Slide 43 text

Your API should be discussed & iterated created early minimal platform friendly

Slide 44

Slide 44 text

minimal @property (nonatomic, assign) CGFloat value; @property (nonatomic, assign) CGFloat minimumValue; @property (nonatomic, assign) CGFloat maximumValue; @property (nonatomic, assign) CGFloat stepSize; @property (nonatomic, assign) BOOL allowOutOfBounds; - (void)setPathForTrack:(UIBezierPath *)path; - (void)setShapeForPointer:(UIBezierPath *)path; @property (nonatomic, retain) UIColor *trackColor; @property (nonatomic, retain) UIColor *pointerColor; @property (nonatomic, retain) UIColor *lowTrackColor; @property (nonatomic, retain) UIColor *highTrackColor; @property (nonatomic, assign) BOOL blendTrackColors; @property (nonatomic, retain) UIColor *innerCircleCentralGradientColor; @property (nonatomic, retain) UIColor *innerCircleBoundaryGradientColor; @property (nonatomic, assign) BOOL showInnerCircleGradient; @property (nonatomic, retain) UIColor *textColor; @property (nonatomic, retain) UIFont *font;

Slide 45

Slide 45 text

Your API should be minimal @property (nonatomic, assign) CGFloat value; @property (nonatomic, assign) CGFloat minimumValue; @property (nonatomic, assign) CGFloat maximumValue; discussed & iterated created early

Slide 46

Slide 46 text

minimal #pragma mark - Knob Appearance /** Specifies the angle of the start of the knob control track. Defaults to -11π/8. */ @property (nonatomic, assign) CGFloat startAngle; /** Specifies the end angle of the knob control track. Defaults to 3π/8. */ @property (nonatomic, assign) CGFloat endAngle; /** Specifies the width in points of the knob control track. Defaults to 2.0. */ @property (nonatomic, assign) CGFloat lineWidth; /** Specifies the length in points of the pointer on the knob. Defaults to 6.0. */ @property (nonatomic, assign) CGFloat pointerLength;

Slide 47

Slide 47 text

Your API should be discussed & iterated created early minimal platform friendly

Slide 48

Slide 48 text

Your API should be @property (nonatomic, assign) CGFloat value; - (void)setValue:(CGFloat)value animated:(BOOL)animated; @property (nonatomic, assign) CGFloat minimumValue; @property (nonatomic, assign) CGFloat maximumValue; @property (nonatomic, assign, getter = isContinuous) BOOL continuous; discussed & iterated created early minimal platform friendly

Slide 49

Slide 49 text

Software Design interaction pattern appropriate subclassing separation of concerns match the platform fl ickr/wscullin

Slide 50

Slide 50 text

Software Design interaction pattern appropriate subclassing separation of concerns match the platform fl ickr/wscullin

Slide 51

Slide 51 text

appropriate subclassing fl ickr/wscullin Responds to events Forms responder chain UIResponder KnobControl

Slide 52

Slide 52 text

appropriate subclassing fl ickr/wscullin Responds to events Forms responder chain UIResponder KnobControl Drawing?

Slide 53

Slide 53 text

appropriate subclassing fl ickr/wscullin Describes UI drawing Gesture recognizers High-level animation UIResponder UIView KnobControl

Slide 54

Slide 54 text

appropriate subclassing fl ickr/wscullin Describes UI drawing Gesture recognizers High-level animation UIResponder UIView KnobControl Interaction?

Slide 55

Slide 55 text

appropriate subclassing fl ickr/wscullin UIResponder UIView KnobControl In-built target-action pattern iOS standard subclasses UIControl

Slide 56

Slide 56 text

appropriate subclassing fl ickr/wscullin @interface SKKnobControl : UIControl ... @end UIResponder UIView KnobControl UIControl

Slide 57

Slide 57 text

interaction pattern appropriate subclassing separation of concerns match the platform fl ickr/wscullin Software Design

Slide 58

Slide 58 text

interaction pattern appropriate subclassing fl ickr/wscullin - (void)createKnobControl { ... [_knobControl addTarget:self action:@selector(knobValueChanged:) forControlEvents:UIControlEventValueChanged]; } - (void)knobValueChanged:(id)sender { // Handle the new value } [self sendActionsForControlEvents:UIControlEventValueChanged]; target-action Control View controller

Slide 59

Slide 59 text

interaction pattern appropriate subclassing fl ickr/wscullin extremely simple integral part of UIControl multiple targets handled target-action

Slide 60

Slide 60 text

interaction pattern appropriate subclassing fl ickr/wscullin extremely simple integral part of UIControl multiple targets handled target-action limited events additional methods

Slide 61

Slide 61 text

interaction pattern appropriate subclassing fl ickr/wscullin [_knobControl addObserver:self forKeyPath:@"value" options:0 context:NULL]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // Demos KVO binding with the knob control if(object == _knobControl && [keyPath isEqualToString:@"value"]) { // Handle the changed value } } target-action KVO View controller View controller

Slide 62

Slide 62 text

interaction pattern appropriate subclassing fl ickr/wscullin target-action KVO integral part of NSObject multiple targets handled don’t have to alter control

Slide 63

Slide 63 text

interaction pattern appropriate subclassing fl ickr/wscullin + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { if ([key isEqualToString:@"value"]) { return NO; } else { return [super automaticallyNotifiesObserversForKey:key]; } } - (void)setValue:(CGFloat)value { // Chain with the animation method version [self setValue:value animated:NO]; } target-action KVO Control Control

Slide 64

Slide 64 text

interaction pattern appropriate subclassing fl ickr/wscullin - (void)setValue:(CGFloat)value animated:(BOOL)animated { if(value != _value) { // Send KVO notification [self willChangeValueForKey:@"value"]; // Save the value to the backing ivar // Make sure we limit it to the requested bounds _value = [self clipToBounds:value]; // Update the UI here ... [self didChangeValueForKey:@"value"]; } } target-action KVO Control

Slide 65

Slide 65 text

interaction pattern appropriate subclassing fl ickr/wscullin target-action KVO integral part of NSObject multiple targets handled don’t have to alter control... only suitable for value changes all routed through one method ...unless advanced behavior

Slide 66

Slide 66 text

interaction pattern appropriate subclassing fl ickr/wscullin @property (nonatomic,copy) void(^valueChangeHandler)(double value); if (self.selectionHandler != NULL) { self.valueChangeHandler(self.value); } - (void)createKnobControl{ ... _knobControl.valueChangeHandler = ^(double value) { // Value is the newly selected value } } target-action KVO command Control Control View controller

Slide 67

Slide 67 text

interaction pattern appropriate subclassing fl ickr/wscullin target-action KVO command fewer methods complete customization

Slide 68

Slide 68 text

interaction pattern appropriate subclassing fl ickr/wscullin target-action KVO command fewer methods complete customization multiple handlers hard potential for retain cycles syntax fun

Slide 69

Slide 69 text

interaction pattern appropriate subclassing fl ickr/wscullin [self.delegate knob:self didChangeValue:self.value]; @interface ViewController - (void)createKnobControl { ... _knobControl.delegate = self; } - (void)knob:(SKKnobControl*)knob didChangeValue:(CGFloat)value { // Handle the new value } target-action KVO command delegation Control View controller @protocol SKKnobControlDelegate - (void)knob:(SKKnobControl *)knob didChangeValue:(CGFloat)value; @end Delegate Protocol

Slide 70

Slide 70 text

interaction pattern appropriate subclassing fl ickr/wscullin completely customizable methods well-understood apple pattern useful for complex controls target-action KVO command delegation

Slide 71

Slide 71 text

interaction pattern appropriate subclassing fl ickr/wscullin completely customizable methods well-understood apple pattern useful for complex controls target-action KVO command delegation multiple delegates problematic boiler plate delegate checking potential for overkill

Slide 72

Slide 72 text

interaction pattern appropriate subclassing fl ickr/wscullin if (self.delegate && [self.delegate respondsToSelector: @selector(knob:didChangeValue:)] ) { [self.delegate knob:self didChangeValue:self.value]; } id< SKKnobControlDelegate > delegateProxy = (id< SKKnobControlDelegate >)[[SDDelegateProxy alloc] initWithDelegate:self.delegate]; [delegateProxy knob:self didChangeValue:self.value]; SDDelegateProxy target-action KVO command delegation

Slide 73

Slide 73 text

interaction pattern appropriate subclassing fl ickr/wscullin completely customizable methods well-understood apple pattern useful for complex controls target-action KVO command delegation multiple delegates problematic boiler plate delegate checking potential for overkill

Slide 74

Slide 74 text

interaction pattern appropriate subclassing fl ickr/wscullin target-action KVO command delegation SK Knob Control update label text link UISlider with SKKnobControl random value button press

Slide 75

Slide 75 text

interaction pattern appropriate subclassing separation of concerns match the platform fl ickr/wscullin Software Design

Slide 76

Slide 76 text

interaction pattern separation of concerns fl ickr/wscullin \ API Appearance Interaction Logic

Slide 77

Slide 77 text

interaction pattern separation of concerns fl ickr/wscullin \ API Appearance Interaction Logic @interface SKAnnulusSegmentLayerRenderer : NSObject #pragma mark - Properties associated with the background annulus @property (nonatomic, readonly, strong) CALayer *annulusLayer; ... #pragma mark - Animation Control Updates - (void)setPointerAngle:(CGFloat)pointerAngle animated:(BOOL)animated; @end

Slide 78

Slide 78 text

interaction pattern separation of concerns fl ickr/wscullin \ API Appearance Interaction Logic @interface SKKnobGestureRecognizer : UIPanGestureRecognizer ... @end

Slide 79

Slide 79 text

interaction pattern separation of concerns fl ickr/wscullin \ API Appearance Interaction Logic

Slide 80

Slide 80 text

interaction pattern appropriate subclassing separation of concerns match the platform fl ickr/wscullin Software Design

Slide 81

Slide 81 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 82

Slide 82 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 83

Slide 83 text

fl ickr/wscullin UIView Core Graphics CALayer Open GLES - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *ring = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:self.startAngle endAngle:self.endAngle clockwise:YES]; ... CGContextAddPath(context, ring.CGPath); CGContextStrokePath(context); ... UIBezierPath *pointer = [UIBezierPath bezierPath]; [pointer moveToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds) - self.pointerLength - self.annulusLineWidth/2.f, CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; [pointer addLineToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds), CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; ... CGContextAddPath(context, pointer.CGPath); CGContextStrokePath(context); }

Slide 84

Slide 84 text

fl ickr/wscullin UIView Core Graphics CALayer Open GLES - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *ring = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:self.startAngle endAngle:self.endAngle clockwise:YES]; ... CGContextAddPath(context, ring.CGPath); CGContextStrokePath(context); ... UIBezierPath *pointer = [UIBezierPath bezierPath]; [pointer moveToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds) - self.pointerLength - self.annulusLineWidth/2.f, CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; [pointer addLineToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds), CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; ... CGContextAddPath(context, pointer.CGPath); CGContextStrokePath(context); }

Slide 85

Slide 85 text

fl ickr/wscullin UIView Core Graphics CALayer Open GLES - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); UIBezierPath *ring = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:self.startAngle endAngle:self.endAngle clockwise:YES]; ... CGContextAddPath(context, ring.CGPath); CGContextStrokePath(context); ... UIBezierPath *pointer = [UIBezierPath bezierPath]; [pointer moveToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds) - self.pointerLength - self.annulusLineWidth/2.f, CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; [pointer addLineToPoint:CGPointMake(CGRectGetWidth(self.pointerLayer.bounds), CGRectGetHeight(self.pointerLayer.bounds) / 2.f)]; ... CGContextAddPath(context, pointer.CGPath); CGContextStrokePath(context); }

Slide 86

Slide 86 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 87

Slide 87 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 88

Slide 88 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 89

Slide 89 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 90

Slide 90 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 91

Slide 91 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 92

Slide 92 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES

Slide 93

Slide 93 text

separation of concerns match the platform fl ickr/wscullin UIView Core Graphics CALayer Open GLES @floriankugler floriankugler.com

Slide 94

Slide 94 text

separation of concerns match the platform fl ickr/wscullin _annulusLayer = [CALayer layer]; self.annulusLayer.delegate = self; - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { // Now have a CoreGraphics context in which to render } CGContextSaveGState(layerContext); { UIBezierPath *ring = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:self.startAngle endAngle:self.endAngle clockwise:YES]; ... CGContextAddPath(layerContext, ring.CGPath); CGContextStrokePath(layerContext); } CGContextRestoreGState(layerContext);

Slide 95

Slide 95 text

separation of concerns match the platform fl ickr/wscullin - (void)setPointerAngle:(CGFloat)pointerAngle animated:(BOOL)animated { if(pointerAngle != _pointerAngle) { _pointerAngle = pointerAngle; [CATransaction begin]; if(animated) { [CATransaction setAnimationDuration:3.f]; } else { [CATransaction setDisableActions:YES]; } self.pointerLayer.transform = CATransform3DMakeRotation(pointerAngle, 0, 0, 1); [CATransaction commit]; } } - (void)setAnnulusColor:(UIColor *)annulusColor { if(annulusColor != _annulusColor) { _annulusColor = annulusColor; [self.annulusLayer setNeedsDisplay]; } }

Slide 96

Slide 96 text

Other concerns accessibility distribution localization legacy flickr/twicepix

Slide 97

Slide 97 text

Other concerns accessibility distribution legacy flickr/twicepix localization

Slide 98

Slide 98 text

Other concerns accessibility distribution legacy flickr/twicepix localization

Slide 99

Slide 99 text

Other concerns accessibility distribution legacy flickr/twicepix localization

Slide 100

Slide 100 text

SK Knob Control

Slide 101

Slide 101 text

SK Knob Control sammyd / SKKnobControl

Slide 102

Slide 102 text

Sounds like a lot of Work

Slide 103

Slide 103 text

cocoapods

Slide 104

Slide 104 text

cocoacontrols

Slide 105

Slide 105 text

shinobicontrols

Slide 106

Slide 106 text

GO Create! flickr/charliematters shinobicontrols.com/360idev @iwantmyrealname github/sammyd