iPhone: How to make a tip balloon programmatically

2019-06-04 07:25发布

问题:

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.

回答1:

// 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


回答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.



回答3:

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.