adding subviews to an NSView to have a chess-like grid - cocoa

I am trying to create a Cocoa UI that consists of two sets of squares (chess-like grids) that will assume different colours while an underlying algorithm is running. When the execution of the algorithm comes to an end, the UI should be able to handle clicks, panning and other gestures.
The hierarchy I have so far is the following (please check the attached code for specifics):
1) the main window that is the window of a window controller
2) a split view with two custom views, mainView and sideView (each one would hold a set of squares)
3) two view controllers (mainViewController and sideViewController)
I would like to be able to load the squares as subviews of mainView and sideView.
I thought of having another custom view, say SquareView with another nib file. My questions would be:
a) how do I create this SquareView so that it can be used to create the squares that will be added to mainView and sideView as subviews to form chess-like grids?
b) how do I add subviews to mainView and sideView to built the two grids? For the sake of simplicity, let's assume there would be four non-overlapping squares for each of the previously mentioned views.
Thank you!
MainView.m
#import "MainView.h"
#implementation MainView
- (void)drawRect:(NSRect)TheRect
{
[[NSColor grayColor] set];
[NSBezierPath fillRect:[self bounds]];
}
SideView.m
#import "SideView.h"
#implementation MainView
- (void)drawRect:(NSRect)TheRect
{
[[NSColor whiteColor] set];
[NSBezierPath fillRect:[self bounds]];
}
MainWindowController.h
#import <Cocoa/Cocoa.h>
#class SideViewController;
#class MainViewController;
#interface MainWindowController : NSWindowController
{
IBOutlet NSSplitView* oMainSplitView;
SideViewController* sideViewController;
MainViewController* mainViewController;
}
#end
MainWindowController.m
#import "MainWindowController.h"
#import "SideViewController.h"
#import "MainViewController.h"
#implementation MainWindowController
- (void)windowDidLoad
{
sideViewController = [[SideViewController alloc] initWithNibName:#"SideView" bundle:nil];
NSView* splitViewLeftView = [[oMainSplitView subviews] objectAtIndex:0];
NSView* sideView = [sideViewController view];
[sideView setFrame:[splitViewLeftView bounds]];
[sideView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[splitViewLeftView addSubview:sideView];
mainViewController = [[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
NSView* splitViewRightView = [[oMainSplitView subviews] objectAtIndex:1];
NSView* mainView = [mainViewController view];
[mainView setFrame:[splitViewRightView bounds]];
[mainView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[splitViewRightView addSubview:mainView];
}

You can make this as simple or as complicated as you desire: simple? do everything you want in MainView's drawRect method; complex: nest NSViews (or NSCell's, or NSBox's, etc.) and have each one draw itself.
Personally, I'd vote to keep it simpleā€¦

a) I think the easiest way would be to create a matrix of NSBoxes, which you could do in code or in IB. Having the squares in a matrix would make it easy to loop through them or access specific ones.
b) I'm not sure what your question is here -- you would do it just as you did in your posted code, using [mainView addSubview:squareMatrix];
After Edit: Actually, it looks like IB won't let you embed NSBoxes in a matrix. In the past, I've made a matrix of subclassed NSButtonCells (to allow background color with no border) that had a grid of 64x64 cells that were clickable and would change color with those clicks. I don't know if you want a fixed number of cells in your views, or do you need to dynamically change the number? Something like this could work for you I think -- I actually created this in code, because IB was really slow in updating with that many cells.
Here is what I did. In my case, I needed cells with no border but with background color, so I had to subclass NSButtonCell, like this:
-(id)initWithRGBAlpha:(NSArray *)rgbAlpha {
if (self == [super init]) {
NSColor *color = [NSColor colorWithCalibratedRed:[[rgbAlpha objectAtIndex:0]doubleValue]
green:[[rgbAlpha objectAtIndex:1]doubleValue]
blue:[[rgbAlpha objectAtIndex:2]doubleValue]
alpha:[[rgbAlpha objectAtIndex:3]doubleValue]];
[self setBackgroundColor:color];
[self setTitle:#""];
[self setBordered:NO];
[self setTag:0];
[self setImageScaling:3];
return self;
}else{
return nil;
}
}
-(void) setState:(NSInteger)value {
if (value == 1) {
self.backgroundColor = self.selectedColor;
[super setState:value];
}else {
self.backgroundColor = self.backgroundColor;
[super setState:value];
}
}
-(void) setBackgroundColor:(NSColor *)color {
backgroundColor = color;
selectedColor = [color colorWithAlphaComponent:.75];
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:self.backgroundColor forKey:#"bColor"];
}
- (id)initWithCoder:(NSCoder *)decoder {
[super initWithCoder:decoder];
self.backgroundColor = [decoder decodeObjectForKey:#"bColor"];
return self;
}
I created the matrix in code, like so:
#implementation RDMatrix
-(void) initWithParentView:(NSView *) cv {
NSNumber *one = [NSNumber numberWithInt:1];
NSArray *colors = [NSArray arrayWithObjects:one,one,one,one,nil];
RDButtonCell *theCell = [[RDButtonCell alloc ]initWithRGBAlpha:colors];
[self initWithFrame:NSMakeRect(200,100,1,1) mode:2 prototype:theCell numberOfRows:64 numberOfColumns:64];
[self setSelectionByRect:TRUE];
[self setCellSize:NSMakeSize(8,8)];
[self sizeToCells];
self.target = self;
self.action = #selector(matrixClick:);
self.backgroundColor = [NSColor lightGrayColor];
self.drawsBackground = TRUE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
[cv addSubview:self];
}
-(void) matrixClick: (id) sender {
for (RDButtonCell *aCell in self.selectedCells){
if ([self.selectedCells count] < 64) {
aCell.backgroundColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
}else{
aCell.backgroundColor = [NSColor colorWithCalibratedRed:0 green:.5 blue:1 alpha:1];
}
}
[self deselectAllCells];
}
#end

Related

NSOutlineView show indentation marker

How do I generate indentation marker for NSOutlineView?
I am not sure if this is an inbuilt functionality because it appears in other apps like Instruments
Update
I tried solving the problem by iterating all the children of the item that the row represents and show the marker on all children rows based on indentation level, but I faced a few problems
How to handle the case where the item has thousands of children. One simply cannot draw marker to every row as NSOutlineView would draw rows as they are displayed
When I scroll the NSOutlineView, the mouse moves out of the specified row but mouseExited is not being called. Thus the user has to manually move the mouse to reload the highlighting.
I had solved this problem but my solution looks hacky hence wanted to know if there is a better solution. And hence the question
First to receive mouseEntered: and mouseExited: events you need to setup a tracking rect using NSTrackingArea.
I would start with a subclass of NSTableRowView thats overwrites setFrame: making sure the tracking rect gets updated when the view is resize:
#interface TableRowView : NSTableRowView {
NSBox *_box;
NSTrackingArea *_trackingArea;
}
#property (weak) id owner;
#property (copy) NSDictionary<id, id> *userInfo;
#property (nonatomic) CGFloat indentation;
#property (nonatomic) BOOL indentationMarkerHidden;
#end
#implementation TableRowView
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
if (_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow owner:[self owner] userInfo:[self userInfo]];
[self addTrackingArea:_trackingArea];
}
#end
To use the NSTableRowView subclass, implement the NSOutlineViewDelegate messages like this:
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
{
TableRowView *view = [[TableRowView alloc] init];
view.owner = self;
view.userInfo = item;
return view;
}
With this in place you're ready to receive mouseEntered: and mouseExited: events. Use NSOutlineView levelForItem: together with indentationPerLevel to calculate the position of the marker NSBox.:
- (void)mouseEntered:(NSEvent *)event
{
id item = [event userData];
CGFloat indentation = [_outlineView levelForItem:item] * [_outlineView indentationPerLevel];
[self setIndentationMarker:indentation hidden:NO item:item];
}
- (void)mouseExited:(NSEvent *)event
{
id item = [event userData];
[self setIndentationMarker:0.0 hidden:YES item:item];
}
Now you get the NSTableRowView subclass by rowViewAtRow:makeIfNecessary: and recursively do the same for all children in your data:
- (void)setIndentationMarker:(CGFloat)indentation hidden:(BOOL)hidden item:(NSDictionary *)item
{
TableRowView *view = [_outlineView rowViewAtRow:[_outlineView rowForItem:item] makeIfNecessary:NO];
view.indentationMarkerHidden = hidden;
view.indentation = indentation;
for (NSMutableDictionary *child in [item objectForKey:#"children"]) {
[self setIndentationMarker:indentation hidden:hidden item:child];
}
}
Now layout the NSBox the NSTableRowView subclass:
#implementation TableRowView
- (instancetype)init
{
self = [super init];
if (self) {
_indentationMarkerHidden = YES;
_box = [[NSBox alloc] init];
_box.boxType = NSBoxCustom;
_box.borderWidth = 0.0;
_box.fillColor = [NSColor tertiaryLabelColor];
_box.hidden = _indentationMarkerHidden;
[self addSubview:_box];
}
return self;
}
- (void)layout
{
[super layout];
NSRect rect = [self bounds];
rect.origin.x = _indentation + 7;
rect.size.width = 10;
_box.frame = rect;
}
- (void)setIndentation:(CGFloat)indentation
{
_indentation = indentation;
[self setNeedsLayout:YES];
}
- (void)setIndentationMarkerHidden:(BOOL)indentationMarkerHidden
{
if (_indentationMarkerHidden != indentationMarkerHidden) {
_indentationMarkerHidden = indentationMarkerHidden;
_box.hidden = indentationMarkerHidden;
}
}
#end
This enough to make a basic version like here:

Custom NSButtonCell, drawBezelWithFrame not called

I'm trying to figure out how to custom draw Buttons in Cocoa/OSX. Since my view is custom drawn I will not use IB and want to do it all in code. I created a subclass of NSButtonCell and a subclass of NSButton. In the Subclass of NSButtonCell I override the Method drawBezelWithFrame:inView: and in the initWithFrame Method of my subclassed NSButton I use setCell to set my CustomCell in the Button. However, drawBezelWithFrame gets not called and I don't understand why. Can someone point out what I've done wrong or what I miss here?
Subclass of NSButtonCell:
#import "TWIButtonCell.h"
#implementation TWIButtonCell
-(void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
{
//// General Declarations
[[NSGraphicsContext currentContext] saveGraphicsState];
//// Color Declarations
NSColor* fillColor = [NSColor colorWithCalibratedRed: 0 green: 0.59 blue: 0.886 alpha: 1];
//// Rectangle Drawing
NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect: NSMakeRect(8.5, 7.5, 85, 25)];
[fillColor setFill];
[rectanglePath fill];
[NSGraphicsContext restoreGraphicsState];
}
#end
Subclass of NSButton:
#import "TWIButton.h"
#import "TWIButtonCell.h"
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
#end
Usage:
- (void)addSendButton:(NSRect)btnSendRectRect
{
TWIButton *sendButton = [[TWIButton alloc] initWithFrame:btnSendRectRect];
[self addSubview:sendButton];
[sendButton setTitle:#"Send"];
[sendButton setTarget:self];
[sendButton setAction:#selector(send:)];
}
Following are the things seems to be missing out from your code.
You are not calling the [super drawRect:dirtyRect]
You are not overriding + (Class)cellClass in the Class(TWIButton) which is derived from NSButton.
Below is the code after changes:
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
//Changes Added!!!
[super drawRect:dirtyRect];
}
//Changes Added!!!!
+ (Class)cellClass
{
return [TWIButtonCell class];
}
#end
Now keep the break point at drawBezelWithFrame and check it will get called.
One might forgo the NSButton subclass, since it looks like you are ONLY using it to instantiate the Cell type within the initializer.
Simply
NSButton *button ...
[button setCell: [[TWIButtonCell alloc] init] autorelease]];
btw. you will probably have a leak in the previous examples since you init and then call setCell that likely has its own retain.

Simple UICollectionView to show images behaves odd: Some Images are displayed correct, some at wrong position, some missing at all

I want to show images in a grid on iPhone using a UICollectionView, showing 3 images a row. For a "dead simple test" (as I thought), I've added 15 JPG images to my project, so that they'll be in my bundle and I can load them simply via [UIImage imageNamed:...].
I think I've done everything correct (setting up & registering UICollectionViewCell subclass, use of UICollectionViewDataSource Protocol methods), however, the UICollectionView behaves very weird:
It shows only a few of the images in the following pattern:
First line shows image 1 & 3, second line is blank, next line like the first again (image 1 & 3 showing properly), fourth line blank, and so on...
If I push a button in my NavBar that triggers [self.collectionView reloadData], random cells appear or disappear. What drives me nuts is that it's not only an issue of images appear or not. Sometime, images also swap between the cells, i.e. they appear for a indexPath they are definitely not wired up!
Here is my code for the cell:
#interface AlbumCoverCell : UICollectionViewCell
#property (nonatomic, retain) IBOutlet UIImageView *imageView;
#end
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:frame];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)dealloc
{
[_imageView release];
[super dealloc];
}
- (void)prepareForReuse
{
[super prepareForReuse];
self.imageView.image = nil;
}
#end
Part of the code for my UICollectionViewController subclass, where 'imageNames' is an NSArray holding all jpg filenames:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[AlbumCoverCell class] forCellWithReuseIdentifier:kAlbumCellID];
}
#pragma mark - UICollectionViewDataSource Protocol methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imageNames count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
AlbumCoverCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kAlbumCellID forIndexPath:indexPath];
NSString *imageName = [self.imageNames objectAtIndex:indexPath.row];
NSLog(#"CV setting image for row %d from file in bundle with name '%#'", indexPath.row, imageName);
cell.imageView.image = [UIImage imageNamed:imageName];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout Protocol methods
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
return CGSizeMake(100, 100);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
{
return UIEdgeInsetsMake(0, 0, 0, 0);
}
From the NSLog statement in cellForItemAtIndexPath: I can see that the method is called for all of the cells (not only the one's displayed) and that the mapping between indexPath.row and filename is correct.
Has anybody an idea what could cause this weird behavior?
In the meantime, I've found the solution. It was actually a very subtle error in the implementation of my UICollectionViewCell subclass AlbumCoverCell.
The problem is that I've set the frame of the cell instance as the frame of the UIImageView subview instead of passing the bounds property of the cell's contentView!
Here is the fix:
#implementation AlbumCoverCell
#synthesize imageView = _imageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// WRONG:
// _imageView = [[UIImageView alloc] initWithFrame:frame];
// RIGHT:
_imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds];
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)prepareForReuse
{
[super prepareForReuse];
// reset image property of imageView for reuse
self.imageView.image = nil;
// update frame position of subviews
self.imageView.frame = self.contentView.bounds;
}
...
#end

