使用Objective-C在iOS SDK中实现漫画气泡

7
我正在开发一个儿童图书应用程序,希望能够动态填充角色对话框,如附图所示。截图来自使用Cocos2D(或3D)开发的灰姑娘应用程序。然而,我想使用objective-c和iOS sdk实现此功能。
一种方法是:
根据字体和大小计算文本的大小(使用NSString方法sizeWithFont:constrainedToSize:),然后绘制一个圆角矩形(使用CALayer)来包围该大小。
为了绘制圆角矩形,设置一个具有背景颜色、边框宽度和边框颜色以及颜色半径的CALayer,并将其添加到UIView对象中。这样就可以很容易地实现气泡效果。
现在,我该如何得到一个指向角色嘴巴的角落?如何从角色嘴巴弹出和弹入CALayer,我该如何实现这个动画?我的主场景(插图)将是一个UIImageview,这个对话框应该以动画方式从角色嘴巴中弹出,在几秒钟后消失,就像它回到角色嘴巴中一样。非常感谢您的建议。
如果您知道某些样例代码/文章,请引导我。
以下是应用程序的视频链接,显示角色对话框如何以气泡形式弹出和弹入:http://www.youtube.com/watch?v=umfNJLyrrNg 附图: enter image description here
3个回答

15

这是我通过修改这个苹果教程来实现的:

https://developer.apple.com/library/ios/samplecode/quartzdemo/Listings/QuartzDemo_QuartzCurves_m.html

- (void)drawRect:(CGRect)rect
{
    CGContextRef context=UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, .5f);
    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
    CGContextSetRGBFillColor(context, 1, 1, 1, 1);

    rect.origin.y++;
    CGFloat radius = cornerRadius;

    CGFloat minx = CGRectGetMinX(rect), midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect);
    CGFloat miny = CGRectGetMinY(rect), midy = CGRectGetMidY(rect), maxy = CGRectGetMaxY(rect);

    CGMutablePathRef outlinePath = CGPathCreateMutable();

    if (![User isCurrentUser:message.user])
    {
        minx += 5;
    
        CGPathMoveToPoint(outlinePath, nil, midx, miny);
        CGPathAddArcToPoint(outlinePath, nil, maxx, miny, maxx, midy, radius);
        CGPathAddArcToPoint(outlinePath, nil, maxx, maxy, midx, maxy, radius);
        CGPathAddArcToPoint(outlinePath, nil, minx, maxy, minx, midy, radius);
        CGPathAddLineToPoint(outlinePath, nil, minx, miny + 20);
        CGPathAddLineToPoint(outlinePath, nil, minx - 5, miny + 15);
        CGPathAddLineToPoint(outlinePath, nil, minx, miny + 10);
        CGPathAddArcToPoint(outlinePath, nil, minx, miny, midx, miny, radius);
        CGPathCloseSubpath(outlinePath);
    
        CGContextSetShadowWithColor(context, CGSizeMake(0,1), 1, [UIColor lightGrayColor].CGColor);
        CGContextAddPath(context, outlinePath);
        CGContextFillPath(context);
    
        CGContextAddPath(context, outlinePath);
        CGContextClip(context);
        CGPoint start = CGPointMake(rect.origin.x, rect.origin.y);
        CGPoint end = CGPointMake(rect.origin.x, rect.size.height);
        CGContextDrawLinearGradient(context, [self normalGradient], start, end, 0);
    }
    else
    {
        maxx-=5;
        CGPathMoveToPoint(outlinePath, nil, midx, miny);
        CGPathAddArcToPoint(outlinePath, nil, minx, miny, minx, midy, radius);
        CGPathAddArcToPoint(outlinePath, nil, minx, maxy, midx, maxy, radius);
        CGPathAddArcToPoint(outlinePath, nil, maxx, maxy, maxx, midy, radius);
        CGPathAddLineToPoint(outlinePath, nil, maxx, miny + 20);
        CGPathAddLineToPoint(outlinePath, nil, maxx + 5, miny + 15);
        CGPathAddLineToPoint(outlinePath, nil, maxx, miny + 10);
        CGPathAddArcToPoint(outlinePath, nil, maxx, miny, midx, miny, radius);
        CGPathCloseSubpath(outlinePath);
    
        CGContextSetShadowWithColor(context, CGSizeMake(0,1), 1, [UIColor lightGrayColor].CGColor);
        CGContextAddPath(context, outlinePath);
        CGContextFillPath(context);
    
        CGContextAddPath(context, outlinePath);
        CGContextClip(context);
        CGPoint start = CGPointMake(rect.origin.x, rect.origin.y);
        CGPoint end = CGPointMake(rect.origin.x, rect.size.height);
        CGContextDrawLinearGradient(context, [self greenGradient], start, end, 0);
    }


    CGContextDrawPath(context, kCGPathFillStroke);

}

