简体   繁体   中英

need help for speeding up custom UIView's subclass

I've started to write a UIView subclass. It will be a control that will let the user scroll blocks and press any of them, very similar to UITableView . Please check the links to images I posted below.

http://i.piccy.info/i7/0577902c1bdba79ac3a26a5d3ce322f9/4-50-285/17627834/2012_12_12_14_06_11.jpg

http://i.piccy.info/i7/91a0a1eec95d74e7c13a789fc69a5582/4-50-285/20953097/2012_12_12_14_06_39.jpg

Here's the source code: (.h file)

#import <UIKit/UIKit.h>
#import "Quartzcore/Quartzcore.h"

@interface OSBlockView : UIView
{
    CGFloat scrollPosition; // from 0 to 1
    CGFloat zoneForFlexion;

    CGSize blockSize;
    NSUInteger blockQuantity;
    CGFloat projection;

    NSDictionary *anglesForHeights;

    CGFloat oldTranslatedY;

    UIGestureRecognizer *panGesture;

    NSArray *blocks;
}

@property (nonatomic, readwrite) CGFloat scrollPosition;
@property (nonatomic, readwrite) CGFloat zoneForFlexion;
@property (nonatomic, readwrite) CGSize blockSize;
@property (nonatomic, readwrite) NSUInteger blockQuantity;
@property (nonatomic, readwrite) CGFloat projection;

@property (nonatomic, retain) NSDictionary *anglesForHeights;

@property (nonatomic, readwrite) CGFloat oldTranslatedY;

@property (nonatomic, retain) UIGestureRecognizer *panGesture;

@property (nonatomic, retain) NSArray *blocks;

- (id)initWithFrame:(CGRect)frame blockHeight:(CGFloat)bHeight projection:(CGFloat)proj andBlocks:(NSArray *)blcks;
+(NSDictionary *)calculateAllHeightsForAnglesWithBlockHeight:(CGFloat)blockHeight andProjection:(CGFloat)proj;
-(NSNumber *)getBlockTopWithBlockNumber:(NSNumber *)bn;
-(NSNumber *)angleValueForBlockHeight:(NSNumber *)h;

+(NSArray *)multicoloredViewsWithSize:(CGSize)size;

@end

and here's the .m file:

#import "OSBlockView.h"

@implementation OSBlockView

@synthesize scrollPosition, zoneForFlexion;
@synthesize blockSize, blockQuantity, projection;
@synthesize anglesForHeights;
@synthesize oldTranslatedY;
@synthesize panGesture;
@synthesize blocks;

int logForTimingIndex=0;
+(void)logFotTimingWithString:(NSString *)str
{
    NSLog(@"time log: %08i %@", logForTimingIndex, str);
    logForTimingIndex++;
}

- (id)initWithFrame:(CGRect)frame blockHeight:(CGFloat)bHeight projection:(CGFloat)proj andBlocks:(NSArray *)blcks;
{
    [[self class] logFotTimingWithString:@"begin init"];
    self = [super initWithFrame:frame];
    if (self)
    {
        self.blockSize=CGSizeMake(frame.size.width, bHeight);
        self.blockQuantity=[blcks count];
        self.blocks=blcks;
        self.projection=proj;
        self.anglesForHeights=[OSBlockView calculateAllHeightsForAnglesWithBlockHeight:bHeight andProjection:proj];
        self.scrollPosition=0.2;
        self.zoneForFlexion=frame.size.height*0.9;

        //adding pan gesture
        self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
        [self addGestureRecognizer:panGesture];
    };
    [[self class] logFotTimingWithString:@"end init"];
    return self;
}


