Cannot set view for NSOpenGLContext - objective-c

(Sorry in advance for the seemingly large amount of code here) I'm trying to create a window with an OpenGL context with Cocoa, but I'm finding that I am unable to set the view property of the NSOpenGLContext that I create.
I cannot simply use an NSOpenGLView, since I need to interface with a C++ drawing backend and use multiple contexts. The code I post here is just me trying to get to grips with handling NSOpenGLContexts, but it will be used in a much larger project. This is why I'm instantiating NSApplication and my NSWindow manually rather than through a NIB/NSApplicationMain.
My main.m file:
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "Delegate.h"
int main(int argc, const char * argv[]) {
[NSApplication sharedApplication];
Delegate* dlg = [[Delegate alloc] init];
[NSApp setDelegate:dlg];
[NSApp run];
return 0;
}
I then have my delegate class, and I'll refrain from posting the file Delegate.h since it'll be pretty obvious what's there given these contents of Delegate.m:
#import <Cocoa/Cocoa.h>
#import "Delegate.h"
#import <OpenGL/gl.h>
#implementation Delegate
- (void) draw
{
[self.glContext makeCurrentContext];
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self.glContext flushBuffer];
}
- (void) applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
self.win = [[NSWindow alloc] initWithContentRect:NSMakeRect(30, 30, 300, 200)
styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask
backing:NSBackingStoreBuffered
defer:YES];
NSOpenGLPixelFormatAttribute glAttributes[] =
{
NSOpenGLPFAColorSize, 24,
NSOpenGLPFAAlphaSize, 8,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
0
};
self.glContext = [[NSOpenGLContext alloc] initWithFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:glAttributes]
shareContext:nil];
[self.glContext setView: [self.win contentView]];
printf("view:%p, contentView:%p\n", [self.glContext view], [self.win contentView]);
[self.win makeKeyAndOrderFront:nil];
[NSTimer
scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(draw)
userInfo:nil
repeats:YES];
}
The window opens just fine. I am able to tell that -applicationDidFinishLaunching and -draw are being called. The window shows up empty however.
The printf call shows that the view property of self.glContext is equal to the address 0x0. I see no documentation or other forum threads about why I would be unable to set the drawable object of an NSOpenGLContext.
I have tried putting the NSOpenGLContext in its own subclass of NSView and adding that subclass as a subview of the window's content view, but with no success.

Try setting the defer parameter of -[NSWindow initWithContentRect:...] to NO. You may also wish to set the GL context's view after ordering the window on-screen.
Basically, -[NSOpenGLContext setView:] can fail if the view's window doesn't yet have a "device". When that's happened to me, it usually logs a message to the console about an "invalid drawable", but I haven't checked in recent versions of the OS.
Also, you need to register as an observer of the NSViewGlobalFrameDidChangeNotification notification from the view and, in response, call -update on the GL context object.

Related

programmatically create initial window of cocoa app (OS X)

