Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing dotted line between the cells of UICollectionView

I have to achieve something like this(please check attached ScreenShot). The best possible solution which came to my mind is UICollectionView. I have created rounded border along the cell and put a button on the cell. Now for the dotted straight line I have used CAShapeLayer with BezierPath and added the layer to my collectionview background with background color set to clear color. Here comes the problem, I am not seeing any dotted line. Here is what I have tried. Now I have couple of questions. While answering please consider me as a beginner.

1) Why my lines are not showing.
2) How to calculate the width between the two cell, right now i am setting a random number.
3) Is there any other approach which can solve this use-case more efficiently.

required

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UIBezierPath *borderPath;
CGFloat borderWidth;

KKCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
borderPath = [UIBezierPath bezierPath];

[borderPath moveToPoint:CGPointMake(cell.frame.origin.x + cell.frame.size.width -1 , cell.frame.origin.y + 2)];
[borderPath addLineToPoint:CGPointMake(cell.frame.origin.x + cell.frame.size.width + 50,  cell.frame.origin.y +2)];
borderWidth = 1.0;
[self dottedLineWithPath:borderPath withborderWidth:borderWidth];
return cell; 
}

/** creating dotted line **/

- (void)dottedLineWithPath:(UIBezierPath *)path withborderWidth:(CGFloat)lineWidth
{
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.strokeStart = 0.0;
shapelayer.strokeColor = [UIColor blackColor].CGColor;
shapelayer.lineWidth = borderWidth;
shapelayer.lineJoin = kCALineJoinRound;
shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2 ], nil];
shapelayer.path = path.CGPath;

[self.milestoneCollection.backgroundView.layer addSublayer:shapelayer];
  }

Here is what I have achieved till now.enter image description here

like image 449
Kunal Kumar Avatar asked Jun 10 '16 11:06

Kunal Kumar


2 Answers

Hi done this using UICollectionViewLayout and draw functions.

screenshot

Code uploaded in path https://github.com/arunpkumar92/OrderStatusCollectionView

- (void)reloadConnections{

    for (CAShapeLayer *shapelayer in self.connectionLines) {
        [shapelayer removeFromSuperlayer];
    }

    self.connectionLines = [NSMutableArray array];

    for (int i = 0; i <= (self.total-2); i++){
        NSArray *paths = [self pathForconnectionLineForIndexPath:i];
        for (UIBezierPath *path in paths) {
            UIColor *color = (self.currentLevel > i) ? [UIColor colorWithRed:0.078 green:0.557 blue:0.067 alpha:1.00] : [UIColor colorWithRed:0.957 green:0.659 blue:0.024 alpha:1.00];
            CAShapeLayer *shapelayer = [self drawdottedLineWithPath:path withborderWidth:1.0f withColor:color];
            [self.collectionView.layer addSublayer:shapelayer];
            [self.connectionLines addObject:shapelayer];
        }
    }

}

- (NSArray *)pathForconnectionLineForIndexPath:(int)index{
    NSMutableArray *paths = [NSMutableArray array];

    int row = index%self.noOfItems;
    int column = index/self.noOfItems;
    CGFloat cellWidth = self.view.frame.size.width/self.noOfItems;
    CGFloat cellHeight = self.view.frame.size.width/self.noOfItems;
    CGFloat connectionLength = cellHeight/2;

    CGFloat leftPadding;
    CGFloat topPadding;

    if (row != (self.noOfItems-1)) {
        leftPadding = cellWidth/4;
        topPadding = cellHeight/2;

        UIBezierPath *borderPath = [UIBezierPath bezierPath];
        CGFloat startX = (3*leftPadding)+(row*cellWidth);
        CGFloat startY = topPadding+(column*cellHeight);
        CGFloat endX = startX + connectionLength;
        CGFloat endY = startY;
        [borderPath moveToPoint:CGPointMake(startX , startY)];
        [borderPath addLineToPoint:CGPointMake(endX,  endY)];
        [self drawDirectionAt:CGPointMake((startX+(connectionLength/2)) , startY) isEnabled:(self.currentLevel > index) isRight:YES];
        [paths addObject:borderPath];

    }

    if ( (row == (self.noOfItems-1)) && (column%2 == 0) ) {
        leftPadding = cellWidth/2;
        topPadding = cellHeight/8;

        UIBezierPath *borderPath = [UIBezierPath bezierPath];
        CGFloat startX = leftPadding+(row*cellWidth);
        CGFloat startY = (7*topPadding)+(column*cellHeight);
        CGFloat endX = startX;
        CGFloat endY = startY + (2*topPadding);
        [borderPath moveToPoint:CGPointMake(startX , startY)];
        [borderPath addLineToPoint:CGPointMake(endX,  endY)];
        [self drawDirectionAt:CGPointMake(startX , (startY+topPadding)) isEnabled:(self.currentLevel > index) isRight:NO];
        [paths addObject:borderPath];

    }

    if ( (row == 0) && (column%2 != 0) ) {
        leftPadding = cellWidth/2;
        topPadding = cellHeight/8;

        UIBezierPath *borderPath = [UIBezierPath bezierPath];
        CGFloat startX = leftPadding+(row*cellWidth);
        CGFloat startY = (7*topPadding)+(column*cellHeight);
        CGFloat endX = startX;
        CGFloat endY = startY + (2*topPadding);
        [borderPath moveToPoint:CGPointMake(startX , startY)];
        [borderPath addLineToPoint:CGPointMake(endX,  endY)];
        [self drawDirectionAt:CGPointMake(startX , (startY+topPadding)) isEnabled:(self.currentLevel > index) isRight:NO];
        [paths addObject:borderPath];

    }

    return paths;
}

