How to achieve horizontal scaling view in iOS? - ios

I have used TSRDialScrollView library to do as per the design requirement in my code.
But couldn't solve few issues.
Referred from below link :
https://www.cocoacontrols.com/controls/trsdialscrollview
I have made few changes in my code
-> To clear background color
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor clearColor] set];
CGContextFillRect(context, rect);
But it is displaying black color.
-> Setting label after view is inverted
- (void)drawLabelWithContext:(CGContextRef)context
atPoint:(CGPoint)point
text:(NSString *)text
fillColor:(UIColor *)fillColor
strokeColor:(UIColor *)strokeColor {
// We want the label to be centered on the specified x value
NSInteger label_y = ((point.y-100) - (boundingBox.width / 2));
}
-> Inverted the whole view by changing minor and major tick length to negative in code.
- (void)drawTicksWithContext:(CGContextRef)context atX:(long)x
{
CGPoint point = CGPointMake(x, self.frame.size.height);
CGContextSetShadowWithColor(
context,
self.shadowOffset,
self.shadowBlur,
self.shadowColor.CGColor);
if ([self isMajorTick:x]) {
[self drawMajorTickWithContext:context
atPoint:point
withColor:self.majorTickColor
width:self.majorTickWidth
length:-self.majorTickLength];
// Draw the text
//
// 1) Take the existing position and subtract off the lead spacing
// 2) Divide by the minor ticks to get the major number
// 3) Add the minimum to get the current value
//
int value = (point.x - self.leading) / self.minorTickDistance + _minimum;
NSString *text = [NSString stringWithFormat:#"%i", value];
[self drawLabelWithContext:context
atPoint:point
text:text
fillColor:self.labelFillColor
strokeColor:self.labelStrokeColor];
} else {
CGContextSaveGState(context);
[self drawMinorTickWithContext:context
atPoint:point
withColor:self.minorTickColor
width:self.minorTickWidth
length:-self.minorTickLength];
// Restore the context
CGContextRestoreGState(context);
}
}
I want scaling to be done as shown below.
1) By adjusting the offset, the view of scrollview should be in middle.(i,e when it is scrolled to minimum value the position of 0 is not at the middle of the view).
2) I want the background color to be clear color.
3) As per the design how to set red color to the middle line which is to be fixed?
4) How to display light grey color in the middle as shown in image ?
I appreciate your help, and please suggest how to achieve this,really i need to complete this task it is very much needed.
Thanks in advance.

TRSDialView.m
file :-
In - (instancetype)initWithFrame:(CGRect)frame add self.opaque = NO;
Inside - (void)drawRect:(CGRect)rect method add following code below CGContextRef context = UIGraphicsGetCurrentContext(); code of line
// Fill the background
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
// CGContextFillRect(context, rect);
CGContextClearRect(context, rect);
The above code will clear background color.
To adjust offset add width constraint to TRSDialView in Storyboard
The red line and grey view can be added as subview in storyboard

Related

iOS - Quartz drawing issue with parent/child views

