Use a transparent image on top of the standard background color of a View - calayer

I' ve a NSOutlineView with a custom background color set with setBackroundColor: method. After that I wan' t to draw a transparent image on top of that background color. I tried with layer but it don' t work. Anyone knows how to do that? thanks

Something like this should work for your case,
Just pasting the relavent code from my application
What i wanted to say is,
You need to make a custom NSOutliveView and override its drawRect,
In case if it wouldn't work in your case, then take a Custom NSView and draw transparent NSOutlineView
- (void)drawRect:(NSRect)rect
{
switch (_backgroundStyle) {
case ImageColorBackgroundStyle:
{
[self drawColor];
[self drawCustomImage:rect];
break;
}
case ColorBackgroundStyle:
[self drawColor];
break;
case PatternBackgroundStyle:
[self drawPattern];
break;
case ImageBackgroundStyle:
[self drawImage];
break;
case GradientBackgroundStyle:
[self drawGradient];
break;
case NoBackgroundStyle:
if([self hasBorder])
[self drawBorder:rect];
return;
[[NSColor clearColor] set];
NSRectFill(rect);
break;
default:
[super drawRect:rect];
break;
}
if([self hasBorder])
[self drawBorder:rect];
}
-(void)drawCustomImage:(NSRect)rect{
NSRect boundsRect = [self bounds];
NSRect imageRect;
NSSize imageSize = [pBGroundImage size];
int x=0;int y=0;
switch (bgImagePosition) {
case img_bg_lowerright:
if(bViewClipped) return;
x = boundsRect.size.width - imageSize.width;
x -= (BORDER_WIDTH*2);
break;
default:
if(!bViewClipped)
return [self drawImage];
break;
}
if(!bViewClipped)
imageRect = NSMakeRect(x,y,imageSize.width,imageSize.height);
else {
imageRect = NSMakeRect(x,y,rect.size.width,rect.size.height);
}
if(imageRect.origin.x < 0 ) imageRect.origin.x = 0;
if(!bViewClipped){
[pBGroundImage drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
}
else {
CGFloat x_Res = 0.0f;
CGFloat y_Res = 0.0f;
x_Res = orignalSize.width/imageSize.width;
y_Res = orignalSize.height/imageSize.height;
CGFloat scaledHeight = orignalSize.height - rect.size.height;
CGFloat orignal_y = scaledHeight/y_Res;
CGFloat orignal_width = imageSize.height - orignal_y;
NSRect srcRect;
/* This is wrt Orignal Image */
srcRect.origin.x = 0;
srcRect.origin.y = orignal_y;
srcRect.size.width = imageSize.width;
srcRect.size.height = orignal_width;
[pBGroundImage drawInRect:rect
fromRect:srcRect
operation:NSCompositeSourceOver fraction:1.0];
}
}
- (void)drawImage
{
// Load the image.
//NSString *imageName = [self GetBackGroundImage];
//NSImage *anImage = [[NSImage alloc ]initByReferencingFile:imageName];
// Find the point at which to draw it.
NSPoint backgroundCenter;
backgroundCenter.x = [self bounds].size.width / 2;
backgroundCenter.y = [self bounds].size.height / 2;
NSPoint drawPoint = backgroundCenter;
drawPoint.x -= [pBGroundImage size].width / 2;
drawPoint.y -= [pBGroundImage size].height / 2;
[pBGroundImage drawInRect:[self bounds] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
//[anImage release];
}

Related

NSView in cocoa refuses to redraw, whatever I try

I'm writing an application on mac to measure colors on screen. For that I have a ColorView that fills a bezierpath with a specific color. When measuring it goes well for most of the patches, but at a given moment the redraw staggers and the view does not get updated anymore. I have been looking for a while now, tried many proposed solutions. None of them are waterproof.
My actual code:
[colorView setColor:[colorsForMeasuring objectAtIndex:index]];
[colorView setNeedsDisplay:YES];
[colorView setNeedsLayout:YES];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
[colorView setNeedsDisplay:YES];
[colorView setNeedsLayout:YES];
[colorView updateLayer];
[colorView displayIfNeeded];
[colorView display];
});
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[NSApp runModalSession:aSession];
The drawcode of my colorView is as follows:
- (void)display
{
CALayer *layer = self.layer;
[layer setNeedsDisplay];
[layer displayIfNeeded];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
[self internalDrawWithRect:self.bounds];
}
- (void)internalDrawWithRect:(CGRect)rect
{
NSRect bounds = [self bounds];
[color set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRect:(CGRect)rect {
[self internalDrawWithRect:rect];
}
Any help would be very welcome! My original code was much simpler, but I kept on adding things that might have helped.
As I said, the original code was rather straightfoward and simple:
The measuring loop:
for ( uint32 index = 0; index < numberOfColors && !stopMeasuring; index++ )
{
#ifndef NDEBUG
NSLog( #"Color on screen:%#", [colorsForMeasuring objectAtIndex:index] );
#endif
[colorView setColor:[colorsForMeasuring objectAtIndex:index]];
[colorView setNeedsDisplay:YES];
[colorView displayIfNeeded];
// Measure the displayed color in XYZ values
CGFloat myRed, myGreen, myBlue, myAlpha;
[[colorsForMeasuring objectAtIndex:index] getRed:&myRed green:&myGreen blue:&myBlue alpha:&myAlpha];
float theR = (float) myRed;
float theG = (float) myGreen;
float theB = (float) myBlue;
xyzData = [measDeviceController measureOnceXYZforRed:255.0f*theR Green:255.0f*theG Blue:255.0f*theB];
if ( [xyzData count] > 0 )
{
measuringResults[index*3] = [[xyzData objectAtIndex:0] floatValue];
measuringResults[(index*3) + 1] = [[xyzData objectAtIndex:1] floatValue];
measuringResults[(index*3) + 2] = [[xyzData objectAtIndex:2] floatValue];
#ifndef NDEBUG
printf("Measured value X=%f, Y=%f, Z=%f\n", measuringResults[index*3], measuringResults[(index*3) + 1], measuringResults[(index*3) + 2]);
#endif
} }
The drawing of the MCTD_ColorView which just is inherited from NSView:
- (void)setColor:(NSColor *)aColor;
{
[aColor retain];
[color release];
color = aColor;
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds = [self bounds];
[color set];
[NSBezierPath fillRect:bounds];
}
- (void)dealloc
{
[color release];
[super dealloc];
}
When running my measurement loop and after putting breakpoints in "setColor" and "drawRect", debugging always stops in setColor but never in drawRect. Without debugging, the view remains white while I should see all different colors appearing.
As can be seen in my first post, I have tried many things to get it drawing, but they all failed.

setNeedsDisplay not resulting in dirtyRect filling entire view

I am having a confusing issue. I try to draw a graph, and call setNeedsDisplay each time new data is added to the graph.
The graph receives new data via addData:(NSNotification*)theNote, which then tries several ways to call setNeedsDisplay to redraw the view. The data does not get redrawn properly, however, at this time. If I drag the window to another screen, or if I click on the graph and call setNeedsDisplay from mouseDown then the view is redrawn correctly. By monitoring the size of dirtyRect using NSLog, I have tracked the problem to a too small dirtyRect. But I cannot even guess how to correct this. Any ideas out there?
//
// graphView.m
// miniMRTOF
//
// Created by シューリ ピーター on 8/1/16.
// Copyright © 2016 シューリ ピーター. All rights reserved.
//
#import "graphView.h"
#implementation graphView
- (id)initWithFrame:(NSRect)frame{
self = [super initWithFrame:frame];
if (self) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(addData:) name:AddDataNotification object:nil];
theData = [[NSMutableArray alloc] init];
NSLog(#"graphView initialized");
}
return self;
}
-(void)addData:(NSNotification*)theNote{
NSLog(#"graphView has gotten the note");
NSMutableArray *theThermalArray = [[NSMutableArray alloc] init];
theThermalArray = [[theNote userInfo] objectForKey:#"theThermals"];
float ave = 0;
int n = 0;
for (int i=0; i<16; i++){
float f_i = [[theThermalArray objectAtIndex:i] floatValue];
ave += f_i;
if(f_i != 0) n++;
}
ave /= n;
[theData addObject:[NSNumber numberWithFloat:ave]];
NSLog(#"graphView has added %0.3f and now has %lu components", ave, (unsigned long)[theData count]);
[self setNeedsDisplay:YES];
[[self.window contentView] setNeedsDisplay:YES];
[self performSelectorOnMainThread:#selector(redraw) withObject:nil waitUntilDone:YES];
}
-(void)redraw{
[self setNeedsDisplay:YES];
[[self.window contentView] setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"dirtyRect: x:%0.2f, y:%0.2f, w:%0.2f, h:%0.2f", dirtyRect.origin.x, dirtyRect.origin.y, dirtyRect.size.width, dirtyRect.size.height);
float xScale = ([self bounds].size.width)/((float)[theData count]+1e-3);//(maxX - minX);
float yScale = [self bounds].size.height/(maxT - minT);
[[NSColor whiteColor] set];
NSRect fillArea = [self bounds];
[NSBezierPath fillRect:fillArea];
if(dirtyRect.size.height < 100){
NSLog(#"small dirtyRect");
[[NSColor grayColor] set];
[NSBezierPath fillRect:dirtyRect];
}
dirtyRect = [self bounds];
int dataCount = (int)[theData count];
NSBezierPath *pathForFrame = [[NSBezierPath alloc] init];
NSPoint P0 = {1,1};
NSPoint P1 = {1, [self bounds].size.height};
NSPoint P2 = {[self bounds].size.width, 1};
[pathForFrame moveToPoint:P0];
[pathForFrame lineToPoint:P1];
[pathForFrame moveToPoint:P0];
[pathForFrame lineToPoint:P2];
[[NSColor redColor] set];
[pathForFrame stroke];
NSLog(#"drawing %i points", dataCount);
NSBezierPath *pathForPlot = [[NSBezierPath alloc] init];
if(dataCount<maxRawPlotData){
if(dataCount>1){
NSPoint p1;
p1.y = [[theData objectAtIndex:0] floatValue];
p1.x = (0-minX)*xScale;
p1.y = (p1.y-minT)*yScale;
[pathForPlot moveToPoint:p1];
NSLog(#"point: %0.1f, %0.1f", p1.x, p1.y);
}
for(int i=1; i<dataCount; i++){
NSPoint p;
p.y = [[theData objectAtIndex:i] floatValue];
p.x = (i-minX)*xScale;
p.y = (p.y-minT)*yScale;
[pathForPlot lineToPoint:p];
NSLog(#"point: %0.1f, %0.1f", p.x, p.y);
}
}
[[NSColor blackColor] set];
[pathForPlot stroke];
[txtMaxX setStringValue:[NSString stringWithFormat:#"%0.0f",maxX]];
}
-(void)controlTextDidEndEditing:(NSNotification *)obj{
NSLog(#"controlTextDidEndEditing");
if([[obj object] isEqualTo:txtMaxT]){
maxT = [txtMaxT floatValue];
[txtMidT setStringValue:[NSString stringWithFormat:#"%0.1f",0.5*(maxT+minT)]];
}
else if([[obj object] isEqualTo:txtMinT]){
minT = [txtMinT floatValue];
[txtMidT setStringValue:[NSString stringWithFormat:#"%0.1f",0.5*(maxT+minT)]];
}
else if([[obj object] isEqualTo:txtMaxX]){
maxX = [txtMaxX floatValue];
}
else if([[obj object] isEqualTo:txtMinX]){
minX = [txtMinX floatValue];
}
else NSLog(#"How'd we get here?");
[[[obj object] window] makeFirstResponder:[[obj object] window].contentView];
[self setNeedsDisplay:YES];
[self display];
}
-(void)mouseDown:(NSEvent *)theEvent{
NSLog(#"mousedown");
[self setNeedsDisplay:YES];
}
#end

GPUImageStillCamera image preview jumps when taking a photo

I am taking a square cropped photo with GPUImageStillCamera and allowing the user to zoom the camera. When the user clicks to take a picture the camera jumps forward for a split second (as if the camera zoomed in even further past the area the user zoomed to and then immediately returns to the correct crop once the image is returned to screen). This only happens when the user has zoomed the camera. If they have not zoomed the camera the flicker/jump does not happen. (The image return has the correct crop whether or not the user has zoomed).
Thoughts?
Creating camera and adding square crop
//Add in filters
stillCamera = [[GPUImageStillCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionBack];
stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
//Creating a square crop filter
cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0.f, (720.0f/1280.0f)/2.0f, 1.f, (720.0f/1280.0f))];
Image zoom method
-(void)imagePinch:(UIPinchGestureRecognizer *)recognizer{ //Controlling the zoom scale as the user pinches the live preview
if (recognizer.state == UIGestureRecognizerStateBegan) {
zoomOutAdder = 0.0f;
if (currentScale > 2) {
zoomOutAdder = currentScale;
}
}
float addition = (recognizer.scale - lastScale);
if (addition > 0) {
addition = addition *1.7;
}
if (addition < 0) {
addition = addition *(1.7+zoomOutAdder);
}
currentScale = currentScale +addition;
lastScale = recognizer.scale;
if (currentScale < 1) {
currentScale = 1;
}
if (currentScale > 4) {
currentScale =4;
}
if (currentScale == 1) {
zoomOutAdder = 0.0f;
}
cameraImagePreview.transform = CGAffineTransformMakeScale(currentScale, currentScale);
if (recognizer.state == UIGestureRecognizerStateEnded) {
lastScale = 1.0f;
}
Take a photo method
//Adjust crop based on zoom scale of the user
CGFloat zoomReciprocal = 1.0f / currentScale;
CGPoint offset = CGPointMake(((1.0f - zoomReciprocal) / 2.0f), (((1.0f- zoomReciprocal)*(720.0f/1280.0f)) / 2.0f) + ((720.0f/1280.0f)/2)) ;
CGRect newCrop = cropFilter.cropRegion;
newCrop.origin.x = offset.x;
newCrop.origin.y = offset.y;
newCrop.size.width = cropFilter.cropRegion.size.width * zoomReciprocal;
newCrop.size.height = cropFilter.cropRegion.size.height *zoomReciprocal;
cropFilter.cropRegion = newCrop;
*/
//Place photo inside an image preview view for the user to decide if they want to keep it.
[stillCamera capturePhotoAsImageProcessedUpToFilter:cropFilter withOrientation:imageOrientation withCompletionHandler:^(UIImage *processedImage, NSError *error) {
//Pause the current camera
[stillCamera pauseCameraCapture];
//Rest of method
ADDED METHODS
- (void) flipCamera {
if (stillCamera.cameraPosition != AVCaptureDevicePositionFront) {
[UIView animateWithDuration:.65 animations:^{
flipCamera.transform = CGAffineTransformMakeScale(-1, 1);
}];
} else {
[UIView animateWithDuration:.65 animations:^{
flipCamera.transform = CGAffineTransformMakeScale(1, 1);
}];
}
[self performSelector:#selector(rotateCamera) withObject:0 afterDelay:.2];
}
- (void) rotateCamera {
[stillCamera rotateCamera];
//Adjust flash settings as needed
[stillCamera.inputCamera lockForConfiguration:nil];
if (stillCamera.cameraPosition != AVCaptureDevicePositionFront) {
[stillCamera.inputCamera setFlashMode:AVCaptureFlashModeOff];
}
NSAttributedString *attributedFlash =
[[NSAttributedString alloc]
initWithString:#"off"
attributes:
#{
NSFontAttributeName : [UIFont fontWithName:#"Roboto-Regular" size:13.0f],
NSForegroundColorAttributeName : [UIColor colorWithWhite:1 alpha:.55],
NSKernAttributeName : #(.25f)
}];
flashLabel.attributedText = attributedFlash;
[UIView animateWithDuration:.2 animations:^{
[flash setTintColor:[UIColor colorWithWhite:1 alpha:.55]];
}];
[stillCamera.inputCamera unlockForConfiguration];
}
- (void) changeFlash {
if (stillCamera.cameraPosition == AVCaptureDevicePositionFront) {//no flash available on front of camera
return;
}
[stillCamera.inputCamera lockForConfiguration:nil];
if (stillCamera.inputCamera.flashMode == AVCaptureFlashModeOff) {
[stillCamera.inputCamera setFlashMode:AVCaptureFlashModeOn];
[self animateFlashWithTintColor:[UIColor colorWithWhite:1 alpha:1] andString:#"on"];
} else if (stillCamera.inputCamera.flashMode == AVCaptureFlashModeOn) {
[stillCamera.inputCamera setFlashMode:AVCaptureFlashModeOff];
[self animateFlashWithTintColor:[UIColor colorWithWhite:1 alpha:.55] andString:#"off"];
}
[stillCamera.inputCamera unlockForConfiguration];
}
- (void) animateFlashWithTintColor:(UIColor *)color andString:(NSString *)text {
//Set new text
NSAttributedString *attributedFlash =
[[NSAttributedString alloc]
initWithString:text
attributes:
#{
NSFontAttributeName : [UIFont fontWithName:#"Roboto-Regular" size:13.0f],
NSForegroundColorAttributeName : [UIColor colorWithWhite:1 alpha:.55],
NSKernAttributeName : #(.25f)
}];
flashLabel.attributedText = attributedFlash;
float duration = .7;
[UIView animateKeyframesWithDuration:duration delay:0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:duration animations:^{
[flash setTintColor:color];
}];
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:.7/duration animations:^{
flash.transform = CGAffineTransformMakeRotation(M_PI);
}];
}completion:^(BOOL finished){
flash.transform = CGAffineTransformIdentity;
}];
}
-(void) usePhoto {
if ([ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized){
NSLog(#"Do Not Have Right To Save to Photo Library");
}
//Save Image to Phone Album & save image
UIImageWriteToSavedPhotosAlbum(takenPhoto.image, nil, nil, nil);
//Save Image to Delegate
[self.delegate saveImageToDatabase:takenPhoto.image];
[self performSelector:#selector(dismissCamera) withObject:0 afterDelay:.4];
}
Some additional code showing the creation of the the various camera elements used to capture a photo.
centerPoint = CGPointMake(self.view.frame.size.width/2, (cameraHolder.frame.size.height+50+self.view.frame.size.height)/2);
cameraImagePreview = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, cameraHolder.frame.size.width, cameraHolder.frame.size.width)];
[cameraHolder addSubview:cameraImagePreview];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTouch:)];
[cameraImagePreview addGestureRecognizer:tapGesture];
UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(imagePinch:)];
[cameraImagePreview addGestureRecognizer:pinchGesture];
float scaleForView = self.view.frame.size.width/720.0;
fullCameraFocusPoint = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 1280*scaleForView)];
fullCameraFocusPoint.center = CGPointMake(cameraHolder.frame.size.width/2, (cameraHolder.frame.size.width/2)+50);
[self.view insertSubview:fullCameraFocusPoint atIndex:0];
takenPhoto = [[UIImageView alloc]initWithFrame:cameraHolder.frame];
takenPhoto.alpha = 0;
[self.view addSubview:takenPhoto];
//Add in filters
stillCamera = [[GPUImageStillCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionBack];
stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
//Creating a square crop filter
cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0.f, (720.0f/1280.0f)/2.0f, 1.f, (720.0f/1280.0f))];
//Create standard vignette filter
vignetteFilter = [[GPUImageVignetteFilter alloc] init]; //1
vignetteFilter.vignetteCenter = CGPointMake(.5, .5);
vignetteFilter.vignetteStart = 0.4f;
vignetteFilter.vignetteEnd = 1.08f;
//Add filters to photo
[cropFilter addTarget:vignetteFilter];
[stillCamera addTarget:cropFilter];
[vignetteFilter addTarget:cameraImagePreview];
[stillCamera startCameraCapture];

Reset nested uiscrollview zooms when scroll ends

Nested uiscrolls in a larger uiscroll need to, when zoomed, reset zoom level when they are off screen. I am trying to reset all of them when the scrolling ends but no luck. Any ideas?
myScrollview = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width,self.view.frame.size.height)];
myScrollview.pagingEnabled = YES;
myScrollview.scrollEnabled =YES;
myScrollview.clipsToBounds = NO;
myScrollview.indicatorStyle = UIScrollViewIndicatorStyleWhite;
myScrollview.showsHorizontalScrollIndicator = YES;
myScrollview.backgroundColor = [UIColor blackColor];
myScrollview.delegate = self;
NSInteger viewcount=4;
NSArray *images = [NSArray arrayWithObjects:[UIImage imageNamed:#"01.png"],[UIImage imageNamed:#"02.png"],[UIImage imageNamed:#"03.png"],[UIImage imageNamed:#"04.png"],nil];
for (int i = 0; i <viewcount; i++)
{
CGFloat x = i * self.view.frame.size.width;
subView = [[UIScrollView alloc]initWithFrame:CGRectMake(x, 0, self.view.frame.size.width, self.view.frame.size.height)];
[subView setBackgroundColor:[UIColor blackColor]];
[subView setCanCancelContentTouches:NO];
subView.clipsToBounds = NO; // default is NO, we want to restrict drawing within our scrollview
subView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
aImageView = [[UIImageView alloc ] initWithImage:[images objectAtIndex:i]];
[self.aImageView setTag:viewcount];
[subView addSubview:aImageView];
[subView setContentSize:CGSizeMake(aImageView.frame.size.width, subView.frame.size.height)];
subView.minimumZoomScale = 1;
subView.maximumZoomScale = 3;
subView.delegate = self;
[subView setScrollEnabled:YES];
subView.contentSize = aImageView.frame.size;
[myScrollview addSubview:subView];
}
myScrollview.contentSize = CGSizeMake(self.view.frame.size.width*viewcount,self.view.frame.size.height);
[self.view addSubview:myScrollview];
}
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
NSLog (#"test");
UIView * view = nil;
view = [subView viewWithTag:0];
//return view;
return [scrollView.subviews objectAtIndex:0];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"Did scroll");
[self resetImageZoom];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
= NSLog(#"Did end decal");
[self resetImageZoom];
}
-(void)resetImageZoom {
NSLog(#"Resetting any image zoom");
for(UIView *view in [myScrollview subviews]) {
//if([view isKindOfClass:[UIScrollView class]]) {
//[(UIScrollView*)view setZoomScale:1.0 animated:NO];
//}
view.transform = CGAffineTransformIdentity;
}
}
That did it...
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"Did end dece");
for (UIView *view in scrollView.subviews) {
if([view isKindOfClass:[UIScrollView class]]) {
[(UIScrollView*)view setZoomScale:1.0 animated:NO];
}
}
}

Custom NSSliderCell knob doesn't follow mouse when tracking

I've implemented a custom NSSliderCell that uses a very different knob in size than the default one (this is for an interactive exhibit - I cannot use any default Mac OS X controls).
While the slider appears and behaves correctly (the knob goes from end to end, etc), when you look carefully you see a weird behaviour: Moving the mouse say, 20 pixels, will result in the knob to move by 30 pixels. This means that the knob might reach the end of the slider (and the slider will have the maximum value) before the mouse will reach the end.
This looks very weird and goes against all expectations. I wonder what do I have to change to ensure that the knob follows the mouse and doesn't move faster.
OK, as always, the solution was the simplest one.
Here is the simplest code you need to have a very custom slider:
#import "CSSSliderCell.h"
#define KNOB_WIDTH 20
#define KNOB_HEIGHT 126
#define SLIDER_WIDTH 13
#implementation CSSSliderCell
- (void)drawKnob:(NSRect)rect
{
// knobImage is an NSImage
[knobImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
}
- (void)drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped
{
NSRect slideRect = cellFrame;
NSColor *backColor = [NSColor redColor];
if ([(NSSlider*) [self controlView] isVertical] == YES)
{
slideRect.size.width = SLIDER_WIDTH;
slideRect.origin.x += (cellFrame.size.width - SLIDER_WIDTH) * 0.5;
} else {
slideRect.size.height = SLIDER_WIDTH;
slideRect.origin.y += (cellFrame.size.height - SLIDER_WIDTH) * 0.5;
}
NSBezierPath *bezierPath = [NSBezierPath bezierPathWithRoundedRect:slideRect xRadius:SLIDER_WIDTH * 0.5 yRadius:SLIDER_WIDTH * 0.5];
[backColor setFill];
[bezierPath fill];
}
- (NSRect)knobRectFlipped:(BOOL)flipped{
CGFloat value = ([self doubleValue] - [self minValue])/ ([self maxValue] - [self minValue]);
NSRect defaultRect = [super knobRectFlipped:flipped];
NSRect myRect = NSMakeRect(0, 0, 0, 0);
if ([(NSSlider*) [self controlView] isVertical] == YES)
{
myRect.size.width = KNOB_WIDTH;
myRect.size.height = KNOB_HEIGHT;
if (!flipped) {
myRect.origin.y = value * ([[self controlView] frame].size.height - KNOB_HEIGHT);
} else {
myRect.origin.y = (1.0 - value) * ([[self controlView] frame].size.height - KNOB_HEIGHT);
}
myRect.origin.x = defaultRect.origin.x;
} else {
myRect.size.width = KNOB_HEIGHT;
myRect.size.height = KNOB_WIDTH;
myRect.origin.x = value * ([[self controlView] frame].size.width - KNOB_HEIGHT);
myRect.origin.y = defaultRect.origin.y;
}
return myRect;
}
- (BOOL)_usesCustomTrackImage
{
return YES;
}
#end
This might have problems, but so far both on horizontal and vertical orientations it works well.
orestis, your code helps me much. Thank you!
However, the knob's position (myRect.origin) is not correct. I modified the code (and also removed the hardcoded macros).
- (NSRect)knobRectFlipped:(BOOL)flipped {
float KNOB_WIDTH = [knobImage size].width;
float KNOB_HEIGHT = [knobImage size].height;
CGFloat value = ([self doubleValue] - [self minValue])/ ([self maxValue] - [self minValue]);
NSRect defaultRect = [super knobRectFlipped:flipped];
NSRect myRect = NSMakeRect(0, 0, 0, 0);
if ([(NSSlider*) [self controlView] isVertical] == YES)
{
//...
} else {
myRect.size.width = KNOB_WIDTH;
myRect.size.height = KNOB_HEIGHT;
myRect.origin.x = value * ([[self controlView] frame].size.width - KNOB_WIDTH);
myRect.origin.y = defaultRect.origin.y + defaultRect.size.height/2.0 - myRect.size.height/2.0; // Fixed the position
}
return myRect;
};

Resources