- (CAShapeLayer *)drawdottedLineWithPath:(UIBezierPath *)path withborderWidth:(CGFloat)lineWidth withColor: (UIColor *)color
{


    CAShapeLayer *shapelayer = [CAShapeLayer layer];
    shapelayer.strokeStart = 0.0;
    shapelayer.strokeColor = color.CGColor;
    shapelayer.lineWidth = lineWidth;
    shapelayer.lineJoin = kCALineJoinRound;
    shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:15],[NSNumber numberWithInt:10 ], nil];
    shapelayer.path = path.CGPath;

    return shapelayer;

}

- (void)drawDirectionAt:(CGPoint)point isEnabled:(BOOL)enabled isRight:(BOOL)isRight{
    NSString *imageName;
    imageName = isRight ? @"right-arrow" : @"down-arrow";
    imageName = [NSString stringWithFormat:@"%@-%@",imageName,(enabled ? @"enabled" : @"disabled")];

    CAShapeLayer* direction = [CAShapeLayer layer];
    direction.opacity = 1.0;
    direction.frame = CGRectMake(0, 0, 16, 16);
    direction.position = point;
    [direction setContents:(id)[[UIImage imageNamed: imageName] CGImage]];
    [self.connectionLines addObject:direction];
    [self.collectionView.layer addSublayer: direction];
}
like image 185
Arun Kumar P Avatar answered Oct 19 '22 14:10

Arun Kumar P


Why my lines are not showing.

To get your drawing visible, you have to add your drawing code directly to UICollectionView instance and not to it's backgroundView property.

[self.milestoneCollection.layer addSublayer:shapelayer];

This works 100%. Using backgroundView may seem like the right thing to do, but they might be using it for completely something else. I doubt backgroundView is either not visible at this time (using default configuration) or ignoring our change requests altogether.

Lines_Drawn_Center_To_Center

How to calculate the width between the two cell, right now i am setting a random number.

-(void)getLineStartPoint:(CGPoint*)startPoint
             andEndPoint:(CGPoint*)endPoint
     fromCellAtIndexPath:(NSIndexPath*)fromIndexPath
       toCellAtIndexPath:(NSIndexPath*)toIndexPath
{
    UICollectionViewLayoutAttributes* fromAttributes =
    [self.milestoneCollection layoutAttributesForItemAtIndexPath:fromIndexPath];

    UICollectionViewLayoutAttributes* toAttributes =
    [self.milestoneCollection layoutAttributesForItemAtIndexPath:toIndexPath];

    *startPoint = fromAttributes.center;
    *endPoint = toAttributes.center;
}

UICollectionViewLayoutAttributes is 100% feature rich in geometrical information about cells, headers, footers, decorationViews at a particular indexPath. For simplicity, I'm now using center. I believe you need to use your logic with frame property.

Is there any other approach, which can solve this use-case more efficiently.

YES, there are always two ways.

  • Getting it done.
  • Getting it done RIGHT.

Of course RIGHT is different according to the situation and time available.

KEEP IN MIND

  • Be careful with indexPath availability or you would very easily crash. Here's a simplest form of guard for this-

    NSInteger numberOfItems = [self collectionView:collectionView numberOfItemsInSection:indexPath.section];
    
    if(indexPath.item < numberOfItems-1)
    {
        CGPoint startPoint, endPoint;
        NSIndexPath* toIndexPath =
        [NSIndexPath indexPathForItem:indexPath.item+1 inSection:indexPath.section];
    
        [self getLineStartPoint:&startPoint
                    andEndPoint:&endPoint
            fromCellAtIndexPath:indexPath
              toCellAtIndexPath:toIndexPath];
    
        [borderPath moveToPoint:startPoint];
        [borderPath addLineToPoint:endPoint];
        borderWidth = 1.0;
        [self dottedLineWithPath:borderPath withborderWidth:borderWidth];
    }
    
  • Also when you try to scroll it (if needed), layers/paths are added every time you create a cell (this results in dark dotted lines due to multiple times adding in the layer hierarchy), you need to take care of this when reusing cells. Have a look at this :-

Line_Drawn_Multiple_Times.png

That should answer all of your questions. Best Of Luck!!

like image 45
Tarun Tyagi Avatar answered Oct 19 '22 12:10

Tarun Tyagi