Scenario
I have two views. One is the "parent" view which contains a "child" view that does the drawing. I refer to the child as QuartzView in the code to follow. QuartzView knows how to draw a square to it's own context.
Issue
When I tell the QuartzView on it's self to draw a square it does so as expected. When I use the parent view to tell QuartsView to draw a square on it's self it draws the square in the lower left corner of the screen at about 1/5 the expected size.
Question
I assume there's some parent/child or context issues here but I'm not sure what they are. How can I get both squares to draw in the exact same place at the exact same size?
Parent ViewController
- (void)drawASquare {
// this code draws the "goofy" square that is smaller and off in the bottom left corner
x = qv.frame.size.width / 2;
y = qv.frame.size.height / 2;
CGPoint center = CGPointMake(x, y);
[qv drawRectWithCenter:center andWidth:50 andHeight:50 andFillColor:[UIColor blueColor]];
}
Child QuartzView
- (void)drawRect:(CGRect)rect
{
self.context = UIGraphicsGetCurrentContext();
UIColor *color = [UIColor colorWithRed:0 green:1 blue:0 alpha:0.5];
// this code draws a square as expected
float w = self.frame.size.width / 2;
float h = self.frame.size.height / 2;
color = [UIColor blueColor];
CGPoint center = CGPointMake(w, h);
[self drawRectWithCenter:center andWidth:20 andHeight:20 andFillColor:color];
}
- (void)drawRectWithCenter:(CGPoint)center andWidth:(float)w andHeight:(float)h andFillColor:(UIColor *)color
{
CGContextSetFillColorWithColor(self.context, color.CGColor);
CGContextSetRGBStrokeColor(self.context, 0.0, 1.0, 0.0, 1);
CGRect rectangle = CGRectMake(center.x - w / 2, center.x - w / 2, w, h);
CGContextFillRect(self.context, rectangle);
CGContextStrokeRect(self.context, rectangle);
}
Note
The opacities are the same for both squares
I turned off "Autoresize subviews" with no noticeable difference
view.contentScaleFactor = [[UIScreen mainScreen] scale]; has not helped
Edit
I'm noticing that the x/y values of the square when drawn the parent starting from the bottom left as 0,0 whereas normally 0,0 would be the top left.
The return value from UIGraphicsGetCurrentContext() is only valid inside the drawRect method. You can not and must not use that context in any other method. So the self.context property should just be a local variable.
In the drawRectWithCenter method, you should store all of the parameters in properties, and then request a view update with [self setNeedsDisplay]. That way, the framework will call drawRect with the new information. The drawRectWithCenter method should look something like this
- (void)drawRectWithCenter:(CGPoint)center andWidth:(float)w andHeight:(float)h andFillColor:(UIColor *)color
{
self.showCenter = center;
self.showWidth = w;
self.showHeight = h;
self.showFillColor = color;
[self setNeedsDisplay];
}
And of course, the drawRect function needs to take that information, and do the appropriate drawing.
I assume there's some parent/child or context issues here but I'm not sure what they are. How can I get both squares to draw in the exact same place at the exact same size?
You normally don't need to worry about the graphics context in your -drawRect: method because Cocoa Touch will set up the context for you before calling -drawRect:. But your -drawASquare method in the view controller calls -drawRectWithCenter:... to draw outside the normal drawing process, so the context isn't set up for your view. You should really have the view do its drawing in -drawRect:. If your view controller wants to make the view redraw, it should call -setNeedsDisplay, like:
[qv setNeedsDisplay];
That will add the view to the drawing list, and the graphics system will set up the graphics context and call the view's -drawRect: for you.
I'm noticing that the x/y values of the square when drawn the parent starting from the bottom left as 0,0 whereas normally 0,0 would be the top left.
UIKit and Core Animation use an upper left origin, but Core Graphics (a.k.a. Quartz) normally uses a lower left origin. The docs say:
The default coordinate system used by Core Graphics framework is LLO-based.

Efficiently draw line numbers + cursor with UITextView

I'm trying to implement something like code-editor. I base my work on CYRTextVIew. Everything works fine except for line numbers and cursor drawing.
First of all it has a bug, which doesn't erase previous cursor position, which results in drawing multiple ones, looks like so:
Here is the code related to that:
- (void)drawRect:(CGRect)rect
{
// Drag the line number gutter background. The line numbers them selves are drawn by LineNumberLayoutManager.
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = self.bounds;
CGFloat height = MAX(CGRectGetHeight(bounds), self.contentSize.height) + 200;
// Set the regular fill
CGContextSetFillColorWithColor(context, self.gutterBackgroundColor.CGColor);
CGContextFillRect(context, CGRectMake(bounds.origin.x, bounds.origin.y, self.lineNumberLayoutManager.gutterWidth, height));
// Draw line
CGContextSetFillColorWithColor(context, self.gutterLineColor.CGColor);
CGContextFillRect(context, CGRectMake(self.lineNumberLayoutManager.gutterWidth, bounds.origin.y, 0.5, height));
if (_lineCursorEnabled)
{
self.lineNumberLayoutManager.selectedRange = self.selectedRange;
NSRange glyphRange = [self.lineNumberLayoutManager.textStorage.string paragraphRangeForRange:self.selectedRange];
glyphRange = [self.lineNumberLayoutManager glyphRangeForCharacterRange:glyphRange actualCharacterRange:NULL];
self.lineNumberLayoutManager.selectedRange = glyphRange;
[self.lineNumberLayoutManager invalidateDisplayForGlyphRange:glyphRange];
}
[super drawRect:rect];
}
I changed one line to this:
[self.lineNumberLayoutManager invalidateDisplayForGlyphRange:NSMakeRange(0, self.text.length)];
And it fixed that. But never mind: both approaches are painfully slow when it comes to UITextView scrolling. It is completely not suitable for production, since working with UITextView is uncomfortable. I also would love to add "error" functionality later, so I could draw red cursor in the places where error occurs.
So the question is, what is the best and the most efficient way to achieve that type of functionality?
Why I told about the bug? I told this to underline that my requirement is to have dynamically positioned cursor, which points only to current line, and you also should note that cursor rect may be much longer that regular line width if line doesnt fit into TextView's frame's width (i.e. line 8 and 9).

