I'm trying to understand the pros and cons of using something CCUIViewWrapper in Cocos2d versus a ported functionality. For instance, would it be better to use a UITableView in a CCUIViewWrapper, or to use the CCTableViewSuite. At first glance, I would assume the wrapper is the better approach since presumably it allows me to do everything the UITableView offers, but are there key details I'm missing? Are there severe limitations that exist with the wrapper either with actually using the apple sdk object or with not being able to take advantage of certain features within Cocos2d that come with a ported object like CCTableView?
I have copied this post from here that may help answer your question. I believe that it would be better to uses cocos2d functions with cocos2d wrappers but you can implement with others it just doesn't integrate as well.
If there are any newbies (like myself) out there that need extra help how to integrate a UIKit item with cocos2d, this might help. As you have read in the first post of this thread Blue Ether has written a wrapper for manipulating UIViews. What is a UIView? Look at http://developer.apple.com/iphone/library/documentation/uikit/reference/uikit_framework/Introduction/Introduction.html, scroll down a little bit and you will see a diagram of different UIView items; things like UIButton, UISlider, UITextView, UILabel, UIAlertView, UITableView, UIWindow and so on. There are more then 20 of them. You can use Blue Ethas code to wrap any of them inside cocos2d. Here I will wrap a UIButton, but experiment with your favorite choice of UIView item.
Create a new cocos 2d Application project.
Make a new NSObject file and call it CCUIViewWrapper. That will give you the files CCUIViewWrapper.h and CCUIViewWrapper.m. Edit (by copy paste) them so they look as Blue Ether has defined them (see the first post in this thread.
Make your HelloWorld.h look like this
// HelloWorldLayer.h
#import "cocos2d.h"
#import <UIKit/UIKit.h>
#import "CCUIViewWrapper.h"
#interface HelloWorld : CCLayer
{
UIButton *button;
CCUIViewWrapper *wrapper;
}
+(id) scene;
#end
and your HelloWorld.m like this
// HelloWorldLayer.m
#import "HelloWorldScene.h"
// HelloWorld implementation
#implementation HelloWorld
+(id) scene
{
CCScene *scene = [CCScene node];
HelloWorld *layer = [HelloWorld node];
[scene addChild: layer];
return scene;
}
-(void)buttonTapped:(id)sender
{
NSLog(#"buttonTapped");
}
// create and initialize a UIView item with the wrapper
-(void)addUIViewItem
{
// create item programatically
button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Touch Me" forState:UIControlStateNormal];
button.frame = CGRectMake(0.0, 0.0, 120.0, 40.0);
// put a wrappar around it
wrapper = [CCUIViewWrapper wrapperForUIView:button];
[self addChild:wrapper];
}
-(id) init
{
if( (self=[super init] )) {
[self addUIViewItem];
// x=160=100+120/2, y=240=260-40/2
wrapper.position = ccp(100,260);
[wrapper runAction:[CCRotateTo actionWithDuration:4.5 angle:180]];
}
return self;
}
- (void) dealloc
{
[self removeChild:wrapper cleanup:true];
wrapper = nil;
[button release];
button=nil;
[super dealloc];
}
#end
Build and Run. This should produce a rotating button in the middle of the scene. You can touch the button while it rotates. It will write a text message in the Console.
CCUIViewWrapper is not a good solution. I have tried and removed it. The problem is that it wraps the UIKit element in a container and adds it as a view on director over the openGL main view. One problem with that is that you will not be able to add any other sprite on top of it. Very limited.
Related
There's a bug in MapKit that can cause duplicate callout views on an annotation. If the timing is just right, an annotation view can get re-used while it is being selected and apparently just before the callout view is actually added to it. As a result, the old callout view gets stuck there, and the new callout will appear on top of or next to it. Here's what this can look like in an OS X app:
There's only one annotation on this map. If you click elsewhere on the map to deselect the annotation, only one of the callouts disappears. In some cases you might have two callouts with completely different information, which is where things get really confusing for someone using your app.
Here's the majority of a sample OS X project I put together that illustrates this bug:
#import MapKit;
#import "AppDelegate.h"
#import "JUNMapAnnotation.h"
#interface AppDelegate () <MKMapViewDelegate>
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet MKMapView *mapView;
#property BOOL firstPin;
- (void)placeAndSelectPin;
- (JUNMapAnnotation *)placePin;
- (void)clearPins;
#end
#implementation AppDelegate
- (IBAction)dropSomePins:(id)sender {
self.firstPin = YES;
[self placeAndSelectPin];
[self performSelector:#selector(placeAndSelectPin) withObject:nil afterDelay:0.0001];
}
#pragma mark - Private methods
- (void)placeAndSelectPin {
[self clearPins];
JUNMapAnnotation *annotation = [self placePin];
[self.mapView deselectAnnotation:annotation animated:NO];
[self.mapView selectAnnotation:annotation animated:YES];
}
- (JUNMapAnnotation *)placePin {
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(50.0,50.0);
JUNMapAnnotation *annotation = [[JUNMapAnnotation alloc] initWithCoordinate:coord];
annotation.title = #"Annotation";
annotation.subtitle = (self.firstPin) ? #"This is an annotation with a longer subtitle" : #"This is an annotation";
[self.mapView addAnnotation:annotation];
self.firstPin = NO;
return annotation;
}
- (void)clearPins {
[self.mapView removeAnnotations:self.mapView.annotations];
}
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[JUNMapAnnotation class]]) {
static NSString *identifier = #"annotationView";
MKPinAnnotationView *view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (view == nil) {
view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
view.canShowCallout = YES;
NSLog(#"new annotation view");
} else {
view.annotation = annotation;
}
return view;
}
return nil;
}
#end
The same bug seems to exist in iOS, though I've had a tougher time recreating it there.
While I'm waiting on Apple to fix this, I'd like to work around it as much as possible. So far I've come up with a few possibilities:
Don't re-use annotation views. From what I can tell this seems like the only way to completely avoid the bug, but it seems pretty inefficient.
When an annotation view is re-used in mapView:viewForAnnotation:, remove all of its subviews. Currently it seems like the callout is the only subview, though it doesn't seem like a particularly safe hack. It also only sort of works—it doesn't prevent duplicate callouts from appearing, it just keeps them from sticking around forever. (When this bug first happens, there actually aren't any subviews yet.)
Combine both of those: if dequeueReusableAnnotationViewWithIdentifier: returns a view that has any subviews, ignore it and create a new one. This seems a lot safer than 2 and isn't nearly as inefficient as 1. But as with 2 it's not a complete workaround.
I've also tried adding deselectAnnotation:animated: in every place I can think of, but I can't find anything that works. I assume that once the annotation view is re-used, the MapView loses track of the first callout, so none of its normal methods will get rid of it.
this is a bit out of left field, but..
try registering the same cell class with 2 different reuse identifiers. in viewForAnnotation:, alternate between using each identifier when dequeueing a cell. this should prevent grabbing from the same queue twice in succession.
Can we change the color of the divider?
Apple documentations says, that we can override -dividerColor in subclass of NSSplitView for this, but it doesn't works for me, or my understanding isn't correct. Also I've try create color layer over divider, e.g.:
colorLayer = [CALayer layer];
NSRect dividerFrame = NSMakeRect([[self.subviews objectAtIndex:0] frame].size.width, [[self.subviews objectAtIndex:0] frame].origin.y, [self dividerThickness], self.frame.size.height);
[colorLayer setBackgroundColor:[color coreGraphicsColorWithAlfa:1]];
[colorLayer setFrame:NSRectToCGRect(dividerFrame)];
[self.layer addSublayer:colorLayer];
Not works.
This answer may be late but:
If you are using Interface Builder, it is possible to change the property by going to the Identity Inspector of the NSSplitView (cmd+alt+3) and adding a User Defined Runtime Attribute for dividerColor of the type Color.
Actually, simply subclassing NSSplitView and overriding -(void)dividerColor works, but works only for thin or thick divider.
I've created simple configurable split view like this:
#interface CustomSplitView : NSSplitView
#property NSColor* DividerColor
#end
#implementation CustomSplitView
- (NSColor*)dividerColor {
return (self.DividerColor == nil) ? [super dividerColor] : self.DividerColor;
}
#end
Then in Interface Builder specify custom class for your split view to be CustomSplitView and add new user defined runtime attribute with key path = DividerColor, type = Color and select desired splitter color.
I've tried subclassing - (void)dividerColor too and I'm not sure why it doesn't work even though I know it's being called (and it's in the documentation).
One way to change the color of the divider is to subclass - (void)drawDividerInRect:(NSRect)aRect. However, for some reason, this method isn't called and I've checked all over the web for answers, but couldn't find anything, so I ended up calling it from drawRect. Here is the code for the subclassed NSSplitView:
-(void) drawRect {
id topView = [[self subviews] objectAtIndex:0];
NSRect topViewFrameRect = [topView frame];
[self drawDividerInRect:NSMakeRect(topViewFrameRect.origin.x, topViewFrameRect.size.height, topViewFrameRect.size.width, [self dividerThickness] )];
}
-(void) drawDividerInRect:(NSRect)aRect {
[[NSColor redColor] set];
NSRectFill(aRect);
}
Based on Palle's answer, but with the possibility to change the color dynamically in code, I'm currently using this solution (Swift 4):
splitView.setValue(NSColor.red, forKey: "dividerColor")
If your splitview control is part of a NSSplitViewController, you should use something like this:
splitViewController?.splitView.setValue(NSColor.red, forKey: "dividerColor")
In Swift and on macOS 11 I was able to achieve this by simply subclassing the NSSPlitView and only override drawDivider()
import Foundation
import AppKit
class MainSplitView: NSSplitView {
override func drawDivider(in rect: NSRect) {
NSColor(named: "separatorLinesColor")?.setFill()
rect.fill()
}
}
I had previously tried some of the other way, listed in here and what used to work stopped working with macOS 11... but it seems that this works.
One important point I haven't seen mentioned anywhere is that if you are overriding drawRect in a split view then you must call super -- otherwise drawDividerInRect: is never called. So, it should go something like this:
- (void)drawRect:(NSRect)dirtyRect {
// your other custom drawing
// call super last to draw the divider on top
[super drawRect:dirtyRect];
}
- (void)drawDividerInRect:(NSRect)aRect {
[[NSColor blueColor] set];
NSRectFill(aRect);
}
What am I doing wrong?
I have an awakeFromNib method in which I am calling a class that is a subview (GameMap). The class exists and I am able to log in the awakeFromNib method as well as log in GameMap's initWithFrame method, but I cannot get GameMap to draw in the window. Here's my AppController.m file's awakeFromNib method:
-(void) awakeFromNib {
//make new game map
GameMap* newMap = [[GameMap alloc] initWithFrame:NSMakeRect(0.0, 0.0, 1000.0, 500.0)];
[[[NSApp mainWindow] contentView]addSubview:newMap];
[newMap release];
}
and in GameMap.m here's the drawRect method
- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFillUsingOperation(rect, NSCompositeSourceOver);
}
in this same app I am calling two other classes from AppController, all subviews of NSView, MakeCircle and MakeRoom that place either a circle (duh, : D) or a rect with a stroke in the window and they work fine, but they are running off of IBOutlet actions (button clicks). Any help would be appreciated.
*NOTE: I have NSRectFillUsingOperation(rect, NSCompositeSourceOver) but this was also failing wiht NSRectFill(rect).
**I can also log the origin.x/size.width, etc. of the passed rect to GameMap from the initWithFrame, so I know it's there.
(I'm away from my computer for a few hours so don't think I'm being rude for not replying, just wanted to get this question out there before I left.)
Just use NSRectFill(rect); instead of NSRectFillUsingOperation(rect, NSCompositeSourceOver);
Have you set the view's class to the GameMap class instead of NSView) in the Interface Builder? When you do this, the GameMap's drawRect() should get called automatically.
Several apps, including the built-in Address Book use a HUD window that is semi-transparent, with large shadowed text. I'd like to implement a similar window in my Cocoa Mac app.
Is there a free implementation of this kind of window somewhere?
If not, what is the best way to implement it?
Here's a sample project that shows how to do it:
http://github.com/NSGod/BlackBorderlessWindow
Basically, you need to create a borderless NSWindow subclass. The easiest way to do this is to set your window size and arrangement in the nib file, and then set its class to be your custom subclass. So while it will still look like a normal window in Interface Builder, at runtime it will appear as you need it to.
#implementation MDBorderlessWindow
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation {
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered defer:deferCreation]) {
[self setAlphaValue:0.75];
[self setOpaque:NO];
[self setExcludedFromWindowsMenu:NO];
}
return self;
}
The alpha value will make the window semi-transparent.
Also, you can create a custom NSView subclass that will draw a round rectangle:
#implementation MDBlackTransparentView
- (id)initWithFrame:(NSRect)frame {
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(NSRect)frame {
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame
xRadius:6.0 yRadius:6.0];
[[NSColor blackColor] set];
[path fill];
}
#end
Like with the window, you simply set the class of the window's contentView to be your custom NSView subclass. (Use outline view mode and click the disclosure triangle to show the nested NSView inside the icon of the window in the nib file). Again, while the view will look ordinary in Interface Builder, it will look okay at runtime.
Then just place an NSTextField on top of view and set the text accordingly.
Note that, in general, borderless windows aren't easy to work with (for example, if you want to be able to drag the window around, you'll need to add that functionality back yourself). Apple has some sample code on how to allow dragging, for instance.
Thank you for sharing this code. Helped me a lot!
You may add the following line...
[self setBackgroundColor:[NSColor clearColor]];
to the init function of the window. This removes the white corners.
I've asked about this earlier but the question itself and all the information in it might have been a little confusing, plus the result i want to get is a little more complicated. So i started a new clean test project to handle just the part that im interested to understand for the moment.
So what i want, is basically this: i have a view container (inherits NSView). Inside, i want to place some images, but not just simple NSImage or NSImageView, but some custom view (inherits NSView also), which itself contains a textfield and an NSImageView. This 'image holder' as i called it, is in a separate nib file (im using this approach since i am guiding myself after an Apple SAmple Application, COCOA SLIDES).
The results i got so far, is something but not what i am expecting. Im thinking i must be doing something wrong in the Interface Builder (not connecting the proper thingies), but i hope someone with more expertise will be able to enlighten me.
Below i'll try to put all the code that i have so far:
//ImagesContainer.h
#import <Cocoa/Cocoa.h>
#interface ImagesContainer : NSView {
}
#end
//ImagesContainer.m
#import "ImagesContainer.h"
#import "ImageHolderView.h"
#import "ImageHolderNode.h"
#class ImageHolderView;
#class ImageHolderNode;
#implementation ImagesContainer
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
//create some subviews
for(int i=0;i<3;i++){
ImageHolderNode *node = [[ImageHolderNode alloc] init];
[self addSubview:[node rootView]];
}
}
NSRunAlertPanel(#"subviews", [NSString stringWithFormat:#"%d",[[self subviews] count]], #"OK", NULL, NULL);
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
[[NSColor blackColor] set];
NSRectFill(NSMakeRect(0,0,dirtyRect.size.width,dirtyRect.size.height));
int i=1;
for(NSView *subview in [self subviews]){
[subview setFrameOrigin:NSMakePoint(10*i, 10)];
i++;
}
}
#end
//ImageHolderView.h
#import <Cocoa/Cocoa.h>
#interface ImageHolderView : NSView {
IBOutlet NSImageView *imageView;
}
#end
//ImageHolderVIew.m
#import "ImageHolderView.h"
#implementation ImageHolderView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
[[NSColor blueColor]set];
NSRectFill(NSMakeRect(10,10, 100, 100));
//[super drawRect:dirtyRect];
}
#end
//ImageHolderNode.h
#import <Cocoa/Cocoa.h>
#class ImageHolderView;
#interface ImageHolderNode : NSObject {
IBOutlet ImageHolderView *rootView;
IBOutlet NSImageView *imageView;
}
-(NSView *)rootView;
-(void)loadUIFromNib;
#end
//ImageHolderNode.m
#import "ImageHolderNode.h"
#implementation ImageHolderNode
-(void)loadUIFromNib {
[NSBundle loadNibNamed:#"ImageHolder" owner: self];
}
-(NSView *)rootView {
if( rootView == nil) {
NSRunAlertPanel(#"Loading nib", #"...", #"OK", NULL, NULL);
[ self loadUIFromNib];
}
return rootView;
}
#end
My nib files are:
MainMenu.xib
ImageHolder.xib
MainMenu is the xib that is generated when i started the new project.
ImageHolder looks something like this:
image link
I'll try to mention the connections so far in the xib ImageHolder :
File's Owner - has class of ImageHolderNode
The main view of the ImageHolder.xib , has the class ImageHolderView
So to resume, the results im getting are 3 blue rectangles in the view container, but i cant seem to make it display the view loaded from the ImageHolder.xib
If anyone wants to have a look at the CocoaSlides sample application , its on apple developer page ( im not allowed unfortunately to post more than 1 links :) )
Not an answer, exactly, as it is unclear what you are asking..
You make a view (class 'ImagesContainer'). Lets call it imagesContainerView.
ImagesContainerView makes 3 Objects (class 'ImageHolderNode'). ImagesContainerView asks each imageHolderNode for it's -rootView (maybe 'ImageHolderView') and adds the return value to it's view-heirarchy.
ImagesContainerView throws away (but leaks) each imageHolderNode.
So the view heirachy looks like:-
+ imagesContainerView
+ imageHolderView1 or maybe nil
+ imageHolderView2 or maybe nil
+ imageHolderView3 or maybe nil
Is this what you are expecting?
So where do you call -(void)loadUIFromNib and wait for the nib to load?
In some code you are not showing?
In general, progress a step at a time, get each step working.
NSAssert is your friend. Try it instead of mis-using alert panels and logging for debugging purposes. ie.
ImageHolderNode *node = [[[ImageHolderNode alloc] init] autorelease];
NSAssert([node rootView], #"Eek! RootView is nil.");
[self addSubview:[node rootView]];
A view of course, should draw something. TextViews draw text and ImageViews draw images. You should subclass NSView if you need to draw something other than text, images, tables, etc. that Cocoa provides.
You should arrange your views as your app requires in the nib or using a viewController or a windowController if you need to assemble views from multiple nibs. Thats what they are for.
EDIT
Interface Builder Connections
If RootView isn't nil then it seems like you have hooked up your connections correctly, but you say you are unclear so..
Make sure the IB window is set to List view so you can see the contents of you nib clearly.
'File's Owner' represents the object that is going to load the nib, right? In your case ImageHolderNode.
Control Click on File's owner and amongst other things you can see it's outlets. Control drag (in the list view) from an outlet to the object you want to be set as the instance var when the nib is loaded by ImageHolderNode. I know you know this already, but there is nothing else to it.
Doh
What exactly are you expecting to see ? An empty imageView? Well, that will look like nothing. An empty textfield? That too, will look like nothing. Hook up an outlet to your textfield and imageView and set some content on them.