//swift var bp = UIBezierPath() let startPoint = CGPointMake(200, 400) let endPoint = CGPointMake(500, 400) let controlPoint = CGPointMake(350, 150) bp.moveToPoint(startPoint) bp.addQuadCurveToPoint(endPoint, controlPoint:controlPoint) var vp = CAShapeLayer() //debug visual path vp.path = bp.CGPath vp.fillColor = UIColor.clearColor().CGColor vp.strokeColor = UIColor.darkGrayColor().CGColor vp.lineWidth = 1.0 self.view.layer.addSublayer(vp) //http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_B.C3.A9zier_curves func getBezierQuadXY(t:CGFloat, p0:CGFloat, p1:CGFloat, p2:CGFloat) -> CGFloat { return ((1-t) * (1-t)) * p0 + 2 * (1-t) * t * p1 + t * t * p2 } func addNode(pathPercent:CGFloat) { var node = CALayer() node.frame = CGRectMake(0, 0, 20, 20) node.cornerRadius = 10.0 node.borderColor = UIColor.darkGrayColor().CGColor node.borderWidth = 1.0 let p = CGPointMake(getBezierQuadXY(pathPercent, startPoint.x, controlPoint.x, endPoint.x), getBezierQuadXY(pathPercent, startPoint.y, controlPoint.y, endPoint.y)) node.position = p self.view.layer.addSublayer(node) } addNode(0.5) addNode(0.7) addNode(0.8) addNode(0.89) addNode(1.0)
2/28/15
iOS Draw Along Bezier Quad Curve Path
2/24/15
iOS Autolayout Constraints on Stacked Views
The containing view (gray border) vertically resizes to fit child views.
Containing view is hidden when child view heights total 0.
5 screenshots
1. View layout before applying constraints
2. 3 child views, heights 100
3. Green view height 0
4. Green and blue view height 0
5. Red view height 20, green view height 40
@implementation ViewController { UIView * cv; //container view, clear w/gray border UIView * tv; //top view, red UIView * mv; //middle view, green UIView * bv; //bottom view, blue NSDictionary * bindings; NSMutableArray * verticalConstraints; } -(void)viewDidLoad { [super viewDidLoad]; [self createControls]; [self setupConstraints]; } -(void)setupConstraints { self.view.translatesAutoresizingMaskIntoConstraints = NO; cv.translatesAutoresizingMaskIntoConstraints = NO; tv.translatesAutoresizingMaskIntoConstraints = NO; mv.translatesAutoresizingMaskIntoConstraints = NO; bv.translatesAutoresizingMaskIntoConstraints = NO; bindings = NSDictionaryOfVariableBindings(cv, tv, mv, bv); verticalConstraints = [NSMutableArray array]; [self setupHorizontalConstraints]; [self setupVerticalConstraints]; } -(void)setupHorizontalConstraints { NSMutableArray * hConstraints = [NSMutableArray array]; //container view [hConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[cv(200)]" options:0 metrics:nil views:bindings]]; //top view [hConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[tv]-|" options:0 metrics:nil views:bindings]]; //match middle view w/top view [hConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[mv(tv)]" options:0 metrics:nil views:bindings]]; //match bottom view w/top view [hConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[bv(tv)]" options:0 metrics:nil views:bindings]]; [self.view addConstraints:hConstraints]; } -(void)setupVerticalConstraints { [self.view removeConstraints:verticalConstraints]; [verticalConstraints removeAllObjects]; [verticalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[cv]-20-|" options:0 metrics:nil views:bindings]]; //view heights, container vertical margin NSDictionary * metrics = @{ @"th" : @(tv.frame.size.height), @"mh" : @(mv.frame.size.height), @"bh" : @(bv.frame.size.height), @"cm" : @(tv.frame.size.height + mv.frame.size.height + bv.frame.size.height > 0 ? 8 : 0) }; [verticalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-cm-[tv(th)][mv(mh)][bv(bh)]-cm-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:bindings]]; [self.view addConstraints:verticalConstraints]; } //init controls w/temp width and position for general layout, before implementing constraints -(void)createControls { int vWidth = 100; int vHeight = 100; int cHeight = vHeight * 3; cv = [[UIView alloc] initWithFrame:CGRectMake(20, 20, vWidth, cHeight)]; tv = [[UIView alloc] initWithFrame:CGRectMake(10, 0, vWidth, vHeight)]; mv = [[UIView alloc] initWithFrame:CGRectMake(10, 100, vWidth, vHeight)]; bv = [[UIView alloc] initWithFrame:CGRectMake(10, 200, vWidth, vHeight)]; cv.layer.borderColor = [UIColor lightGrayColor].CGColor; cv.layer.borderWidth = 1; tv.backgroundColor = [UIColor redColor]; mv.backgroundColor = [UIColor greenColor]; bv.backgroundColor = [UIColor blueColor]; [self.view addSubview:cv]; [cv addSubview:tv]; [cv addSubview:mv]; [cv addSubview:bv]; } -(void)hideView:(UIView *)aView { aView.frame = CGRectMake(0, 0, aView.frame.size.width, 0); [self setupVerticalConstraints]; } -(void)resizeView:(UIView *)aView height:(int)aHeight { aView.frame = CGRectMake(0, 0, aView.frame.size.width, aHeight); [self setupVerticalConstraints]; } -(void)showView:(UIView *)aView { aView.frame = CGRectMake(0, 0, aView.frame.size.width, 100); [self setupVerticalConstraints]; }
2/21/15
iOS Bee Flight Simulator
Bees animate from left to right along bezier path
Tap to toggle visible flight paths
iPad landscape screenshots
Key Tech Features
Core Graphics Layers
Scalable Bezier Paths
Core Animation
Tap to toggle visible flight paths
iPad landscape screenshots
Key Tech Features
Core Graphics Layers
Scalable Bezier Paths
Core Animation
CAShapeLayer * visibleFlightPath1; //ivars CAShapeLayer * visibleFlightPath2; -(void)setup { CALayer * b1 = getBumblebee(); CALayer * b2 = getBumblebee(); CGPathRef fp1 = getFlightPath(0, 1.0f, 1.0f); CGPathRef fp2 = getFlightPath(80, 3.0f, 0.8f); CAKeyframeAnimation * a1 = getAnimationWithPath(fp1, 3.3, 0); [b1 addAnimation:a1 forKey:nil]; [self.view.layer addSublayer:b1]; CAKeyframeAnimation * a2 = getAnimationWithPath(fp2, 3.9, 0); [b2 addAnimation:a2 forKey:nil]; [self.view.layer addSublayer:b2]; visibleFlightPath1 = getVisibleFlightPath(fp1); visibleFlightPath2 = getVisibleFlightPath(fp2); [self.view.layer addSublayer:visibleFlightPath1]; [self.view.layer addSublayer:visibleFlightPath2]; } CAGradientLayer * getBumblebee() { CAGradientLayer * b = [CAGradientLayer layer]; b.frame = CGRectMake(0, 0, 25, 25); b.borderColor = [UIColor darkGrayColor].CGColor; b.borderWidth = 0.5f; b.cornerRadius = 10.0f; b.masksToBounds = YES; CGColorRef black = [UIColor blackColor].CGColor; CGColorRef yellow = [UIColor yellowColor].CGColor; b.colors = @[ (__bridge id)black, (__bridge id)yellow, (__bridge id)black, (__bridge id)yellow ]; //vertical gradient b.startPoint = CGPointMake(0.0f, 0.5f); b.endPoint = CGPointMake(1.0f, 0.5f); b.locations = @[ [NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.33], [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.9], [NSNumber numberWithFloat:1.0] ]; return b; } CGPathRef getFlightPath(const int yOffset, const CGFloat factor1, const CGFloat factor2) { const CGPoint startPoint = CGPointMake(20, (200 + (yOffset * factor1))); const CGFloat normalFlightPoints[] = { 220, 200, 420, 200, 620, 200, 820, 200 }; UIBezierPath * fp = [UIBezierPath bezierPath]; [fp moveToPoint:startPoint]; //add normal flight points const int count = sizeof(normalFlightPoints)/sizeof(CGFloat); const int cpOffset = 100; const CGFloat cpYOffsetFactor = (cpOffset * factor1); for (int i = 0, s = 0; i < count; i++, s++) { const CGFloat x = normalFlightPoints[i]; const CGFloat y = normalFlightPoints[i + 1] + (yOffset * factor1); const CGPoint p = CGPointMake(x, y); const CGFloat cpYHighLowOffset = ((s % 2) ? cpYOffsetFactor : -cpYOffsetFactor); const CGPoint cp = CGPointMake((p.x - cpOffset), (p.y + cpYHighLowOffset)); [fp addQuadCurveToPoint:p controlPoint:cp]; //connect flight points i += 1; } //add pseudo random flight points const CGFloat w = (yOffset * factor1) * factor2; [fp addQuadCurveToPoint:CGPointMake(700, 175 + w) controlPoint:CGPointMake(850, 150 + w)]; [fp addQuadCurveToPoint:CGPointMake(900, 200 + w) controlPoint:CGPointMake(550, 200 + w)]; const CGFloat randomYOffset = (arc4random_uniform(2) == 0 ? -cpOffset : cpOffset); [fp addCurveToPoint:CGPointMake(1200, fp.currentPoint.y + randomYOffset) controlPoint1:CGPointMake(1000, fp.currentPoint.y) controlPoint2:CGPointMake(1100, fp.currentPoint.y)]; return fp.CGPath; } CAKeyframeAnimation * getAnimationWithPath(CGPathRef aPath, CFTimeInterval aDuration, CFTimeInterval aTimeOffset) { CAKeyframeAnimation * a = [CAKeyframeAnimation animation]; a.keyPath = @"position"; a.path = aPath; a.duration = aDuration; a.timeOffset = aTimeOffset; a.repeatCount = HUGE_VALF; a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return a; } CAShapeLayer * getVisibleFlightPath(CGPathRef aPath) { CAShapeLayer * fp = [CAShapeLayer layer]; fp.path = aPath; fp.fillColor = [UIColor clearColor].CGColor; fp.strokeColor = [UIColor darkGrayColor].CGColor; fp.lineWidth = 1.0f; fp.zPosition = -1.0f; return fp; } //tap to toggle visible flight paths -(void)tap:(UIGestureRecognizer*)sender { visibleFlightPath1.hidden = !visibleFlightPath1.hidden; visibleFlightPath2.hidden = !visibleFlightPath2.hidden; } -(void)viewDidLoad { [super viewDidLoad]; UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; [self.view addGestureRecognizer:tap]; [self setup]; }
Subscribe to:
Posts (Atom)