Usually I am making iOS app but now I am trying to make an OS X app, and I am lost at the very beginning. Say the style I make the iOS apps are totally programmatic, there's no xib files or whatsoever just because that I have a lot more control by typing than dragging. However in OS X programming, it starts with some xib files with the menu items and a default window. There are quite a lot of items in the menu items so that's probably not something I want to mess around, but I want to programmatically create my first window myself.
So I did this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSUInteger windowStyleMask = NSTitledWindowMask|NSResizableWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask;
NSWindow* appWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(200, 200, 1280, 720) styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
appWindow.backgroundColor = [NSColor lightGrayColor];
appWindow.minSize = NSMakeSize(1280, 720);
appWindow.title = #"Sample Window";
[appWindow makeKeyAndOrderFront:self];
_appWindowController = [[AppWindowController alloc] initWithWindow:appWindow];
[_appWindowController showWindow:self];
}
So here, I have created a window first, and use that windowController to init this window. The window does show up in this way, but I can only specify the inner elements, like buttons and labels here, but not in the windowController. It makes me feel bad so I tried another way.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
_appWindowController = [[AppWindowController alloc] init];
[_appWindowController showWindow:self];
}
and after this I want to set the other elements in the loadWindow: function in the windowController like this:
- (void)loadWindow
{
[self.window setFrame:NSMakeRect(200, 200, 1280, 720) display:YES];
self.window.title = #"Sample window";
self.window.backgroundColor = [NSColor lightGrayColor];
NSButton* sampleButton = [[NSButton alloc] initWithFrame:NSRectFromCGRect(CGRectMake(100, 100, 200, 23))];
sampleButton.title = #"Sample Button!";
[sampleButton setButtonType:NSMomentaryLightButton];
[sampleButton setBezelStyle:NSRoundedBezelStyle];
[self.window.contentView addSubview:sampleButton];
NSLog(#"Loaded window!");
[self.window makeKeyAndOrderFront:nil];
}
Unfortunately, this never works. the loadWindow: never gets called, nor windowDidLoad:. Where did they go?
And please don't ask why I don't use nibs. I wish to make some highly customized views inside, possibly OpenGL, so I don't think nibs can handle it. I am greatly appreciated if anyone could help. Thanks.
And also, who knows how to even start the menu items from scratch, programmatically?
I am using the latest Xcode.
I spent an entire Sunday digging into this problem myself. Like the person asking the question, I prefer coding iOS and OSX without nib files (mostly) or Interface Builder and to go bare metal. I DO use NSConstraints though. It is probably NOT WORTH avoiding IB if you're doing simpler UIs, however when you get into a more complex UI it gets harder.
It turns out to be fairly simple to do, and for the benefit of the "Community" I thought I'd post a concise up to date answer here. There ARE some older Blog Posts out there and the one I found most useful were the ones from Lap Cat Software. 'Tip O The Hat' to you sir!
This Assumes ARC. Modify your main() to look something like this:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char *argv[])
{
NSArray *tl;
NSApplication *application = [NSApplication sharedApplication];
[[NSBundle mainBundle] loadNibNamed:#"MainMenu" owner:application topLevelObjects:&tl];
AppDelegate *applicationDelegate = [[AppDelegate alloc] init]; // Instantiate App delegate
[application setDelegate:applicationDelegate]; // Assign delegate to the NSApplication
[application run]; // Call the Apps Run method
return 0; // App Never gets here.
}
You'll note that there is still a Nib (xib) in there. This is for the main menu only. As it turns out even today (2014) apparently no way to easily set the position 0 menu item. That's the one with the title = to your App name. You can set everything to the right of it using [NSApplication setMainMenu] but not that one. So I opted to keep the MainMenu Nib created by Xcode in new projects, and strip it down to just the position 0 item. I think that is a fair compromise and something I can live with. One brief plug for UI Sanity... when you're creating Menus please follow the same basic pattern as other Mac OSX Apps.
Next modify the AppDelegate to look something like this:
-(id)init
{
if(self = [super init]) {
NSRect contentSize = NSMakeRect(500.0, 500.0, 1000.0, 1000.0);
NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
window.backgroundColor = [NSColor whiteColor];
window.title = #"MyBareMetalApp";
// Setup Preference Menu Action/Target on MainMenu
NSMenu *mm = [NSApp mainMenu];
NSMenuItem *myBareMetalAppItem = [mm itemAtIndex:0];
NSMenu *subMenu = [myBareMetalAppItem submenu];
NSMenuItem *prefMenu = [subMenu itemWithTag:100];
prefMenu.target = self;
prefMenu.action = #selector(showPreferencesMenu:);
// Create a view
view = [[NSTabView alloc] initWithFrame:CGRectMake(0, 0, 700, 700)];
}
return self;
}
-(IBAction)showPreferencesMenu:(id)sender
{
[NSApp runModalForWindow:[[PreferencesWindow alloc] initWithAppFrame:window.frame]];
}
-(void)applicationWillFinishLaunching:(NSNotification *)notification
{
[window setContentView:view]; // Hook the view up to the window
}
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
[window makeKeyAndOrderFront:self]; // Show the window
}
And Bingo... you're good to go! You can start working from there in the AppDelegate pretty much like you're familiar with. Hope that helps!
UPDATE: I don't create menus in code anymore as I've shown above. I've discovered you can edit MainMenu.xib source in Xcode 6.1. Works nice, very flexible and all it takes is a little experimentation to see how it works. Faster than messing around in code and easy to localize! See the picture to understand what I am on about:
See https://github.com/sindresorhus/touch-bar-simulator/blob/master/Touch%20Bar%20Simulator/main.swift
In main.swift
let app = NSApplication.shared()
let delegate = AppDelegate()
app.delegate = delegate
app.run()
Swift 4:
// File main.swift
autoreleasepool {
// Even if we loading application manually we need to setup `Info.plist` key:
// <key>NSPrincipalClass</key>
// <string>NSApplication</string>
// Otherwise Application will be loaded in `low resolution` mode.
let app = Application.shared
app.setActivationPolicy(.regular)
app.run()
}
// File Application.swift
public class Application: NSApplication {
private lazy var mainWindowController = MainWindowController()
private lazy var mainAppMenu = MainMenu()
override init() {
super.init()
delegate = self
mainMenu = mainAppMenu
}
public required init?(coder: NSCoder) {
super.init(coder: coder) // This will newer called.
}
}
extension Application: NSApplicationDelegate {
public func applicationDidFinishLaunching(_ aNotification: Notification) {
mainWindowController.showWindow(nil)
}
}
Override the -init method in your AppWindowController class to create the window and then call super's -initWithWindow: method (which is NSWindowController's designated initializer) with that window.
But I generally agree with the comments that there's little reason to avoid NIBs.

