开发者

Fastest way to render UIButtons using layers and Grand Central Dispatch?

I have a grid of 30 UIButtons and potentially even more, subclassed to be rendered using layers: a base CALayer, a CAShapeLayer, a CAGradientLayer and a CATextLayer. I am trying to minimize the overall time required to render/display the buttons when the corresponding xib file is loaded. If I simply setup each button in turn in viewDidLoad, the time required for the view to appear is about 5-6 seconds, which is clearly too much.

In order to speed-up the buttons setup, I am using Grand Central Dispatch as follows. In viewDidLoad, I setup each button layers using dispatch_async on the global queue (adding to the base layer the shape and the gradient layers), so that the buttons can be rendered in a different thread. A the end of the block, the CATextLayer is added to the gradient layer.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

    CGRect base_bounds = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height - self.layer.bounds.size.height * 0.10f);
    CGPoint move_point = CGPointMake(0.0f, base_bounds.size.height * 0.10f);
    self.layer.masksToBounds = NO;
    baseLayer = [CALayer layer];

    baseLayer.cornerRadius = 10.0;
    baseLayer.shadowOffset = CGSizeMake(0.0f, 2.0f);
    baseLayer.shadowOpacity = 1.5f;
    baseLayer.shadowColor = [UIColor blackColor].CGColor;
    baseLayer.shadowRadius = 2.5f;
    baseLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
    baseLayer.position    = move_point;

    CAShapeLayer *shape = [CALayer layer];
    shape.bounds = base_bounds;
    shape.cornerRadius = 10.0;
    shape.anchorPoint      = CGPointMake(0.0f, 0.0f);
    shape.position         = move_point;
    shape.backgroundColor = [UIColor darkGrayColor].CGColor;

    gradient = [CAGradientLayer layer];
    gradient.anchorPoint      = CGPointMake(0.0f, 0.0f);
    gradient.position         = CGPointMake(0.0f, 0.0f);
    gradient.bounds           = base_bounds;
    gradient.cornerRadius     = 10.0;
    gradient.borderColor      = [UIColor colorWithRed:0.72f
                                                green:0.72f
                                                 blue:0.72f
                                               开发者_如何转开发 alpha:1.0].CGColor;
    gradient.borderWidth      = 0.73;
    gradient.colors = [NSArray arrayWithObjects:
                       (id)[UIColor whiteColor].CGColor,
                       (id)[UIColor whiteColor].CGColor,
                       nil];


    [baseLayer addSublayer:shape];
    [baseLayer addSublayer:gradient];
    [self.layer addSublayer:baseLayer];

    [textLayer setBounds:gradient.bounds];
           [textLayer setPosition:CGPointMake(CGRectGetMidX(textLayer.bounds), CGRectGetMaxY(textLayer.bounds) - 6)];
           [textLayer setString:self.titleLabel.text]; 
           [textLayer setForegroundColor:[UIColor blackColor].CGColor];
           [gradient addSublayer:textLayer];

});

This approach reduce the overall time to about 2-3 seconds. I am wondering if anyone can suggest a faster way to render the buttons. Please note that I am not interested to any solution which discards the use of layers.

Thank you in advance.


Perhaps I'm missing the point but wouldn't you be better off overriding the UIButton drawRect: method and doing your drawing in CoreGraphics (CG) things would be drawn A LOT faster than seconds, you can easily do gradients, text, images with the CG API. If I understand correctly you have 4 layers per button and 30+ buttons in the same view (120+ layers)? If so, I don't think you are meant to draw so many layers (rendering/blending all of them individually would explain the huge render time). Another possibility would be to have 4 big layers, for all of the buttons.


Here's my idea,

Separate your CALayers from UIButton - UIKit won't allow anything on the background thread.

When you have an opportunity in a screen preceding the button grid, use [buttonGridViewController performSelectorInBackground:renderCALayers] to render your CALayers in the background.

When you load your button grid in viewDidLoad, overlay UIButtons with type UIButtonTypeCustom, and backgroundColor = [UIColor clearColor] over the top of your 'button' CALayers (make sure to call bringSubviewToFront on the UIButtons so they get the touch events).

If you don't know how many buttons you are rendering, you might want to pre-render the maximum number you might display.

If you have any questions please comment.

EDIT:

A few related items,

What to replace a UIButton to improve frame rate?

UI design - Best way to deal with many UIButtons

How to get touch event on a CALayer?

I believe the only way you'll get quicker load from this point is to either replace or remove UIButtons altogether and intercept touch events with another approach. That way all rendering can be done in a background thread before the view is presented.


Have you considered using NSOperation to generate your button's layers? In your code, you'd instantiate an operation queue and then iterate through each button, firing an operation (passing the respective parameters required to generate that button's layer) into the queue as each button is initiated.

Within your NSOperation, generate your layer using CoreGraphics, then pass the layer back to the button that initiated the operation (in this case, the destination button) via your completion handler (be sure to call that on the main thread). This should give you pretty good performance as your buttons layer's will be rendered off the main thread using CoreGraphics, then passed back to UIKit for presentation only after the layer has been rendered.

NSOperation sits atop GCD, has a marginal overhead but provides benefits in your case, such as the ability to set dependent prerequisites and prioritization.

Also, I'm pretty sure you shouldn't call UIColor in the GCD block you shared.

Hope this is helpful!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