How do I re-define size/origin of app window in code, overriding nib/xib file parameters, with Obj-C, Xcode 11.3.1 on Mac OS X 10.15.2 (Catalina)? - objective-c

I'll try to keep it short. I want to create a 3D FPS game, just for myself, that can run on multiple platforms, but I figured that to keep it simple, perhaps it is best to start off with something that is exclusively for macOS. I opted for Objective-C because
(a) Window Application projects in Xcode can only be coded either in Obj-C or Swift (since we are dealing with Cocoa API) and
(b) Obj-C is closer to old-school then Swift.
But before I learn to draw/render 2D-shapes on the window's canvas by writing code, I have to learn to invoke an application window with its properties set to my liking. I've spent hours doing research and experimenting with chunks of code. This is what I've tried: I open with
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Then I go with ...
1)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSRect frame = [window frame];
frame.origin.x = 100;
frame.origin.y = 200;
frame.size.width = 100;
frame.size.height = 500;
[window setFrame: frame display: YES];
... and close with ...
NSApplicationMain(argc, argv); // runs the win display function.
}
return (0) ;
}
But no visible changes. Nothing really gets reset. So instead of (1) I tried ...
2)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSPoint newOrigin;
newOrigin.x = 400;
newOrigin.y = 100;
[window setFrameOrigin : newOrigin];
Still nothing. Then instead of (2) I tried:
3)
NSWindowController* controller = [[NSWindowController alloc]
initWithWindowNibName:#"MainMenu"];
[controller showWindow:nil];
Great. Now it's spitting out something I don't understand, especially since I'm new to Obj-C:
2020-02-08 21:53:49.782197-0800
tryout_macApp2[14333:939233] [Nib Loading] Failed
to connect (delegate) outlet from
(NSWindowController) to (AppDelegate): missing
setter or instance variable
I remember dicing around with an ApplicationDelegate, with CGSizeMake(), etc., but it just made the experience really inundating and frustrating. Nothing happened. Then there are NSView, NSViewController, and other classes, which is really mindboggling and begs the question: why are there so many classes when all I want to do is override the preset origin of the window and the dimensions preset by the MainMenu.xib file? (By the way, this project is derived from a Window Application project provided by Xcode.)
I really can't think of anything else to add to give you the entire picture of my predicament, so if you feel that something is missing, please chime in.
[Edit:] Moving forward to phase 2 of my project here: How do I paint/draw/render a dot or color a pixel on the canvas of my window with only a few lines in Obj-C on Mac OS X using Xcode?.

The short answer is that main() is too early to be trying to do this. Instead, implement -applicationDidFinishLaunching: on your app delegate class, and do it there. Leave main() as it was originally created by Xcode's template.
After that, I would say to obtain the window (if there's only going to be one main one), it's better to add an outlet to your app delegate and then, in the NIB, connect that outlet to the window. Then, you can use that outlet whenever you want to refer to the window.
Also, make sure that Visible at Launch is disabled for the window in the NIB. That's so you configure it as you want before showing it.
For a more complex app, it's probably better to not put a window into the Main Menu NIB. Instead, make a separate NIB for the window. Then, load it using a window controller object and ask that for its window.

I love Objective-C but also feel your pain, it has this testy ability to frustrate you endlessly.
I have not really developed a game but let me try and point you in the right direction. I think you need a UIViewController.
Now each UIViewController has a built in UIView that sort of represents the visible portion of it. You can use this or add a UIView and use that, whichever depends on your implementation. For now I'd suggest add a separate UIView and use that rather. Once you're comfortable you can then move the implementation to the UIViewController's view if you need to.
Anyhow, for now, create a UIView subclass, say MyGame or something, as for now all your code will end up there.
To do all of the above is not easy, especially if its the first time. If you can follow some tutorial it will be great. Even if the tutorial just adds a button, you can use it and replace the button with your view.
Anyhow, now that you've got that running and the view you've added shows up in green or some other neon colour just to verify that you can indeed change its properties, you're good to go.
Now you start. In MyGame, implement the
-(void)drawRect:(CGRect)rect
message, grab the context through
UIGraphicsGetCurrentContext
and start drawing lines and stuff on it, basically the stuff I understand you are interested in doing. You can also, through the same context, change the origin of what you are doing.
Hope this helps.

Related

Xcode (Obj-C) Updating a UIView (NSRect) after the window is already visible to the user

I am fairly new to creating Xcode projects using Objective-C and i'm trying to make a simple 2d graphics program to show me how well my graphing code works before I implement it elsewhere.
I have gotten the drawing of everything right but the problem comes when I want to clear the strokes and stroke new lines. From what I see from when I run the program the view will only display what I have executed once it hits that #end at the end of the implementation. The issue with this is that by the time it hits the #end the code has already been run. and I can't figure out if I need to recall the class in a loop to update the view each time (or where or how to do this, perhaps something in main.m?) or if I need to call a function to update the view before it exits the implementation because right now all the lines are just overwriting each other before the user can see anything.
Here is the interface in my header file (connected to a UIView):
#interface GraphView : NSView
- (void)drawRect:(NSRect)dirtyRect;
#end
Implementation file:
Here is how I am creating my rectangle:
- (void)drawRect:(NSRect)dirtyRect {
[self clearGrid:dirtyRect];
[self drawLines];
}
- (void)clearGrid:(NSRect)theRect {
//Wiping the slate clean
[super drawRect:theRect];
[self.window setBackgroundColor:[NSColor whiteColor]];
...
Here is what I am using to draw my lines:
NSBezierPath* eqLine = [NSBezierPath bezierPath];
[[NSColor greenColor] setStroke];
[eqLine setLineWidth:5.0];
[eqLine moveToPoint:NSMakePoint([self convertToPixels:previousX], [self convertToPixels:previousY])];
[eqLine lineToPoint:NSMakePoint([self convertToPixels:finalX], [self convertToPixels:finalY])];
[eqLine stroke];
I have been searching for the past few days now on how I could solve this but so far it hasn't turned up anything, perhaps i'm just not searching for the right thing. Any information is helpful even if it's just a point to a resource that I can look at. Let me know if any additional information is needed. Thanks!
It's clear that you're a noobie to this, which is not a crime. So there's a lot to address here. Let's take things one at a time.
To answer your basic question, the -drawRect: method is called whenever a view needs to draw its graphic representation, whatever that is. As long as nothing changes that would alter its graphical representation, the -drawRect: method will only be received once.
To inform Cocoa (or CocoaTouch) that the graphic representation has changed, you invalidate all, or a portion of, your view. This can be accomplished in many ways, but the simplest is by setting the needsDisplay property to YES. Once you do that, Cocoa will (at some point in the future) call the -drawRect: method again to modify the graphic representation of your view. Wash, rinse, repeat.
So here's the basic flow:
You create your class and add it to a view. Cocoa draws your view the first time when it appears by sending your class a -drawRect: message.
If you want your view to change, you invalidate a portion of the view (i.e. view.needsDisplay = YES). Then you just sit back and wait. Very soon, Cocoa will send -drawRect: again, your class draws the new and improved image, and it appears on the screen.
Many changes will cause your view to be invalidated, and subsequently redrawn, automatically—say, if it's resized. But unless you make a change that Cocoa knows will require your view to redraw itself, you'll have to invalidate the view yourself.
Now for the nit-picking, which I hope you understand is all an effort to help you understand what's going on and improve your code...
Your subject line says UIView but your code example subclasses NSView so I'm actually not sure if you're writing a macOS or an iOS app. It doesn't matter too much, because at this level the differences are minimal.
Your -drawRect: calls [self clearGrid:..., when then calls [super drawRect:... Don't do this. As a rule, never use super except from within the overloaded method of the same name. In other words, -drawRect: can use [super drawRect:, but no other methods should. It's not "illegal", but it will save you grief in the long run.
Your -clearGrid: method sets the backgroundColor of the window. Don't do this. The window's background color is a property, and your -drawRect: method should only be drawing the graphical representation of your view—nothing more, nothing less.
You're calling [super drawRect: from within a direct subclass of NSView (or UIView). While that's OK, it's unnecessary. Both of these base classes clearly document that their -drawRect: method does nothing, so there's nothing to be gained by calling it. Again, it won't cause any harm, it's just pointless. When your -drawRect: method begins execution, a graphics context has already been set up, cleared, and is ready to draw into. Just start drawing.
#end is not a statement. It does not get "executed". It's just a word that tell the compiler that the source code for your class has come to an end. The stuff that gets executed are the methods, like -drawRect:.
In your #interface section you declared a -drawRect: method. This is superfluous, because the -drawRect: method is already declared in the superclass.

How to use NSWindowController class

I am sorry if this seems trivial, but I am sure its a reasonable question to ask here.
I worked a lot around the NSWindowController class, and it seems the only way to get it
to work fully (for my purpose), is by creating a new xib-file along with it.
My question is, would it be somehow feasible to work with MainMenu.xib and the NSWindowController class and an instantiated object controller, to get interaction with the windows' content. So far without xib the only code segments getting executed are within awakeFromNib. The purpose being, I want to save xib-file space, complexity and have it easily integrate with a bigger project. Just fyi this is not a document-based project.
Should I choose a different subclass of NSObject other than NSWindowController? Or is it not possible?
The code required to run for the class to be working fully is as follows:
- (void) tableViewSelectionDidChange:(NSNotification *)notification
{
NSInteger selectedRow = [logsTableView selectedRow];
if ([directoryList containsObject:[directoryList objectAtIndex:selectedRow]])
{
NSString *logContent = [NSString stringWithContentsOfFile:[directoryList objectAtIndex:selectedRow]
encoding:NSUTF8StringEncoding
error:NULL];
if (logContent != NULL)
{
[logsTextView setString:logContent];
} else
{
[logsTextView setString:#"No permission to read log"];
}
}
}
NSWindowController usually wants to create the window it controls, which means you either need to give it a XIB file that contains the window to create or override the various window creation methods to customize the window in code. So it's probably not feasible to use an already-instantiated window from a different XIB with your NSWindowController.
That said, I almost always create a a XIB and an NSWindowController subclass for every window in my apps. Even the preferences window gets its own window controller class. The only exception would be extremely simple windows, but even now I'm struggling to think of a good example.
Your method isn't being called because window controller instance isn't set as the table view's delegate. The typical pattern here is to create your window in a XIB, set your window controller as the custom class of the File's Owner object, and then hook up the table view's delegate and dataSource outlets to File's Owner. This makes your window controller the table view's data source and delegate, and the connections will be established automatically when the XIB is loaded.

Cocoa window dragging

I'm using addGlobalMonitorForEventsMatchingMask to listen to events in Cocoa:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask
handler:^(NSEvent *event) {
NSLog(#"Dragged...");
}];
Though I would like to know if I'm dragging/moving a window (and which window that is, I can find the focused window though when holding command and dragging a window it doesn't get focus as far as I know.)
So, can I detect whether or not I'm dragging a window?
Update:
I now have a class: "SATest : NSObject <NSWindowDelegate>" in which I implement the windowDidMove method (and later perhaps windowWillMove too.) Though, now the next step would be attaching this to a window, right? So my question now is: How do I attach the delegate to all windows of all apps?
Update 2:
I now can find a list of all open windows on the screen:
AXUIElementRef _systemWideElement;
_systemWideElement = AXUIElementCreateSystemWide();
CFArrayRef _windows;
AXUIElementCopyAttributeValues(_systemWideElement, kAXWindowsAttribute, 0, 100, &_windows);
Now I have to iterate over the windows, and of each get it's NSWindow so I can add my delegate to it: [window setDelegate:self];
Update 3: To be clear, this question is about detecting the dragging of ALL windows of ALL apps. Not only the windows of my own app.
Also, I'm very new to this event and window management stuff, so no need to keep your answer short I'm happy to read a lot :P
Thanks!
-P
To find out if a window is being dragged you need to have an object that acts as the delegate of the window by responding to the following messages of the NSWindowDelegate protocol:
windowWillMove - this tells the delegate that the window is about to move.
windowDidMove - this tells the delegate that the window has moved.
You can retrieve the NSWindow object in question by sending object to the notification parameter sent to these methods:
e.g.
NSWindow draggedWindow = [notification object];
More information can be found here.
Update:
In response to your request about getting this information for all windows the NSApplication class provides a method which returns an array of all windows owned by the application. The typical way of getting this information is to use one of the NSApplicationDelegate methods to get a reference to your application object.
For example, in your app delegate (pseudocode):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSApplication * app = [aNotification object];
// you now have a reference to your application.
// and can iterate over the collection of windows and call
// [window setDelegate:self]; for each window.
}
Note that you will need to add / remove you delegates as windows are added and removed. The best method for doing this is – applicationDidUpdate:.
This should be enough to get you started solving your problem.
As suggested by Benjamin the answer lies in the accessibility API. I was looking around in this for a while, even before I asked this question, but never got it to do what I wanted. I now have found a pretty nice solution.
At a high-level I do the following:
Listen to the mouse down event and remember both on which window you clicked and it's location.
Listen to the mouse up event and check if the location has changed, if so you know you moved a window
You can do something similar for the size if you also want to know if you resized. There might be a better solution, but after days of trying stuff this is the only way I got it to work the way I wanted.
Hope this helps anyone who was looking for something similar.
-Pablo

show quick look preview in a view

I am trying to show the preview of a file in a View instead of in a panel. All examples I found are about QLPreviewPanel. :(
Thanks in advance for your help.
Starting in OS X 10.7, there is a public API for this: QLPreviewView.h, which is part of the QuickLookUI framework (which is part of the Quartz framework). There doesn't seem to be documentation on it, but the header file provides some basic info.
It seems like Apple really wants you to use the QLPreviewPanel; the only possibility I see is "scraping" the preview, by setting the panel to layer-backed and getting the contents of the correct sublayer. Something like this (although I haven't gotten it to work):
[[[QLPreviewPanel sharedPreviewPanel] contentView] setWantsLayer:YES];
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
NSLog(#"layer: %#", [[[QLPreviewPanel sharedPreviewPanel] contentView] layer]);
// I believe there are two sublayers
id QLcontents = [[[[[[QLPreviewPanel sharedPreviewPanel] contentView] layer] sublayers] objectAtIndex:0] contents];
NSLog(#"contents: %#", QLcontents);
[myView layer].contents = QLcontents;
[myView layer] setNeedsDisplay];
Not a final solution by any means (this doesn't work as is) but maybe it'll point you in a useful direction.
UPDATE: Just stumbled across a category on NSImage, written by a fellow named Matt Gemmell, which uses QLThumbnailImageCreate. Look for "NSImage+QuickLook" on his source code page. He seems to imply that the QuickLook Panel actually uses QLThumbnailImageCreate. I think that function may be the best way to go. The category might make your life a little easier.
I've made some headway with this. The sublayers' contents were nil. But the old-fashioned subviews work:
[[[QLPreviewPanel sharedPreviewPanel] contentView] setWantsLayer:YES];
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
NSArray* subviews = [[[QLPreviewPanel sharedPreviewPanel] contentView] subviews] ;
for (id subview in subviews) {
// The first view is the one we want, which is an unsubclassed NSView.
// The second is a QLPreviewTitleBarView. However, instead of relying
// on that order, we check for the class.
if ([subview isMemberOfClass:[NSView class]]) {
NSLog(#"frame of subview: %#", NSStringFromRect([subview frame])) ;
// The following statement will *remove* the desired subview from
// the QLPreviewPane and place it into myWindow instead.
[[myWindow contentView] addSubview:subview];
break ;
}
}
It even seems to update myWindow when I send -reloadData to the QLPreviewPane. I'm feeling slimy. Not sure what to do next, though. One problem is to deal with the arbitrary size of the subview. One of the reasons why I don't like QLPreviewPane is that there is no control over the window size; it gets a view from the generator at some arbitrary size and splats it on the screen. I guess I could put it into a scroll view. Another issue is how to deal with the QLPreviewPanel which is still on the screen. Maybe set its frame origin to off-screen But I need to go work on another task right now. Any further ideas would be appreciated.
Later. I think this approach is going to be to problematic. First, I tried to get rid of the QLPreviewPane window by sending it a setFrameOrigin:NSMakePoint(10000, 10000). Result: [QL] Assertion failure ([event window] == window) - Wrong window in event. Then I tried to omit the call to -makeKeyAndOrderFront: and instead skip up to -reloadData. Result: [QL] QLError(): -[QLPreviewPanel reloadData] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
The second error is understandable, however it indicates that the QLPreviewPanel will not try to find its data source, as it does, until it is made key or ordered front. The first assertion, however, indicates that, besides not providing a proper API to get the preview data directly, maybe Apple has laid some traps for the casual hacker like me.
If I come back to this, next time I'll try the more serious hack proposed by Ken Apeslagh.

How to activate a custom screensaver preview in Cocoa/Obj-C?

I have created a fairly simple screensaver that runs on Mac OS 10.6.5 without issue.
The configuration screen has accumulated quite a few different options and I'm trying to implement my own preview on the configureSheet window so the user (just me, currently) can immediately see the effect of a change without having to OK and Test each change.
I've added an NSView to the configureSheet and set the custom class in Interface Builder to my ScreenSaverView subclass. I know that drawRect: is firing, because I can remove the condition for clearing the view to black, and my custom preview no longer appears with the black background.
Here is that function (based on several fine tutorials on the Internet):
- (void)drawRect:(NSRect)rect
{
if ( shouldDrawBackground )
{
[super drawRect:rect];
shouldDrawBackground = NO;
}
if (pausing == NO)
[spiroForm drawForm];
}
The spiroForm class simply draws itself into the ScreenSaverView frame using NSBezierPath and, as mentioned, is not problematical for the actual screensaver or the built-in System Preferences preview. The custom preview (configureView) frame is passed into the init method for, um, itself (since its custom class is my ScreenSaverView subclass.) The -initWithFrame method is called in configureSheet before returning the configureSheet object to the OS:
[configureView initWithFrame:[configureView bounds] isPreview:YES];
Maybe I don't have to do that? It was just something I tried to see if it was required for drawing.
I eventually added a delegate to the configureSheet to try triggering the startAnimation and stopAnimation functions of my preview via windowWillBeginSheet and windowWillEndSheet notifications, but those don't appear to be getting called for some reason. The delegate is declared as NSObject <NSWindowDelegate> and I set the delegate in the configureSheet method before returning the configureSheet object.
I've been working on this for days, but haven't been able to find anything about how the OS manages the ScreenSaverView objects (which I think is what I'm trying to emulate by running my own copy.)
Does anybody have any suggestions on how to manage this or if Apple documents it somewhere that I haven't found? This isn't really required for the screensaver to work, I just think it would be fun (I also looked for a way to use the OS preview, but it's blocked while the configureSheet is activated.)
OK, there are a couple of 'duh' moments involved with the solution:
First of all, I was setting the delegate for the sheet notifications to the sheet itself. The window that the sheet belongs to gets the notifications.
Secondly, that very window that the sheet belongs to is owned by System Preferences, I don't see any way to set my delegate class as a delegate to that window, so the whole delegate thing doesn't appear to be a viable solution.
I ended up subclassing NSWindow for the configureSheet so that I could start and stop animation on my preview by over-riding the makeKeyWindow and close methods.
- (void) makeKeyWindow
{
if (myPreview != nil)
if ( ! [myPreview isAnimating])
{
[myPreview startAnimation];
}
[super makeKeyWindow];
}
I also had to add an IBOutlet for my preview object itself and connect it in Interface Builder.
Still working out a couple of issues, but now when I click on my screensaver Options button, my configureSheet drops down and displays its own preview while you set options. Sheesh. The hoops I jump through for these little niceties. Anyway, I like it. Onward and upward.