- (CGGradientRef)normalGradient
{

    NSMutableArray *normalGradientLocations = [NSMutableArray arrayWithObjects:
                                               [NSNumber numberWithFloat:0.0f],
                                               [NSNumber numberWithFloat:1.0f],
                                               nil];


    NSMutableArray *colors = [NSMutableArray arrayWithCapacity:2];

    UIColor *color = [UIColor whiteColor];
    [colors addObject:(id)[color CGColor]];
    color = [StyleView lightColorFromColor:[UIColor cloudsColor]];
    [colors addObject:(id)[color CGColor]];
    NSMutableArray  *normalGradientColors = colors;

    int locCount = [normalGradientLocations count];
    CGFloat locations[locCount];
    for (int i = 0; i < [normalGradientLocations count]; i++)
    {
        NSNumber *location = [normalGradientLocations objectAtIndex:i];
        locations[i] = [location floatValue];
    }
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();

    CGGradientRef normalGradient = CGGradientCreateWithColors(space, (__bridge CFArrayRef)normalGradientColors, locations);
    CGColorSpaceRelease(space);

    return normalGradient;
}
这导致了以下结果... 在这里输入图片描述


1
这看起来不错,但你能否添加一条关于使用的注释。即,如何从想要将一些文本放入气泡中到实际将其放入气泡中。 - ChrisC
1
@user2011985 你可以创建一个UILabel或UITextField的子类,并添加drawRect方法。我稍微修改了代码以使其更明显。 - Misha
你能提供Swift演示吗?@Misha - amit gupta
我发现,至少在IOS 11上,当我尝试这样做时,如果文本左对齐,则会超出气泡的边缘,因此我不得不使用重写的方法进行缩进 - (void)drawTextInRect:(CGRect)rect { UIEdgeInsets insets = {0, 10, 0, 10}; [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)]; } - ammianus
哦,Quartz Demo的链接现在已经失效了,在苹果网站上找不到它。有人有正确的链接吗? - ammianus
显示剩余2条评论

7
另一种方法是使用具有cap插图的图像。看一下UIImage方法:
resizableImageWithCapInsets:
基本上,您可以创建一个最小大小的气泡图像,并在保持“气泡”边框外观的同时将其无限地拉伸。
下面的代码指定了一个图像,在拉伸/调整图像时保留顶部、左侧、底部和右侧的12个像素:
UIImage *bubble = [[UIImage imageNamed:@"bubble.png"] 
                            resizableImageWithCapInsets:UIEdgeInsetsMake(12, 12, 12, 12)];
UIImageView *imgView = [[[UIImageView alloc] initWithImage:bubble] autorelease];
imgView.frame = CGRectZero;

UIImageView的尺寸变化动画是免费的:

[UIView animateWithDuration:0.3
                 animations:^(void) {
                     imgView.frame = CGRectMake(50, 50, 200, 100);
                 } completion:^(BOOL finished) {

                 }];

谢谢。我该如何使用resizableImageWithCapInsets:来制作指向角色嘴巴的对话框?另外,我该如何实现对话框的打开和关闭动画? - Alex
1
是的,Alex,尖角将成为图像的一部分。请参见我上面的澄清。 - hundreth
非常感谢。我应该在哪里打印实际的对话文本?我可以使用NSString和/或UILabel吗?对话应该打印在气泡内部。 - Alex
根据你的NSString计算出尺寸,创建一个UILabel,并将该UILabel视图添加到气泡图像视图之上。 - hundreth
回答Alex的问题“我应该在哪里打印实际的对话文本”,我发现这篇帖子很有帮助https://dev59.com/f2w05IYBdhLWcg3w11W0。 - Michael Osofsky
显示剩余2条评论

3

three20框架(实际上是Three20Style)有一个名为TTSpeechBubbleShape的形状窗口类,这基本上就是您要寻找的内容。

现在,我认为您有两个选择:

  1. 要么使用Three20;或

  2. 分析TTSpeechBubbleShape类以查看如何实现并在您的应用程序中执行相同操作。


谢谢。如果我使用TTSpeechBubbleShape实现,希望我的应用程序被苹果接受不会有任何问题?我该如何动画化气泡的打开和关闭? - Alex
我认为你不会在审批方面遇到任何问题;我的一个使用聊天气泡的应用程序在一段时间前已经获得了批准...尝试使用动画帧大小,它会起作用的。 - sergio
@sergio 嗨,我正在尝试实现这个,但SpeechBubble类是从TTShape继承而来的,而TTShape又是从NSObject继承而来的。我该如何将这个NSObject转换成一个视图? - GangstaGraham
无法打开链接或通过搜索找到TTSpeechBubbleShape。 - kelin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接