SpriteKit解坑系列(四):进度条

//
//  TCProgressBarNode.h
//  TCProgressBarNode
//
//  Created by Charles Chamblee on 7/23/15.
//  Copyright (c) 2015 Tony Chamblee. All rights reserved.
//

#import <SpriteKit/SpriteKit.h>

@interface TCProgressBarNode : SKNode

/** Current progress of the progress bar, value between 0.0 and 1.0 */
@property (nonatomic) CGFloat progress;

/** Configurable title label, displayed centered in the progress bar by default */
@property (nonatomic, strong, readonly) SKLabelNode *titleLabelNode;

- (void)setFillColor:(UIColor *)fillColor;

/** Initialize a plain progress bar with the given colors and sizes. */
- (instancetype)initWithSize:(CGSize)size
             backgroundColor:(UIColor *)backgroundColor
                   fillColor:(UIColor *)fillColor
                 borderColor:(UIColor *)borderColor
                 borderWidth:(CGFloat)borderWidth
                cornerRadius:(CGFloat)cornerRadius;

/** Initialize a custom progress bar with the given textures for background, fill and overlay layers */
- (instancetype)initWithBackgroundTexture:(SKTexture *)backgroundTexture
                              fillTexture:(SKTexture *)fillTexture
                           overlayTexture:(SKTexture *)overlayTexture;

@end

//
//  TCProgressBarNode.m
//  TCProgressBarNode
//
//  Created by Charles Chamblee on 7/23/15.
//  Copyright (c) 2015 Tony Chamblee. All rights reserved.
//

#import "TCProgressBarNode.h"

@interface TCProgressBarNode ()

@property (nonatomic, strong) SKSpriteNode *backgroundSpriteNode;
@property (nonatomic, strong) SKCropNode *fillCropNode;
@property (nonatomic, strong) SKSpriteNode *fillSpriteNode;
@property (nonatomic, strong) SKSpriteNode *overlaySpriteNode;

@property (nonatomic, strong) SKTexture *backgroundTexture;
@property (nonatomic, strong) SKTexture *fillTexture;
@property (nonatomic, strong) SKTexture *overlayTexture;

@property (nonatomic, strong) UIColor *backgroundColor;
@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, strong) UIColor *borderColor;

@property (nonatomic, strong) SKLabelNode *titleLabelNode;;

@property (nonatomic) CGSize size;
@property (nonatomic) CGFloat cornerRadius;
@property (nonatomic) CGFloat borderWidth;

@end

@implementation TCProgressBarNode

#pragma mark - Properties

- (void)setProgress:(CGFloat)progress
{
    if (_progress != progress)
    {
        _progress = MIN(MAX(progress, 0.0), 1.0);
        
        [self progressDidChange];
    }
}

#pragma mark - Init / Dealloc

- (instancetype)init
{
    return [self initWithSize:CGSizeZero
              backgroundColor:nil
                    fillColor:nil
                  borderColor:nil
                  borderWidth:0.0
                 cornerRadius:0.0];
}

- (instancetype)initWithSize:(CGSize)size
             backgroundColor:(UIColor *)backgroundColor
                   fillColor:(UIColor *)fillColor
                 borderColor:(UIColor *)borderColor
                 borderWidth:(CGFloat)borderWidth
                cornerRadius:(CGFloat)cornerRadius
{
    NSParameterAssert(backgroundColor);
    NSParameterAssert(fillColor);
    NSParameterAssert(borderColor);
    
    self = [super init];
    
    if (self)
    {
        _size = size;
        _backgroundColor = backgroundColor;
        _fillColor = fillColor;
        _borderColor = borderColor;
        _cornerRadius = cornerRadius;
        _borderWidth = borderWidth;
        
        [self progressBarNodeCommonInit];
    }
    
    return self;
}

- (instancetype)initWithBackgroundTexture:(SKTexture *)backgroundTexture
                              fillTexture:(SKTexture *)fillTexture
                           overlayTexture:(SKTexture *)overlayTexture
{
    NSParameterAssert(backgroundTexture);
    NSParameterAssert(fillTexture);
    NSParameterAssert(overlayTexture);
    
    self = [super init];
    
    if (self)
    {
        _backgroundTexture = backgroundTexture;
        _fillTexture = fillTexture;
        _overlayTexture = overlayTexture;
        
        [self progressBarNodeCommonInit];
    }
    
    return self;
}

#pragma mark - Initialization

- (void)progressBarNodeCommonInit
{
    _progress = 0.0;
    
    [self initializeBackgroundSpriteNode];
    [self initializeFillCropNode];
    [self initializeFillSpriteNode];
    [self initializeOverlaySpriteNode];
    [self initializeTitleLabelNode];
    [self progressDidChange];
}

- (void)initializeBackgroundSpriteNode
{
    /* If a custom background texture wasn't provided, we generate a custom texture from
     CAShapeLayer (because SKShapeNode sucks) */
    if (!_backgroundTexture)
    {
        [self initializeBackgroundTexture];
    }
    
    _backgroundSpriteNode = [SKSpriteNode spriteNodeWithTexture:_backgroundTexture];
    
    [self addChild:_backgroundSpriteNode];
}