Rounded corners for NSView subclass not working

I've got a custom NSView subclass which I want to have rounded corners. I use the following code in the .m file:
#import "ItemImageSelectionView.h"
#implementation ItemImageSelectionView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.wantsLayer = YES;
self.layer.frame = self.frame;
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor colorWithCalibratedRed:0.0 green:0.5 blue:1 alpha:1] set];
NSRectFill(dirtyRect);
[self.layer setCornerRadius:5.0];
}
#end
I use this code to initialize the view, pretty default:
NSView *imageSelectionView = [[ItemImageSelectionView alloc] initWithFrame:CGRectMake(imageView.frame.origin.x - 2, imageView.frame.origin.y - 2, imageView.frame.size.width + 4, imageView.frame.size.height + 4)];
[self addSubview:imageSelectionView positioned:NSWindowBelow relativeTo:imageView];
But it doesn't set any rounded corners! What am I doing wrong?
You don't need to set rounded corners in the drawrect method; set that in the init. Also, you should probably call the super method, [super drawRect:dirtyRect] in your overrided method. I'm not sure how your fill custom will interact with the layers, though. You may need to set [self.layer setMasksToBounds:YES];

Objective-J Cappuccino want a list of buttons on main menu, when I click the panel refreshes with UI of button selected

Dear all, I am new to objective-j/c and cappuccino not really sure how this all fits together.
The code below is taken from http://github.com/jfahrenkrug/CappuccinoLocations1
What I need to do is:
I need a landing main menu which is a CPView called ie MainView with five or so buttons, when you click on the LocationButton on MainView is replaces MainView by with LocationView, which displays the contents of jfahrenkrug's work. A similar effect will happen with each other button.
What is the correct Objective-c/j way of handling this approach?
#import <Foundation/CPObject.j>
#import "src/Location/LocationView.j"
#implementation AppController : CPObject
{
LocationView locationView;
}
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask],
mainContentView = [theWindow locationView],
bounds = [locationView bounds];
[mainContentView setBackgroundColor:[CPColor colorWithRed:212.0 /255.0 green:221.0/ 255.0 blue:230.0/255.0 alpha:1.0]];
locationView = [[LocationView alloc] initWithFrame:CGRectMake(0,0,920.0,590.0)];
[locationView setCenter:[mainContentView center]];
[locationView setBackgroundColor:[CPColor whiteColor]]
[locationView setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin];
var shadow = [[CPShadowView alloc] initWithFrame:CGRectMakeZero()];
[shadow setFrameForContentFrame:[locationView frame]];
[shadow setAutoresizingMask:CPViewMinXMargin | CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin];
[mainContentView addSubview:shadow];
[mainContentView addSubview:locationView];
[theWindow orderFront:self];
}
Now we have the locationView.j
#import "LocationsController.j"
#import "LocationListView.j"
#import "MapController.j"
#import "LocationsToolbar.j"
#import "LocationDetailView.j"
#import "LocationDetailController.j"
#implementation LocationView : CPView
{
LocationsController locationsController;
LocationListView locationListView;
MapController mapController;
MKMapView mapView;
CPTextField coordinatesLabel;
LocationsToolbar locationsToolbar;
LocationDetailView locationDetailView;
LocationDetailController locationDetailController;
CPTextField searchField;
// id delegate #accessors;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if(self){
locationsController = [[LocationsController alloc] init];
[locationsController loadExampleLocations];
locationListView = [[LocationListView alloc] initWithFrame:CGRectMake(0.0,0.0,226.0,400.0)];
[locationListView setContent:[locationsController locations]];
[locationListView setDelegate:locationsController];
[locationsController setLocationListView:locationListView];
var locationScrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(10.0,65.0,243.0,400.0)];
[locationScrollView setDocumentView:locationListView];
[locationScrollView setAutohidesScrollers:YES];
[[locationScrollView self] setBackgroundColor:[CPColor whiteColor]];
[self addSubview:locationScrollView];
mapController = [[MapController alloc] init];
mapView = [[MKMapView alloc] initWithFrame:CGRectMake(510,65,400,400) apiKey:'' ];
[mapView setDelegate:self];
mapController.mapView = mapView;
[self addSubview:mapView];
coordinatesLabel = [[CPTextField alloc] initWithFrame:CGRectMake(510,465,200,35)];
[coordinatesLabel setTextColor:[CPColor colorWithHexString:#"009900"]];
[coordinatesLabel setFont:[CPFont systemFontOfSize:14.0]];
[coordinatesLabel setEditable:NO];
[coordinatesLabel setStringValue:#"-/-"];
[mapController setCoordinatesLabel:coordinatesLabel];
[self addSubview:coordinatesLabel];
locationsToolbar = [[LocationsToolbar alloc] initWithFrame:CGRectMake(10.0,467.0,226.0,25.0)];
[locationsToolbar setDelegate:locationsController];
[self addSubview:locationsToolbar];
locationDetailController = [[LocationDetailController alloc] init];
locationDetailController.mapController = mapController;
locationsController.locationDetailController = locationDetailController;
[mapController setDelegate:locationDetailController];
locationDetailView = [[LocationDetailView alloc] initWithFrame:CGRectMake(510,490,400,90)];
[locationDetailView setDelegate:locationDetailController];
[locationDetailController setLocationDetailView:locationDetailView];
[self addSubview:locationDetailView];
searchField = [CPTextField roundedTextFieldWithStringValue:#"" placeholder:#"Location" width:200.0];
[searchField setFrameOrigin:CGPointMake(510.0,35.0)];
[searchField setDelegate:self];
[self addSubview:searchField];
var searchButton = [[CPButton alloc] initWithFrame:CGRectMake(710.0,37.0,60.0,24.0)];
[searchButton setTitle:"Search"];
[searchButton setTarget:self];
[searchButton setAction:#selector(searchLocation)];
[self addSubview:searchButton];
}
return self;
}
I'm not sure I understand your question but essentially a window has a default view called the content view. You get it like so:
var contentView = [theWindow contentView];
To the content view you can add subviews (and buttons).
[contentView addSubview:myLocationView];
The location of those subviews is determined by the 'frame' of the subview.
[myLocationView setFrame:CGRectMake(10, 10, 100, 100)];
You can replace the content view's subviews with something else by either removing the previous views or using the setSubviews message.
[contentView setSubviews:[aButton, anotherButton]];
So essentially if you want to swap out one view for another, call 'setSubviews' on its super view with the new views you want. Hope that helps to get you started.

Resources