Need Infinite zooming for my Drawing View which is on UIscrollView - ios

Is it possible to zoom infinite? as i have a floor drawing functionality, i need to zoom even for a small room to be display as larger by implementing infinite zooming. If i'm zooming more than 15 times of original drawing view, drawing is disappearing and displays nothing.
Any suggestions would be appreciated!!
Here is my sample code block:
self.scrView.maximumZoomScale=200;
self.scrView.minimumZoomScale=0.5;
#pragma mark ScrollView Deleagte
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.objDrawingView;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
[self.objDrawingView.layer setContentsScale:self.objDrawingView.transform.a*[[UIScreen mainScreen] scale]];
[self.objDrawingView setNeedsDisplay];
}

If you are doing the drawing yourself in drawRect, it sounds better performance wise (and of course because of your "ignoring bogus layer" message), to have some own values containing the scale and the offset, and respecting this in your drawing code. This way the layer's size will always stay the same and there is no need to draw that huge layer if most of it is outside of the displays bounds.
Of course it shouldn't be a child of the scrollView then, so you need to get the values from scrollViewDidScroll as well to calculate your currently visible rect.
Example of the idea:
if the current code in drawRect would look like
CGContextFillRect( CGRectMake(x,y,width,height) );
and you are scaling this by scaling the whole layer, I am suggesting to have a variable containing the current scale and don't scale the whole layer, like:
CGContextFillRect( CGRectMake(x* _zoomScale,y* _zoomScale,width* _zoomScale,height* _zoomScale) );
or even better use the scale and translate methods on the CGContext then your drawing code would be independent from these. The first three lines in drawRect should then be:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, -_contentOffset.x, -_contentOffset.y);
CGContextScaleCTM(context, _zoomScale, _zoomScale);
make zoomScale and contentOffset a property and set it in the UIScrollViewDelegate methods.

Not sure if reading the transform.a is the right approach. Maybe better to use the scale variable from the method instead.

Related

Having trouble with graphics context

I am working on multiple terminal screen app, for that I have a custom UIView subclass for the terminal views. Every time I need a new terminal screen, I prepare a new view.
This view class draws the text using a CGContextRef. The problem I am facing is that the context only draws the text of the last view that was created, e.g. if I have 3 terminals and drawing on first/second, it still draws on the third one.
My code so far:
-(void)drawRect:(CGRect)rect{
contxt = UIGraphicsGetCurrentContext();
}
-(void)setNeedsDisplayInRect:(CGRect)rect{
UIGraphicsPushContext(contxt);
//CGContextSaveGState(contxt);
CGContextSetTextMatrix(contxt,CGAffineTransformIdentity);
if (translated) {
CGContextScaleCTM(contxt, 1, -1);
translated = NO;
}
CGRect rectConvert = CGRectMake(rect.origin.x, rect.origin.y-screenWindowHeight, rect.size.width, rect.size.height);
CGContextSetFillColorWithColor(contxt, bgColor.CGColor);
CGContextFillRect(contxt, rectConvert);
if (!isDeleteChar) {
CGContextSetFillColorWithColor(contxt, fgColor.CGColor);
[displayString drawInRect:rectConvert withFont:font lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft];
}
if (ul == EXTENDED_5250_UNDERLINE) {
CGContextSetFillColorWithColor(contxt, fgColor.CGColor);
[#"_" drawInRect:rectConvert withFont:font lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentLeft];
}
//CGContextRestoreGState(contxt);
UIGraphicsPopContext();
}
Finally I solved it by own using
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]]; just after setNeedsDisplay.
First and foremost, you should not be doing drawing in the -setNeedsDisplayRect: method, all of your drawing code should be in drawRect: instead. This way, the main runloop can better organize redrawing of the views.
Second, I suspect the variables that you are using for your CGRect conversions are faulty and are drawing outside of the view bounds. You can test this premise by clipping the view's drawing (set layer.masksToBounds to YES for the views)
If this is the case, you can adjust them to be relative to the view (all drawing within the view should be relative to its bounds, not its frame). When drawing the view, assume a canvas that stretches the bounds property of the view, i.e origin at (0,0) and size of (width,height).
Now, it is worth also discussing what the rect property passed to drawRect: really is, it is not guaranteed to be the entire bounds of the view, so you should not assume that. It is the portion of the view that needs to be redrawn, however, common practice (for simpler views) is to ignore that parameter and just redraw the entire view. Once this becomes too expensive (or you need the drawing to be more optimal), you can look into doing partial redraws of your view.
All in all, it is difficult to diagnose the full problem without seeing the entire UIView subclass code.

