How to stop the animation on a determinate NSProgressIndicator? - cocoa

NSProgressIndicator allows stopAnimation: when isIndeterminate == YES, but how does one stop the animation for determinate progress bars?
For context, the progress bar I am trying to do this with is a child of an NSView, which itself is the view property of an NSMenuItem. Sending ridiculously high numbers to setAnimationDelay: does what I want, but only temporarily -- when the parent menu is closed and re-opened, the progress bar is animated again.
(Possibly unnecessary disclaimer: I swear this is a legit use case; I have to be able to visually (i.e.: without using text) display the progress of very-long-running tasks which may pause and re-start as needed by the backend. Answers which boil down to "UI design: ur doin it rong" will be not be accepted unless accompanied by a brilliant alternative suggestion. ;) )

Subclass NSProgressIndicator like this, it also works with Lion:
#interface UnanimatedProgressIndicator : NSProgressIndicator {
#private
BOOL isAnimating;
}
#end
#interface NSProgressIndicator (HeartBeat)
- (void) heartBeat:(id)sender; // Apple internal method for the animation
#end
#implementation UnanimatedProgressIndicator
- (void) startAnimation:(id)sender
{
isAnimating = YES;
[super startAnimation:sender];
}
- (void) stopAnimation:(id)sender
{
isAnimating = NO;
[super stopAnimation:sender];
}
- (void) heartBeat:(id)sender
{
if (isAnimating)
[super heartBeat:sender];
}
#end

Solved the problem using the same approach as described previously, with one small change.
When the menu's delegate receives menuNeedsUpdate:, I send setAnimationDelay: (with the sufficiently huge number as the arg) to each progress bar. This is working reliably now, so I'm happy enough with it.

Related

NSTrackingArea not fully working when not key window

A bit of context first. Essentially I have a window that is covering the desktop. On it I have a few WebKit WebView views which allow user interaction. By deafult, as one would expect, when another application is active it does not receive these events (such as hovering, mouse entered, and clicking). I can make it work by clicking my window first, then moving the mouse, but this is not good for usability. I've also managed to make it activate the window when the cursor enters, but it's far from ideal and rather hacky.
So instead I'm trying to use a tracking area. At the moment on the WebViews superview I have this tracking area:
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:[self visibleRect]
options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways
owner:self
userInfo:nil];
This works as I want it to, I'm receiving the all the mouse events. However, the WebViews don't seem to be responding as intended. JavaScript mouse move events only fire when I hold and drag, not just hover and drag.
I've tried using hitTest to get the correct view, but nothing seems to work. Here's an example method, I'm using the isHandlingMouse boolean because without it an infinite loop seemed to be created for some reason:
- (NSView *)handleTrackedMouseEvent: (NSEvent *)theEvent{
if(isHandlingMouse)
return nil;
isHandlingMouse = true;
NSView *hit = [self hitTest: theEvent.locationInWindow];
if (hit && hit != self) {
return hit;
}
return nil;
}
- (void)mouseMoved:(NSEvent *)theEvent{
NSView *hit = [self handleTrackedMouseEvent: theEvent];
if (hit){
[hit mouseMoved: theEvent];
}
isHandlingMouse = false;
}
The 'hit' view, is a WebHTMLView, which appears to be a private class. Everything seems like it should be working,but perhaps there's something I'm doing that's breaking it, or I'm sending the event to the WebHTMLView incorrectly.
Post a sample Xcode project to make it easier for people to test solutions to this problem.
I was doing something similar and it took a lot of trial and error to find a solution. You will likely need to subclass NSWindow and add - (BOOL)canBecomeKeyWindow { return YES; }, then whenever you detect the mouse is over your window, you might call [window orderFrontRegardless] just so it can properly capture the mouse events.

Understanding NSPopover with ARC

I'm somewhat puzzled by object lifetimes under ARC. Here’s a scenario which I think is probably common.
1) In response to some event, was load an NSViewController from a nib.
- (IBAction) doIt: (id) sender
{
InfoController *editor=[[InfoController alloc]initWithNibName:#"InfoController" bundle:nil];
[editor show: .... ]
}
2) The InfoController then displays an NSPopover.
3) Sometime later, the user clicks outside the NSPopover. The popover closes itself.
But when does the InfoController get released? For that matter, what's keeping it alive after doIt returns? In my implementation, InfoController is a data source and delegate for controls in its NSPopover, but in general data sources and delegates aren't retained, right?
I realize your question is a bit old now, but I came across it while researching a retain cycle with my NSViewController and NSPopover:
The NSPopover contentViewController property is retaining your NSViewController. That is why you can show the popover like you (and I) do as a response to an action, without another object retaining it. What I found though, is that to properly release the NSViewController under ARC, the contentViewController should be set to nil when the popover is closed. This is in my NSViewController subclass:
- (void)popoverDidClose:(NSNotification *)notification
{
self.popover.contentViewController = nil;
}

scrollRectToVisible UITextField doesn't scroll with Autolayout

