Looking for documentation/help to make new Views
Hi everybody.
I've been struggling with Xcode's Cocoa documentation for a few days and as i didn't have any internet connection, i nearly rip off my hairs ^^
I would like to create custom views but I just don't understand what's the pattern. For exemple, look at this safari text field:
How should i proceed to have the same one??
Thanks,
Mhedhbi Bilel
I wrote a tutorial about custom views and it draws a clock.
Rather curiously, it draws a different clock in the Dock, just to show you how to have a continuously updating Dock Item.

The tutorial's available from: http://clanmills.com/files/QuartzClock.pdf
I'm not however convinced that Safari uses a CustomView. I think it might be possible to do most of that with the controls provided by Apple. Could you tell me which parts of the Safari tool bar you believe require custom views?
@clanmills: It's the addressbar/Google search. I think they can send drawRect: messages to text fields, because they're subclasses of NSView (right?).
Yes, kompilesoft is right, Safari uses a custom view to display the progress bar embedded in the addressbar. Also, the Previous/Forward buttons must be custom views because i don't find any button of that shape in interface builder.
I spent quite a long time today on this question and finally got some answers.
You need to subclass a NSControl class in order to override the drawRect: method so that custom drawing can be accomplished. In the peculiar case of Safari addressbar, I guess it's a subclass of NSTextField (which is also a subclass of NSControl).
MyTextField.h
#import <Cocoa/Cocoa.h>
@interface MyTextField : NSTextField {
NSNumber *progress;
}
- (void)progressChanged:(NSNumber*)newProgress;
- (NSNumber*)concreteWidthWithProgress;
@end
MyTextField.m
#import "MyTextField.h"
@interface MyTextFieldCell : NSTextFieldCell {
}
@end
@implementation MyTextFieldCell
@end
@implementation MyTextField
- (void)drawRect:(NSRect)dirtyRect {
[NSGraphicsContext saveGraphicsState];
[super drawRect:dirtyRect];
NSRect aRect= NSMakeRect(0, 0, [[self concreteWidthWithProgress] intValue], [self frame].size.height);
NSBezierPath *aPath= [NSBezierPath bezierPathWithRect:aRect];
NSColor *aColor= [NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.5];
[aColor set];
[aPath fill];
[NSGraphicsContext restoreGraphicsState];
}
+ (Class)cellClass {
return [MyTextFieldCell class];
}
//these methods are not complete and were designed to implement the redraw of the "blue progress bar" as the progress advances
- (void)progressChanged:(NSNumber*)newProgress {
[newProgress retain];
[progress release];
progress = newProgress;
[self drawRect:[self frame]];
}
- (NSNumber*)concreteWidthWithProgress {
NSNumber *toBeReturned= [[NSNumber alloc] initWithFloat: ([progress floatValue] * [self frame].size.width)];
return [toBeReturned autorelease];
}
@end
Here's a screenshot of the application running. Don't bother with the blue circle, it's a huge subclass of NSButton.
I am still training and will report my new advancement ^
Good job! I'm sure this is a fine app. The NSSegmentedControl does back/forth for you.
I did some progress on the question. I am currently trying to code a web navigator and i began with subclassing the NSTabView class. When I undestood that i had to deal with too many apple methods, I prefered creating my own tab view by subclassing NSView.
Here is what the main window look like:
Here is the source:
//
// TabView.h
//
// Created by Bilel Mhedhbi on 20/08/10.
// Copyright 2010 None. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "TabViewItem.h"
@interface TabView : NSView {
NSMutableArray *arrayOfTabs;
NSRect contentRect;
NSRect addTabButtonRect;
NSRect goAndBackButtonsRect;
BOOL mouseDownOnTab;
BOOL mouseDownOnBack, mouseDownOnNext;
NSObject *applicationDelegate;
}
@property (readonly) NSMutableArray *arrayOfTabs;
@property (assign) NSObject *applicationDelegate;
- (void)addTabItemWithURL:(NSURL*)anURL;
- (TabItem*)keyTab;
- (NSMutableArray*)arrayOfTabs;
@end
//
// TabView.m
//
// Created by Bilel Mhedhbi on 20/08/10.
// Copyright 2010 None. All rights reserved.
//
#import "TabView.h"
#define EMPTY_RECT NSMakeRect(-1, -1, -1, -1)
@implementation TabView
@synthesize arrayOfTabs, applicationDelegate;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
arrayOfTabs= [[NSMutableArray alloc] init];
NSString *a= [[NSBundle mainBundle] pathForResource:@"NavBackButton" ofType:@"png"];
NSString *b= [[NSBundle mainBundle] pathForResource:@"NavForwardButton" ofType:@"png"];
NSLog(@"A: %@\nB: %@\n", a, b);
//backButtonImage= [[NSImage alloc] initByReferencingFile: a];
//nextButtonImage= [[NSImage alloc] initByReferencingFile: b];
contentRect= NSMakeRect(0.0, 0.0, frame.size.width, frame.size.height - 45.0);
[self addTabItemWithURL:[NSURL fileURLWithPath:@"/Users/bilelmhedbi/a.webarchive"]];
[self addTabItemWithURL:[NSURL fileURLWithPath:@"/Users/bilelmhedbi/b.webarchive"]];
[self addTabItemWithURL:[NSURL fileURLWithPath:@"/Users/bilelmhedbi/c.webarchive"]];
mouseDownOnTab= NO;
mouseDownOnBack= NO;
mouseDownOnNext= NO;
}
return self;
}
- (void)dealloc {
[arrayOfTabs release];
//[backButtonImage release];
//[nextButtonImage release];
[super dealloc];
}
- (BOOL)mouseDownCanMoveWindow {
return NO;
}
- (NSMutableArray*)arrayOfTabs {
return arrayOfTabs;
}
- (void)addTabItemWithURL:(NSURL*)anURL {
[arrayOfTabs makeObjectsPerformSelector:@selector(setIsKey:) withObject:NO];
[arrayOfTabs addObject:[[[TabItem alloc] init] autorelease]];
/*if (![[NSFileManager defaultManager] fileExistsAtPath:[anURL path]] || anURL == nil) {
NSLog(@"File doesn't exist at %@\n", [anURL path]);
}*/
[[arrayOfTabs lastObject] setUrl:anURL];
[[arrayOfTabs lastObject] setIsKey:YES];
[[arrayOfTabs lastObject] setTitle:@"New Tab"];
NSRect rect= NSMakeRect(0, self.frame.size.height - 20.0, 0.0, 20.0);
CGFloat widthOfTab;
if ((self.frame.size.width / ([arrayOfTabs count])) >= 200.0)
widthOfTab= 200.0;
else if ((self.frame.size.width / ([arrayOfTabs count])) < 200.0)
widthOfTab= [self frame].size.width / ([arrayOfTabs count] - 1);
rect.origin.x= 0.0 + ([arrayOfTabs count] - 1) * widthOfTab;
rect.size.width= widthOfTab;
[[arrayOfTabs lastObject] setTabRect:rect];
[[NSApp delegate] loadPage: [arrayOfTabs lastObject]];
//[self setNeedsDisplay:YES];
}
- (void)removeKeyTab {
int index;
for(TabItem *item in arrayOfTabs)
if ([item isKey])
index= [arrayOfTabs indexOfObject:item];
[arrayOfTabs removeObjectAtIndex:index];
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)fitTabWidth {
int numberOfTabs= [arrayOfTabs count];
CGFloat widthOfTab;
int indexOfCurrentTab=0;
for(TabItem* item in arrayOfTabs) {
if (((self.frame.size.width) / numberOfTabs) >= 150.0 )
widthOfTab= 150.0;
else if ((self.frame.size.width) / numberOfTabs < 150.0)
widthOfTab= [self frame].size.width / (numberOfTabs);
CGFloat toRemove;
toRemove= 20.0 / numberOfTabs;
widthOfTab-= toRemove;
NSPoint point= NSMakePoint(0.0 + indexOfCurrentTab * widthOfTab, [self frame].size.height - 20.0);
NSSize size= NSMakeSize(widthOfTab, 20.0);
[item setSize:size];
[item setOrigin:point];
indexOfCurrentTab++;
}
addTabButtonRect= NSMakeRect([self frame].size.width - 20.0, [[arrayOfTabs lastObject] tabRect].origin.y, 20.0, 20.0);
}
- (void)setNeedsDisplay:(BOOL)flag {
[self fitTabWidth];
[super setNeedsDisplay:YES];
}
- (void)setTabKey:(TabItem*)item {
TabItem *previousKeyTab= [self keyTab];
if (previousKeyTab == item) {
return;
}
for(TabItem *anItem in arrayOfTabs)
[anItem setIsKey:NO];
[item setIsKey:YES];
[[NSApp delegate] loadPage:item];
}
- (TabItem*)keyTab {
for(TabItem *item in arrayOfTabs)
if ([item isKey]) {
return item;
}
}
- (void)drawRect:(NSRect)dirtyRect {
[self fitTabWidth];
//ADD TAB BUTTON
NSBezierPath *addTabButton= [NSBezierPath bezierPathWithRect:addTabButtonRect];
[[NSColor redColor] set];
[addTabButton fill];
[[NSColor blackColor] set];
[addTabButton moveToPoint:NSMakePoint(addTabButtonRect.origin.x + addTabButtonRect.size.width/2.0, addTabButtonRect.origin.y+2.0)];
[addTabButton lineToPoint:NSMakePoint(addTabButtonRect.origin.x + addTabButtonRect.size.width/2.0, addTabButtonRect.origin.y + addTabButtonRect.size.height - 2.0)];
[addTabButton moveToPoint:NSMakePoint(addTabButtonRect.origin.x + 2.0, addTabButtonRect.origin.y + addTabButtonRect.size.height/2.0)];
[addTabButton lineToPoint:NSMakePoint(addTabButtonRect.origin.x + addTabButtonRect.size.width - 2.0, addTabButtonRect.origin.y + addTabButtonRect.size.height/2.0)];
[addTabButton stroke];
//BACK AND FORWARD BUTTONS
NSImage *backButtonImage;
NSImage *nextButtonImage;
NSRect backButtonRect= NSMakeRect(self.frame.origin.x + 14.0, self.frame.size.height - 45.0, 27.0, 23.0);
NSRect nextButtonRect= NSMakeRect(self.frame.origin.x + 14.0 + 27.0, self.frame.size.height - 45.0, 26.0, 23.0);
if (mouseDownOnBack) {
NSString *a= [[NSBundle mainBundle] pathForResource:@"NavBackButtonPushed" ofType:@"png"];
backButtonImage= [[[NSImage alloc] initByReferencingFile:a] autorelease];
}
else {
NSString *a= [[NSBundle mainBundle] pathForResource:@"NavBackButton" ofType:@"png"];
backButtonImage= [[[NSImage alloc] initByReferencingFile:a] autorelease];
}
if (mouseDownOnNext) {
NSString *a= [[NSBundle mainBundle] pathForResource:@"NavForwardButtonPushed" ofType:@"png"];
//NSLog(@"Nav next button pushed: %@", a);
nextButtonImage= [[[NSImage alloc] initByReferencingFile:a] autorelease];
}
else {
NSString *a= [[NSBundle mainBundle] pathForResource:@"NavForwardButton" ofType:@"png"];
//NSLog(@"Nav next button: %@", a);
nextButtonImage= [[[NSImage alloc] initByReferencingFile:a] autorelease];
}
[backButtonImage drawInRect:backButtonRect fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[nextButtonImage drawInRect:nextButtonRect fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
//SHOW KEY TAB
for(TabItem *item in arrayOfTabs) {
[item drawRect:EMPTY_RECT];
NSRect oneTabRect= NSMakeRect(item.tabRect.origin.x, item.tabRect.origin.y, item.tabRect.size.width, item.tabRect.size.height);
NSBezierPath *oneTabPath= [NSBezierPath bezierPathWithRect:oneTabRect];
if ([item isKey]) {
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.35] set];
}
else
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.15] set];
[oneTabPath fill];
}
//DRAW TAB SEPARATION
for(TabItem *item in arrayOfTabs){
[[NSColor blackColor] set];
NSRect rect= [item tabRect];
NSBezierPath *path= [NSBezierPath bezierPathWithRect:rect];
[path stroke];
}
for(int i=0; i< [arrayOfTabs count]; i++) {
TabItem *anItem= [arrayOfTabs objectAtIndex:i];
NSLog(@"Number %ld: %@\n", i, [arrayOfTabs objectAtIndex:i]);
}
}
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint eventLocation= [self convertPoint:[theEvent locationInWindow] fromView:nil];
NSRect backButtonRect= NSMakeRect(self.frame.origin.x + 14.0, self.frame.size.height - 45.0, 27.0, 23.0);
NSRect nextButtonRect= NSMakeRect(self.frame.origin.x + 14.0 + 27.0, self.frame.size.height - 45.0, 26.0, 23.0);
if([self mouse:eventLocation inRect:backButtonRect]) {
mouseDownOnBack= YES;
[self setNeedsDisplay:YES];
}
else
mouseDownOnBack= NO;
if ([self mouse:eventLocation inRect:nextButtonRect]) {
mouseDownOnNext= YES;
[self setNeedsDisplay:YES];
}
else
mouseDownOnNext= NO;
if ([self mouse:eventLocation inRect:addTabButtonRect]) {
[self addTabItemWithURL:nil];
return;
}
for(TabItem *item in arrayOfTabs)
if ([self mouse:eventLocation inRect:item.tabRect]) {
mouseDownOnTab= YES;
[self setTabKey:item];
}
if (mouseDownOnTab) {
[self setNeedsDisplay:YES];
}
}
- (void)mouseDragged:(NSEvent *)theEvent {
NSPoint eventLocation= [self convertPoint:[theEvent locationInWindow] fromView:nil];
NSRect backButtonRect= NSMakeRect(self.frame.origin.x + 14.0, self.frame.size.height - 45.0, 27.0, 23.0);
NSRect nextButtonRect= NSMakeRect(self.frame.origin.x + 14.0 + 27.0, self.frame.size.height - 45.0, 26.0, 23.0);
if([self mouse:eventLocation inRect:backButtonRect])
mouseDownOnBack= YES;
else {
mouseDownOnBack= NO;
[self setNeedsDisplay:YES];
}
if ([self mouse:eventLocation inRect:nextButtonRect])
mouseDownOnNext= YES;
else {
mouseDownOnNext= NO;
[self setNeedsDisplay:YES];
}
for(TabItem *item in arrayOfTabs) {
if ([self mouse:eventLocation inRect:item.tabRect]) {
mouseDownOnTab= YES;
break;
}
else {
mouseDownOnTab= NO;
}
}
}
- (void)mouseUp:(NSEvent *)theEvent {
mouseDownOnTab= NO;
mouseDownOnBack= NO;
mouseDownOnNext= NO;
[self setNeedsDisplay:YES];
}
@end
I am still having some bugs bug they are due to the controller.
I am waiting for your comments ^^
This looks like very good work. Perhaps you could build your code into a sample app and share it with us. Do you have a question here?
I don't think he has questions: "I am waiting for your comments ^^"
Very, very nice. I'd like this app! :)
Well, this is library code, so I'd have to build an app to house it. We'd all be on the same page, and then more able to help with the bugs and issues, if all had the same sample app.
So..... mhedbibilel can you post the code for an application that includes your code? Zip/compress your project (please delete the build directory). It should be about 50-60k as a zip. Then we can all admire your work and help you with suggestions and bug fixes.