how do I merge two UIViews?

i'm not really sure if my title is really appropriate. But i'm trying to make a app that lets users draw on the screen with multitouch. If more then one touch is on the screen it will alloc custom UIview that performs all drawing and add that to the view. The problem occurs if more then 6 touches is down, then it will start too lag and fps will drop. To fix this I could make it so all drawing is made on the same view. However, this nullifies the one of the features of my app which the ability to hide some views by setting alpha to 0. So is there any way to "merge" the drawing made in these views into a separate view of some kind (layer,uiimageview or uiview) to fix the fps problems?
- (void) drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef cacheImage = CGBitmapContextCreateImage(cacheContext);
CGContextDrawImage(context, self.bounds, cacheImage);
CGImageRelease(cacheImage);
}

Difference drawing on CALayer and UIView

I have one component that has an UIView subclass and a custom CAlayer into it.
In the UIView there is a circle that is drawn with CoreGraphics, and this is the code:
CGRect b = self.bounds;
int strokeSize = 2;
CGRect arcBounds = CGRectMake(b.origin.x+1, b.origin.y+1, b.size.width-2, b.size.height-2);
CGContextSaveGState(ctx); {
CGContextSetLineWidth(ctx, strokeSize);
CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
CGContextStrokeEllipseInRect(ctx, arcBounds);
} CGContextRestoreGState(ctx);
when I draw that circle in the drawRect method inside the UIView it works perfect and the circle is drawn smooth and looks great.
The problem appears when I draw another circle just over this one, but the second one is drawn in the CALayer, actually in the drawInContext method of my custom CALayer. Using just the same code the circle doesn't looks good, and have some "pixellation" on the borders.
Any clues on what can be happening? Thanks in advance.
This is due to the contentsScale property. When you have a custom CALayer the default value of this property is 1.0.
The default value of this property is 1.0. For layers attached to a
view, the view changes the scale factor automatically to a value that
is appropriate for the current screen. For layers you create and
manage yourself, you must set the value of this property yourself
based on the resolution of the screen and the content you are
providing. Core Animation uses the value you specify as a cue to
determine how to render your content. Source.
If you have a retina device and you draw with the contentsScale set to 1.0, it will result in that pixelated look you described. In order to fix this you should set the layer's contentsScale to the one of the screen.
[self.layer setContentsScale:[[UIScreen mainScreen] scale]];
This issue does not happen when you draw the circle in the drawRect method of your UIView since there the default contentsScaleFactor is already the one of the screen.
For views that implement a custom drawRect: method and are associated
with a window, the default value for this property is the scale factor
associated with the screen currently displaying the view. Source.

UIView's contentScaleFactor depends on implementing drawRect:?