IOS - Working with autolayout and drawRect

I'm working with autolayout and I have a RoundView class (subview of UIButton) which drawRect method is:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 0.0;
CGContextSetLineWidth(context, lineWidth);
CGContextSetFillColorWithColor(context, _currentBackgroundColor.CGColor);
CGContextAddEllipseInRect(context, self.bounds);
CGContextFillPath(context);
}
Then I add it to self (a UIView), set its width and height using autolayout. Everything looks great.
But when I change its size constraints (to have a bigger view), and call
[self layoutIfNeeded];
in an animation block, the circle pixels look bigger and ugly.
Do I do this the correct way ?
I just got it from this post !
The problem was that the os wasn't calling drawRect at each animation step.
To force it, in RoundView's init:
self.contentMode = UIViewContentModeRedraw;
You need to take the content scale into account for the retina displays [UIDevice mainScreen].scale. See answers to this (question)[CGContext and retina display

Line isn't drawing at the top corner

I'm attempting to draw two lines -- I understand that 0 is suppose to start at the corner, but for me it seems off. I know it isn't the code that's wrong and I've tried doing negative numbers for the x-coordinate, but the line disappears.
Those who wanted the size of my frames:
2014-03-29 21:12:28.294 touchScreenClockTest[5178:70b] height = 568.000000
2014-03-29 21:12:28.295 touchScreenClockTest[5178:70b] width = 320.000000
What I want it to look like:
What it looks like:
// Draw the top line
CGContextRef drawTopLine = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(drawTopLine, [UIColor whiteColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(drawTopLine, 1.5);
CGContextMoveToPoint(drawTopLine, 0,5); //start at this point
CGContextAddLineToPoint(drawTopLine, 300, 5); //draw to this point
// and now draw the Path!
CGContextStrokePath(drawTopLine);
// Draw the bottom line
CGContextRef drawBotLine = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(drawBotLine, [UIColor whiteColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(drawBotLine, 1.5);
CGContextMoveToPoint(drawBotLine, 0, 30); //start at this point
CGContextAddLineToPoint(drawBotLine, 300, 30); //draw to this point
// and now draw the Path!
CGContextStrokePath(drawBotLine);
Also one more question. I wanted to know how to put the navigation button on my own. I have the image of the navigation, but I didn't know what I would need to call to place the image UIView? (since I don't have a storyboard)

UILabel: background dependent color

I have tried to search for solutions for this problem, but I am not even able to put it correctly in words I guess.
Basically I have a bar that gets filled up with a color while an operation proceeds. I have a label with the progress percentage that has the same color has the fill color, so I need it to change when the fill color is on the back. Something like this:
Is it possible in anyway to achieve this result? And in case, how?
The easiest way is to create a UIView subclass that has a progress property and overwrites -drawRect:.
All the code you need is this:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Set up environment.
CGSize size = [self bounds].size;
UIColor *backgroundColor = [UIColor colorWithRed:108.0/255.0 green:200.0/255.0 blue:226.0/255.0 alpha:1.0];
UIColor *foregroundColor = [UIColor whiteColor];
UIFont *font = [UIFont boldSystemFontOfSize:42.0];
// Prepare progress as a string.
NSString *progress = [NSString stringWithFormat:#"%d%%", (int)round([self progress] * 100)];
NSMutableDictionary *attributes = [#{ NSFontAttributeName : font } mutableCopy];
CGSize textSize = [progress sizeWithAttributes:attributes];
CGFloat progressX = ceil([self progress] * size.width);
CGPoint textPoint = CGPointMake(ceil((size.width - textSize.width) / 2.0), ceil((size.height - textSize.height) / 2.0));
// Draw background + foreground text
[backgroundColor setFill];
CGContextFillRect(context, [self bounds]);
attributes[NSForegroundColorAttributeName] = foregroundColor;
[progress drawAtPoint:textPoint withAttributes:attributes];
// Clip the drawing that follows to the remaining progress' frame.
CGContextSaveGState(context);
CGRect remainingProgressRect = CGRectMake(progressX, 0.0, size.width - progressX, size.height);
CGContextAddRect(context, remainingProgressRect);
CGContextClip(context);
// Draw again with inverted colors.
[foregroundColor setFill];
CGContextFillRect(context, [self bounds]);
attributes[NSForegroundColorAttributeName] = backgroundColor;
[progress drawAtPoint:textPoint withAttributes:attributes];
CGContextRestoreGState(context);
}
- (void)setProgress:(CGFloat)progress {
_progress = fminf(1.0, fmaxf(progress, 0.0));
[self setNeedsDisplay];
}
You can expand the class as needed with properties for background color, text color, font, etc.
Two UIViews.
Let's call one the background and the other the progressBar. progressBar is stacked on top of background with the same origin on their common superview.
They both have a UILabel as subview, and both labels at the same origin relative to their parent. background has a dark backgroundColor and it's label has light textColor and the progress view has things the other way around.
progressBar has a narrower frame width than background and has clipsToBounds==YES
The trick is, with the views' origins the same and the labels' origins the same, and clipsToBounds on the top view, everything is going to look right.
Drop those two views into a new UIView subclass called ReallyCoolProgressView, and give it one public method:
-(void)setProgress:(float)progress
progress is a number from 0.0 to 1.0. The method scales the progressBar width and sets both label's text #"Progress %f", progress*100
Just an idea of "faking" the effect you want.
You may create a subclass of UIView with one background label (white and blue text) and a subview in front of it with blue background color and white text.
You may animate after the width of the front label from 0 to 100% on the background label. You may have to check if you need to pu the label inside a subview to avoid displacement of the text while increasing width.
Swift 4 version of Morten's answer:
class ProgressLabel: UIView {
var progressBarColor = UIColor(red:108.0/255.0, green:200.0/255.0, blue:226.0/255.0, alpha:1.0)
var textColor = UIColor.white
var font = UIFont.boldSystemFont(ofSize: 42)
var progress: Float = 0 {
didSet {
progress = Float.minimum(100.0, Float.maximum(progress, 0.0))
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
// Set up environment.
let size = self.bounds.size
// Prepare progress as a string.
let progressMessage = NSString(format:"%d %%", Int(progress))
var attributes: [NSAttributedStringKey:Any] = [ NSAttributedStringKey.font : font ]
let textSize = progressMessage.size(withAttributes: attributes)
let progressX = ceil(CGFloat(progress) / 100 * size.width)
let textPoint = CGPoint(x: ceil((size.width - textSize.width) / 2.0), y: ceil((size.height - textSize.height) / 2.0))
// Draw background + foreground text
progressBarColor.setFill()
context.fill(self.bounds)
attributes[NSAttributedStringKey.foregroundColor] = textColor
progressMessage.draw(at: textPoint, withAttributes: attributes)
// Clip the drawing that follows to the remaining progress' frame.
context.saveGState()
let remainingProgressRect = CGRect(x: progressX, y: 0.0, width: size.width - progressX, height: size.height)
context.addRect(remainingProgressRect)
context.clip()
// Draw again with inverted colors.
textColor.setFill()
context.fill(self.bounds)
attributes[NSAttributedStringKey.foregroundColor] = progressBarColor
progressMessage.draw(at: textPoint, withAttributes: attributes)
context.restoreGState()
}
}
To make it easier without overriding drawRect:, you can create 2 UIViews to work around that.
Blue background UIView (A) contains white UILabel. (Clipping subviews turns on)
White background UIView (B) contains blue UILabel.
A will overlay on B.
Note: 2 UILabels will have the same sizes, same fonts and same positions.
By adjusting width of UIView A, you will make the process bar works as you wish.
The best and the cleanest way to do that is to use masks.
The view hierarchy of your progress bar should look like this:
Base
Subview at index #1 View showing progress
Subview at index #2 UILabel with text color the same as base
Subview at index #3 UILabel with text color the same as the view showing progress
We will be applying mask to #3. We start applying mask as soon as the view showing progress reaches the frame of #3. The frame of the mask has to follow the progress. When #3 is gradually masked, the label underneath reveals and you achieve the effect nicely and with a little effort.
Here you are how to create masks with CALayers.

Resources