2

Please provide the code to make a tip balloon programmatically, like Grindr has.

I want it to be sized automatically, based on the text & font-size. And, I want to be able to change the location of the arrow. If it's on an end, it should be a right triangle. Otherwise, it should be an eqilateral triangle.

tip balloon

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Your question specifically asked how to make that balloon programatically, but if you created a PNG file, loaded it into a `UIImage` and sent the left/top cap width you would be done in, literally, 15 minutes. You don't get the dynamic positioning of the balloon-tip, but if you don't really need it, doing this the lazy way may be a better way. – kubi May 18 '11 at 20:09

3 Answers3

8
// AppDelegate.h

@interface AppDelegate : NSObject <UIApplicationDelegate> {

}

@property (nonatomic, retain) UIWindow *window;

@end

// AppDelegate.m

#import "AppDelegate.h"
#import "TipBalloon.h"

@implementation AppDelegate

@synthesize window;

#pragma mark NSObject

- (void)dealloc {
    [window release];
    [super dealloc];
}

#pragma mark UIApplicationDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Add the tip balloon.
    TipBalloon *textExample =
    [[TipBalloon alloc] initAtPoint:CGPointMake(6.0f, 30.0f) withText:
     @"Hello world! I like to make things. Yay! They are really cool things!"];
    [window addSubview:textExample];
    [textExample release];

    window.backgroundColor = [UIColor brownColor];
    [window makeKeyAndVisible];
    return YES;
}

@end

// TipBalloon.h

@interface TipBalloon : UIView {

}

@property (nonatomic, copy) NSString *text;

- (id)initAtPoint:(CGPoint)point withText:(NSString *)string;
- (void)drawOutlineInContext:(CGContextRef)context;
- (void)drawTextInContext:(CGContextRef)context;

@end

// TipBallon.m

#import "TipBalloon.h"

// TODO make some of these instance variables to add more customization.
static const CGFloat kArrowOffset = 0.0f;
static const CGFloat kStrokeWidth = 2.0f;
static const CGFloat kArrowSize = 14.0f;
static const CGFloat kFontSize = 12.0f;
static const CGFloat kMaxWidth = 196.0f;
static const CGFloat kMaxHeight = CGFLOAT_MAX;
static const CGFloat kPaddingWidth = 12.0f;
static const CGFloat kPaddingHeight = 10.0f;

@implementation TipBalloon

@synthesize text;

#pragma mark NSObject

- (void)dealloc {
    [text release];
    [super dealloc];
}

#pragma mark UIView

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    [self drawOutlineInContext:contextRef];
    [self drawTextInContext:contextRef];
}

#pragma mark TipBalloon

- (id)initAtPoint:(CGPoint)point withText:(NSString *)string {
    CGSize size = [string sizeWithFont:[UIFont systemFontOfSize:kFontSize]
                     constrainedToSize:CGSizeMake(kMaxWidth, kMaxHeight)
                         lineBreakMode:UILineBreakModeWordWrap];
    CGRect rect = CGRectMake(point.x, point.y, size.width+kPaddingWidth*2.0f,
                             size.height+kPaddingHeight*2.0f+kArrowSize);
    if ((self = [super initWithFrame:rect])) {
        self.text = string;
        UIColor *clearColor = [[UIColor alloc] initWithWhite:0.0f alpha:0.0f];
        self.backgroundColor = clearColor;
        [clearColor release];
    }
    return self;
}

- (void)drawOutlineInContext:(CGContextRef)context {
    CGRect rect = self.bounds;
    rect.origin.x += (kStrokeWidth/2.0f);
    rect.origin.y += kStrokeWidth + kArrowSize;
    rect.size.width -= kStrokeWidth;
    rect.size.height -= kStrokeWidth*1.5f + kArrowSize;

    CGFloat radius = 11.0f;
    CGFloat x_left = rect.origin.x;
    CGFloat x_right = x_left + rect.size.width;
    CGFloat y_top = rect.origin.y;
    CGFloat y_bottom = y_top + rect.size.height;

    CGContextBeginPath(context);
    CGContextSetLineWidth(context, kStrokeWidth);
    CGContextSetRGBStrokeColor(context, 0.0f/255.0f, 255.0f/255.0f, 0.0f/255.0f, 1.0f); // green
    CGContextSetGrayFillColor(context, 1.0f, 1.0f); // white background
    CGContextMoveToPoint(context, x_left+radius, y_top);
    CGContextAddLineToPoint(context, x_left+radius+kArrowOffset, y_top);

    // Draw triangle.
//    CGContextAddLineToPoint(context, x_left+radius+kArrowOffset+kArrowSize/2.0f, y_top-kArrowSize);
    CGContextAddLineToPoint(context, x_left+radius+kArrowOffset, y_top-kArrowSize);
    CGContextAddLineToPoint(context, x_left+radius+kArrowOffset+kArrowSize, y_top);

    static const CGFloat F_PI = (CGFloat)M_PI;
    CGContextAddArc(context, x_right-radius, y_top+radius, radius, 3.0f*F_PI/2.0f, 0.0f, 0);
    CGContextAddArc(context, x_right-radius, y_bottom-radius, radius, 0.0f, F_PI/2.0f, 0);
    CGContextAddArc(context, x_left+radius, y_bottom-radius, radius, F_PI/2.0f, F_PI, 0);
    CGContextAddArc(context, x_left+radius, y_top+radius, radius, F_PI, 3.0f*F_PI/2.0f, 0);
    CGContextClosePath(context);
    CGContextDrawPath(context, kCGPathFillStroke);
}

- (void)drawTextInContext:(CGContextRef)context {
    CGRect rect = self.bounds;
    rect.origin.x += kPaddingWidth;
    rect.origin.y += kPaddingHeight + kArrowSize;
    rect.size.width -= kPaddingWidth*2.0f;
    rect.size.height -= kPaddingHeight*2.0f;

    CGContextSetGrayFillColor(context, 0.0f, 1.0f); // black text
    [text drawInRect:rect withFont:[UIFont systemFontOfSize:kFontSize]
       lineBreakMode:UILineBreakModeWordWrap];
}

@end
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
2

I wouldn't add another view just to put the triangle on it, have you considered drawing the rounded border and triangle with an UIBezierPath? That will give you more flexibility in your drawing and you can keep everything in just one view.

elibud
  • 8,119
  • 2
  • 21
  • 21
  • Hmmm... I've heard about `UIBezierPath` but don't know how to use it. Do you know any good examples? I just ended up using Quartz to draw the rounded corners and the arrow for the tip balloon. – ma11hew28 May 19 '11 at 00:13
  • I can't remember where I learned, but basically is like a CGPath but to draw it you set the properties on the path instead of in the context. This link: http://nachbaur.com/blog/core-animation-part-4 looks like a nice example, but remember that you don't necessarily need a CAShapeLayer to draw with a UIBezierPath. – elibud May 19 '11 at 13:45
  • Cool. I added my solution. http://stackoverflow.com/questions/6049682/iphone-how-to-make-a-tip-balloon-programmatically/6052438#6052438 – ma11hew28 May 26 '11 at 13:18
0

You can use a UIView on your main view and call it when you need it. You will however have to make the UIView transparent and probably will need to use an image as the main part of the balloon. In that scenario, you can just use UILabel's to set the desired message.

WrightsCS
  • 50,551
  • 22
  • 134
  • 186