[self.scrollView scrollRectToVisible:textField.bounds animated:YES];
I can't seem to get my UIScrollView to scroll at all so that it doesn't obscure my UITextField. I thought that scrollRectToVisible would be my savior but it looks like a no go. Maybe I'm missing something like translating the coordinates of my textField to my scrollView. Either way check out my sample project.
https://github.com/stevemoser/Programming-iOS-Book-Examples/tree/master/ch20p573scrollViewAutoLayout2
Oh, and this project might be missing the delegate connection but I checked that and it still doesn't scroll.
I've seen other questions similar to this but none that mention Autolayout.
I was having issues with scrollRectToVisible:: as well after converting to Auto Layout. I just changed it to a direct call to setContentOffset:: and it started working again.
I had the same problem, I wanted to scroll an autolayouted UITextEdit into view without making it the first responder.
For me the issue was that the bounds of the UITextField were set later on during the auto layout pass, so if you do it immediately after setting up the layout the bounds are not valid yet.
To workaround I did create a descendant of UITextField, did overwrite setBounds: and added a 0 timer to scroll into view "later on" (You can't scroll in that moment because the auto layout pass of the system might no be finished at that point)
#interface MyTextField: UITextField
{
bool _scrollIntoView;
}
..
#end
#implementation MyTextField
-(void)setBounds:(CGRect)bounds
{
bool empty=CGRectIsEmpty(self.bounds);
bool isFirstResponder=self.isFirstResponder;
[super setBounds:bounds];
if (empty && !isFirstResponder && _scrollIntoView)
[self performSelector:#selector(scrollIntoViewLater) withObject:nil afterDelay:0];
else if (empty && isFirstResponder)
[self performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
}
-(void)scrollIntoViewLater
{
CGRect r=[scrollView convertRect:self.bounds fromView:self];
[scrollView scrollRectToVisible:r animated:TRUE];
}
#end
If the field should be additionally editable with the on screen keyboard, simply call becomeFirstResponder later on: it scrolls automagically into view above the keyboard using the private scrollTextFieldToVisible API which in turn calls scrollRectToVisible:animated: of the scrollview.
Your sample link is broken btw...

Activity Indicator above Button prevents Click Recognition

I have an UIButton "bn" and an UIActivityIndicator "ai" which is above the button (ai.center = bn.center).
As long as ai is visible and animating, I can't press the Button underneath ai's frame but out of the range I can.
Do I have to add GestureRecognition to ai or is there a smarter way to click on the "ai".
Kind regards. $h#rky
Can you simply set ai.userInteractionEnabled = NO;? I'm surprised it is enabled anyway, on an activity indicator - is this a standard component or have you made a subclass?
As an aside, it is usually poor UI design to have an interactive element that is covered by another view, particularly one which is used to indicate that something is "busy", but in your example of a clickable thumbnail image, it seems to make sense.
You need to override the UIView method
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
in the parent view. If the hit is within the button the you return the button, else you return nil. This is part of the the discussion on this method:
This method traverses the view hierarchy by sending the
pointInside:withEvent: message to each subview to determine which
subview should receive a touch event. If pointInside:withEvent:
returns YES, then the subview’s hierarchy is traversed; otherwise, its
branch of the view hierarchy is ignored. You rarely need to call this
method yourself, but you might override it to hide touch events from
subviews.
EDIT:
Say you have a UIView subclass which contains bn and ai, you can implement the method like this
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(bn.frame, point)) {
return bn;
}
return nil;
}
that way your button will get the touch events (if they are within its frame) no matter if something is on top of it or not. You do not need to do anything else.
Use the following stuff:
First, create subclass for UIActivityIndicator with the following method override:
#interface MyActivityIndicator: UIActivityIndicator
#end
#implementation MyActivityIndicator
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return NO;
}
#end
After that, use MyActivityIndicator everywhere in your project instead of UIActivityIndicator (in NIB file or in your code, depends where do you create it)

-[NSResponder swipeWithEvent:] not called

I am writing an application targeting OS X Lion and Snow Leopard. I have a view that I want to have respond to swipe events. My understanding is that three-finger swipes will call -[NSResponder swipeWithEvent:] if that method is implemented in my custom view. I have already looked at this question and corresponding answers, and tried the following modified stub implementation of Oscar Del Ben's code:
#implementation TestView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
}
- (void)swipeWithEvent:(NSEvent *)event {
NSLog(#"Swipe event detected!");
}
- (void)beginGestureWithEvent:(NSEvent *)event {
NSLog(#"Gesture detected!");
}
- (void)endGestureWithEvent:(NSEvent *)event {
NSLog(#"Gesture end detected!");
}
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouseDown event detected!");
}
#end
This compiles and runs fine, and the view renders as expected. The mouseDown: event is properly registered. However, none of the other events are triggered. Neither the begin/endGestureWithEvent: methods, nor the swipeWithEvent: method. Which makes me wonder: do I need to set a project/application setting somewhere to properly receive and/or interpret gestures? Thanks in advance for the help.
To receive swipeWithEvent: messages, you have to ensure that the 3 finger swipe gesture is not mapped to anything that might cause a conflict. Go to System preferences -> Trackpad -> More Gestures, and set these preferences to one of the following:
Swipe between pages:
Swipe with two or three fingers, or
Swipe with three fingers
Swipe between full-screen apps:
Swipe left or right with four fingers
Specifically, the swipe between full-screen apps should not be set to three fingers, otherwise you will not get swipeWithEvent: messages.
Together, these two preference settings cause swipeWithEvent: messages to be sent to the first responder.
Of course, you still have to implement the actual swipe logic. And if you want to perform a fluid scroll-swipe à la iOS, then you will need to do a little more work. There is an example of how to do this in the Lion App Kit release notes under the section "Fluid Swipe Tracking."
See http://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html
try with [self setAcceptsTouchEvents:YES]; where it says // Initialization code here.
Not sure if it's the problem, but only the key window receives Gestures. Is your window key?
Is your view accepting first responders?
- (BOOL) acceptsFirstResponder
{
return YES;
}

Resources