Ok i'm trying to create a ruled notebook view.
The code to create this is below
#interface QuickNoteNoteTextView ()
#property (nonatomic) CGPoint startPoint;
#property (nonatomic) CGPoint endPoint;
#property (nonatomic, retain) UIColor *lineColor;
#end
#implementation QuickNoteNoteTextView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
// Get the graphics context
CGContextRef ctx = UIGraphicsGetCurrentContext();
[super drawRect:rect];
// Get the height of a single text line
NSString *alpha = #"ABCD";
CGSize textSize = [alpha sizeWithFont:self.font constrainedToSize:self.contentSize lineBreakMode:NSLineBreakByWordWrapping ];
NSUInteger height = textSize.height;
// Get the height of the view or contents of the view whichever is bigger
textSize = [self.text sizeWithFont:self.font constrainedToSize:self.contentSize lineBreakMode:NSLineBreakByWordWrapping ];
NSUInteger contentHeight = (rect.size.height > textSize.height) ? (NSUInteger)rect.size.height : textSize.height;
NSUInteger offset = 6 + height; // MAGIC Number 6 to offset from 0 to get first line OK ???
contentHeight += offset;
// Draw ruled lines
CGContextSetRGBStrokeColor(ctx, 0, 0, 0, 1);
for(int i=offset;i < contentHeight;i+=height) {
CGPoint lpoints[2] = { CGPointMake(0, i), CGPointMake(rect.size.width, i) };
CGContextStrokeLineSegments(ctx, lpoints, 2);
}
//vertical line
self.startPoint = CGPointMake(22.0, self.frame.size.height * -1);
self.endPoint = CGPointMake(22.0, self.frame.size.height * 2);
self.lineColor = [UIColor redColor];
CGContextSetShouldAntialias(ctx, NO);
CGContextSetStrokeColorWithColor(ctx, [self.lineColor CGColor]);
CGContextSetLineWidth(ctx, 1);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, self.startPoint.x, self.startPoint.y);
CGContextAddLineToPoint(ctx, self.endPoint.x, self.endPoint.y);
CGContextMoveToPoint(ctx, self.startPoint.x + 2.0f, self.startPoint.y);
CGContextAddLineToPoint(ctx, self.endPoint.x + 2.0f, self.endPoint.y);
CGContextDrawPath(ctx, kCGPathStroke);
}
This code correctly draws horizontal ruled lines in a UITextview and a vertical margin line on the left.
The last thing i'm trying to do is shift the text in my textview right so that it sits to the right of the vertical line.
Any ideas?
OK I solved the problem by using contentInset to push the text to the right by 20 pixels, then amended my drawRect to extend the horizontal line by -20 to account for the contentInset
Related
Im trying to setMinimumTrackImage of the slider using an image with CAGradientLayer, lets say using blue and red colors.
what happens is that the full track gets the gradient color, its starts with red, and sliding the Thumb to the right reveals the blue.
I want the color to start from red to blue up to the Thumb, ands "stretch" as the Thumb moves.
any ideas ? I though about setting the slider.maximumValue = slider...width
and change the gradient image as I listen to the slider value change but it didn't work
I don't think you'll be successful trying to set the min track image.
Options are a completely custom slider...
or
Set the min track image to a clear image, add an imageView with the gradient image behind the slider, stretch the frame of the imageView to match the thumb movement.
Here's an example:
and the code (just a starting point... would be much better to wrap it into a subclass):
SliderTestViewController.h
//
// SliderTestViewController.h
//
// Created by Don Mag on 10/31/19.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface SliderTestViewController : UIViewController
#end
NS_ASSUME_NONNULL_END
SliderTestViewController.m
//
// SliderTestViewController.m
//
// Created by Don Mag on 10/31/19.
//
#import "SliderTestViewController.h"
#import "UIImage+Utils.h"
#interface SliderTestViewController ()
#property (strong, nonatomic) UIImageView *theFakeSliderTrackImageView;
#property (strong, nonatomic) UISlider *theSlider;
#property (strong, nonatomic) NSLayoutConstraint *imgWidthConstraint;
#end
#implementation SliderTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// instantiate a slider
_theSlider = [UISlider new];
// instantiate an image view to use as our custom / fake "left side" of the slider track
_theFakeSliderTrackImageView = [UIImageView new];
// we want the image to stretch
_theFakeSliderTrackImageView.contentMode = UIViewContentModeScaleToFill;
// create a horizontal gradient image to use for our "left side" of the slider track
// the image will be stretched... using a width of 128 seems reasonable
UIImage *gradImg = [UIImage gradientImageWithSize:CGSizeMake(128.0, 4.0) startColor:[UIColor blueColor] endColor:[UIColor redColor] startPoint:CGPointMake(0.0, 0.0) endPoint:CGPointMake(1.0, 0.0)];
// set the gradient image to our image view
_theFakeSliderTrackImageView.image = gradImg;
// create a clear image to use for the slider's min track image
UIImage *clearImg = [UIImage imageWithColor:[UIColor clearColor] size:CGSizeMake(1.0, 1.0)];
// set min track image to clear image
[_theSlider setMinimumTrackImage:clearImg forState:UIControlStateNormal];
// set max track image if desired
// [_theSlider setMaximumTrackImage:anImage forState:UIControlStateNormal];
_theFakeSliderTrackImageView.translatesAutoresizingMaskIntoConstraints = NO;
_theSlider.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_theFakeSliderTrackImageView];
[self.view addSubview:_theSlider];
[NSLayoutConstraint activateConstraints:#[
// constrain the slider centerY with 20-pts leading / trailing
[_theSlider.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
[_theSlider.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:20.0],
[_theSlider.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20.0],
// constrain image view centerY to slider centerY
[_theFakeSliderTrackImageView.centerYAnchor constraintEqualToAnchor:_theSlider.centerYAnchor constant:0.0],
// constrain image view leading to slider leading
[_theFakeSliderTrackImageView.leadingAnchor constraintEqualToAnchor:_theSlider.leadingAnchor constant:0.0],
// image view height to 5-pts (adjust as desired)
[_theFakeSliderTrackImageView.heightAnchor constraintEqualToConstant:5.0],
]];
// init imageView width constraint to 0.0
_imgWidthConstraint = [_theFakeSliderTrackImageView.widthAnchor constraintEqualToConstant:0.0];
_imgWidthConstraint.active = YES;
[_theSlider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self updateSliderGradientImage];
}
- (void)updateSliderGradientImage {
// set "fake track" imageView width to origin.x of thumb rect (plus 2 for good measure)
CGRect trackRect = [_theSlider trackRectForBounds:_theSlider.bounds];
CGRect thumbRect = [_theSlider thumbRectForBounds:_theSlider.bounds trackRect:trackRect value:_theSlider.value];
_imgWidthConstraint.constant = thumbRect.origin.x + 2;
}
- (void)sliderChanged:(id)sender {
[self updateSliderGradientImage];
}
#end
UIImage+Utils.h
//
// UIImage+Utils.h
//
// Created by Don Mag on 10/31/19.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface UIImage (Utils)
+ (nullable UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size;
+ (nullable UIImage *)gradientImageWithSize:(CGSize)size startColor:(UIColor *)startColor endColor:(UIColor *)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
#end
NS_ASSUME_NONNULL_END
UIImage+Utils.m
//
// UIImage+Utils.m
//
// Created by Don Mag on 10/31/19.
//
#import "UIImage+Utils.h"
#implementation UIImage (Utils)
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
if (!color || size.height < 1 || size.width < 1)
return nil;
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull context) {
[color setFill];
[context fillRect:renderer.format.bounds];
}];
return image;
}
+ (UIImage *)gradientImageWithSize:(CGSize)size startColor:(UIColor *)startColor endColor:(UIColor *)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
if (!startColor || !endColor)
return nil;
CFArrayRef colors = (__bridge CFArrayRef) [NSArray arrayWithObjects:
(id)startColor.CGColor,
(id)endColor.CGColor,
nil];
CGGradientRef g = CGGradientCreateWithColors(nil, colors, nil);
startPoint.x *= size.width;
startPoint.y *= size.height;
endPoint.x *= size.width;
endPoint.y *= size.height;
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
CGContextDrawLinearGradient(rendererContext.CGContext, g, startPoint, endPoint, kCGGradientDrawsAfterEndLocation);
}];
return gradientImage;
}
#end
So I'm still learning this Objective C lark and i've hit a stump. All I want to do is use the value from Sliderchanged to populate the 'CGFloat lineWidth =', Hopefully to have the end result of changing the width of the crumbPath line...
******************EDIT*********************
CrumbPathView.h
#import "CrumbPathView.h"
#import "CrumbPath.h"
#import "FirstViewController.h"
#interface CrumbPathView (FileInternal)
- (CGPathRef)newPathForPoints:(MKMapPoint *)points
pointCount:(NSUInteger)pointCount
clipRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale;
#end
#implementation CrumbPathView
-(IBAction)sliderChanged:(UISlider *)sender
{
NSLog(#"slider value = %f", sender.value);
}
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
CGFloat lineWidth = 30;
// outset the map rect by the line width so that points just outside
// of the currently drawn rect are included in the generated path.
MKMapRect clipRect = MKMapRectInset(mapRect, -lineWidth, -lineWidth);
[crumbs lockForReading];
CGPathRef path = [self newPathForPoints:crumbs.points
pointCount:crumbs.pointCount
clipRect:clipRect
zoomScale:zoomScale];
[crumbs unlockForReading];
if (path != nil)
{
CGContextAddPath(context, path);
CGContextSetRGBStrokeColor(context, 0.0f, 0.0f, 1.0f, 0.5f);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);
CGPathRelease(path);
}
}
#end
#implementation CrumbPathView (FileInternal)
static BOOL lineIntersectsRect(MKMapPoint p0, MKMapPoint p1, MKMapRect r)
{
double minX = MIN(p0.x, p1.x);
double minY = MIN(p0.y, p1.y);
double maxX = MAX(p0.x, p1.x);
double maxY = MAX(p0.y, p1.y);
MKMapRect r2 = MKMapRectMake(minX, minY, maxX - minX, maxY - minY);
return MKMapRectIntersectsRect(r, r2);
}
#define MIN_POINT_DELTA 5.0
- (CGPathRef)newPathForPoints:(MKMapPoint *)points
pointCount:(NSUInteger)pointCount
clipRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
{
// The fastest way to draw a path in an MKOverlayView is to simplify the
// geometry for the screen by eliding points that are too close together
// and to omit any line segments that do not intersect the clipping rect.
// While it is possible to just add all the points and let CoreGraphics
// handle clipping and flatness, it is much faster to do it yourself:
//
if (pointCount < 2)
return NULL;
CGMutablePathRef path = NULL;
BOOL needsMove = YES;
#define POW2(a) ((a) * (a))
// Calculate the minimum distance between any two points by figuring out
// how many map points correspond to MIN_POINT_DELTA of screen points
// at the current zoomScale.
double minPointDelta = MIN_POINT_DELTA / zoomScale;
double c2 = POW2(minPointDelta);
MKMapPoint point, lastPoint = points[0];
NSUInteger i;
for (i = 1; i < pointCount - 1; i++)
{
point = points[i];
double a2b2 = POW2(point.x - lastPoint.x) + POW2(point.y - lastPoint.y);
if (a2b2 >= c2) {
if (lineIntersectsRect(point, lastPoint, mapRect))
{
if (!path)
path = CGPathCreateMutable();
if (needsMove)
{
CGPoint lastCGPoint = [self pointForMapPoint:lastPoint];
CGPathMoveToPoint(path, NULL, lastCGPoint.x, lastCGPoint.y);
}
CGPoint cgPoint = [self pointForMapPoint:point];
CGPathAddLineToPoint(path, NULL, cgPoint.x, cgPoint.y);
}
else
{
// discontinuity, lift the pen
needsMove = YES;
}
lastPoint = point;
}
}
#undef POW2
// If the last line segment intersects the mapRect at all, add it unconditionally
point = points[pointCount - 1];
if (lineIntersectsRect(lastPoint, point, mapRect))
{
if (!path)
path = CGPathCreateMutable();
if (needsMove)
{
CGPoint lastCGPoint = [self pointForMapPoint:lastPoint];
CGPathMoveToPoint(path, NULL, lastCGPoint.x, lastCGPoint.y);
}
CGPoint cgPoint = [self pointForMapPoint:point];
CGPathAddLineToPoint(path, NULL, cgPoint.x, cgPoint.y);
}
return path;
}
#end
CrumbPathView.h
#import <MapKit/MapKit.h>
#import <UIKit/UIKit.h>
#interface CrumbPathView : MKOverlayView
{
}
- (IBAction)sliderChanged:(id)sender;
#end
I've added the rest of the code from the .h & .m ...
I think you are mostly there. Your slider is probably stored in a propery...just check it like you would any other property when you need its value.
Example:
-(IBAction)sliderChanged:(UISlider *)sender
{
//[self drawMapRect...]
}
-(void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:CGContextRef)context
{
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
CGFloat lineWidth = self.mySliderControl.value;
//Do something with lineWidth
}
Did you tried this ?
-(IBAction)sliderChanged:(UISlider *)sender
{
NSLog(#"slider value = %f", sender.value);
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
CGFloat lineWidth = (UISlider*)sender.value;
//Set value on crumbpath using slider value
}
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
}
I have a UITextView where the user can create notes and save into a plist file.
I want to be able to show lines just like a normal notebook. The problem I have is
that the text won't align properly.
The image below explains the problem quite well.
This is the background I use to create the lines like the Notes.app
This is my code for creating the background for my UITextView:
textView.font = [UIFont fontWithName:#"MarkerFelt-Thin" size:19.0];
textView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed: #"Notes.png"]];
I know that the UIFont.lineHeight property is only available in > iOS 4.x.
So I wonder if there is another solution to my problem?
You should try and draw your lines programmatically rather than using an image. Here's some sample code of how you could accomplish that. You can subclass UITextView and override it's drawRect: method.
NoteView.h
#import <UIKit/UIKit.h>
#interface NoteView : UITextView <UITextViewDelegate> {
}
#end
NoteView.m
#import "NoteView.h"
#implementation NoteView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:0.6f alpha:1.0f];
self.font = [UIFont fontWithName:#"MarkerFelt-Thin" size:19];
}
return self;
}
- (void)drawRect:(CGRect)rect {
//Get the current drawing context
CGContextRef context = UIGraphicsGetCurrentContext();
//Set the line color and width
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.2f].CGColor);
CGContextSetLineWidth(context, 1.0f);
//Start a new Path
CGContextBeginPath(context);
//Find the number of lines in our textView + add a bit more height to draw lines in the empty part of the view
NSUInteger numberOfLines = (self.contentSize.height + self.bounds.size.height) / self.font.leading;
//Set the line offset from the baseline. (I'm sure there's a concrete way to calculate this.)
CGFloat baselineOffset = 6.0f;
//iterate over numberOfLines and draw each line
for (int x = 0; x < numberOfLines; x++) {
//0.5f offset lines up line with pixel boundary
CGContextMoveToPoint(context, self.bounds.origin.x, self.font.leading*x + 0.5f + baselineOffset);
CGContextAddLineToPoint(context, self.bounds.size.width, self.font.leading*x + 0.5f + baselineOffset);
}
//Close our Path and Stroke (draw) it
CGContextClosePath(context);
CGContextStrokePath(context);
}
#end
MyViewController.h
#import <UIKit/UIKit.h>
#import "NoteView.h"
#interface MyViewController : UIViewController <UITextViewDelegate> {
NoteView *note;
}
#property (nonatomic, retain) NoteView *note;
#end
MyViewController.m
#import "MyViewController.h"
#import "NoteView.h"
#define KEYBOARD_HEIGHT 216
#implementation MyViewController
#synthesize note;
- (void)loadView {
[super loadView];
self.note = [[[NoteView alloc] initWithFrame:self.view.bounds] autorelease];
[self.view addSubview:note];
note.delegate = self;
note.text = #"This is the first line.\nThis is the second line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\n";
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[note setNeedsDisplay];
}
- (void)textViewDidBeginEditing:(UITextView *)textView {
CGRect frame = self.view.bounds;
frame.size.height -= KEYBOARD_HEIGHT;
note.frame = frame;
}
- (void)textViewDidEndEditing:(UITextView *)textView {
note.frame = self.view.bounds;
}
- (void)dealloc {
[note release];
[super dealloc];
}
Take a look at Apple's documentation for Managing the Keyboard, specifically "Moving Content That Is Located Under the Keyboard". It explains how to listen for NSNotifcations and adjust your views properly.
I think the problem is with your image, the yellow space over the line is creating the problem.
You should edit the image.
And nice work.
I have a storyboard which loads loads a custom UIView. Also a sub view is added to the view in the storyboard. It worked fine until I overwrote the drawRect method of the sub view, then I just saw a black rectangle instead of the subview. Here is the code:
#import <UIKit/UIKit.h>
#import "MySubview.h"
#interface MyView : UIView
#end
#import "MyView.h"
#implementation MyView
- (void) awakeFromNib
{
CGRect frame = [self frame];
MySubview* sv = [[MySubview alloc] initWithFrame:frame];
[self addSubview:sv];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#end
#import <UIKit/UIKit.h>
#interface MySubview : UIView
#property (retain, nonatomic) NSString* text;
#property (retain, nonatomic) UILabel* label;
#end
#import "MySubview.h"
#implementation MySubview
#synthesize text, label;
- (void)attachLabel
{
text = #"Hello";
label = [[UILabel alloc] init];
[label setText:text];
[label setFont:[UIFont fontWithName:#"Futura" size:18]];
[label setBackgroundColor:[UIColor clearColor]];
[label sizeToFit];
CGRect labelFrame = label.frame;
labelFrame.origin.x = (self.frame.size.width - labelFrame.size.width) / 2;
labelFrame.origin.y = (self.frame.size.height - labelFrame.size.height) / 2;
label.frame = labelFrame;
[self addSubview:label];
}
- (void)awakeFromNib
{
[self attachLabel];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self attachLabel];
}
return self;
}
// Works if I comment this out!
- (void)drawRect:(CGRect)rect
{
}
#end
Update - Added drawing code below:
- (void)drawRectWithRoundBorders:(CGRect)rect
{
[super drawRect:rect];
// Parameters used for drawing.
const CGFloat lineWidth = 5;
const CGFloat shadowOffset = 3;
const CGFloat shadowBlur = 4;
const CGFloat spaceToBB = 10; // Space to the bounding box of this view.
const CGFloat cornerRadii = 5;
const CGFloat lineColor[4] = { 0, 0, 0, 1 };
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetStrokeColor(ctx, lineColor);
CGContextSetShadow(ctx, CGSizeMake(shadowOffset, shadowOffset), shadowBlur);
CGRect innerRect = rect;
innerRect.size.width -= 2*spaceToBB;
innerRect.size.height -= 2*spaceToBB;
innerRect.origin.x += spaceToBB;
innerRect.origin.y += spaceToBB;
UIBezierPath *path =
[UIBezierPath bezierPathWithRoundedRect:innerRect
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(cornerRadii, cornerRadii)
];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
- (void)drawRect:(CGRect)rect
{
[self drawRectWithRoundBorders:rect];
}
Update
It seems to work when I fill the bounding box of the sub view with some color first.
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat white[] = {1, 1, 1, 0.5};
CGContextSetFillColor(ctx, white);
CGContextAddRect(ctx, rect);
CGContextFillPath(ctx);
Just add [self setOpaque:NO] in your initWithFrame: method.
That's because your drawRect is doing nothing. You override drawRect if you want to do custom drawing. So:
If you don't want to do custom drawing then don't override drawRect.
If you do want to do custom drawing then actually do something in drawRect.
I am new to drawing with Cocoa, and I am making some software which will have sliders similar to these found in GarageBand:
GB Sliders http://img33.imageshack.us/img33/2668/schermafbeelding2010061r.png
These look beautiful and can be controld by moving the mouse up and down.
Can you help me with customizing NSSliders by subclassing them, so I can make them look and behave exactly as in GarageBand? Thanks.
I have one image for the knob which should be rotated as they do not need to be in 3D .
The simplest way is to create a NSView subclass that handles both the mouse management and the drawing.
There is a sample code that can help you to start named "TLayer". It is part of the Examples of the XCode 3.1.4. It contains a circular custom view that controls the offset and the radius of the shadow drawn for layers. It is easy to understand and easy to extend.
Note: as it does not seems to be available on the Apple website, so I have pasted the sources below.
ShadowOffsetView.h
#import <AppKit/AppKit.h>
extern NSString *ShadowOffsetChanged;
#interface ShadowOffsetView : NSView
{
CGSize _offset;
float _scale;
}
- (float)scale;
- (void)setScale:(float)scale;
- (CGSize)offset;
- (void)setOffset:(CGSize)offset;
#end
ShadowOffsetView.m
#import "ShadowOffsetView.h"
NSString *ShadowOffsetChanged = #"ShadowOffsetChanged";
#interface ShadowOffsetView (Internal)
- (NSCell *)cell;
#end
#implementation ShadowOffsetView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
_offset = CGSizeZero;
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (float)scale
{
return _scale;
}
- (void)setScale:(float)scale
{
_scale = scale;
}
- (CGSize)offset
{
return CGSizeMake(_offset.width * _scale, _offset.height * _scale);
}
- (void)setOffset:(CGSize)offset
{
offset = CGSizeMake(offset.width / _scale, offset.height / _scale);
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
}
}
- (BOOL)isOpaque
{
return NO;
}
- (void)setOffsetFromPoint:(NSPoint)point
{
float radius;
CGSize offset;
NSRect bounds;
bounds = [self bounds];
offset.width = (point.x - NSMidX(bounds)) / (NSWidth(bounds) / 2);
offset.height = (point.y - NSMidY(bounds)) / (NSHeight(bounds) / 2);
radius = sqrt(offset.width * offset.width + offset.height * offset.height);
if (radius > 1) {
offset.width /= radius;
offset.height /= radius;
}
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter]
postNotificationName:ShadowOffsetChanged object:self];
}
}
- (void)mouseDown:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)mouseDragged:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds;
CGContextRef context;
float x, y, w, h, r;
bounds = [self bounds];
x = NSMinX(bounds);
y = NSMinY(bounds);
w = NSWidth(bounds);
h = NSHeight(bounds);
r = MIN(w / 2, h / 2);
context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextTranslateCTM(context, x + w/2, y + h/2);
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextClip(context);
CGContextSetGrayFillColor(context, 0.910, 1);
CGContextFillRect(context, CGRectMake(-w/2, -h/2, w, h));
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.616, 1);
CGContextStrokePath(context);
CGContextAddArc(context, 0, -2, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.784, 1);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, r * _offset.width, r * _offset.height);
CGContextSetLineWidth(context, 2);
CGContextSetGrayStrokeColor(context, 0.33, 1);
CGContextStrokePath(context);
}
#end
Well, for the actual drawing you'd either have to have images for each rotation angle of the knob (easier to implement) and then just draw the proper one.
(While for a real realistic 3d look—even if possible—programmatic drawing wouldn't be worth its time, I guess.)
Or draw the knob by code. This article should give you an idea I think:
http://katidev.com/blog/2008/03/07/how-to-create-a-custom-control-with-nsview/
(For both, the mouse event handling and basic NSBezerPath drawing of circular and rotating knob-like elements)