Memory keep increasing even though I set it to nil (screenshot enclosed)

In my ParentViewController I type this:
- (void)updateView:(NSInteger)_index article:(AObject *)aObject {
UIView *aUIView = [someUIViews objectAtIndex:_index];
// clear view
for (UIView *view in aUIView.subviews) {
[view removeFromSuperview];
}
// create view
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;
}
In ChildViewController I type this:
.h file
#interface ChildViewController : UIViewController <UIWebViewDelegate> {
AObject *aObject;
UIWebView *webView;
}
.m file
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:(BOOL)animated];
webView.delegate = nil;
webView = nil;
aObject = nil;
}
Remark:
I have to use addChildViewController because I use UIWebViewDelegate in ChildViewController. If I do not use addChildViewController, I cannot retain the ViewController and functions of UIWebViewDelegate cannot be called.
I use ARC
Program Objective:
There is a scrolling view implementing by UIScrollView and everytime I scroll the screen to another page, function updateView will be called.
After I scroll to another page, the previous page will be disappeared so viewWillDisappear in ChildViewController will be called.
Screenshot:
http://postimage.org/image/mzmksm9d5/
I cannot post image directly because of low reputation here.
Based on the figure, ParentViewController is called at about the 4th second. Then the memory keep increasing rapidly without objects released. At about the 30th second, ParentViewController is gone away by clicking the back button on the navigation bar. Memory is dropped a bit but still somethings are remaining.
Goal:
What I wish to do is that the object of ChildViewController can be destroyed when viewWillDisappear is called and it is no longer used. So I expect the memory would always drop after it increased a bit. Otherwise, the memory will increase proportional to scrolling times and crash until memory is fully used.
Thanks in advance for any help you are able to provide.
Not sure if this will solve the problem, but try removing cvc.view explicitly from parent view.
Also, you are creating ChildViewController, but you are not releasing it. By calling init you increased its retain count, but you are not decreasing it anywhere. Like this (if you do not have some more code), your ChildViewController will not be released since retain count will never be 0 again.
ChildViewController *cvc = [[ChildViewController alloc] init:aObject context:managedObjectContext];
cvc.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self addChildViewController:cvc];
[aUIView addSubview:cvc.view];
cvc = nil;

EXC_BAD_ACCESS when dragging UIScrollView

In a controller I'm creating a UIScrollView. I'm trying to set this viewcontroller as the UISCrollview delegate and to implement the delegate's method in order to add (later) a UIPageControl.
I've read a bit, and found this link, this other link and other here on SO, and some useful tutorial all around the web, but I don't understand what I'm doing wrong. Everytime a scroll the UIScrollView, the app crashes with an EXC_BAD_ACCESS error.
Here's my .h file
#import <UIKit/UIKit.h>
#interface StatsViewController : UIViewController <UIScrollViewDelegate> {
UIScrollView *scrollView;
UIPageControl *pageControl;
}
#end
Then in my .m file, I'm creating the scrollview and trying to define the delegate method like this:
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger boxWidth = self.view.frame.size.width;
NSInteger boxHeight = 412;
scrollView = [ [UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, boxHeight)];
scrollView.pagingEnabled = TRUE;
scrollView.delegate = self;
NSInteger numberOfViews = 2;
StatBreatheCounter *breatheCounter = [ [StatBreatheCounter alloc] init];
breatheCounter.view.frame = CGRectMake(0, 0, boxWidth, boxHeight);
[scrollView addSubview:breatheCounter.view];
BreatheLocationViewController *breatheLocation = [ [BreatheLocationViewController alloc] init];
breatheLocation.view.frame = CGRectMake(320, 0, boxWidth, boxHeight);
[scrollView addSubview:breatheLocation.view];
scrollView.contentSize = CGSizeMake(self.view.frame.size.width * numberOfViews, boxHeight);
[self.view addSubview:scrollView];
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
NSLog(#"RUNNING");
}
...but every time I slide on the scroll view, the app is crashing.
Now, I'm quite a n00b on Ojective-C, but I feel I'm missing something. Browsing around everything points on the fact that the delegate could be deallocated early, and when the user trigger the action, no one is handling the method (sorry for the explanation :)).
...but if the delegate it's the viewcontroller itself, how could it be deallocated?
As you can see, I'm quite confused :(
Any help would be really appreciated
--
EDIT:
I'm going to include here the solution founded thanks with your comments and answer.
When I posted my question I was so convinced that the error was coming from the way I was creating the UIScrollView and setting its delegate that I didn't realize that the problem was (as everything was suggesting, btw :)) I was allocating the StateViewController in its parent without declaring any "strong" reference to it (again, sorry for the explanation, I'm really a n00b in this).
Thanks a lot for your helping in pointing me on the right direction
It looks like you are losing reference to the delegate during scroll. I would look into any other release events around StatsViewController or other events that could cause it to be dereferenced.

