Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Drawing a Rounded Rect Custom Alert

AllahAllah Posts: 144Registered Users
edited April 2012 in iPhone SDK Development
Hey guys,

I'm trying to make a kind of overlay view that looks similar to the attached image (excuse the poor-quality drawing and picture).

I want this view to be black on the inside, but to have a white boundary. At the moment I've looked at a few tutorials to see how the effect might be achieved:

- I looked at this folder animation tutorial because it has the arrow effect that I want.
- Then, because my view will change size I figure that I wouldn't be able to just photoshop it. So I looked up how to draw rounded rectangles.

Now the problem that I have is with the second link, whenever I draw something using it, there's a black rounded rectangle, but it looks like a square cut-out (because of the white boundary, look at second attached image).

I want to spin these two tutorials to achieve the effect, however the big problem is including the arrow in the CGContextPath.

Any help would be greatly appreciated!
IMG_0598.jpg
1 x 1 - 125K
Screen Shot 2012-04-04 at 13.24.07.png
1 x 1 - 8K
Post edited by Allah on

Replies

  • DomeleDomele Posts: 2,991Registered Users @ @ @ @ @
    edited April 2012
    You should just make an image of it and resize it on the fly by using UIImage's – resizableImageWithCapInsets: for iOS 5+ or – stretchableImageWithLeftCapWidth:topCapHeight: for iOS 5-.
    If you are looking for a quality developer, I'm your man. Give me a PM if you are interested.<br />
    <br />
    New app - See screenshots and details at <a href="http://www.globaclock.com" target="_blank">www.globaclock.com</a>.<br />
    <br />
    If you want to
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    Domele wrote: »
    You should just make an image of it and resize it on the fly by using UIImage's – resizableImageWithCapInsets: for iOS 5+ or – stretchableImageWithLeftCapWidth:topCapHeight: for iOS 5-.

    But then if it were to get stretched downwards then the white strip on the top and bottom would be bigger than the left and right strips.

    EDIT: Been searching relentlessly and found this clever little snippet Now I've just got to find a way to incorporate the arrow
  • Duncan CDuncan C Posts: 9,028Tutorial Authors, Registered Users @ @ @ @ @ @ @
    edited April 2012
    Allah wrote: »
    But then if it were to get stretched downwards then the white strip on the top and bottom would be bigger than the left and right strips.

    EDIT: Been searching relentlessly and found this clever little snippet Now I've just got to find a way to incorporate the arrow

    No it wouldn't. Stretchable images have top/bottom/left/right regions that are not changed at all. It just duplicates pixels at the edges of the ends to fill in. That's what you want.

    Alertnately, you could create the shape you want manually using a CGPath and

    CGPathMoveToPoint/CGPathAddLineToPoint/CGPathAddArcToPoint.

    Here is a link that explains how to draw your own rounded rectangle:

    Drawing Rounded Rectangles | Cocoanetics

    You could insert a couple of line segments in that which would give you the arrow that you want.
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    Duncan C wrote: »
    Alertnately, you could create the shape you want manually using a CGPath and

    CGPathMoveToPoint/CGPathAddLineToPoint/CGPathAddArcToPoint.

    Here is a link that explains how to draw your own rounded rectangle:

    Drawing Rounded Rectangles | Cocoanetics

    You could insert a couple of line segments in that which would give you the arrow that you want.

    But as soon as I add the arrow in, the border is just going to shift up a few pixels, because the border will just go around the whole rectangular view.
  • Duncan CDuncan C Posts: 9,028Tutorial Authors, Registered Users @ @ @ @ @ @ @
    edited April 2012
    Allah wrote: »
    But as soon as I add the arrow in, the border is just going to shift up a few pixels, because the border will just go around the whole rectangular view.

    Sorry, I don't understand what you mean. Post your code.

    The second illustration in your samples is far too small to be recognizable. Can you post to an image hosting site like imageShack and post a link rather than uploading the file to this board?
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    Duncan C wrote: »
    Sorry, I don't understand what you mean. Post your code.

    The second illustration in your samples is far too small to be recognizable. Can you post to an image hosting site like imageShack and post a link rather than uploading the file to this board?

    Okay ignore the second picture. Here is what I'm trying to achieve http://imageshack.us/photo/my-images/651/img0599ov.jpg/

    And here is the code that I am currently using:
    - (void)drawRect:(CGRect)rect {
    
        CGContextRef context=UIGraphicsGetCurrentContext();
        CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); 
        CGContextSetLineWidth(context, 5);
        
        CGFloat radius = 5.0; 
    
        CGFloat minx = CGRectGetMinX(rect), midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect); 
        CGFloat miny = CGRectGetMinY(rect), midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect); 
        CGFloat arrowBottomX = 30;
        
        CGContextMoveToPoint(context, minx, midy); 
        CGContextAddArcToPoint(context, minx, miny, arrowBottomX-30, miny, radius); 
        
        CGContextAddLineToPoint(context, arrowBottomX, miny-30);
        CGContextAddLineToPoint(context, arrowBottomX+30, miny);
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); 
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); 
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); 
        CGContextClosePath(context); 
        CGContextDrawPath(context, kCGPathFillStroke);
        
    }
    

    But with that code the edges aren't very good and the arrow seems to go off the bounds of the UIView.
  • Duncan CDuncan C Posts: 9,028Tutorial Authors, Registered Users @ @ @ @ @ @ @
    edited April 2012
    Allah wrote: »
    Okay ignore the second picture. Here is what I'm trying to achieve ImageShack® - Online Photo and Video Hosting

    And here is the code that I am currently using:
    - (void)drawRect:(CGRect)rect {
    
        CGContextRef context=UIGraphicsGetCurrentContext();
        CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); 
        CGContextSetLineWidth(context, 5);
        
        CGFloat radius = 5.0; 
    
        CGFloat minx = CGRectGetMinX(rect), midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect); 
        CGFloat miny = CGRectGetMinY(rect), midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect); 
        CGFloat arrowBottomX = 30;
        
        CGContextMoveToPoint(context, minx, midy); 
        CGContextAddArcToPoint(context, minx, miny, arrowBottomX-30, miny, radius); 
        
        CGContextAddLineToPoint(context, arrowBottomX, miny-30);
        CGContextAddLineToPoint(context, arrowBottomX+30, miny);
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); 
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); 
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); 
        CGContextClosePath(context); 
        CGContextDrawPath(context, kCGPathFillStroke);
        
    }
    

    But with that code the edges aren't very good and the arrow seems to go off the bounds of the UIView.

    I'd probably do this with a CAShapeLayer added as an additional layer in my view, rather than doing it in drawRect.

    In general you are better off if you don't override the drawRect method.
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    Duncan C wrote: »
    I'd probably do this with a CAShapeLayer added as an additional layer in my view, rather than doing it in drawRect.

    In general you are better off if you don't override the drawRect method.

    Thanks for your help, I managed to figure it out literally right before you posted. Turns out that all I needed to do was to make the layer transparent (ie. opaque = NO) and draw the arrow within the bounds instead of extending out of it.
  • Duncan CDuncan C Posts: 9,028Tutorial Authors, Registered Users @ @ @ @ @ @ @
    edited April 2012
    Allah wrote: »
    Thanks for your help, I managed to figure it out literally right before you posted. Turns out that all I needed to do was to make the layer transparent (ie. opaque = NO) and draw the arrow within the bounds instead of extending out of it.

    Why don't you post your code so others can learn from it?
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    Duncan C wrote: »
    Why don't you post your code so others can learn from it?
    CGContextRef context=UIGraphicsGetCurrentContext();
        CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); 
        CGContextSetLineWidth(context, 3);
        
        CGFloat radius = 5.0; 
    
        CGFloat minx = CGRectGetMinX(rect)+5, midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect)-5; 
        CGFloat miny = CGRectGetMinY(rect)+35, midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect)-5; 
        CGFloat arrowBottomX = 60;
    
        CGContextMoveToPoint(context, minx, midy); 
        CGContextAddArcToPoint(context, minx, miny, arrowBottomX, miny, radius); 
        CGContextAddLineToPoint(context, arrowBottomX-30, miny);
        CGContextAddLineToPoint(context, arrowBottomX, miny-30);
        CGContextAddLineToPoint(context, arrowBottomX+30, miny);
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); 
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); 
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); 
        CGContextClosePath(context); 
        CGContextDrawPath(context, kCGPathFillStroke);
    

    and then in where it's declared:
    self.h = [[HelpView alloc] initWithFrame:CGRectMake(10.0, 20.0, 300.0, 150.0)];
    
        self.h.opaque = NO;
        self.h.alpha = 0.9;
        [self.view addSubview:self.h];
    

    I've still got a few things to add to it such as handling text and I want to create a method where you just select a point for the arrow to point to and then everything is calculated off of that.
  • AllahAllah Posts: 144Registered Users
    edited April 2012
    The effect I am trying to recreate is similar to the menu the shows up when you right click something in your dock

    Hey guys, I've been working on this for the past few days (when I've had time) but I'm running into a few problems with redrawing the view, I wish to release this into the public domain when it is finished. Here is the code in my class' .m file:
    -(id)initAtPoint:(CGPoint)point {
        
        if (point.y < 218) {
            rect1 = CGRectMake(10, point.y, 300, 480-44-20-point.y-10);
            
        }else {
            rect1 = CGRectMake(10, 10, 300, point.y-10);
        }
        self = [super initWithFrame:rect1];
        
        
        latest = point;
        
        return self;
    }
    
    - (void)drawRect:(CGRect)rect {
        
        NSLog(@"drawRect");
        if (context == nil) {
            context=UIGraphicsGetCurrentContext();
        }
        CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); 
        CGContextSetLineWidth(context, 3);
        
        CGFloat radius = 5.0; 
        
        CGFloat minx, midx, maxx, miny, midy, maxy;
        
        if (latest.y < 218) {
            minx = CGRectGetMinX(rect)+5, midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect)-5; 
            miny = CGRectGetMinY(rect)+35, midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect)-5; 
        }else {
            minx = CGRectGetMinX(rect)+5, midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect)-5; 
            miny = CGRectGetMinY(rect)+5, midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect)-35;
        }
        CGFloat arrowBottomX = 60;
        
        CGContextMoveToPoint(context, minx, midy); 
        CGContextAddArcToPoint(context, minx, miny, arrowBottomX, miny, radius); 
        
        if (latest.y < 218) {
            CGContextAddLineToPoint(context, latest.x-(arrowBottomX/2), miny);
            CGContextAddLineToPoint(context, latest.x, miny-(arrowBottomX/2));
            CGContextAddLineToPoint(context, latest.x+(arrowBottomX/2), miny);
        }
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); 
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); 
        
        if (latest.y > 218) {
            
            CGContextAddLineToPoint(context, latest.x+(arrowBottomX/2), maxy);
            CGContextAddLineToPoint(context, latest.x, maxy+(arrowBottomX/2));
            CGContextAddLineToPoint(context, latest.x-(arrowBottomX/2), maxy);
        }
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); 
        CGContextClosePath(context); 
        CGContextDrawPath(context, kCGPathFillStroke);
        
        textView = [[UITextView alloc] initWithFrame:CGRectMake(minx+5, miny, rect.size.width-10, rect.size.height-10)];
        //[textView setText:@"hello"];
        [textView setTextColor:[UIColor whiteColor]];
        [textView setBackgroundColor:[UIColor clearColor]];
        textView.editable = NO;
        textView.alpha = 0;
        
        tableViewUse = [[UITableView alloc] initWithFrame:CGRectMake(minx+5, miny+5, rect.size.width-20, rect.size.height-50)];
        tableViewUse.delegate = self;
        tableViewUse.dataSource = self;
        tableViewUse.alpha = 0;
        
        [textView removeFromSuperview];
        [tableViewUse removeFromSuperview];
        
        [self insertSubview:textView atIndex:1];
        [self insertSubview:tableViewUse atIndex:1];
        
        context = nil;
    }
    
    
    - (void)moveToPoint:(CGPoint)point {
        
        
        
        if (point.y < 218) {
            rect1 = CGRectMake(10, point.y, 300, 480-44-20-point.y-10);
            
        }else {
            rect1 = CGRectMake(10, 10, point.x-10, point.y-10);
        }
        
        [self setNeedsDisplayInRect:rect1];
    }
    
    - (void)text:(NSString *)string {
        tableViewUse.alpha = 0;
        [textView setText:string];
        textView.alpha = 1;
    }
    
    - (void)displayTableWithArray:(NSArray *)array {
        textView.alpha = 0;
        tableArray = array;
        tableViewUse.alpha = 1;
        [tableViewUse reloadData];
    }
    
    
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        JPTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        
        if (cell==nil) {
            cell = [[JPTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }
        [cell setTitle:[tableArray objectAtIndex:indexPath.row]];
        
        NSLog(@"cellForRowCalled");
        
        
        return cell;
    }
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        
        return [tableArray count];
    }
    

    Okay so a few things going on, firstly the initAtPoint method accepts the point of the arrow and then makes a suitable rectangle for it. That method is fine, it works completely. However, what is not fine is when i attempt to redraw the view when I call moveToPoint:, when [self setNeedsDisplayInRect:rect1] is called the view does redraw but it redraws over itself in the position define in the init method. So to combat this I tried to set my instance to nil and then recreate the view thusly:
    - (void)moveToPoint:(CGPoint)point {
        
        jp = nil;
        
        jp = [[JPOverlay alloc] initAtPoint:point];
        [self.view addSubview:jp];
    }
    

    Now this redraws the view in the correct place, but I can't get the UITableView/UITextView to show in this new instance. Also, I believe this may be messy as there will be a lot of alloc and dealloc going on if the user is trigger happy. So ideally I'd like to have the moveToPoint method in my original class.

    Thank you for any help!
Sign In or Register to comment.