How to create bubbles & arrows in iPhone help page about using buttons? - iphone

Most of the apps today provides the tutorial that teaches the user to how to use the buttons in the app. This help page is normally in black color with a little alpha value (so only the background will semi-visible) with a bubble box that contains the text to define controls and the arrow which points to the corresponding component.
Here is the sample image.. (From the app MyThings)
This is the normal page
And if you swipe from bottom to top, the help view will appear like..
Here is my doubts:
Which one is best to create the images & text in this help view? Drawing the images & texts using Core Graphics or by simply putting an UIImageView with a png image? Which one is efficient?
There is no problem in implementing a UIImageView with a ready-made png image. From my knowledge, the problem here is the image file size and the loading time. If we consider the drawing method, my mind things about the following problems.
Drawing a rectangle is so easy. (Refer here). But what about drawing a rectangle with a curved corners?? Is there any function available that handle this case?
Then the arrow, that points the corresponding component.. How can we find the exact point that should be pointed? (till iPhone 4S, there is no problem I hope, iPhone 5 has different height)
How to draw this pointers from the particular position in the rectangle?
Any ideas?
Just confused!!

Drawing the bubbles & arrows with CG is better, IMHO. It will work even if you change completely the app (if you draw them correctly pointing to the center of the button, for instance). With images, you'll need to have several copies for the different displays resolutions and scales. Also, you'll need to update the arrows if you change anything.
I don't see any performance drawback. Both methods will draw the bubbles very fast. Also, think that you can cache the CG generated images for future use.
See these questions to know how to draw bubbles:
How to draw a “speech bubble” on an iPhone?
How to make a Thought Bubble using core graphic in iPhone
It seems logical to use the center of the button each bubble point to. Your drawing methods needs to know where to point the arrow and the current orientation (if the app rotates). It should take into account other bubbles, to avoid overlap. You can divide the space in rows and columns and assign free space to each bubble.
For better user experience, those bubbles should not consume taps. If you tap a button when the bubbles are visible, the intended action should be performed (instead of just hiding the bubbles and require a second tap).

in your case i would refer resizable and reusable images.I also have many overlay screens in my app and i ended up wit generating 5 6 generic items arrows, label background and rectangular background image for the uilabel.
i know drawing rectangular is easy but it might be just overload sometimes.
if you want to change the direction of those arrows you can apply layer transformation to a UIImageview as following
arrowIamgeView.transform = CGAffineTransformMakeRotation(-M_PI / 20);
and for rounded corners of rectangles i assume you could user textfields with a certain background color and set their layers' cornerradius property.