- (void)initializeBackgroundTexture
{
    CAShapeLayer *backgroundLayer = [self newShapeLayerFromBoundsPath];
    
    backgroundLayer.lineWidth = 0.0f;
    backgroundLayer.fillColor = _backgroundColor.CGColor;
    
    _backgroundTexture = [self textureFromLayer:backgroundLayer];
}

/* The fill crop node uses the same texture as the background sprite to mask the fill sprite,
 effectively masking it to the outline of the bar background. */
- (void)initializeFillCropNode
{
    _fillCropNode = [SKCropNode new];
    
    // mask to background texture
    _fillCropNode.maskNode = [SKSpriteNode spriteNodeWithTexture:_backgroundTexture];
    _fillCropNode.zPosition = 18;
    [self addChild:_fillCropNode];
}

/* The fill sprite node is sandwiched between the background sprite node and the overlay sprite node,
 and it is masked to the background texture by the fill crop node */
- (void)initializeFillSpriteNode
{
    if (_fillTexture)
    {
        _fillSpriteNode = [SKSpriteNode spriteNodeWithTexture:_fillTexture];
    }
    else
    {
        _fillSpriteNode = [SKSpriteNode spriteNodeWithColor:_fillColor size:_size];
    }
    
    _fillSpriteNode.anchorPoint = CGPointMake(0.0f, 0.5f);
    _fillSpriteNode.position = CGPointMake(-round(_size.width / 2.0), 0.0);
    _fillSpriteNode.zPosition = 19;
    
    [_fillCropNode addChild:_fillSpriteNode];
}

- (void)setFillColor:(UIColor *)fillColor {
    _fillColor = fillColor;
    [_fillSpriteNode setColor:_fillColor];
}

/* The overlay sprite node sits on top of the fill and background sprite nodes, giving the proper appearance
  of a bar being filled on the inside */
- (void)initializeOverlaySpriteNode
{
    /* If a custom overlay texture wasn't provided, we generate a custom texture from
     CAShapeLayer (because SKShapeNode sucks) */
    if (!_overlayTexture)
    {
        [self initializeOverlayTexture];
    }
    
    _overlaySpriteNode = [SKSpriteNode spriteNodeWithTexture:_overlayTexture];
    _overlaySpriteNode.zPosition = 20;
    [self addChild:_overlaySpriteNode];
}

- (void)initializeOverlayTexture
{
    CAShapeLayer *shapeLayer = [self newShapeLayerFromBoundsPath];
    
    shapeLayer.strokeColor = _borderColor.CGColor;
    shapeLayer.lineWidth = _borderWidth;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    
    _overlayTexture = [self textureFromLayer:shapeLayer];
}

- (void)initializeTitleLabelNode
{
    _titleLabelNode = [[SKLabelNode alloc] initWithFontNamed:@"Helvetica"];
    
    _titleLabelNode.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
    _titleLabelNode.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
    _titleLabelNode.fontColor = [UIColor whiteColor];
    _titleLabelNode.fontSize = 12.0f;
    _titleLabelNode.zPosition = 21;
    
    [self addChild:_titleLabelNode];
}

#pragma mark - Utility

- (CAShapeLayer *)newShapeLayerFromBoundsPath
{
    CAShapeLayer *shapeLayer = [CAShapeLayer new];
    CGFloat halfBorderWidth = round(_borderWidth / 2.0);

    shapeLayer.frame = CGRectMake(0.0, 0.0, _size.width, _size.height);
    
    /* Inset the path so that we don't stroke outside of our bounds */
    shapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(shapeLayer.bounds, halfBorderWidth, halfBorderWidth) cornerRadius:_cornerRadius].CGPath;
    
    return shapeLayer;
}

/* We are going from CALayer -> UIImage -> SKTexture here, so that we can create SKSpriteNodes instead of SKShapeNodes (SKShapeNode doesn't work well with SKCropNode),
 which are then plugged into the maskNode property of a SKCropNode.  This approach also conveniently allows us to use the same code path for custom textures and generated 
 textures if you just want a basic progress bar */
- (SKTexture *)textureFromLayer:(CALayer *)layer
{
    CGFloat width = layer.frame.size.width;
    CGFloat height = layer.frame.size.height;
    
    // value of 0 for scale will use device's main screen scale
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetAllowsAntialiasing(context, YES);
    CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
    CGContextFillRect(context, CGRectMake(0.0, 0.0, width, height));
    
    [layer renderInContext:context];
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    SKTexture *texture = [SKTexture textureWithImage:image];
    
    return texture;
}

#pragma mark - Interface Updates

- (void)progressDidChange
{
    CGFloat halfBorderWidth = round(_borderWidth / 2.0);

    CGFloat fillWidth = self.size.width - self.borderWidth;
    CGFloat fillHeight = self.size.height - self.borderWidth;
    CGFloat width = halfBorderWidth + round(fillWidth * _progress);
    CGFloat height = halfBorderWidth + fillHeight;
    
    _fillSpriteNode.size = CGSizeMake(width, height);
}

@end


你可能感兴趣的:(SpriteKit解坑系列(四):进度条)