I'm having trouble with the animation of some subclassed indeterminate NSProgressIndicators. They start and stop animating without any issues. However, if I minimise the window while animating, stopAnimation: / StopAnimation(NSObject sender) is called, which makes sense to save resources if the window is not visible. I assume this is invoked from the cocoa framework itself looking at the stacktrace.
The problem then arises when the window is restored, the animation is not resumed.
I've seen you can use the NSCoding Protocol and can override encodeWithEncoder: / EncodeTo(NSCoder encoder) to save some state, and then use that saved state in initWithCoder: / AppProgressIndicatorBar(NSCoder coder) to resume. But the problem here was that my encodeWithEncoder: / EncodeTo(NSCoder encoder) was never called.
Looking at this SO question and answer, it should be handled automatically if the object needs to be serialized. So I'm not sure why it's not being called.
That same answer says you can do it explicitly with NSKeyedArchiver, but then I would need to listen with NSWindowDelegate to know when the window is minimizing / restoring. In which case, I could just use this, and not use the NSCoding Protocol...
This just feels dirty, and I would imagine this is a very common scenario. So how should / do you resume animation? I'm new to cocoa, coming from a mostly .NET background, and I think this problem is a symptom of my limited cocoa knowledge.
I'm using Xamarin Mac, and have tried to give the Objective-C and C# method signatures. I'll be happy for a solution in either, I'll be able to (hopefully!) convert it to the C# equivalent.
For completeness, here is my current Xamarin Mac subclass using the NSCoder Protocol where EncodeTo is not being called. I'm running OS X 10.11.3 and Xamarin Studio 5.10.2.
[Register("AppProgressIndicatorBar")]
public class AppProgressIndicatorBar : NSProgressIndicator, INSCoding
{
...
public AppProgressIndicatorBar(NSCoder coder) : base(coder)
{
...
}
...
public override void EncodeTo(NSCoder encoder)
{
base.EncodeTo(encoder);
...
}
...
}
You should be able to use the NSWindowWillMiniaturizeNotification, NSWindowDidMiniaturizeNotification and NSWindowDidDeminiaturizeNotification notifications or the windowWillMiniaturize:, windowDidMiniaturize: and windowDidDeminiaturize: Window delegate methods to track the state of your window and restore the state of your progress bar when the window deminiaturises (is that really a word?).
HTH
Related
I’m attempting to update a Cocoa app for the first time in perhaps 8 years. It seems to build OK, and mostly runs fine too. I can edit text, but the insertion point doesn’t blink.
I use an NSTextView subclass to display text. I’m rusty at Cocoa, so I am guessing something changed with app napping or the like. Is there anything I need to do to make sure insertion points blink? More likely, what did I break to prevent periodic updating in the modern battery-friendly way?
I have the same problem with NSTextView and after multiples tests, it seems that bug happens with every basic textView on macOS 10.14 for me.
I've searched for multiple fixes on the web with no result. The best workaround I've found is to set the textView delegate to itself. Then, on didProcessEditing, call updateInsertionPointStateAndRestartTimer on the main thread as the following :
class CustomTextView: NSTextView {
override func awakeFromNib() {
super.awakeFromNib()
// 1. Set the textStorage delegate
textStorage?.delegate = self
}
}
extension CustomTextView: NSTextStorageDelegate {
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
// 2. On the main thread, update the insertion point
DispatchQueue.main.async {
self.updateInsertionPointStateAndRestartTimer(true)
}
}
OK, just in case this bites someone else: I had switched to dark mode. Apparently the default insertion point is white, so I couldn’t see it flash against the white background. So I need to properly support dark mode.
Xcode 7.0.1 (7A1001)
iOS 9.0
I made a custom #IBDesignable UIView, only implementing the drawRect function. Like so:
#IBDesignable
class CustomView: UIView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// Lots more drawing code after this
// ...
}
}
While I was writing this drawRect, I had a storyboard with several different instances of this rendering on a view controller. When I made a change in drawRect and saved, these changes would be reflected in the storyboard in close to real time. There was even an IBInspectable String which allowed me to change an enum type and draw differently depending on that. The views could be resized in the storyboard, and the new width and height would be accounted for in my drawing code, and the drawing would be scaled and translated, exactly as I intended.
This was all working fine, and I had not made any changes to that UIView subclass, but I did delete my test views in the storyboard while building out newer views which would actually end up using this custom view.
After doing a lot of dancing around with constraints, I was ready to place my custom view where it belonged. But it does not work. While the identity inspector acknowledges my custom class as an IBDesignable, it hangs on "Updating" status.
To prove I wasn't going crazy, I made entirely new #IBDesignable UIView subclasses which implemented simple drawRects or even completely empty drawRects. These are now having the same issue and are not rendering on the storyboard.
Note that I can run this in the simulator and the drawing appears as I expect at runtime. So what happened to the storyboard's ability to give that nice feedback I expect from an IBDesignable?
Things I have tried:
Clean / clean build folder / clearing XCode Derived Data directory
Restarting XCode
Deleting / commenting out drawRect guts
Making the most simple #IBDesignables with HelloWorld-like tutorials
Using existing known working #IBDesignables
Creating fresh UIViewController in storyboard to place custom view
Toggled: Editor > Automatically Refresh Views
Editor > Refresh All Views
Editor > Debug Selected Views
All these attempts continue to show "Updating" in Custom Class section of the identity inspector.
Clearly something beyond my implementation is causing a problem here. Anybody else having this issue? Can anybody point me in the right direction? Not sure what else to try.
In the Identity inspector where it says "Module" add your AppName
I'm working on a screenshot Mac app. I'm trying to rebuilt what happens when you press Cmd-Ctrl-Shift-4: the cross hair cursor and the selection rectangle for the screenshot.
I'm using a custom borderless NSWindow on top of all other windows. I disabled the cursor to draw my own along with the selection rectangle.
My problem is that as soon as I click & drag to capture a screenshot, my app gets activated (because the click is intercepted by my shielding window).
Is there a way how I can receive the click in my custom view/window without having my app get activated?
I tried using an NSPanel with the NSNonactivatingPanelMask flag, but in this case, I have a problem with the cursor: I can't draw my own when another app is active, because I can't hide the cursor for other apps...
Actually, I have a new, better answer to this question involving more undocumented goodies. Here it is for future posterity:
There is an undocumented method on NSWindow that does exactly what you want:
#interface NSWindow (Private)
- (void )_setPreventsActivation:(bool)preventsActivation;
#end
[myWindow _setPreventsActivation:true];
This stops the window from activating both itself and its application when the user clicks on it.
The standard warnings about using undocumented APIs of course apply: Apple may change this at some point (although it's been around for many OS X versions so there's a good chance they won't) and using this may get your app rejected from the Mac app store.
For what it's worth, there's another way to make the cursor invisible globally other than creating a giant window. It involves some undocumented APIs if that's something you can use:
extern "C" {
typedef int CGSConnection;
void CGSSetConnectionProperty(int, int, const void *, const void *);
int CGSMainConnectionID();
}
void allowHidingCursorForBackgroundOnlyApp()
{
CFStringRef propertyString = CFStringCreateCopy(NULL, CFSTR("SetsCursorInBackground"));
CGSSetConnectionProperty(CGSMainConnectionID(), CGSMainConnectionID(), propertyString, kCFBooleanTrue);
CFRelease((CFTypeRef)propertyString);
}
Combine that with judicious use of event taps to capture and filter out mouse clicks, and you can create the same effect as the built-in screen shot feature.
I pray that there is a better way to do this now, but when I had to do something similar I ended up letting my window/view ignore all mouse input, then I used a CGEventTap (see Quarts Event Services documentation) to capture mouse events globally(without removing them from the event queue). I them mapped them manually to my window, created a custom copy NSEvent and manually dispatched it to my window.
The huge downside here (aside from complexity) is that I recall needing to run as root to be able to install the event tap. However, I think there is a way to get permission though universal access.
I'm completely unsure if dispatching a custom NSEvent directly to the window will have the same side effect of activating your application; especially since many things have changed since 10.6... I would suggest a simple test to see if this is feasible before pursuing it.
One more idea, you can override - (BOOL)_isNonactivatingPanel private method in NSWindow subclass:
#implementation MyWindow
- (BOOL)_isNonactivatingPanel
{
return YES;
}
#end
Voila, you got behaviour similar to NSPanel :)
I'm a newbie in Mac Dev. I come from iPhone dev.
My question relates to non-modal windows management. It's quite different from the iPhone and it's memory management model.
Say for example, i have a preference window, i can use something like that to show the window:
-(IBAction)showPreferenceController:(id)sender {
if (!preferenceController) {
preferenceController = [[PreferenceController alloc]init];
}
[preferenceController showWindow:preferenceController];
}
But with this code, the window will stay in memory during app life because the window is never released.
To avoid that, I could also use the method described here :
stackoverflow.com/questions/1391260/who-owns-an-nswindowcontroller-in-standard-practice
Create in PreferenceController a + (id) sharedInstance and release the window using (void)windowWillClose:(NSNotification *)notification
I see many cocoa code samples where non-modal windows are never released.
For example here : http://www.mattballdesign.com/blog/2008/10/01/building-a-preferences-window/ : The preference panel and all the subviews are created in awakeFromNib and so will live in memory during all app life.
If you take for example Xcode app, there are a lot of non-modal windows :
- Global Find window (CMD+MAJ+F)
- App Info Panel
- Help Window
- ...
I suppose that these windows are released when they are closed to keep memory as low as possible.
I would like some advices to know the best way to manage non-modal windows in a cocoa app.
Keep in memory? Releasing asap?
I know a mac has a lot of memory compared to an iPhone but I also think it's not good to keep in memory objects we are not using.
Thanks.
Edited with Rob post :
I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.
I don't how to do that without a singleton (+sharedController).
I explain what I mean with this example:
In the app Controller :
#interface AppController : NSObject <NSApplicationDelegate> {
Implementation :
-(IBAction)showPreferenceController:(id)sender {
if (!preferenceController) {
preferenceController = [[PreferenceController alloc]init];
}
[preferenceController showWindow:preferenceController];
}
In the preferences controller :
#interface PreferenceController : NSWindowController <NSWindowDelegate>
Implementation :
- (void)windowWillClose:(NSNotification *)notification {
[self autorelease];self=nil;
}
It will crash when i close and reopen after the window : preferenceController is released but not equal to nil. So I need to set preferenceController to nil when the window is closed.
There is no problem to do that with a singleton.
Without singleton, I should set appController as the delegate of Preference Window to be able to set preferenceController to nil when the window is closed. But i don't like that way.
Edited with Preston comments
I didn't say it but I want only one instance of my non-modal window even if we call -(IBAction)showPreferenceController:(id)sender several times.
That's why I test if preferenceController is equal to nil or not in appController.
So, I need to set preferenceController to nil in appController if we close the window.
So the solution would be :
In AppController, listening NSWindowWillCloseNotification:
- (void)windowWillClose:(NSNotification *)notification {
if ([notification object] == [preferenceController window]) {
[preferenceController autorelease];
preferenceController = nil;
}
}
Is it correct? Is this the only one solution? because it seems a little bit complicated just to manage my non modal window...
You're thinking in all the right ways here. It's not ok to leak memory just because you have a lot of it. That said, it is common not to release windows just because they close on Mac. You should generally hold onto them if you think they're going to be opened again to avoid the cost of reloading them.
There are two schools of thought on NSWindow: owned and unowned. I am of the "owned" school of thought. I generally give every window an owner that retains it with an ivar and releases it when appropriate. Generally that is the delegate, sometimes it's the app controller. In -windowShouldClose:, I send -autorelease to the window and set my pointer to nil so I'll recreate the window later. This is similar to the technique you cite, though whether or not to use +sharedController for the Controller isn't related; you can do this whether you have a shared controller or not.
The unowned school of thought is to use NSWindow's -setReleasedWhenClosed: so that it automatically releases itself when it closes. I believe several of the windows you reference do it this way when they are provided by the system, since there might not be a delegate. This can be useful in certain kinds of panels, but I would be careful with it as a general pattern. It's better to have explicit memory management in almost all cases.
If you check "Release When Closed" for your window in Interface Builder or set it programmatically, it will release itself. You can also have the window's delegate autorelease the window in the windowShouldClose: delegate method.
If you want to release the window controller when the window closes, have your window controller listen for its window's NSWindowWillCloseNotification and do a [self autorelease] in that method. I'm not a fan of creating a singleton preferences controller always sticking around that the user will, in practice, rarely interact with.
I'm learning how to build programs with Cocoa. I'm using a sample Apple application that records video from a webcam. I'd like to start and stop the video by capturing the key press. I've tried to override the keydown event but I've read that it's not possible in an NSObject. How can I handle this kind of event?
The class of application extends a NSObject class.
This is the code:
- (void)keyDown:(NSEvent *)event {
NSLog(#"Hi there");
NSString *characters = [event characters];
if ([characters length]) {
switch ([characters characterAtIndex:0]) {
case NSUpArrowFunctionKey:
NSLog(#"Key UP");
break;
}
}
}
I've tried to override Keydown event but I've read that It's not possible in an NSObject.
Correct. Only a responder can respond to events.
How can I handle this kind of event?
Implement a responder. Subclassing NSWindow or NSWindowController will work. Make sure you make your actual window or window controller an instance of your subclass.
The Cocoa documentation explains further.
The class of application extends a NSObject class.
Why? Normally, the principal class of the application bundle is NSApplication or a subclass of that—and there aren't many good reasons to subclass NSApplication.
PS: What's a very good book to start learn MacOS Programming?
I didn't learn by the Hillegass book, myself (I stuck to Apple's docs), but it's a very popular recommendation and I have read it and can tell you it's good.
From the Cocoa Event-Handling Guide - The Responder Chain:
The responder chain is a linked series of responder objects to which an event or action message is applied. When a given responder object doesn’t handle a particular message, the object passes the message to its successor in the chain (that is, its next responder).
When you press a key the window receives the keyDown event. Then it dispatches the event to the first responder, that usually is the control with a blue bezel around its border (try to click on the address field in Safari or Firefox, when it's blue-bezeled then it has first-responder status).
If the first responder does not eat the keypress (the Safari address field does eat it when it displays a character) then it passes it down the responder chain to the next responder in the view hierarchy, then to the window and to the window controller as you can see in the Guide. (Take care that the action responder is another story.)
So you have to implement the keyDown: on a view of your window or in the window itself, if it has no views that eat events. The simplest way to test is to override the keyDown: method of an empty window
To put your hands into the inner workings you can even try overriding the sendEvent: method of a window. sendEvent: dispatches the events to the views of the window, and from there you can for example log all the events managed by the window.
Subclassing NSWindow or
NSWindowController will work.
Similarly, you can subclass NSView and override its event-handling methods.
What's a very good book to start learn
MacOS Programming?
Learn Objective-C on the Mac by Dalrymple is really straightforward, covers enough basics and moves fast enough to get you off the ground in a short bit. It touches on everything from Xcode and Interface Builder to OOP and Objective-C practices. Particularly helpful to beginners (IMHO) are the source file organization and Foundation kit chapters.
Best of luck!