Passing data to a UIView - failure

I have a strange problem that I've never encountered before,
I have data in my viewController that I want to display in a UIView.
It's an iPad App which involve a SplitView Controller, when I click on an element within the table view (masterView) it execute a function in my detailViewController (via a protocol).
A function is executed which launch a UIView and send data to it:
myController:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}
DocumentView.h
#import <UIKit/UIKit.h>
#import "Document.h"
#import "DocumentInfo.h"
#class Document;
#class DocumentInfo;
#interface DocumentView : UIView
#property(strong,nonatomic) Document *doc;
#property(strong,nonatomic) DocumentInfo *doc_info;
#end
DocumentView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILabel *titreDoc=[[UILabel alloc] initWithFrame:CGRectMake(20, 32, 339, 21)];
titreDoc.textColor = [self makeColorRGB_RED:66 GREEN:101 BLUE:149];
titreDoc.font = [UIFont fontWithName:#"System" size:(24.0)];
[self addSubview:titreDoc];
NSLog(#"%# - %#",doc,doc_info);
titreDoc.text=#"Nouveau Document";
}
return self;
}
My view is well displayed (I mean the label appear) but impossible to get the data which would have been passed to it... (the NSLog print (null) (null) )
Anybody know the reason why?
The problem seems pretty straight forward. You initialize your view (which means that you run - (id)initWithFrame:(CGRect)frame) and after that you're setting the data, so it's normal that you see null values into your init method because the ivars have not being set yet. What you could do is to modify your init method in order to construct your view taking into account these ivars. Something like this perhaps:
- (id)initWithFrame:(CGRect)frame doc:(Document *)doc docInfo:(DocumentInfo *)docInfo;
ps. If you choose to make your custom init method do not forget to call the designated initializer (-initWithFrame:) before any customization.
The reason the NSLog prints null is because the doc and doc_info are nil when the initWithFrame method is called. The doc and doc_info properties are set after the initWithFrame method is called in selectionChanged: method. Add the NSLog function after line 3 in selectionChanged method like this:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
NSLog(#"%# - %#",doc,doc_info);
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}

problems with creating an NSWindow

I am new to Cocoa, and I am just experimenting with creating a window programmatically (without using Interface Builder).
I start a new Cocoa Application in Xcode, then I remove the window from the nib file in Interface Builder to replace it with my own one.
In the main function, I add the code:
NSWindow* myWindow;
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
When I try to build and run the application, I receive the following error message:
Error (1002) creating CGSWindow
Why does this happen??? What is a CGSWindow by the way?
Rainer
You probably don't have a connection to the window server yet. That's NSApplication's job, so try creating the shared application first.
If that doesn't help, I'd just go through with my usual application layout: Create an NSObject subclass for a custom controller, instantiate this from your application delegate's applicationWillFinishLaunching: and release it in applicationWillTerminate:, and have your custom controller's init method create the window. The application object will definitely be running by this point (as main does nothing but call NSApplicationMain, which gets/creates the shared application and tells it to run), so you should definitely have your connection to the window server and so be able to create the window.
Move the code you've written into the following method from your App Delegate implementation file -
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSWindow* myWindow;
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
}
and it should load your window perfectly.
It's worth noting that it's never safe and is not a good practice to create any UI related objects in your main function.
If you want to display a completely empty window from scratch this is all the code you'll need:
//if you used a template this will be already in the file:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];
int retval=UIApplicationMain(argc,argv,nil,#"SimpleWindowAppDelegate");
[pool release];
return retVal;
}
#implementation SimpleWindowAppDelegate : NSObject <UIApplicationDelegate>
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
UIWindow *window=[[UIWindow alloc] initWithFrame:[[UIDevice mainScreen] bounds]];
//You could create views and add them here:
//UIView *myView=[[UIView alloc] initWithFrane:CGRectMake(0,0,50,50)];
//[window addSubView:myView];
//[myView release];
[window makeKeyAndVisible];
}
#end
The only code that you will probably ever want to have in your main() function in a Cocoa application is automatically created for you by XCode (if you are using it).
I suggest that you add the code that you want into your applicationDelegate's -applicationDidFinishLaunching: method
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];;
}