In my app, I use UIKit Dynamics including a UICollisionBehavior to have a menu bounce when it opens and when it closes. The code I'm using for this is below. This has been working fine for iOS8. With iOS9 (including iOS9.1 beta 2 just released), however, I'm finding an odd issue. On the surface, the menu I'm bouncing with this bouncing animation wasn't full closing after opening and then closing it. Looking more closely, I find that the boundaries for the UICollisionBehavior are computed with the same values across iOS8 and iOS9.
Menu opening collision boundary: (798,330) to (1024,330)
Which represents a line, on screen, where the bottom of the menu should finally rest after opening and bouncing.
Menu closing collision boundary: (798,-280) to (1024,-280)
Which represents a line, off screen, where the top of the menu should finally rest after closing and bouncing.
The problem comes in iOS9 where the menu UIView doesn't actually end up resting finally at these boundaries. After opening, the menu frame looks like this with iOS9:
(798, -1.5; 226, 330) [This is: (x, y; w, h)]
and after closing, the menu frame looks like:
(798, -278.5; 226, 330)
BUT, this should actually be:
(798, 0; 226, 330) (after opening)
(798, -280; 226, 330) (after closing)
Anyone else seeing these issues with iOS9 and collision behaviors?
I'm about to put a hack in my code (search for "HACK" below), which I'll make selective for iOS9, but I really don't like these hacks!
BounceAnimation.h
//
// BounceAnimation.h
// Petunia
//
// Created by Christopher Prince on 12/18/14.
// Copyright (c) 2014 Spastic Muffin, LLC. All rights reserved.
//
// Animates an object through a straight line path, up, down, left or right until it lands, after which it bounces. This requires iOS8 or later.
#import <Foundation/Foundation.h>
#interface BounceAnimation : NSObject
// distnace is for the viewToAnimate to travel until it lands and bounces, in points. You must set this before calling run.
- (instancetype) initWithReferenceView: (UIView *) referenceView viewToAnimate: (UIView *) viewToAnimate andDistance: (CGFloat) distance;
// Direction and distance will be obtained from animateToPoint, and should be consistent with the constraints for the direction property below. I.e., the animateToPoint should be down, left, right, or up from the origin of the viewToAnimate.
- (instancetype) initWithReferenceView: (UIView *) referenceView viewToAnimate: (UIView *) viewToAnimate andFinalPoint: (CGPoint) animateToPoint;
// One shot animation. You can only call run once.
- (void) run;
// Called when animation completes, if given. Called when all the bouncing is done.
#property (nonatomic, strong) void (^completion)(void);
// Called when first contact is made with the boundary, just as the first bounce is about to begin.
#property (nonatomic, strong) void (^firstImpactCallback)(void);
// Only keeps weak references to the views passed in the init method.
#property (nonatomic, weak, readonly) UIView *referenceView;
#property (nonatomic, weak, readonly) UIView *viewToAnimate;
// Rate at which the object accelerates towards the boundary. Same units as magnitude for UIGravityBehavior. Defaults to 1.0.
#property (nonatomic) CGFloat accelerationRate;
// Defaults to 0. Units are points per second.
#property (nonatomic) CGFloat initialVelocity;
// This is a unit vector.
// dx (first component) is rightwards; e.g., dx=0, no right/left; dx=-1, is left one unit
// dy (second component) is downwards; e.g., dy=1, down one unit.
// Defaults to (0, 1), downwards.
// Right now, dx and dy can be 0, 1, or -1. One of dx and dy must be 0.
#property (nonatomic, readonly) CGVector direction;
#end
BounceAnimation.m
//
// BounceAnimation.m
// Petunia
//
// Created by Christopher Prince on 12/18/14.
// Copyright (c) 2014 Spastic Muffin, LLC. All rights reserved.
//
#import "BounceAnimation.h"
#import "Vector.h"
#import "UIDevice+Extras.h"
#interface BounceAnimation()<UIDynamicAnimatorDelegate, UICollisionBehaviorDelegate>
{
UIDynamicAnimator *_animator;
UIGravityBehavior *_gravityBehavior;
UICollisionBehavior *_collision;
UIDynamicItemBehavior *_velocity;
CGPoint _linearVelocity;
CGFloat _distanceInPoints;
BOOL _calledFirstImpactCallback;
}
#property (nonatomic, weak) UIView *referenceView;
#property (nonatomic, weak) UIView *viewToAnimate;
#property (nonatomic) CGVector direction;
#end
#define INITIAL_GRAVITY_MAGNITUDE 1.0
#implementation BounceAnimation
- (void) setupWithReferenceView: (UIView *) referenceView andViewToAnimate: (UIView *) viewToAnimate;
{
AssertIf([UIDevice ios7OrEarlier], #"Don't have at least iOS8!");
self.referenceView = referenceView;
self.viewToAnimate = viewToAnimate;
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:referenceView];
_animator.delegate = self;
_gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewToAnimate]];
_gravityBehavior.magnitude = INITIAL_GRAVITY_MAGNITUDE;
_velocity = [[UIDynamicItemBehavior alloc] initWithItems:#[viewToAnimate]];
}
// referenceView is just the view on top of which we're doing our animation. E.g., it could be self.view of a view controller. viewToAnimate must be a subview of the reference view.
- (instancetype) initWithReferenceView: (UIView *) referenceView viewToAnimate: (UIView *) viewToAnimate andDistance: (CGFloat) distanceInPoints;
{
self = [super init];
if (self) {
AssertIf(distanceInPoints <= 0.0, #"Invalid distance: %f", _distanceInPoints);
_distanceInPoints = distanceInPoints;
[self setupWithReferenceView:referenceView andViewToAnimate:viewToAnimate];
[self setDirection:CGVectorMake(0.0, 1.0)];
}
return self;
}
- (instancetype) initWithReferenceView: (UIView *) referenceView viewToAnimate: (UIView *) viewToAnimate andFinalPoint: (CGPoint) animateToPoint;
{
self = [super init];
if (self) {
// Need to compute distance and direction.
CGVector direction = [Vector subFirst:[Vector fromPoint:animateToPoint] from:[Vector fromPoint:viewToAnimate.frameOrigin]];
SPASLogDetail(#"direction after subtraction: %#", NSStringFromCGVector(direction));
// Special case: No direction because same start and finish.
if (direction.dy + direction.dx == 0.0) {
_distanceInPoints = 0.0;
}
else {
direction = [Vector normalize:direction]; // vectorNormalize(direction);
_distanceInPoints = [Vector distanceFromPoint:viewToAnimate.frameOrigin toPoint:animateToPoint];
}
SPASLogDetail(#"finalPoint: %#, direction: %#, distance: %f", NSStringFromCGPoint(animateToPoint), NSStringFromCGVector(direction), _distanceInPoints);
[self setupWithReferenceView:referenceView andViewToAnimate:viewToAnimate];
[self setDirection:direction];
}
return self;
}
- (void) setInitialVelocity:(CGFloat)initialVelocity;
{
_initialVelocity = initialVelocity;
// Only positive speeds in the velocity are relevant. Negative speeds reduce the velocity, they don't go the other direction.
_linearVelocity =
CGPointMake(initialVelocity * fabs(_direction.dx),
initialVelocity * fabs(_direction.dy));
}
- (void) setAccelerationRate:(CGFloat)accelerationRate;
{
_accelerationRate = accelerationRate;
_gravityBehavior.magnitude = accelerationRate;
}
// I'm only doing left, right, up, down animations because of the problem of rotating the viewToAnimate. I'm not sure I'll ever have a case where I want a rotated animated view. (Hmmm. If I want to do some kind of continuous animation, arbitrary direction with non-rotated objects could be cool!)
- (void) setDirection:(CGVector)direction;
{
if (_collision) {
[_animator removeBehavior:_collision];
_collision = nil;
}
if (_distanceInPoints == 0.0) {
// Why bother?
SPASLogDetail(#"Zero distance");
return;
}
// 9/24/15; HACK
//_distanceInPoints += 1.5;
// Since we're doing vector operations with one of the init methods above, the following seems risky!
//AssertIf(direction.dy != 0.0 && direction.dy != -1.0 && direction.dy != 1.0, #"Invalid dy: %f", direction.dy);
//AssertIf(direction.dx != 0.0 && direction.dx != -1.0 && direction.dx != 1.0, #"Invalid dx: %f", direction.dx);
_direction = direction;
_gravityBehavior.gravityDirection = direction;
_collision = [[UICollisionBehavior alloc] initWithItems:#[self.viewToAnimate]];
_collision.collisionDelegate = self;
CGPoint startBoundary;
CGPoint endBoundary;
#define SMALL_VALUE 0.05
BOOL (^closeToZero)(CGFloat) = ^(CGFloat value) {
if (value > -SMALL_VALUE && value < SMALL_VALUE) {
return YES;
}
else {
return NO;
}
};
if (closeToZero(direction.dx)) {
// Vertical motion.
CGFloat yBoundary = direction.dy * _distanceInPoints + self.viewToAnimate.frameY;
if (direction.dy > 0.0) {
// If we're going down, then we need to add the height of the self.viewToAnimate to our boundary. This is because the origin coords are in the *upper*, left of the viewToAnimate.
yBoundary += self.viewToAnimate.frameHeight;
}
startBoundary = CGPointMake(self.viewToAnimate.frameX, yBoundary);
endBoundary = CGPointMake(self.viewToAnimate.frameX + self.viewToAnimate.frameWidth, yBoundary);
}
else {
// Horizontal motion.
CGFloat xBoundary = direction.dx * _distanceInPoints + self.viewToAnimate.frameX;
if (direction.dx > 0.0) {
// If we're going to the right, then we need to add the width of the self.viewToAnimate to our boundary. This is because the origin coords are in the upper, *left* of the viewToAnimate.
xBoundary += self.viewToAnimate.frameWidth;
}
startBoundary = CGPointMake(xBoundary, self.viewToAnimate.frameY);
endBoundary = CGPointMake(xBoundary, self.viewToAnimate.frameY + self.viewToAnimate.frameHeight);
}
SPASLog(#"startBoundary: %#, endBoundary: %#", NSStringFromCGPoint(startBoundary), NSStringFromCGPoint(endBoundary));
[_collision addBoundaryWithIdentifier:#"barrier"
fromPoint:startBoundary
toPoint:endBoundary];
[_animator addBehavior:_collision];
}
- (void) run;
{
if (_collision) {
[_animator addBehavior:_gravityBehavior];
[_velocity addLinearVelocity:_linearVelocity forItem:self.viewToAnimate];
[_animator addBehavior:_velocity];
}
else {
if (self.completion) {
self.completion();
}
}
}
#pragma mark - UIDynamicAnimatorDelegate methods
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator*)animator;
{
if (self.completion) {
self.completion();
}
}
#pragma mark -
#pragma mark - UICollisionBehaviorDelegate methods
// This isn't the method that gets called in our case.
//- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p;
{
if (!_calledFirstImpactCallback) {
_calledFirstImpactCallback = YES;
if (self.firstImpactCallback) {
self.firstImpactCallback();
}
}
}
#pragma mark -
#end
I'm working in Swift, and I am seeing the same behavior as you do with 1.5 points offset. It seems to be unrelated to the resolution of the device (1x, 2x or 3x), all have 1.5 points offset.
However, using the the path-based API, the problem seems to not be there anymore:
let collisionBehavior: UICollisionBehavior = ...
let topLeft: CGPoint = ...
let bottomLeft: CGPoint = ...
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, topLeft.x, topLeft.y)
CGPathAddLineToPoint(path, nil, bottomLeft.x, bottomLeft.y)
let bezierPath = UIBezierPath(CGPath: path)
collisionBehavior.addBoundaryWithIdentifier("myID", forPath: bezierPath)
Converting this to Objective-C is easy enough:
UICollisionBehavior *collisionBehavior = ...
CGPoint topLeft = ...
CGPoint bottomLeft = ...
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, topLeft.x, topLeft.y);
CGPathAddLineToPoint(path, nil, bottomLeft.x, bottomLeft.y);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath: path];
[collisionBehavior addBoundaryWithIdentifier: #"myID" forPath: path];
I have a overlay image (.png) on my map that consists of a transparent bit in the middle, and colored sides so the user can only focus on the middle part. However do to the shape of that middle bit, quite a bit is visible at some sides.
I'm trying to detect a tap on the OverlayView so I can ignore it and only accept touches in the designated area.
I followed the following tut at Ray Wenderlich's site for adding the overlay:
The image overlay is drawn like this:
#implementation PVParkOverlayView
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
//UIImage *imageTest = _overlayImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
//orientation testing
//CGContextRotateCTM(context, 0);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
}
I have a gesture recognizer on my mapview and am trying to detect the tap there:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint tapPoint = [gestureRecognizer locationInView:self.mapView];
CLLocationCoordinate2D tapCoord = [self.mapView convertPoint:tapPoint toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoord);
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
for (id<MKOverlay> overlay in self.mapView.overlays) {
if([overlay isKindOfClass:[PVParkOverlay class]]){
NSLog(#"overlay is present");
/*
MKPolygon *polygon = (MKPolygon*) overlay;
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = polygon.points;
for (int p=0; p < polygon.pointCount; p++){
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, FALSE)){
// ... found it!
NSLog(#"I've found it!");
}
//CGPathRelease(mpr);
*/
}
}
I know that the overlay is there, but since it is a drawn image I can't find a way to convert this to polygon points to use this code (if even possible).
Any other methods I can use for this?
I also found following sample code but the viewForOverlay method is deprecated:
- (void)mapTapped:(UITapGestureRecognizer *)recognizer
{
MKMapView *mapView = (MKMapView *)recognizer.view;
id<MKOverlay> tappedOverlay = nil;
for (id<MKOverlay> overlay in mapView.overlays)
{
MKOverlayView *view = [mapView viewForOverlay:overlay];
if (view)
{
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [view.superview convertRect:view.frame toView:mapView];
// Get touch point in the mapView's coordinate system
CGPoint point = [recognizer locationInView:mapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point))
{
tappedOverlay = overlay;
break;
}
}
}
NSLog(#"Tapped view: %#", [mapView viewForOverlay:tappedOverlay]);
}
I'm trying to trace 2 paths on screen through a series of vertices (connect the dots style). Each should be a different color, and each has its own list of vertices.
I started out by creating a class which can trace a path, then creating 2 instances of this class, one for each path. I overrode the draw method. It worked just fine except for some reason only the first instance of the class called the draw method. I figured it was a problem with OpenGL so I did it again using CCDrawNode and it still had the same bug.
Only one instance (blackPath) draws any objects on screen. In fact the scheduled updateEndpoint: method is not even called for the whitePath object, although it is successfully created.
My Drawer.m Class:
const float size = 10;
const float speed = 5;
ccColor4F pathColor;
int numPoints;
NSArray * path;
CGPoint endPoint;
#implementation Drawer
-(id)initWithPath:(NSArray*)p andColorIsBlack:(BOOL)isBlack{
self = [super init];
// Record input
path = p.copy;
pathColor = ccc4f(1.0f, 1.0f, 1.0f, 1.0f);
if(isBlack){
pathColor = ccc4f(0.0f, 0.0f, 0.0f, 1.0f);
}
// Set variables
numPoints = 1;
endPoint = [[path firstObject] position];
NSLog(#"Drawer initialized with path of length %u and color %hhd (isblack)", p.count, isBlack);
[self schedule:#selector(updateEndpoint:)];
return self;
}
-(void)updateEndpoint:(ccTime)dt{
NSLog(#"(%f, %f, %f, %f) Path", pathColor.r, pathColor.g, pathColor.b, pathColor.a);
[self drawDot:endPoint radius:size color:pathColor];
CGPoint dest = [[path objectAtIndex:numPoints] position];
float dx = dest.x - endPoint.x;
float dy = dest.y - endPoint.y;
// new coords are current + distance * sign of distance
float newX = endPoint.x + MIN(speed, fabsf(dx)) * ((dx>0) - (dx<0));
float newY = endPoint.y + MIN(speed, fabsf(dy)) * ((dy>0) - (dy<0));
endPoint = ccp(newX, newY);
if(endPoint.x == dest.x && endPoint.y == dest.y){
if(numPoints < path.count-1){
numPoints+=1;
}
else{
[self unschedule:#selector(updateEndpoint:)];
}
}
}
And here is where I instantiate the objects:
-(id) init{
self = [super init];
[self addAllCards];
[self addScore];
xShrinkRate = [[Grid getInstance] sqWidth] / shrinkTime;
yShrinkRate = [[Grid getInstance] sqHeight] / shrinkTime;
dropList = [NSMutableArray new];
notDropList = [NSMutableArray new];
[self schedule:#selector(dropCard:) interval:0.075];
[self schedule:#selector(shrinkCards:)];
Drawer * whitePath = [[Drawer alloc] initWithPath:[[Score getInstance] whitePath] andColorIsBlack:false];
[self addChild:whitePath];
Drawer * blackPath = [[Drawer alloc] initWithPath:[[Score getInstance] blackPath] andColorIsBlack:true];
[self addChild:blackPath];
return self;
}
Change the (non-const) global variables to instance variables of the class like so:
#implementation Drawer
{
ccColor4F pathColor;
int numPoints;
NSArray * path;
CGPoint endPoint;
}
Anyone know of any good up to date tutorials out there that show how can one animate a sprite based on accelerometer movement. I want to animate a bird to sway to the position the device was pointed to. For example if the player decides to move the bird to the left via the accelerometer I would like for my bird to play an animation that is swaying to the left.
// Accelerometer
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
birdSpeedY = 9.0 + acceleration.x*15;
birdSpeedX = -acceleration.y*20;
}
// Updating bird based on accelerometer
-(void)updateBird {
float maxY = winSize.height - bird.contentSize.height/2;
float minY = bird.contentSize.height/2;
float newY = bird.position.y + birdSpeedY;
newY = MIN(MAX(newY, minY), maxY);
float maxX = winSize.width - bird.contentSize.width/2;
float minX = bird.contentSize.width/2;
float newX = bird.position.x + birdSpeedX;
newX = MIN(MAX(newX, minX), maxX);
bird.position = ccp(newX, newY);
}
// Making background scroll automatically
-(void)update:(ccTime)dt {
[self updateBird];
CGPoint backgroundScrollVel = ccp(-100, 0);
parallaxNode.position = ccpAdd(parallaxNode.position, ccpMult(backgroundScrollVel, dt));
}
-(id)init {
self = [super init];
if (self != nil) {
winSize = [CCDirector sharedDirector].winSize;
CCSpriteFrameCache *cache=[CCSpriteFrameCache sharedSpriteFrameCache];
[cache addSpriteFramesWithFile:#"birdAtlas.plist"];
NSMutableArray *framesArray=[NSMutableArray array];
for (int i=1; i<10; i++) {
NSString *frameName=[NSString stringWithFormat:#"bird%d.png", i];
id frameObject=[cache spriteFrameByName:frameName];
[framesArray addObject:frameObject];
}
// animation object
id animObject=[CCAnimation animationWithFrames:framesArray delay:0.1];
// animation action
id animAction=[CCAnimate actionWithAnimation:animObject restoreOriginalFrame:NO];
animAction=[CCRepeatForever actionWithAction:animAction];
bird=[CCSprite spriteWithSpriteFrameName:#"bird1.png"];
bird.position=ccp(60,160);
CCSpriteBatchNode *batchNode=[CCSpriteBatchNode batchNodeWithFile:#"birdAtlas.png"];
[self addChild:batchNode z:100];
[batchNode addChild:bird];
[bird runAction:animAction];
self.isAccelerometerEnabled = YES;
[self scheduleUpdate];
[self addScrollingBackgroundWithTileMapInsideParallax];
}
return self;
}
- (void) dealloc
{
[super dealloc];
}
#end
You can try the Accelerometer methods with it and change the position of the Sprite using ccp(). You also need to know is that project for the Landscape or Portrait in the Mode.
You Can Try the Stuff below
- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
{
[lbl setString:[NSString stringWithFormat:#"X=>%.2lf Y=>%.2lf",(double)acceleration.x,(double)acceleration.y]];
double x1= -acceleration.y *10;
double y1= acceleration.x *15;
if(acceleration.x >0.05)
{
y1*=spped_incr; // Make Movement Here
}
[Sprite_Name runAction:[CCMoveTo actionWithDuration:0.1f position:ccpAdd(ccp(x1,y1), Sprite_Name.position)]];
}
The Above Stuff is for the Landscape Mode.... if You need in Portrait Mode you need to change the Axis and use the TRY & Error Method.
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;
};