+(NSArray *)multicoloredViewsWithSize:(CGSize)size
{
    [[self class] logFotTimingWithString:@"multicoloredViewsWithSize begin"];
    //generating multicoloured blocks
    NSMutableArray *blocks=[[NSMutableArray alloc] init];
    for (int i=0; i<13; i++)
    {
        UIColor *blockColor;
        switch (i)
        {
            case 0:
            {
                blockColor=[UIColor colorWithRed:0.75 green:0.75 blue:0.75 alpha:1.0];
                break;
            }
            case 1:
            {
                blockColor=[UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
                break;
            }
            case 2:
            {
                blockColor=[UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
                break;
            }
            case 3:
            {
                blockColor=[UIColor colorWithRed:0.5 green:0.0 blue:0.0 alpha:1.0];
                break;
            }
            case 4:
            {
                blockColor=[UIColor colorWithRed:1.0 green:1.0 blue:0.0 alpha:1.0];
                break;
            }
            case 5:
            {
                blockColor=[UIColor colorWithRed:0.5 green:0.5 blue:0.0 alpha:1.0];
                break;
            }
            case 6:
            {
                blockColor=[UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
                break;
            }
            case 7:
            {
                blockColor=[UIColor colorWithRed:0.0 green:0.5 blue:0.0 alpha:1.0];
                break;
            }
            case 8:
            {
                blockColor=[UIColor colorWithRed:0.0 green:1.0 blue:1.0 alpha:1.0];
                break;
            }
            case 9:
            {
                blockColor=[UIColor colorWithRed:0.0 green:0.5 blue:0.5 alpha:1.0];
                break;
            }
            case 10:
            {
                blockColor=[UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:1.0];
                break;
            }
            case 11:
            {
                blockColor=[UIColor colorWithRed:0.0 green:0.0 blue:0.5 alpha:1.0];
                break;
            }
            case 12:
            {
                blockColor=[UIColor colorWithRed:1.0 green:0.0 blue:1.0 alpha:1.0];
                break;
            }
            case 13:
            {
                blockColor=[UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];
                break;
            }
            default:
            {
                NSLog(@"PANIC: unknown block index");
                blockColor=[UIColor blackColor];
                break;
            }
        }
        UIView *bView=[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
        [bView setBackgroundColor:blockColor];
        [bView.layer setBorderWidth:3];
        [bView.layer setBorderColor:[[UIColor whiteColor] CGColor]];
        UILabel *blockLabel=[[UILabel alloc] initWithFrame:CGRectMake(2.5, 2.5, bView.frame.size.width-5, bView.frame.size.height-5)];
        [blockLabel setBackgroundColor:[UIColor clearColor]];
        [blockLabel setText:[NSString stringWithFormat:@"block number %i", i]];
        [blockLabel setFont:[UIFont boldSystemFontOfSize:20]];
        [blockLabel setTextColor:[UIColor whiteColor]];
        [blockLabel setTextAlignment:UITextAlignmentCenter];

        [bView addSubview:blockLabel];
        [blocks addObject:bView];
    };
    [[self class] logFotTimingWithString:@"multicoloredViewsWithSize end"];
    return blocks;
}



- (void)drawRect:(CGRect)rect
{
    //NSLog(@"\n\n\n\n\n\n\n\n\n");
    [[self class] logFotTimingWithString:@"drawRect begin"];

    //NSLog(@"drawRect called");

    //NSLog(@"self.subview.count: %i", [self.subviews count]);

    //[[self class] logFotTimingWithString:@"subviews remove begin"];
    for (UIView* subview in self.subviews)
    {
        //NSLog(@"subview removed");
        [subview removeFromSuperview];
    };
    //[[self class] logFotTimingWithString:@"subviews remove end"];



    //calculating top coordinates of blocks
    //NSLog(@"calculating block tops...");

    //[[self class] logFotTimingWithString:@"calculating block tops begin"];
    NSMutableArray *blockTops=[[NSMutableArray alloc] init];
    for (unsigned int blockCounter=0; blockCounter<blockQuantity; blockCounter++)
    {
        [blockTops addObject:[self getBlockTopWithBlockNumber:[NSNumber numberWithInt:blockCounter]]];
    };
    //[[self class] logFotTimingWithString:@"calculating block tops end"];


    /*
     for (unsigned int index=0; index!=[blockTops count]; index++)
     {
     NSLog(@"top %i at %i", index, [[blockTops objectAtIndex:index] intValue]);
     };
     */

    //calculating block heights
    //NSLog(@"calculating block heights...");

    //[[self class] logFotTimingWithString:@"calculating block heights begin"];
    NSMutableArray *blockHeights=[[NSMutableArray alloc] init];
    for (unsigned int index=0; index<[blockTops count]; index++)
    {
        if (([blockTops count]-1)!=index)
        {
            [blockHeights addObject:[NSNumber numberWithInt:([[blockTops objectAtIndex:(index+1)] intValue]-[[blockTops objectAtIndex:index] intValue])]];
        }
        else
        {
            if ((self.frame.size.height-[[blockTops objectAtIndex:index] intValue])<blockSize.height)
            {
                [blockHeights addObject:[NSNumber numberWithInt:(self.frame.size.height-[[blockTops objectAtIndex:index] intValue])]];
            }
            else
            {
                [blockHeights addObject:[NSNumber numberWithInt:blockSize.height]];
            };
        };
    };
    //[[self class] logFotTimingWithString:@"calculating block heights end"];


    for (unsigned int blockCounter=0; blockCounter<blockQuantity; blockCounter++)
    {
        //NSLog(@"\n\n");
        //[[self class] logFotTimingWithString:@"draw cycle begin"];

        //[self preLoadBlockAtY:[blockTops objectAtIndex:blockCounter] withHeight:[blockHeights objectAtIndex:blockCounter] andIndex:[NSNumber numberWithUnsignedInt:blockCounter]];

        //NSLog(@"    preLoadingSampleBlockAtY: %g withHeight: %g andIndex: %i", [y doubleValue], [height doubleValue], [index unsignedIntValue]);

        NSNumber *y=[blockTops objectAtIndex:blockCounter];
        NSNumber *height=[blockHeights objectAtIndex:blockCounter];
        //NSNumber *index=[NSNumber numberWithUnsignedInt:blockCounter];
        //NSLog(@"loading block %02u y:%g h:%g", [index unsignedIntValue], [y doubleValue], [height doubleValue]);
        if ([height intValue]>2)
        {


            BOOL up;
            if ([y doubleValue]>self.frame.size.height/2.0)
            {
                up=true;
            }
            else
            {
                up=false;
            };

            //[[self class] logFotTimingWithString:@"bview init begin"];

            UIView *bView=[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[blocks objectAtIndex:blockCounter]]];            
            [bView setFrame:CGRectMake(0.0, [y doubleValue], self.frame.size.width, blockSize.height)];
            //[bView.layer setMasksToBounds:YES];



            //[[self class] logFotTimingWithString:@"bview init end"];

            //NSLog(@"    blockImage frame y: %g", blockImage.frame.origin.y);
            //NSLog(@"    blockImage frame height:   %g", blockImage.frame.size.height);

            double oldHeight=bView.frame.size.height;

            //NSLog(@"    preanchor: %@", NSStringFromCGRect(bView.frame));


            //[[self class] logFotTimingWithString:@"bview anchor begin"];

            //setting anchor point
            if (up)
            {
                [bView.layer setAnchorPoint:CGPointMake(0.5, 1.0)];
                [bView setFrame:CGRectMake(bView.frame.origin.x, bView.frame.origin.y+bView.frame.size.height/2.0, bView.frame.size.width, bView.frame.size.height)];
            }
            else
            {
                [bView.layer setAnchorPoint:CGPointMake(0.5, 0.0)];
                [bView setFrame:CGRectMake(bView.frame.origin.x, bView.frame.origin.y-bView.frame.size.height/2.0, bView.frame.size.width, bView.frame.size.height)];
            };

            //[[self class] logFotTimingWithString:@"bview anchor end"];

            //NSLog(@"    postanchor: %@", NSStringFromCGRect(bView.frame));


            //[[self class] logFotTimingWithString:@"bview transform begin"];

            if ([height doubleValue]!=blockSize.height)
            {

                //preparing transform
                CATransform3D basicTrans = CATransform3DIdentity;
                basicTrans.m34 =1.0/-projection;

                //calculating angle
                double angle= [[self angleValueForBlockHeight:height] doubleValue];
                double rangle;
                if (up)
                {
                    rangle=angle/360*(2.0*M_PI);
                }
                else
                {
                    rangle=(360.0-angle)/360*(2.0*M_PI);
                };


                //NSLog(@"    angle: %g", angle);

                //transforming
                bView.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);

                //NSLog(@"    blockImage frame y: %g", blockImage.frame.origin.y);
                //NSLog(@"    blockImage frame height:   %g", blockImage.frame.size.height);
            };

            //[[self class] logFotTimingWithString:@"bview transform end"];

            //[[self class] logFotTimingWithString:@"bview post transform begin"];

            double newHeight=bView.frame.size.height;

            if (up)
            {
                [bView setCenter:CGPointMake(bView.center.x, bView.center.y-(oldHeight-newHeight))];
            };

            //NSLog(@"    blockImage frame y: %g", bView.frame.origin.y);
            //NSLog(@"    blockImage frame height:   %g", bView.frame.size.height);


            //adding subview
            [self addSubview:bView];

            //[[self class] logFotTimingWithString:@"bview post transform end"];
        }
        else
        {
            //do not need to draw blocks with very low height
        };
        //[[self class] logFotTimingWithString:@"draw cycle end"];
        //NSLog(@"\n\n");
    };
    [[self class] logFotTimingWithString:@"drawRect end"];
    //NSLog(@"\n\n\n\n\n\n\n\n\n\n");
}

-(NSNumber *)angleValueForBlockHeight:(NSNumber *)h
{
    //[[self class] logFotTimingWithString:@"    angleValueForBlockHeight begin"];

    //NSLog(@"angleValueForBlockHeight: %g  called", [h doubleValue]);

    //searching for closest key
    double minDistance=blockSize.height;
    double distance;
    NSString *minKey=@"";
    for(NSString *key in anglesForHeights)
    {
        if ([[anglesForHeights objectForKey:key] doubleValue]==[h doubleValue])
        {
            //match found
            //NSLog(@"returned: %g", [key doubleValue]);
            return [NSNumber numberWithDouble:[key doubleValue]];
        };
        distance=fabs([[anglesForHeights objectForKey:key] doubleValue]-[h doubleValue]);
        if (distance<minDistance)
        {
            minDistance=distance;
            minKey=key;
        };
    };
    //NSLog(@"returned: %g", [blockSizesForAngles objectForKey:minKey]);

    //[[self class] logFotTimingWithString:@"    angleValueForBlockHeight end"];
    return [NSNumber numberWithDouble:[minKey doubleValue]];
}


+(NSDictionary *)calculateAllHeightsForAnglesWithBlockHeight:(CGFloat)blockHeight andProjection:(CGFloat)proj
{
    //NSLog(@"calculateAllHeightsForAnglesWithBlockHeight:%g andProjection:%g called", blockHeight, proj);

    //[[self class] logFotTimingWithString:@"    calculateAllHeightsForAnglesWithBlockHeight:andProjection begin"];

    NSMutableDictionary *res=[[NSMutableDictionary alloc] init];
    for (double i=0.0; i<=90.0; i=i+1.0)
    {
        //NSLog(@"i: %g", i);
        UIView *block=[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, blockHeight)];        
        [block.layer setAnchorPoint:CGPointMake(0.5, 1.0)];


        CATransform3D basicTrans = CATransform3DIdentity;   
        double rangle;
        basicTrans.m34 =1.0/-proj;
        rangle=i/360*(2.0*M_PI);

        //NSLog(@"rangle: %g Pi", rangle/M_PI);

        block.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
        //NSLog(@"calculated block height: %g for angle: %g", block.frame.size.height, i);
        if ([res objectForKey:[NSString stringWithFormat:@"%i", (int)roundf(i)]]==nil)
        {
            [res setObject:[NSString stringWithFormat:@"%i", (int)floor(block.frame.size.height)] forKey:[NSNumber numberWithDouble:i]];
        };
    };
    //NSLog(@"        result (size: %i): %@", [res count], [res debugDescription]);

    //[[self class] logFotTimingWithString:@"    calculateAllHeightsForAnglesWithBlockHeight:andProjection end"];
    return [NSDictionary dictionaryWithDictionary:res];
}


-(NSNumber *)getBlockTopWithBlockNumber:(NSNumber *)bn
{
    //NSLog(@"getBlockTopWithBlockNumber: %i", [bn intValue]);
    //[[self class] logFotTimingWithString:@"    getBlockTopWithBlockNumber begin"];

    int t=[bn intValue];
    CGFloat h=self.blockSize.height;
    float v = self.blockQuantity * h - self.zoneForFlexion;
    float vp = v * self.scrollPosition;

    float z = t * h;
    float alpha = (self.frame.size.height-self.zoneForFlexion) / (self.scrollPosition*self.scrollPosition + (1-self.scrollPosition) * (1-self.scrollPosition));

    float f;
    if (z < vp)
    {
        f = z / v;
        //NSLog(@"        %i", (int)(alpha * (f * f)));
        //[[self class] logFotTimingWithString:@"    getBlockTopWithBlockNumber end"];
        return [NSNumber numberWithInt:(int)(alpha * (f * f))];
    };
    if (z <= vp + self.zoneForFlexion)
    {
        //NSLog(@"        %i", (int)(z + alpha * (self.scrollPosition * self.scrollPosition) - vp));
        //[[self class] logFotTimingWithString:@"    getBlockTopWithBlockNumber end"];
        return [NSNumber numberWithInt:(int)(z + alpha * (self.scrollPosition * self.scrollPosition) - vp)];
    }
    else
    {
        f = (blockQuantity*h-z) / v;
        //NSLog(@"        %i", (int)(self.frame.size.height - alpha * (f * f)));
        //[[self class] logFotTimingWithString:@"    getBlockTopWithBlockNumber end"];
        return [NSNumber numberWithInt:(int)(self.frame.size.height - alpha * (f * f))];
    };
}

double oldTranslatedY=0.0;

-(IBAction)handlePanGesture:(UIPanGestureRecognizer *)sender
{
    //NSLog(@"handlePanGesture: called");
    //[[self class] logFotTimingWithString:@"    handlePanGesture begin"];

    double translatedY = [sender translationInView:self].y;
    double delta;

    if (fabs(translatedY)<fabs(oldTranslatedY))
    {
        [sender setTranslation:CGPointZero inView:self];
        oldTranslatedY=0.0;
        delta=0.0;
    }
    else
    {
        delta=translatedY-oldTranslatedY;
        oldTranslatedY=translatedY;
    };

    //NSLog(@"translatedY: %g", translatedY);
    double pOffset=delta/((blockQuantity*blockSize.height)-self.frame.size.height);

    self.scrollPosition=self.scrollPosition-pOffset;

    if (self.scrollPosition<0.0)
    {
        self.scrollPosition=0.0;
    }
    else if(self.scrollPosition>1.0)
    {
        self.scrollPosition=1.0;
    };

    [[self class] logFotTimingWithString:@"    handlePanGesture end"];

    [self setNeedsDisplay];
}

Everything is working fine on simulator. But it is too slow on the real device.

I need help in 2 things:

  1. In the moment of drawing the blocks: If I know what height value the block should have, how can I calculate the angle which I will use to rotate the block to fit height? It also depends on projection value. Now it is working with an ugly solution that precalculates heights for all angles (from 0 to 90) after the view is initialized and saves to dictionary, and when we need to know the specific angle for block rotation to fit height, we just search for the closest value of height from this hardcoded dictionary and use the appropriate angle. It is toooo ugly. I tried to avoid this by calculating the needed value during drawing, but I had no luck (need formula).

  2. Optimization in another parts.

Help, me please, I've stuck on this problem.

Put calculations on another thread. Avoid working with GUI in background threads! :)

[self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM