I've started to write a UIView subclass. It will be a control that will let the user scroll blocks and press any of them, very similar to UITableView.
Please check the links to images I posted below.
http://i.piccy.info/i7/0577902c1bdba79ac3a26a5d3ce322f9/4-50-285/17627834/2012_12_12_14_06_11.jpg
http://i.piccy.info/i7/91a0a1eec95d74e7c13a789fc69a5582/4-50-285/20953097/2012_12_12_14_06_39.jpg
Here's the source code:
(.h file)
#import <UIKit/UIKit.h>
#import "Quartzcore/Quartzcore.h"
#interface OSBlockView : UIView
{
CGFloat scrollPosition; // from 0 to 1
CGFloat zoneForFlexion;
CGSize blockSize;
NSUInteger blockQuantity;
CGFloat projection;
NSDictionary *anglesForHeights;
CGFloat oldTranslatedY;
UIGestureRecognizer *panGesture;
NSArray *blocks;
}
#property (nonatomic, readwrite) CGFloat scrollPosition;
#property (nonatomic, readwrite) CGFloat zoneForFlexion;
#property (nonatomic, readwrite) CGSize blockSize;
#property (nonatomic, readwrite) NSUInteger blockQuantity;
#property (nonatomic, readwrite) CGFloat projection;
#property (nonatomic, retain) NSDictionary *anglesForHeights;
#property (nonatomic, readwrite) CGFloat oldTranslatedY;
#property (nonatomic, retain) UIGestureRecognizer *panGesture;
#property (nonatomic, retain) NSArray *blocks;
- (id)initWithFrame:(CGRect)frame blockHeight:(CGFloat)bHeight projection:(CGFloat)proj andBlocks:(NSArray *)blcks;
+(NSDictionary *)calculateAllHeightsForAnglesWithBlockHeight:(CGFloat)blockHeight andProjection:(CGFloat)proj;
-(NSNumber *)getBlockTopWithBlockNumber:(NSNumber *)bn;
-(NSNumber *)angleValueForBlockHeight:(NSNumber *)h;
+(NSArray *)multicoloredViewsWithSize:(CGSize)size;
#end
and here's the .m file:
#import "OSBlockView.h"
#implementation OSBlockView
#synthesize scrollPosition, zoneForFlexion;
#synthesize blockSize, blockQuantity, projection;
#synthesize anglesForHeights;
#synthesize oldTranslatedY;
#synthesize panGesture;
#synthesize blocks;
int logForTimingIndex=0;
+(void)logFotTimingWithString:(NSString *)str
{
NSLog(#"time log: %08i %#", logForTimingIndex, str);
logForTimingIndex++;
}
- (id)initWithFrame:(CGRect)frame blockHeight:(CGFloat)bHeight projection:(CGFloat)proj andBlocks:(NSArray *)blcks;
{
[[self class] logFotTimingWithString:#"begin init"];
self = [super initWithFrame:frame];
if (self)
{
self.blockSize=CGSizeMake(frame.size.width, bHeight);
self.blockQuantity=[blcks count];
self.blocks=blcks;
self.projection=proj;
self.anglesForHeights=[OSBlockView calculateAllHeightsForAnglesWithBlockHeight:bHeight andProjection:proj];
self.scrollPosition=0.2;
self.zoneForFlexion=frame.size.height*0.9;
//adding pan gesture
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self addGestureRecognizer:panGesture];
};
[[self class] logFotTimingWithString:#"end init"];
return self;
}
+(NSArray *)multicoloredViewsWithSize:(CGSize)size
{
[[self class] logFotTimingWithString:#"multicoloredViewsWithSize begin"];
//generating multicoloured blocks
NSMutableArray *blocks=[[NSMutableArray alloc] init];
for (int i=0; i<13; i++)
{
UIColor *blockColor;
switch (i)
{
case 0:
{
blockColor=[UIColor colorWithRed:0.75 green:0.75 blue:0.75 alpha:1.0];
break;
}
case 1:
{
blockColor=[UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
break;
}
case 2:
{
blockColor=[UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
break;
}
case 3:
{
blockColor=[UIColor colorWithRed:0.5 green:0.0 blue:0.0 alpha:1.0];
break;
}
case 4:
{
blockColor=[UIColor colorWithRed:1.0 green:1.0 blue:0.0 alpha:1.0];
break;
}
case 5:
{
blockColor=[UIColor colorWithRed:0.5 green:0.5 blue:0.0 alpha:1.0];
break;
}
case 6:
{
blockColor=[UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
break;
}
case 7:
{
blockColor=[UIColor colorWithRed:0.0 green:0.5 blue:0.0 alpha:1.0];
break;
}
case 8:
{
blockColor=[UIColor colorWithRed:0.0 green:1.0 blue:1.0 alpha:1.0];
break;
}
case 9:
{
blockColor=[UIColor colorWithRed:0.0 green:0.5 blue:0.5 alpha:1.0];
break;
}
case 10:
{
blockColor=[UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:1.0];
break;
}
case 11:
{
blockColor=[UIColor colorWithRed:0.0 green:0.0 blue:0.5 alpha:1.0];
break;
}
case 12:
{
blockColor=[UIColor colorWithRed:1.0 green:0.0 blue:1.0 alpha:1.0];
break;
}
case 13:
{
blockColor=[UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];
break;
}
default:
{
NSLog(#"PANIC: unknown block index");
blockColor=[UIColor blackColor];
break;
}
}
UIView *bView=[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
[bView setBackgroundColor:blockColor];
[bView.layer setBorderWidth:3];
[bView.layer setBorderColor:[[UIColor whiteColor] CGColor]];
UILabel *blockLabel=[[UILabel alloc] initWithFrame:CGRectMake(2.5, 2.5, bView.frame.size.width-5, bView.frame.size.height-5)];
[blockLabel setBackgroundColor:[UIColor clearColor]];
[blockLabel setText:[NSString stringWithFormat:#"block number %i", i]];
[blockLabel setFont:[UIFont boldSystemFontOfSize:20]];
[blockLabel setTextColor:[UIColor whiteColor]];
[blockLabel setTextAlignment:UITextAlignmentCenter];
[bView addSubview:blockLabel];
[blocks addObject:bView];
};
[[self class] logFotTimingWithString:#"multicoloredViewsWithSize end"];
return blocks;
}
- (void)drawRect:(CGRect)rect
{
//NSLog(#"\n\n\n\n\n\n\n\n\n");
[[self class] logFotTimingWithString:#"drawRect begin"];
//NSLog(#"drawRect called");
//NSLog(#"self.subview.count: %i", [self.subviews count]);
//[[self class] logFotTimingWithString:#"subviews remove begin"];
for (UIView* subview in self.subviews)
{
//NSLog(#"subview removed");
[subview removeFromSuperview];
};
//[[self class] logFotTimingWithString:#"subviews remove end"];
//calculating top coordinates of blocks
//NSLog(#"calculating block tops...");
//[[self class] logFotTimingWithString:#"calculating block tops begin"];
NSMutableArray *blockTops=[[NSMutableArray alloc] init];
for (unsigned int blockCounter=0; blockCounter<blockQuantity; blockCounter++)
{
[blockTops addObject:[self getBlockTopWithBlockNumber:[NSNumber numberWithInt:blockCounter]]];
};
//[[self class] logFotTimingWithString:#"calculating block tops end"];
/*
for (unsigned int index=0; index!=[blockTops count]; index++)
{
NSLog(#"top %i at %i", index, [[blockTops objectAtIndex:index] intValue]);
};
*/
//calculating block heights
//NSLog(#"calculating block heights...");
//[[self class] logFotTimingWithString:#"calculating block heights begin"];
NSMutableArray *blockHeights=[[NSMutableArray alloc] init];
for (unsigned int index=0; index<[blockTops count]; index++)
{
if (([blockTops count]-1)!=index)
{
[blockHeights addObject:[NSNumber numberWithInt:([[blockTops objectAtIndex:(index+1)] intValue]-[[blockTops objectAtIndex:index] intValue])]];
}
else
{
if ((self.frame.size.height-[[blockTops objectAtIndex:index] intValue])<blockSize.height)
{
[blockHeights addObject:[NSNumber numberWithInt:(self.frame.size.height-[[blockTops objectAtIndex:index] intValue])]];
}
else
{
[blockHeights addObject:[NSNumber numberWithInt:blockSize.height]];
};
};
};
//[[self class] logFotTimingWithString:#"calculating block heights end"];
for (unsigned int blockCounter=0; blockCounter<blockQuantity; blockCounter++)
{
//NSLog(#"\n\n");
//[[self class] logFotTimingWithString:#"draw cycle begin"];
//[self preLoadBlockAtY:[blockTops objectAtIndex:blockCounter] withHeight:[blockHeights objectAtIndex:blockCounter] andIndex:[NSNumber numberWithUnsignedInt:blockCounter]];
//NSLog(#" preLoadingSampleBlockAtY: %g withHeight: %g andIndex: %i", [y doubleValue], [height doubleValue], [index unsignedIntValue]);
NSNumber *y=[blockTops objectAtIndex:blockCounter];
NSNumber *height=[blockHeights objectAtIndex:blockCounter];
//NSNumber *index=[NSNumber numberWithUnsignedInt:blockCounter];
//NSLog(#"loading block %02u y:%g h:%g", [index unsignedIntValue], [y doubleValue], [height doubleValue]);
if ([height intValue]>2)
{
BOOL up;
if ([y doubleValue]>self.frame.size.height/2.0)
{
up=true;
}
else
{
up=false;
};
//[[self class] logFotTimingWithString:#"bview init begin"];
UIView *bView=[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[blocks objectAtIndex:blockCounter]]];
[bView setFrame:CGRectMake(0.0, [y doubleValue], self.frame.size.width, blockSize.height)];
//[bView.layer setMasksToBounds:YES];
//[[self class] logFotTimingWithString:#"bview init end"];
//NSLog(#" blockImage frame y: %g", blockImage.frame.origin.y);
//NSLog(#" blockImage frame height: %g", blockImage.frame.size.height);
double oldHeight=bView.frame.size.height;
//NSLog(#" preanchor: %#", NSStringFromCGRect(bView.frame));
//[[self class] logFotTimingWithString:#"bview anchor begin"];
//setting anchor point
if (up)
{
[bView.layer setAnchorPoint:CGPointMake(0.5, 1.0)];
[bView setFrame:CGRectMake(bView.frame.origin.x, bView.frame.origin.y+bView.frame.size.height/2.0, bView.frame.size.width, bView.frame.size.height)];
}
else
{
[bView.layer setAnchorPoint:CGPointMake(0.5, 0.0)];
[bView setFrame:CGRectMake(bView.frame.origin.x, bView.frame.origin.y-bView.frame.size.height/2.0, bView.frame.size.width, bView.frame.size.height)];
};
//[[self class] logFotTimingWithString:#"bview anchor end"];
//NSLog(#" postanchor: %#", NSStringFromCGRect(bView.frame));
//[[self class] logFotTimingWithString:#"bview transform begin"];
if ([height doubleValue]!=blockSize.height)
{
//preparing transform
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 =1.0/-projection;
//calculating angle
double angle= [[self angleValueForBlockHeight:height] doubleValue];
double rangle;
if (up)
{
rangle=angle/360*(2.0*M_PI);
}
else
{
rangle=(360.0-angle)/360*(2.0*M_PI);
};
//NSLog(#" angle: %g", angle);
//transforming
bView.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
//NSLog(#" blockImage frame y: %g", blockImage.frame.origin.y);
//NSLog(#" blockImage frame height: %g", blockImage.frame.size.height);
};
//[[self class] logFotTimingWithString:#"bview transform end"];
//[[self class] logFotTimingWithString:#"bview post transform begin"];
double newHeight=bView.frame.size.height;
if (up)
{
[bView setCenter:CGPointMake(bView.center.x, bView.center.y-(oldHeight-newHeight))];
};
//NSLog(#" blockImage frame y: %g", bView.frame.origin.y);
//NSLog(#" blockImage frame height: %g", bView.frame.size.height);
//adding subview
[self addSubview:bView];
//[[self class] logFotTimingWithString:#"bview post transform end"];
}
else
{
//do not need to draw blocks with very low height
};
//[[self class] logFotTimingWithString:#"draw cycle end"];
//NSLog(#"\n\n");
};
[[self class] logFotTimingWithString:#"drawRect end"];
//NSLog(#"\n\n\n\n\n\n\n\n\n\n");
}
-(NSNumber *)angleValueForBlockHeight:(NSNumber *)h
{
//[[self class] logFotTimingWithString:#" angleValueForBlockHeight begin"];
//NSLog(#"angleValueForBlockHeight: %g called", [h doubleValue]);
//searching for closest key
double minDistance=blockSize.height;
double distance;
NSString *minKey=#"";
for(NSString *key in anglesForHeights)
{
if ([[anglesForHeights objectForKey:key] doubleValue]==[h doubleValue])
{
//match found
//NSLog(#"returned: %g", [key doubleValue]);
return [NSNumber numberWithDouble:[key doubleValue]];
};
distance=fabs([[anglesForHeights objectForKey:key] doubleValue]-[h doubleValue]);
if (distance<minDistance)
{
minDistance=distance;
minKey=key;
};
};
//NSLog(#"returned: %g", [blockSizesForAngles objectForKey:minKey]);
//[[self class] logFotTimingWithString:#" angleValueForBlockHeight end"];
return [NSNumber numberWithDouble:[minKey doubleValue]];
}
+(NSDictionary *)calculateAllHeightsForAnglesWithBlockHeight:(CGFloat)blockHeight andProjection:(CGFloat)proj
{
//NSLog(#"calculateAllHeightsForAnglesWithBlockHeight:%g andProjection:%g called", blockHeight, proj);
//[[self class] logFotTimingWithString:#" calculateAllHeightsForAnglesWithBlockHeight:andProjection begin"];
NSMutableDictionary *res=[[NSMutableDictionary alloc] init];
for (double i=0.0; i<=90.0; i=i+1.0)
{
//NSLog(#"i: %g", i);
UIView *block=[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, blockHeight)];
[block.layer setAnchorPoint:CGPointMake(0.5, 1.0)];
CATransform3D basicTrans = CATransform3DIdentity;
double rangle;
basicTrans.m34 =1.0/-proj;
rangle=i/360*(2.0*M_PI);
//NSLog(#"rangle: %g Pi", rangle/M_PI);
block.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
//NSLog(#"calculated block height: %g for angle: %g", block.frame.size.height, i);
if ([res objectForKey:[NSString stringWithFormat:#"%i", (int)roundf(i)]]==nil)
{
[res setObject:[NSString stringWithFormat:#"%i", (int)floor(block.frame.size.height)] forKey:[NSNumber numberWithDouble:i]];
};
};
//NSLog(#" result (size: %i): %#", [res count], [res debugDescription]);
//[[self class] logFotTimingWithString:#" calculateAllHeightsForAnglesWithBlockHeight:andProjection end"];
return [NSDictionary dictionaryWithDictionary:res];
}
-(NSNumber *)getBlockTopWithBlockNumber:(NSNumber *)bn
{
//NSLog(#"getBlockTopWithBlockNumber: %i", [bn intValue]);
//[[self class] logFotTimingWithString:#" getBlockTopWithBlockNumber begin"];
int t=[bn intValue];
CGFloat h=self.blockSize.height;
float v = self.blockQuantity * h - self.zoneForFlexion;
float vp = v * self.scrollPosition;
float z = t * h;
float alpha = (self.frame.size.height-self.zoneForFlexion) / (self.scrollPosition*self.scrollPosition + (1-self.scrollPosition) * (1-self.scrollPosition));
float f;
if (z < vp)
{
f = z / v;
//NSLog(#" %i", (int)(alpha * (f * f)));
//[[self class] logFotTimingWithString:#" getBlockTopWithBlockNumber end"];
return [NSNumber numberWithInt:(int)(alpha * (f * f))];
};
if (z <= vp + self.zoneForFlexion)
{
//NSLog(#" %i", (int)(z + alpha * (self.scrollPosition * self.scrollPosition) - vp));
//[[self class] logFotTimingWithString:#" getBlockTopWithBlockNumber end"];
return [NSNumber numberWithInt:(int)(z + alpha * (self.scrollPosition * self.scrollPosition) - vp)];
}
else
{
f = (blockQuantity*h-z) / v;
//NSLog(#" %i", (int)(self.frame.size.height - alpha * (f * f)));
//[[self class] logFotTimingWithString:#" getBlockTopWithBlockNumber end"];
return [NSNumber numberWithInt:(int)(self.frame.size.height - alpha * (f * f))];
};
}
double oldTranslatedY=0.0;
-(IBAction)handlePanGesture:(UIPanGestureRecognizer *)sender
{
//NSLog(#"handlePanGesture: called");
//[[self class] logFotTimingWithString:#" handlePanGesture begin"];
double translatedY = [sender translationInView:self].y;
double delta;
if (fabs(translatedY)<fabs(oldTranslatedY))
{
[sender setTranslation:CGPointZero inView:self];
oldTranslatedY=0.0;
delta=0.0;
}
else
{
delta=translatedY-oldTranslatedY;
oldTranslatedY=translatedY;
};
//NSLog(#"translatedY: %g", translatedY);
double pOffset=delta/((blockQuantity*blockSize.height)-self.frame.size.height);
self.scrollPosition=self.scrollPosition-pOffset;
if (self.scrollPosition<0.0)
{
self.scrollPosition=0.0;
}
else if(self.scrollPosition>1.0)
{
self.scrollPosition=1.0;
};
[[self class] logFotTimingWithString:#" handlePanGesture end"];
[self setNeedsDisplay];
}
Everything is working fine on simulator. But it is too slow on the real device.
I need help in 2 things:
In the moment of drawing the blocks: If I know what height value the block should have, how can I calculate the angle which I will use to rotate the block to fit height? It also depends on projection value. Now it is working with an ugly solution that precalculates heights for all angles (from 0 to 90) after the view is initialized and saves to dictionary, and when we need to know the specific angle for block rotation to fit height, we just search for the closest value of height from this hardcoded dictionary and use the appropriate angle.
It is toooo ugly. I tried to avoid this by calculating the needed value during drawing, but I had no luck (need formula).
Optimization in another parts.
Help, me please, I've stuck on this problem.
Put calculations on another thread. Avoid working with GUI in background threads! :)
[self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>];
Related
I am trying to make a table view cell that shows ratings for songs in a playlist. I have successfully created the cell so that it shows the current number of stars, and also an indication of how a new setting will be when you hover your mouse cursor over a cell to give a new rating.
The problem is that while mouseEnter, mouseExit and mouseMove works like a charm, I get no messages for mouseDown, which is required to actually change the value of the cell.
I have searched all over the Internet, but I can't find any solution to how to solve this problem anywhere. I have spent so many hours trying to sort this. I hope anyone have any answer or hint what I can do. Thank you.
The full code for the current implementation is as follows:
#import "FavouriteCellView.h"
#implementation FavouriteCellView {
NSTrackingArea *_trackingArea;
int _starsRated; //The current rating value
BOOL _hovering; //YES if the mouse is currently hovering over this cell
int _starsHovering; //The number of stars hovering, if the mouse is hovering over this cell
}
- (void)awakeFromNib {
[super awakeFromNib];
_starsRated = 1;
_hovering = NO;
_starsHovering = 0;
[self createTrackingArea];
}
- (void)createTrackingArea
{
_trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited |NSTrackingActiveInActiveApp | NSTrackingMouseMoved owner:self userInfo:nil];
[self addTrackingArea:_trackingArea];
}
- (void)updateTrackingAreas{
[self removeTrackingArea:_trackingArea];
_trackingArea = nil;
[self createTrackingArea];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// CGFloat middleX = [self bounds].size.width / 2.0f;
CGFloat middleY = [self bounds].size.height / 2.0f;
CGFloat starDivs = [self bounds].size.width / 5.0f;;
NSColor *starSelectedColor = [NSColor colorWithDeviceRed:0.8f green:0.0f blue:0.4f alpha:1.0f];
NSColor *starUnselectedColor = [NSColor colorWithDeviceRed:0.5f green:0.5f blue:0.5f alpha:1.0f];
NSColor *starHoverColor = [NSColor colorWithDeviceRed:1.0f green:0.843f blue:0.0f alpha:1.0f];
NSColor *starHoverColorSelected = [NSColor colorWithDeviceRed:0.9f green:0.843f blue:0.6f alpha:1.0f];
for (int i = 0; i < 5; i++) {
NSColor *useColor = [NSColor redColor];
if (_hovering && (i <= _starsHovering)) {
if (i <= _starsRated) {
useColor = starHoverColorSelected;
} else {
useColor = starHoverColor;
}
} else if (i <= _starsRated) {
useColor = starSelectedColor;
} else {
useColor = starUnselectedColor;
}
[self star:NSMakePoint((starDivs / 2.0f) + starDivs * i, middleY) color:useColor];
}
}
-(void)star:(NSPoint)center color:(NSColor *)color {
[color set];
CGFloat t = (2.0f * M_PI) / 10.0f;
NSBezierPath *path = [[NSBezierPath alloc] init];
CGFloat radii1 = 12.0f;
CGFloat radii2 = 4.0f;
CGFloat rot = M_PI / 2.0f;
BOOL first = YES;
for (int i = 0; i < 10; i++) {
CGFloat pointX = cos(t * i + rot) * radii1 + center.x;
CGFloat pointY = sin(t * i + rot) * radii1 + center.y;
CGFloat tempRadii = radii1;
radii1 = radii2;
radii2 = tempRadii;
if (first) {
first = NO;
[path moveToPoint:NSMakePoint(pointX, pointY)];
}
else {
[path lineToPoint:NSMakePoint(pointX, pointY)];
}
}
[path closePath];
[path fill];
/*
[[NSColor blackColor] set];
[path setLineWidth:0.25f];
[path stroke];
*/
}
-(NSView *)hitTest:(NSPoint)aPoint {
//THIS GETS CALLED
return self;
}
-(BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
printf("$"); //DOES NOT GET CALLED
return YES;
}
-(BOOL)acceptsFirstResponder {
printf("!"); //DOES NOT GET CALLED
return YES;
}
-(BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
printf("8"); //DOES NOT GET CALLED
return YES;
}
-(void)mouseDown:(NSEvent *)theEvent {
printf("o"); //DOES NOT GET CALLED
_starsRated = _starsHovering;
}
-(void)mouseUp:(NSEvent *)theEvent {
printf("O"); //DOES NOT GET CALLED
}
-(void)mouseEntered:(NSEvent *)theEvent {
//DOES GET CALLED
_hovering = YES;
[self setNeedsDisplay:YES];
}
-(void)mouseExited:(NSEvent *)theEvent {
//DOES GET CALLED
_hovering = NO;
[self setNeedsDisplay:YES];
}
-(void)mouseMoved:(NSEvent *)theEvent {
//DOES GET CALLED
NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
mouseLocation = [self convertPoint: mouseLocation
fromView: nil];
int newStarsHoveringValue = mouseLocation.x / ([self bounds].size.width / 5.0f);
if (newStarsHoveringValue != _starsHovering) {
_starsHovering = newStarsHoveringValue;
[self setNeedsDisplay:YES];
}
}
#end
It was a bit fiddly, but I managed to create a solution that works. I subclassed NSTableView, then overrode mouseDown with the following code:
-(void)mouseDown:(NSEvent *)theEvent {
NSPoint globalLocation = [theEvent locationInWindow];
NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
NSInteger clickedRow = [self rowAtPoint:localLocation];
if (clickedRow != -1) {
NSInteger clickedColumn = [self columnAtPoint:localLocation];
if (clickedColumn != -1) {
if (clickedColumn == 3) {
FavouriteCellView *fv = [self viewAtColumn:clickedColumn row:clickedRow makeIfNecessary:NO];
if (fv != nil) {
[fv mouseDown:theEvent];
}
return;
}
}
}
[super mouseDown:theEvent];
}
Now it works exactly like I wanted.
I am trying to implement a custom NSTextField that a) changes color when active and b) has a label in the top left hand corner.
I have the following implementation:
In TestTextField.h
#interface TestTextField : NSTextField
- (id)initFullWidthWithLabel:(NSString *)label andPreset:(NSString *)preset;
...
#end
In TestTextField.m
#interface TestTextField() {
BOOL _thisFieldIsActive;
}
#property (nonatomic, strong) NSTextField *label;
#end
#implementation TestTextField
- (id)initFullWidthWithLabel:(NSString *)label andPreset:(NSString *)preset {
self = [super initWithFrame:NSZeroRect];
if (self) {
_thisFieldIsActive = NO;
[self setFocusRingType:NSFocusRingTypeNone];
// small label top left
_label = [[NSTextField alloc] initWithFrame:NSZeroRect];
_label.stringValue = label;
if (preset) {
self.stringValue = preset;
}
else {
self.stringValue = #"0";
}
[self layoutUI];
}
return self;
}
- (void)turnActiveOff {
[self toggleActive:NO];
}
- (void)toggleActive:(BOOL)active {
_thisFieldIsActive = active;
if (_thisFieldIsActive) {
self.backgroundColor = [NSColor blueColor];
self.textColor = [NSColor whiteColor];
_label.textColor = [NSColor whiteColor];
}
else {
self.backgroundColor = [NSColor clearColor];
self.textColor = [NSColor blackColor];
_label.textColor = [NSColor grayColor];
}
}
- (BOOL)becomeFirstResponder {
NSLog(#"BecomeFirstResponder");
[self selectText:self];
[self toggleActive:YES];
return [super becomeFirstResponder];
}
- (void)textDidEndEditing:(NSNotification *)notification {
NSLog(#"DidEndEditing");
[self toggleActive:NO];
[super textDidEndEditing:notification];
}
- (void)layoutUI {
self.alignment = NSRightTextAlignment;
self.font = [NSFont fontWithName:#"HelveticaNeue-Light" size:32.0f];
self.layer.borderColor = [NSColor whiteColor].CGColor;
self.layer.borderWidth = 1.0f;
[self.layer setCornerRadius:4.0f];
// small label top left
_label.font = [NSFont fontWithName:#"HelveticaNeue-Light" size:12.0f];
_label.alignment = NSLeftTextAlignment;
_label.textColor = [NSColor grayColor];
_label.stringValue = [_label.stringValue uppercaseString];
_label.selectable = NO;
_label.editable = NO;
_label.drawsBackground = NO;
_label.bezeled = NO;
[self addSubview:_label];
NSDictionary *metrics = #{#"borderPadding": #5};
_label.translatesAutoresizingMaskIntoConstraints = NO;
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(borderPadding)-[_label(100)]" options:0 metrics:metrics views:#{ #"_label" : _label }]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(borderPadding)-[_label(30)]" options:0 metrics:metrics views:#{ #"_label" : _label }]];
}
In my ViewController I implement these TestTextFields by simply calling the custom initFullWidthWithLabel:andPreset: method and adding them as a subview to the VCs view
I can see that the label gets positioned correctly, however as soon as the the field becomes active and the backgroundColor is set, it seems to cover up the label. How can I make sure the label stays on top?
Even when the backgorund coloring is turned off, the label remains hidden.
Thanks
The core of the solution was to use a custom subclassed NSTextFieldCell with one method:
- (NSRect)drawingRectForBounds:(NSRect)rect {
NSRect rectInset = NSMakeRect(rect.origin.x + 100.0f, rect.origin.y, rect.size.width - 100.0f, rect.size.height);
return [super drawingRectForBounds:rectInset];
}
I want to make a custom NSButton with a solid color for its background but there is this blue highlight around the button that I can't figure out how to get rid of. Does anyone know how to get rid of it?
Thanks!
EDIT:
Here's the code for the button:
#implementation ZDButton
- (id)init {
if (self = [super init]) {
self.title = #"";
self.isSelected = false;
[self setBordered:NO];
[self setBackgroundColor:[NSColor whiteColor]];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor colorWithCalibratedRed:arc4random_uniform(100) / 100.0
green:arc4random_uniform(100) / 100.0
blue:arc4random_uniform(100) / 100.0
alpha:1.0] set];
NSRectFill(dirtyRect);
NSDictionary *att = nil;
NSMutableParagraphStyle *style =
[[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:NSLineBreakByWordWrapping];
[style setAlignment:NSLeftTextAlignment];
att = [[NSDictionary alloc] initWithObjectsAndKeys:
style, NSParagraphStyleAttributeName,
[NSColor whiteColor],
NSForegroundColorAttributeName, nil];
int width = [self.title sizeWithAttributes:att].width;
int height = [self.title sizeWithAttributes:att].height;
int x = (dirtyRect.size.width - width) / 2;
int y = (dirtyRect.size.height - height) / 2;
NSRect centeredText = NSRectFromCGRect(CGRectMake(x, y, width, height));
[self.title drawInRect:centeredText withAttributes:att];
}
#end
I have a problem in my App. I set a badge value at one of the tabs in the UITabBar. The Badge value is correctly red and the circle around the badge value is correctly in white. The problem is, that the color of the text is gray (160, 160, 160). It is the same color like the normal state tabbaritem text is, but I set this color nowhere in the app code and I do not know where this color come from.
I searched for that issue in the whole net since weeks but I cannot find any solution. The only answer I found everywhere is, that it is not possible to change the color of the text of the badge value. But if it is not possible, why is it changed in my app?
I hope, that somebody can help me with that issue...
http://www.luventas-webdesign.de/stackoverflow/screenshot_badgevalue.png
Like the color is in my app
http://www.luventas-webdesign.de/stackoverflow/screenshot_like_it_should.png
Like the color should normally be...
Edit 02.11.2012 - Code
Creation of TabBarController:
#import "ExtendedTabBarController.h"
#import "configuration.h"
#implementation ExtendedTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
[[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor colorWithRed:207.0/255.0 green:70.0/255.0 blue:61.0/255.0 alpha:1], UITextAttributeTextColor, [UIFont fontWithName:#"KievitPro-Regular" size:10.0], UITextAttributeFont, nil] forState:UIControlStateSelected];
[[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1], UITextAttributeTextColor, [UIFont fontWithName:#"KievitPro-Regular" size:10.0], UITextAttributeFont, nil] forState:UIControlStateNormal];
[self.tabBar sizeToFit];
UIView *tabbarBackgroundColorView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0, self.view.bounds.size.width, 49)];
[tabbarBackgroundColorView setBackgroundColor:[UIColor colorWithRed:233.0/255.0 green:233.0/255.0 blue:233.0/255.0 alpha:1]];
[self.tabBar insertSubview:tabbarBackgroundColorView atIndex:0];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return UIInterfaceOrientationIsPortrait(interfaceOrientation); // only portrait orientation
}
/**
* orientation for iOS6
**/
-(NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
#end
Call in AppDelegate:
ExtendedTabBarController *tabBarController = [[ExtendedTabBarController alloc] init];
[self setTabBarController:tabBarController];
[[UITabBar appearance] setBackgroundImage:[UIImage imageNamed:#"menu_bg"]];
// code for initialize View- and NavigationControllers...
self.tabBarController.viewControllers = #[highlightsNavigationController, categoryNavigationController, searchNavigationController, favoritesNavigationController, imprintNavigationController];
self.window.rootViewController = self.tabBarController;
[[UITabBar appearance] setSelectionIndicatorImage:[[UIImage alloc] init]];
Set the badge value:
int viewCount = 0;
NSUserDefaults * defs = [NSUserDefaults standardUserDefaults];
NSDictionary * dict = [defs dictionaryRepresentation];
for (id key in dict) {
if([key rangeOfString:#"_highlighted"].location != NSNotFound && [[[dict objectForKey:key] objectAtIndex:0] isEqualToString:#"YES"]) {
viewCount++;
}
}
UITabBarItem *tbi = (UITabBarItem *)[self.tabBarController.tabBar.items objectAtIndex:3];
if(viewCount <= 0) {
tbi.badgeValue = nil;
} else {
tbi.badgeValue = nil;
tbi.badgeValue = [NSString stringWithFormat:#"%d", viewCount];
}
Code for overwritten UILabel:
// -- file: UILabel+VerticalAlign.h
#pragma mark VerticalAlign
#interface UILabel (VerticalAlign)
- (void)alignTop;
- (void)alignBottom;
- (void)awakeFromNib;
-(id)initWithFrame:(CGRect)frame;
#end
#import "UILabel+VerticalAlign.h"
// -- file: UILabel+VerticalAlign.m
#implementation UILabel (VerticalAlign)
- (void)alignTop {
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i<newLinesToPad; i++)
self.text = [self.text stringByAppendingString:#"\n "];
}
- (void)alignBottom {
CGSize fontSize = [self.text sizeWithFont:self.font];
double finalHeight = fontSize.height * self.numberOfLines;
double finalWidth = self.frame.size.width; //expected width of label
CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
for(int i=0; i<newLinesToPad; i++)
self.text = [NSString stringWithFormat:#" \n%#",self.text];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setFont:[UIFont fontWithName:#"KievitPro-Regular" size:12.0]];
}
-(id)initWithFrame:(CGRect)frame
{
id result = [super initWithFrame:frame];
if (result) {
[self setFont:[UIFont fontWithName:#"KievitPro-Regular" size:12.0]];
}
return result;
}
#end
I found a solution for my problem on my own:
I must remove the following lines from the overwritten UILabel:
- (void)awakeFromNib
{
[super awakeFromNib];
[self setFont:[UIFont fontWithName:#"KievitPro-Regular" size:12.0]];
}
-(id)initWithFrame:(CGRect)frame
{
id result = [super initWithFrame:frame];
if (result) {
[self setFont:[UIFont fontWithName:#"KievitPro-Regular" size:12.0]];
}
return result;
}
Maybe someone can explain me, why this lines change the text color of the badge value, before we can close this post?
Instead of setting the default UILabel font using a category, use the UILabel's appearance method to set the font:
[[UILabel appearance] setFont:[UIFont fontWithName:#"KievitPro-Regular" size:12.0]];
When I tested this the text for the badge appeared as the normal white color.
I am trying to create an NSMatrix of NSButtonCells where between zero and four buttons can be selected (toggled on). I have tried the following (test) code, but am not sure how I can provide the functionality I require. Perhaps it's not possible with NSMatrix and I need to look at an alternative control, or create my own?
#interface MatrixView : NSView
{
NSScrollView *_scrollView;
NSMatrix *_matrixView;
}
#end
#implementation MatrixView
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame. frameRect=%#", NSStringFromRect(frameRect));
self = [super initWithFrame:frameRect];
if (self != nil)
{
_scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, frameRect.size.width, frameRect.size.height)];
[_scrollView setBorderType:NSNoBorder];
[_scrollView setHasVerticalScroller:YES];
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
NSSize contentSize = [_scrollView contentSize];
contentSize.height = 300;
// Make it 3 x however-many-buttons-will-fit-the-height
CGFloat gap = 8.0;
CGFloat width = (contentSize.width / 3.0) - (gap * 2.0);
NSUInteger rows = (contentSize.height / (width + gap));
NSLog(#"width=%f, rows=%lu", width, rows);
NSButtonCell *prototype = [[NSButtonCell alloc] init];
[prototype setTitle:#"Hello"];
[prototype setButtonType:NSToggleButton];
[prototype setShowsStateBy:NSChangeGrayCellMask];
_matrixView = [[NSMatrix alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)
mode:NSListModeMatrix
prototype:prototype
numberOfRows:rows
numberOfColumns:3];
[_matrixView setCellSize:NSMakeSize(width, width)];
[_matrixView setIntercellSpacing:NSMakeSize(gap, gap)];
[_matrixView setAllowsEmptySelection:YES];
[_matrixView sizeToCells];
[_scrollView setDocumentView:_matrixView];
[self addSubview:_scrollView];
[self setAutoresizesSubviews:YES];
[prototype release];
}
return self;
}
...
I got this to work with the following subclass of NSMatrix. I added one property, onCount, to keep track of how many buttons were in the on state:
#implementation RDMatrix
#synthesize onCount;
-(id) initWithParentView:(NSView *) cv {
NSButtonCell *theCell = [[NSButtonCell alloc ]init];
theCell.bezelStyle = NSSmallSquareBezelStyle;
theCell.buttonType = NSPushOnPushOffButton;
theCell.title = #"";
if (self = [super initWithFrame:NSMakeRect(200,150,1,1) mode:2 prototype:theCell numberOfRows:4 numberOfColumns:4]){
[self setSelectionByRect:FALSE];
[self setCellSize:NSMakeSize(40,40)];
[self sizeToCells];
self.target = self;
self.action = #selector(buttonClick:);
self.drawsBackground = FALSE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
self.mode = NSHighlightModeMatrix;
self.onCount = 0;
[cv addSubview:self];
return self;
}
return nil;
}
-(IBAction)buttonClick:(NSMatrix *)sender {
NSUInteger onOrOff =[sender.selectedCells.lastObject state];
if (onOrOff) {
self.onCount += 1;
}else{
self.onCount -= 1;
}
NSLog(#"%ld",self.onCount);
if (self.onCount == 5) {
[sender.selectedCells.lastObject setState:0];
self.onCount -= 1;
}
}
When you try to select the 5th button it will flash on, but then go off. This could be a problem depending on how you are using the state of these buttons. I just logged them with this method:
-(IBAction)checkMatrix:(id)sender {
NSIndexSet *indxs = [self.mat.cells indexesOfObjectsPassingTest:^BOOL(NSButtonCell *cell, NSUInteger idx, BOOL *stop) {
return cell.state == NSOnState;
}];
NSLog(#"%#",indxs);
}
After Edit: I didn't like the way my first method flashed the button on briefly before turning it off again when you try to click the 5th button. I found what I think is a better solution that involves overriding mouseDown in the matrix subclass (if you want to try this, you should delete the setAction and setTarget statements and delete the buttonClick method):
-(void)mouseDown:(NSEvent *) event {
NSPoint matPoint = [self convertPoint:event.locationInWindow fromView:nil];
NSInteger row;
NSInteger column;
[self getRow:&row column:&column forPoint:matPoint];
NSButtonCell *cell = [self cellAtRow:row column:column];
if (self.onCount < 4 && cell.state == NSOffState) {
cell.state = NSOnState;
self.onCount += 1;
}else if (cell.state == NSOnState) {
cell.state = NSOffState;
self.onCount -= 1;
}
}