Finally I did it!!
From my one-day experience, I come to know that there is three ways to handle this situation.
Simply creating needed images as png files and just use UIImageView to show. (But remember, you should have a same image with different resolutions. This will increase your app size).
Second way of doing is, Showing the bubbles (or maybe a rectangle) by creating labels, text fields, etc.. with a arrow image. You may simply transform the image to change the pointing place of the arrow. (As, Ilker Baltaci said in the previous answer).
Third way is, by Core Graphics. You can draw whatever you want here. As of my knowledge, increasing the app's size or increasing the memory consume by initializing/allocating/retaining labels & text fields, we can try this by Core Graphics.
I've three views where I want to implement this help screen facility. For just three screen, I can use any of the three method, because, there is no much difference related to performance if we use it for little-needed situaions. But I tried with the third way, because, I really don't knew anything about Core Graphics. So its just for learning..
Problems that I faced:
The arrow should point the center of the button(or maybe top of the button). So finding this exact location is complex. I have used this feature only in 3 pages in my app. Each page contains maximum of 4 icons to describe. So I just hard coded the values (My another luck is, the app does not support landscape mode). But, if you want to use this feature for so many pages, you should define some arrays and a method that finds the center of icons by calculating its center with reference to, their origin, height & width, blah, blah..
The another problem is, you should ensure that the bubbles are not overlapping anywhere. Here also, we need a global method that finds the location for each bubble. The size of the bubble depends on, the text size that is going to be placed inside it. This is more complex problem and for just 3 screens, I'm not going to define a global method with hundreds of calculations. So I again just hard coded the origin point, height & width of each bubble with reference to the self.view
Last but not least.. The Arrow!! The height of arrow may vary with the bubble's place. The width of the bubble also may vary with height. Also, you should know the side & points of the bubble from which the arrow goes to the button. If you already fixed the places of the bubbles in your mind, these are not at all a problem for you.. :P
Now let's come the coding part,
- (void) createRect:(CGRect)rect xPoint:(float)x yPoint:(float)y ofHeight:(float)height ofWidth:(float)width toPointX:(float)toX toPointY:(float)toY withString:(NSString *)helpString inDirection:(NSString *)direction
{
float distance = 5.0;
float widthOfLine = 5.0;
float arrowLength = 15.0;
//Get current context
CGContextRef context = UIGraphicsGetCurrentContext();
//Set width of border & colors of bubble
CGContextSetLineWidth(context, widthOfLine);
CGContextSetStrokeColorWithColor(context, [[UIColor darkGrayColor] CGColor]);
CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.9] CGColor]);
CGContextBeginPath(context);
CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x+width, y);
CGContextAddQuadCurveToPoint(context, x+width, y, x+width+distance, y+distance);
CGContextAddLineToPoint(context, x+width+distance, y+distance+height);
CGContextAddQuadCurveToPoint(context, x+width+distance, y+distance+height, x+width, y+distance+height+distance);
CGContextAddLineToPoint(context, x, y+distance+height+distance);
CGContextAddQuadCurveToPoint(context, x, y+distance+height+distance, x-distance, y+distance+height);
CGContextAddLineToPoint(context, x-distance, y+distance);
CGContextAddQuadCurveToPoint(context, x-distance, y+distance, x, y);
CGContextDrawPath(context, kCGPathFillStroke);
//Draw curvely arrow from bubble to button (but without arrow mark)
CGContextBeginPath(context);
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGPoint startPoint = CGPointMake(x+(width/2.0), y+distance+distance+height);
CGPoint endPoint = CGPointMake(toX, toY);
CGContextMoveToPoint(context, startPoint.x, startPoint.y+5.0);
if ([direction isEqualToString:#"left"])
{
CGContextAddCurveToPoint(context, startPoint.x, startPoint.y+5.0, endPoint.x, endPoint.y, toX-10, toY);
}
else
{
CGContextAddCurveToPoint(context, startPoint.x, startPoint.y+5.0, endPoint.x, endPoint.y, toX+10, toY);
}
CGContextStrokePath(context);
//Draw the arrow mark
CGContextBeginPath(context);
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
if ([direction isEqualToString:#"left"])
{
CGContextMoveToPoint(context, toX-10.0, toY-arrowLength);
CGContextAddLineToPoint(context, toX-10.0, toY);
CGContextAddLineToPoint(context, toX-10.0+arrowLength, toY);
}
else
{
CGContextMoveToPoint(context, toX+10.0, toY-arrowLength);
CGContextAddLineToPoint(context, toX+10.0, toY);
CGContextAddLineToPoint(context, toX+10.0-arrowLength, toY);
}
CGContextStrokePath(context);
.......
.......
}
You can call this method from the drawRect: method like this..
[self createRect:rect xPoint:30.0 yPoint:250.0 ofHeight:100.0 ofWidth:100.0 toPointX:48.0 toPointY:430.0 withString:#"xxxxxxx" inDirection:#"left"];
[self createRect:rect xPoint:160.0 yPoint:100.0 ofHeight:100.0 ofWidth:100.0 toPointX:260.0 toPointY:420.0 withString:#"yyyyyyy" inDirection:#"right"];
And the final image will be like this..
I skipped triangle shape arrow, because this arrow type gives a handwriting effect.
Thanks to #djromero and Ilker Baltaci for your valuable answers.
My next confusion is about Drawing Texts!!!!! :P

Related

Custom Pattern Pencil Sketch Drawing

Using Core Graphics, I want the painting app functionality and here the user can have the custom image pattern drawing functionality.I followed the proceeding snippet :
enter code here
UIGraphicsBeginImageContext(self.view.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0f];
[T_BImage drawAtPoint:CGPointMake(rotX-(T_BImage.size.width)/2, rotY-(T_BImage.size.height)/2) blendMode:kCGBlendModeNormal alpha:1.0f];
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Here , is the output.
But ,when the user continuously drags fast on the screen , then the image is not sequentially drawn whereas the same code works fine with slow drag
Any help will be appreciated.
The problem with your code is as the drag speeds up, there will be continuous calls to the same method which might be rendering this slow. If you are planning to paint the pattern image, I suggest you try and look into the apple sample code here
You will have to replace the pattern image in the above sample project with your custom pattern image. See how it goes. One point to be noted is that the pattern image has to be square shaped(same width and height)
Finally, the image is drawn with the direct proportion of user's drawing speed. This was achieved by calculating the smallest set of points between touches_began and touches_moved delegate methods.i.e. All the set of possible points from minimum (starting) CGPoint to Maximum(Inbetween moving or end)point was manually calibrated and those points are passed through the image drawing function concurrently with separate thread.
Thanks to Brad-Larson for his remarkable .
response

CoreGraphics - How do I draw rects one by one on a plane?

I have a sort of progress bar which is actually a visual representation of the read content of a book. This bar is a horizontal line which is filled up at areas the book was read at. As an example, if it's a 100 paged book, and the user has read only page 1 to 10 and 90 to 100, the progress bar will show the 10% area at the extreme left and 10% area at the extreme right filled with color.
I am currently using this code to draw rect for one reading session:
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect innerRect = CGRectInset(rect, 5, 5);
CGRect ProgressIndicator = CGRectMake(innerRect.origin.x, innerRect.origin.y, 20, innerRect.size.height);
CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);
CGContextSetLineWidth(context, 5);
CGContextMoveToPoint(context, CGRectGetMinX(ProgressIndicator), CGRectGetMinY(ProgressIndicator));
CGContextAddLineToPoint(context, CGRectGetMaxX(ProgressIndicator), CGRectGetMinY(ProgressIndicator));
CGContextAddLineToPoint(context, CGRectGetMaxX(ProgressIndicator), CGRectGetMaxY(ProgressIndicator));
CGContextAddLineToPoint(context, CGRectGetMinX(ProgressIndicator), CGRectGetMaxY(ProgressIndicator));
CGContextClosePath(context);
CGContextStrokePath(context);
CGContextSetFillColorWithColor(context, [[UIColor blueColor]CGColor]);
CGContextFillRect(context, ProgressIndicator);
Now, this is just for one reading session and draws only one rect as the rectangular "read" portion on the bar. How do I manage the drawing if I have multiple rects (say in an array) to draw on the bar. I am afraid that I'll lose the current drawing once I draw another rect.
How do I use "for" loop to draw those multiple rects?
I know this might be a very dumb question but I tried finding a way around it but didn't succeed.
I'm going to suggest thinking about this a different way; keeping track of an arbitrary number of 'reading sessions' could be a very inefficient representation.
In situations like this rather than concatenating every time data from a 'reading session' I would suggest that a sparse representation might make your drawing easier - just keep track of which pages have been read, period, in an array or dictionary or a custom object.
Then, your drawing code could simply loop over the storage and draw a section in one color for 'read' and not draw, or draw another color, etc, for 'unread'.

iPhone Core Graphics thicker dashed line for subview

I have a UIView and within it I've drawn a line using Core Graphics by overriding drawRect. This view also contains one subview which also draws a line. However, whilst both views are using pretty much the same code (for testing purposes at least), the lines drawn on them do not appear the same:
As you can see - the dashed line at the top is noticeably thicker than the bottom one and I have no idea why. Below is the code used by the two UIViews in their drawRect methods. If you have any idea why this is happening then I'd appreciate your help and advice!
First View:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGFloat dashes[] = {1,1};
CGContextSetLineDash(context, 0.0, dashes, 2);
CGContextSetLineWidth(context, 0.6);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
CGContextStrokePath(context);
SubUIView *view = [[SubUIView alloc] initWithFrame:rect];
[self addSubview:view];
[view release];
The view is definitely only being drawn once. I appreciate drawRect may not be the best place for adding a subview but the problem remains even with it added in the main initWithFrame method.
Second View:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGFloat dashes[] = {1,1};
CGContextSetLineDash(context, 0.0, dashes, 2);
CGContextSetLineWidth(context, 0.6);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMidY(rect));
CGContextStrokePath(context);
It could be a result of anti-aliasing if your rect does not fall on integers. You can disable anti-aliasing with CGContextSetShouldAntialias( context, NO ). I think there's also a function for making a rect integral, but I can't remember what it is.
First, you should fix the problem of the drawing code being WET*. You say you're doing this “for testing purposes”, but this actually makes testing harder, since you have to change both pieces of code and/or keep straight which version you're working on. The worst case is when you change both pieces of code in different ways and have to merge them by hand.
I'd say move the dashed-line code to the subview, add properties for anything the two pieces of code need to do differently, and create two subviews (and not in drawRect:—seriously).
As for the actual problem: Well, I don't see a big image, I see a tiny image, and I can only guess that the greater boldness of the upper line than that of the lower line means that the upper line is thicker.
By the way, the rect is not necessarily the bounds of your image. Don't ever assume that it is, or you will get funky drawing when it isn't. Assume that it some section of the bounds—possibly, but possibly not, the whole thing. When you mean [self bounds], say [self bounds].
The problem is most likely the difference between CGRectGetMidY([self bounds]) and CGRectGetMaxY([self bounds]). One includes a fraction that splits a pixel, whereas the other is a multiple of one pixel or close to it. (Not necessarily a multiple of 1—on a Retina Display, 1 pt = 2 pixels, so 1 pixel = 0.5 pt.) Try flooring both numbers and optionally adding 0.5, and see which way you like better.
There's no way to make it work out perfectly with a 0.6-pt line width. There is simply no whole number of pixels that works out to. All you can do is work out what looks best and do that.
*Written Elsewhere Too, the opposite of DRY.

How to set up a user Quartz2D coordinate system with scaling that avoids fuzzy drawing?

This topic has been scratched once or twice, but I am still puzzled. And Google was not friendly either.
Since Quartz allows for arbitrary coordinate systems using affine transforms, I want to be able to draw things such as floorplans using real-life coordinate, e.g. feet.
So basically, for the sake of an example, I want to scale the view so that when I draw a 10x10 rectangle (think a 10-inch box for example), I get a 60x60 pixels rectangle.
It works, except the rectangle I get is quite fuzzy. Another question here got an answer that explains why. However, I'm not sure I understood that reason why, and moreover, I don't know how to fix it. Here is my code:
I set my coordinate system in my awakeFromNib custom view method:
- (void) awakeFromNib {
CGAffineTransform scale = CGAffineTransformMakeScale(6.0, 6.0);
self.transform = scale;
}
And here is my draw routine:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect r = CGRectMake(10., 10., 10., 10.);
CGFloat lineWidth = 1.0;
CGContextStrokeRectWithWidth(context, r, lineWidth);
}
The square I get is scaled just fine, but totally fuzzy. Playing with lineWidth doesn't help: when lineWidth is set smaller, it gets lighter, but not crisper.
So is there a way to set up a view to have a scaled coordinate system, so that I can use my domain coordinates? Or should I go back and implementing scaling in my drawing routines?
Note that this issue doesn't occur for translation or rotation.
Thanks
the [stroked] rectangle I get is quite fuzzy.
Usually, this is because you plotted the rectangle on whole-number co-ordinates and your line width is 1.
In PostScript (and thus in its descendants: AppKit, PDF, and Quartz), drawing units default to points, 1 point being exactly 1/72 inch. The Mac and iPhone currently* treat every such point as 1 pixel, regardless of the actual resolution of the screen(s), so, in a practical sense, points (by default, on the Mac and iPhone) are equal to pixels.
In PostScript and its descendants, integral co-ordinates run between points. 0, 0, for example, is the lower-left corner of the lower-left point. 1, 0 is the lower-right corner of that same point (and the lower-left corner of the next point to the right).
A stroke is centered on the path you're stroking. Thus, half will be inside the path, half outside.
In the (conceptually) 72-dpi world of the Mac, these two facts combine to produce a problem. If 1 pt is equal to 1 pixel, and you apply a 1-pt stroke between two pixels, then half of the stroke will hit each of those pixels.
Quartz, at least, will render this by painting the current color into both pixels at one-half of the color's alpha. It determines this by how much of the pixel is covered by the conceptual stroke; if you used a 1.5-pt line width, half of that is 0.75 pt, which is three-quarters of each 1-pt pixel, so the color will be rendered at 0.75 alpha. This, of course, goes to the natural conclusion: If you use a 2-pt line width, each pixel is completely covered, so the alpha will be 1. That's why you can see this effect with a 1-pt stroke and not a 2-pt stroke.
There are several workarounds:
Half-point translation: Exactly what it says on the box, you translate up and right by half a point, compensating for the aforementioned 1-pt-cut-in-half division.
This works in simple cases, but flakes out when you involve any other co-ordinate transformations except whole-point translations. That is to say, you can translate by 30, 20 and it'll still work, but if you translate by 33+1/3, 25.252525…, or if you scale or rotate at all, your half-point translation will be useless.
Inner stroke: Clip first, then double the line width (because you're only going to draw half of it), then stroke.
This can require gstate juggling if you have a lot of other drawing to do, since you don't want that clipping path affecting your other drawing.
Outer stroke: Essentially the same as an inner stroke, except that you reverse the path before clipping.
Can be better (less gstate juggling) than an inner stroke if you're sure that the paths you want to stroke won't overlap. On the other hand, if you also want to fill the path, the gstate juggling returns.
*This won't last forever. Apple's been dropping hints for some time that they're going to change at least the Mac's drawing resolution at some point. The API foundation for such a change is pretty much all there now; it's all a matter of Apple throwing the switch.
Well, as often, explaining the issue lead me to a solution.
The problem is that the view transform property is applied to it after it has been drawn into a bit buffer. The scaling transform has to be applied before drawing, ie. in the drawRect method. So scratch the awakeFromNib I gave, and here is a correct drawRect:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform scale = CGAffineTransformMakeScale(6.0, 6.0);
CGContextConcatCTM(context, scale);
CGRect r = CGRectMake(10., 10., 10., 10.);
CGFloat lineWidth = 0.1;
CGContextStrokeRectWithWidth(context, r, lineWidth);
}

Create a porthole animation transition on the iPhone?

Can anyone recommend how I might be able to create a view transition that looks like a circle expanding from the center of the view?
Any pointers would be helpful, or maybe a link to some similar code, even in a different language.
If you want to do that for images.. then you could use a mask and then make it bigger
Here is how you do it for images
CGRect bounds = CGRectMake(0.0f, 0.0f, 100, 100);
// Create a new path
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
// Add circle to path
CGPathAddEllipseInRect(path, NULL, bounds);
CGContextAddPath(context, path);
// Clip to the circle and draw the image
CGContextClip(context);
[image drawInRect:bounds]; //HOW WOULD YOU CLIP A VIEW INSTEAD OF AN IMAGE?
CFRelease(path);
Perhaps fill the screen with black, and then remove the black using a EllipseInRect that starts at the center point of the animation, and slowly increases in size and then deletes another segment, which loops until it's completed or hits an upper limit.
The smoothness of the animation would be controlled by how much the size of the Rect (which the Ellipse is then created inside of) increases every x amount of time.
The speed of the animation would be controlled by how often you increase the size of the rect.
Check out CoreGraphics if you haven't already. It's pretty powerful.