I have stumbled on a weird thing. It looks like UIView's contentScaleFactor is always 1, even on Retina devices, unless you implement drawRect:. Consider this code:
#interface MyView : UIView
#end
#implementation MyView
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame: frame];
if (self) {
NSLog(#"%s %g %g %g", __PRETTY_FUNCTION__, self.contentScaleFactor, self.layer.contentsScale, [UIScreen mainScreen].scale);
}
return self;
}
- (void) didMoveToWindow
{
if (self.window)
NSLog(#"%s %g %g %g", __PRETTY_FUNCTION__, self.contentScaleFactor, self.layer.contentsScale, [UIScreen mainScreen].scale);
}
#end
On a Retina device it prints the following:
-[MyView initWithFrame:] 1 1 2
-[MyView didMoveToWindow] 1 1 2
If I add an empty implementation of drawRect: like this:
- (void) drawRect: (CGRect) rect
{
}
it works as expected:
-[MyView initWithFrame:] 2 2 2
-[MyView didMoveToWindow] 2 2 2
So it looks like it doesn't really matter if the view is in any view hierarchy and what kind of screen it is displayed on. The only thing that does matter is if the view implements drawRect: or not.
Is that a bug or a feature? I know I can change didMoveToWindow as below to fix it
- (void) didMoveToWindow
{
if (self.window)
self.contentScaleFactor = self.window.screen.scale;
}
but the default behavior still bugs me.
You may ask why I need contentScaleFactor at all if I don't draw anything. That's because I just set self.layer.contents to a ready-made image and then stretch the image with contentStretch. However, the image doesn't stretch properly on Retina devices unless contentScaleFactor is set correctly, even though a #2x image is used. To be precise, it works correctly unless a #2x image is used. This is, I guess, a bug.
Can anyone share your insight into why contentScaleFactor behaves this way? Is it specific to iOS 5 only?
Presumably, if you don't override drawRect: then UIKit knows that a UIView doesn't draw anything so it takes the (presumably) fast case of having a layer that has a content scale of 1. As soon as you override drawRect: though, it knows it needs to set up a layer that is of the correct content scale that you can draw into if you want to. It doesn't know that you do nothing in drawRect: though so it can't make the same assumption as before.
In fact all that is alluded to in the docs:
For views that implement a custom drawRect: method and are associated with a window, the default value for this property is the scale factor associated with the screen currently displaying the view.
Why don't you just override drawRect: and in that, draw your image? Or you could probably get away with what you're currently doing and have a stub drawRect:. Given what the docs say, I'd say that's perfectly reasonable to assume it's going to continue to work and is correct behaviour.
Native drawing technologies, such as Core Graphics, take the current scale factor into account for you. For example, if one of your views implements a drawRect: method, UIKit automatically sets the scale factor for that view to the screen’s scale factor. In addition, UIKit automatically modifies the current transformation matrix of any graphics contexts used during drawing to take into account the view’s scale factor. Thus, any content you draw in your drawRect: method is scaled appropriately for the underlying device’s screen.

Fill In UIView With Multiple Colors iOS

I'd like to fill in a UIView Background with multiple colors. I want to use it as a status bar of sorts, so if 1/2 the necessary steps are completed, the UIView Background will be 1/2 green and 1/2 red. When the user completes more steps (say 2/3), more of the UIView Background turns green (2/3 in this case).
I'm guessing I need to override
-(void) drawREct: (CGRect) rect
I imagine I would get the UIView, figure out how big it is, and divide it into 2 rectangles and then fill in those rectangles.
Another option would be to add 2 UIViews programmatically, but I'm a fan of IB.
Is it possible to divy up a UIView like I want?
Thanks
This is not an IB solution, but you can subclass UIView and override the drawRect method to add a custom gradient. This will allow you to put any number of colors you like and have them transition hard or smoothly. There are many nice tutorials online that should different ways to do this (some more elaborate, some quite simple).
Another option that is fairly simple, is to override drawRect, make the background red, then fill a rectangle that takes up the bottom half of the view. It doesn't allow for fancy or smooth transitions between colors, but it's very easy to implement. For instance, something along these lines should work:
-(void)drawRect:(CGRect)rect {
CGRect upperRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height * percentDone);
CGRect lowerRect = CGRectMake(rect.origin.x, rect.origin.y + (rect.size.height * percentDone), rect.size.width, rect.size.height *(1-percentDone));
[[UIColor redColor] set];
UIRectFill(upperRect);
[[UIColor greenColor] set];
UIRectFill(lowerRect);
}
Here percentDone is a float (declare, property(nonatomic), synthesize) that you can tie to the user's steps. Then just update the view when the user does something by
splitView.percentDone = .5;
[splitView setNeedsDisplay];
You can smooth this out with animations as well.
An easy way would be to set the background color for your background view to, say, red. Then add another view to that background view and call that the indicator view. Size and position the indicator view to cover the background, and set its background color to green. Connect the indicator view to an outlet in your view controller, and have the view controller adjust its width (or height) as necessary to correspond with the progress of the task at hand.
Another way would be as #PengOne suggests: create a custom view, give it a 'progress' property, and override -drawRect: to draw the contents appropriately. If you're going this route, there's nothing to stop you from getting a little creative. Instead of just filling two rectangles, make the boundary between the two colors a little more interesting. You could add ripples or bubbles that, with an appropriate sound effect, might look like a container filling with liquid. Or you could do a Qix-like animation that slowly fills the screen... ;-)

Resources