Draw into a NSView via mouse event - objective-c

i am new to programming, objective-c (and stackoverflow). I'm learning and moving forward very slowly ;) but then I ran into a problem, which google couldn't solve. I have a single window and a NSview and then added a mouse event that should draw the coordinates into my view, but it doesn't. The funny thing is: it is drawn when the mouse moves over the window buttons of my apps window...
- (void)drawRect:(NSRect)dirtyRect {
NSPoint imagePos = NSMakePoint(0, 0);
NSImage *aImage = [NSImage imageNamed:#"mw_bg01.png"];
[aImage dissolveToPoint:imagePos fraction:1.0];
}
- (void)mouseDown:(NSEvent*)theEvent;{
mouseLoc = [NSEvent mouseLocation];
mousePosX = mouseLoc.x;mousePosY = mouseLoc.y;
NSString* mouseString = [NSString stringWithFormat:#"%d", mousePosX];
NSPoint textPoint = NSMakePoint(5, 5);
NSMutableDictionary *textAttrib = [[NSMutableDictionary alloc] init];
[textAttrib setObject:[NSFont fontWithName:#"Helvetica Light" size:10]
forKey:NSFontAttributeName];
[textAttrib setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
[mouseString drawAtPoint:textPoint withAttributes:textAttrib];
}
I don't know how to go on, any suggestions? Thanks!

You shouldn't do drawing in the -mouseDown: method. Rather, you must do all your drawing in -drawRect: (or methods that you call from -drawRect:). Try something like this:
#interface MyView ()
#property NSPoint lastMousePoint;
#end
#implementation MyView
- (void)drawLastMousePoint
{
NSString *mouseString = NSStringFromPoint(self.lastMousePoint);
NSPoint textPoint = NSMakePoint(5, 5);
NSMutableDictionary *textAttrib = [[NSMutableDictionary alloc] init];
[textAttrib setObject:[NSFont fontWithName:#"Helvetica Light" size:10]
forKey:NSFontAttributeName];
[textAttrib setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
[mouseString drawAtPoint:textPoint withAttributes:textAttrib];
}
- (void)drawRect:(NSRect)dirtyRect
{
NSPoint imagePos = NSMakePoint(0, 0);
NSImage *aImage = [NSImage imageNamed:#"mw_bg01.png"];
[aImage dissolveToPoint:imagePos fraction:1.0];
[self drawLastMousePoint];
}
- (void)mouseDown:(NSEvent*)theEvent;
{
self.lastMousePoint = [theEvent locationInWindow];
[self setNeedsDisplay:YES];
}
#end
When you get a mouse down event, you simply store the location of the mouse down. The drawing is done in -drawLastMousePoint which you call in your -drawRect: method. Since you know you need to redraw anytime the mouse is clicked, you call -setNeedsDisplay: to inform the view that it needs to be redrawn. Note that the redraw doesn't happen immediately, rather, it will happen the next time through the run loop. In other words, you're saying "hey, something changed, and I need to draw my view's contents again. Please call -drawRect: again as soon as possible!"
One other note: +[NSEvent mouseLocation] is really designed for getting the current mouse location outside the event stream. Typically, in a -mouseDown: method, you call -locationInWindow on the NSEvent passed as the argument to the method. If you need to convert to local/view coordinates, you should call [self convertPoint:[theEvent locationInWindow] fromView:nil];.

Related

How could the mere definition of a method in a parent view affect a CAAnimation in a subview?

I have a view that is drawing "marching ants", a dashed line that moves along an NSBezierPath
Looks like this:
CAAnimation* currentLayerAnimation = [self.marchingBlackLayer animationForKey:NEVER_TRANSLATE(#"linePhase")];
if (currentLayerAnimation == nil)
{
float lineWidth = 0.8 / self.previousZoomLevel;
float lineLengthFloat = 5.0 * lineWidth;
NSNumber* lineLength = [[NSNumber alloc] initWithFloat: lineLengthFloat];
self.marchingBlackLayer.lineWidth = lineWidth;
self.marchingBlackLayer.strokeColor = [[NSColor blackColor] CGColor];
self.marchingBlackLayer.fillColor = [[NSColor clearColor] CGColor];
self.marchingBlackLayer.lineDashPattern = #[lineLength, lineLength];
self.marchingWhiteLayer.lineWidth = lineWidth;
self.marchingWhiteLayer.strokeColor = [[NSColor whiteColor] CGColor];
self.marchingWhiteLayer.fillColor = [[NSColor clearColor] CGColor];
self.marchingWhiteLayer.lineDashPattern = #[lineLength, lineLength];
CABasicAnimation* blackMarchingAntsAnimation;
NSNumber* doubleLineLength = [[NSNumber alloc] initWithFloat: (lineLengthFloat * 2)];
blackMarchingAntsAnimation = [CABasicAnimation animationWithKeyPath:NEVER_TRANSLATE(#"lineDashPhase")];
[blackMarchingAntsAnimation setFromValue:#0.0f];
[blackMarchingAntsAnimation setToValue:doubleLineLength];
[blackMarchingAntsAnimation setDuration:0.5f];
[blackMarchingAntsAnimation setRepeatCount:HUGE_VALF];
CABasicAnimation* whiteMarchingAntsAnimation;
NSNumber* tripleLineLength = [[NSNumber alloc] initWithFloat: (lineLengthFloat * 3) ];
whiteMarchingAntsAnimation = [CABasicAnimation animationWithKeyPath:NEVER_TRANSLATE(#"lineDashPhase")];
[whiteMarchingAntsAnimation setFromValue:lineLength];
[whiteMarchingAntsAnimation setToValue:tripleLineLength];
[whiteMarchingAntsAnimation setDuration:0.5f];
[whiteMarchingAntsAnimation setRepeatCount:HUGE_VALF];
[self.marchingBlackLayer addAnimation:blackMarchingAntsAnimation forKey:NEVER_TRANSLATE(#"linePhase")];
[self.marchingWhiteLayer addAnimation:whiteMarchingAntsAnimation forKey:NEVER_TRANSLATE(#"linePhase")];
}
[self.layer addSublayer:self.marchingBlackLayer];
[self.layer addSublayer:self.marchingWhiteLayer];
Everything is dandy. However, the view that is drawing this path has two parent views.
This View's parent is an NSClipView, who's parent is an NSScrollView subclass.
And just by overriding scrollWheel: in the NSScrollView subclass like so:
- (void)scrollWheel:(NSEvent *)theEvent
{
[super scrollWheel:theEvent];
}
The ants animation disappears.
The scrollWheel: method never gets hit, and if I delete that method and run, the ants appear again.
I am completely baffled how this is possible, and am convinced it must have something to do with threading? Unfortunately drawing is my least knowledgable area of Cocoa, so it's difficult to figure out where to start debugging
EDIT:
Throwing in pictures so ya'll can maybe get a better idea of what I'm talking about
Here is before: (take my word that the dashed line is moving around)
Here is after defining the scrollWheel: override (and that's literally the only difference):
EDIT2: Setting a breakpoint on all exceptions doesn't get hit, and the console doesn't output any warnings or anything
EDIT3: Dispatching the animation creation and adding onto the main thread didn't fix it

Cocoa resize window to fit screen

I have 2 monitors, and I'd like to get my videoWindow to be placed and scaled to the size of the second monitor. I'd like to do this programmatically, as the second monitor resolution may change. I'm able to get the window placed in the bottom-left of the second monitor, but I'm not able to scale it to fit.
The warning on this line:
[self.videoWindow setFrame: screenRect];
Is: 'NSWindow' may not respond to 'setFrame'
// inside my .h file
#property (assign) IBOutlet NSWindow *videoWindow;
// inside my .m file
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
NSRect videoPreviewScreenRect;
NSArray *screenArray = [NSScreen screens];
//Using index of 1, to get secondary monitor
NSScreen *videoPreviewScreen = [screenArray objectAtIndex: 1];
NSRect screenRect = [videoPreviewScreen frame];
videoPreviewScreenRect = [videoPreviewScreen visibleFrame];
// Get and set the screen origin based on the second monitors origin
NSPoint videoScreenOrigin ;
videoScreenOrigin.x = videoPreviewScreenRect.origin.x;
videoScreenOrigin.y = videoPreviewScreenRect.origin.y;
[self.videoWindow setFrameOrigin: videoScreenOrigin];
// **** THIS LINE DOESN'T WORK ****
[self.videoWindow setFrame: screenRect];
[self.videoWindow setBackgroundColor: NSColor.blackColor];
[self.videoWindow display];
[self.videoWindow makeKeyAndOrderFront:nil];
}
I was able to figure out what the issue was.
[self.videoWindow setFrame: screenRect];
needed to be changed to this:
[[self videoWindow] setFrame:screenRect display:YES animate:NO];

Receive global TouchEvents with NSEvent

I just try to receive Touch Events globally in the Window and not only in a view.
In my code, you can see below, i will get the absolute position of the touch in the magic trackpad. As long as the cursor is inside the view (the red NSRect) it works fine, but how can i receive touches outside of this view.
I searched for solutions in many communities and the apple devcenter but found nothing.
I think the problem is this: NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil]; Isn't there a method in NSEvent that gets every touch?
Hope anybody can help me.
Here my Implementation:
#implementation MyView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setAcceptsTouchEvents:YES];
myColor = [NSColor colorWithDeviceRed:1.0 green:0 blue:0 alpha:0.5];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
NSRect bounds = [self bounds];
[myColor set];
[NSBezierPath fillRect:bounds];
}
- (void)touchesBeganWithEvent:(NSEvent *)ev {
NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseTouching inView:nil];
for (NSTouch *touch in touches) {
NSPoint fraction = touch.normalizedPosition;
NSSize whole = touch.deviceSize;
NSPoint wholeInches = {whole.width / 72.0, whole.height / 72.0};
NSPoint pos = wholeInches;
pos.x *= fraction.x;
pos.y *= fraction.y;
NSLog(#"%s: Finger is touching %g inches right and %g inches up "
#"from lower left corner of trackpad.", __func__, pos.x, pos.y);
}
}
Calling touchesMatchingPhase:inView: with nil for the last parameter (the way you are doing) will get all touches. The problem is that touchesBeganWithEvent: will simply not fire for a control that isn't in the touch area.
You can make the view the first responder, which will send all events to it first. See becomeFirstResponder and Responder object

Subclass of UIPageControl refresh only after uiscrollview move, not before

the problem I've met today is with my subclass of UIPageControl. When I initialize it, the frame (specifically the origin) and image of dots stays default, which is the problem, since I want it to change right after initialization. However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images.
What could be the problem?
Code:
CustomPageControl.m
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
activeImage = [UIImage imageNamed:#"doton.png"];
inactiveImage = [UIImage imageNamed:#"dotoff.png"];
return self;
}
- (void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView *dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
[dot setFrame:CGRectMake(i * 13.5, 1.5, 17, 17)];
}
}
- (void)setCurrentPage:(NSInteger)currentPage
{
[super setCurrentPage:currentPage];
[self updateDots];
}
#end
ChoosingView.m - init part
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 160, 300)];
[scrollView setBackgroundColor:[UIColor clearColor]];
[scrollView setDelaysContentTouches:NO];
[scrollView setCanCancelContentTouches:YES];
[scrollView setClipsToBounds:NO];
[scrollView setScrollEnabled:YES];
[scrollView setPagingEnabled:YES];
[scrollView setShowsHorizontalScrollIndicator:NO];
[scrollView setShowsVerticalScrollIndicator:NO];
pageControl = [[CustomPageControl alloc] initWithFrame:CGRectMake(200, 300, 80, 20)];
[pageControl setBackgroundColor:[UIColor clearColor]];
pageControl.numberOfPages = 6;
[pageControl setCurrentPage:0];
the last line is when I would expect the UIPageControl to refresh, however that does not happen.
Does this happen with the standard UIPageControl implementation?
Your problem states that your objects subViews (eg the UIImageViews) rects/size are not initialising to your desired size/position.
I implemented this code in my project with a nib rather than programmatically and I needed to call -(void)updateDots to set it as its initial condition was the standard dots..
I dont see how the UIScrollView has any bearing impact on this unless somehow its linked to your -(void)updateDots function (E.g. your setting the currentIndex of your custom page control). You state, "However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images."
Because they "jump to the right position with correct images" it means that your -(void)updateDots function must be getting called. I dont see any other explanation.
Also your iteration loop assumes that all the UIViews in your .subViews array are UIImageViews, although fairly safe to assume this, I would check to see if the UIView is a UIImageView with reflection.

Subclass of NSButton: call of action method crashes

In my application there is a subclass of NSWindowController. In - (void)awakeFromNib I create an instance of a NSView subclass to create a bottom placed button bar:
self.buttonBar = [[CNButtonBar alloc] initWithSize:NSMakeSize(tableViewRect.size.width-1, 25)];
This CNButtonBar thing has a method to add buttons to itself. I create two of it:
[buttonBar addButtonWithTitle:nil
image:[NSImage imageNamed:NSImageNameAddTemplate]
target:self
action:#selector(btnAccountAddAction:)
alignment:CNBarButtonAlignLeft];
[buttonBar addButtonWithTitle:nil
image:[NSImage imageNamed:NSImageNameRemoveTemplate]
target:self
action:#selector(btnAccountRemoveAction:)
alignment:CNBarButtonAlignLeft];
These two action methods are defined in the same instance of NSViewController where I send the two addButtonWithTitle:... messages.
The methods, used to set the action are defined as followed:
- (void)btnAccountAddAction:(id)sender
{
....
}
- (void)btnAccountRemoveAction:(id)sender
{
....
}
But, it doesn't work. Each time if I click one of these two buttons my application crashes throwing an exception like this one:
-[__NSArrayM btnAccountAddAction:]: unrecognized selector sent to instance 0x1001454e0
Examined this exception is absolutely correct. NSArray doesn't have such a method I want to call. But, from time to time the object that throws this exception is changing. Some times there is a __NSData object, some times a __NSDictionary and so on. It seems that the receiver is anything but my NSWindowController subclass. But the given instance ID 0x1001454e0 is the same my NSWindowController has.
It makes me want to tear my hair out! What's going wrong here...?
Thanks.
UPDATE
I have to say, that both, the CNButtonBar and CNButtonBarButton are completely drawn by code. The complete method of addButtonWithTitle:image:target:action:alignment: is here (remember, this happens in CNButtonBar, a subclass of NSView (should I use NSViewController instead of it?), CNButtonBarButton is a subclass of NSButton):
- (void)addButtonWithTitle:(NSString*)title image:(NSImage*)image target:(id)target action:(SEL)action alignment:(CNBarButtonAlign)align
{
if (self.buttons == nil)
self.buttons = [[NSMutableArray alloc] init];
CNButtonBarButton *button = [[CNButtonBarButton alloc] init];
[self.buttons addObject:button];
button.title = title;
button.image = image;
button.target = target;
button.action = action;
button.align = align;
NSRect buttonRect;
NSSize titleSize = NSMakeSize(0, 0);
if (button.title.length > 0) {
NSMutableParagraphStyle* textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
[textStyle setAlignment: NSCenterTextAlignment];
NSColor *textColor = [[NSColor blackColor] colorWithAlphaComponent:0.9];
NSShadow* textShadow = [[NSShadow alloc] init];
[textShadow setShadowColor: [NSColor whiteColor]];
[textShadow setShadowOffset: NSMakeSize(0, -1)];
[textShadow setShadowBlurRadius: 0];
button.titleTextAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Helvetica Neue" size:11.0], NSFontAttributeName,
textShadow, NSShadowAttributeName,
textColor, NSForegroundColorAttributeName,
textStyle, NSParagraphStyleAttributeName,
nil];
titleSize = [title sizeWithAttributes:button.titleTextAttributes];
buttonRect = NSMakeRect(0, 0, roundf(titleSize.width) + kTextInset * 2, NSHeight(self.frame) - 1);
}
if (button.image != nil) {
NSSize imageSize = [image size];
if (button.title.length == 0) {
buttonRect = NSMakeRect(0, 0, imageSize.width + kImageInset * 2, NSHeight(self.frame) - 1);
} else {
buttonRect = NSMakeRect(0, 0,
(imageSize.width + kImageInset * 2) + (roundf(titleSize.width) + kTextInset),
NSHeight(self.frame) - 1);
}
}
switch (self.align) {
case CNButtonBarAlignNormal: {
switch (button.align) {
case CNButtonBarButtonAlignLeft: {
button.frame = NSMakeRect(offsetLeft, 0, NSWidth(buttonRect), NSHeight(buttonRect));
offsetLeft += 1 * [self.buttons indexOfObject:button] + NSWidth(button.frame);
break;
}
case CNButtonBarButtonAlignRight: {
button.frame = NSMakeRect(offsetRight - NSWidth(buttonRect), 0, NSWidth(buttonRect), NSHeight(buttonRect));
offsetRight -= 1 * [self.buttons indexOfObject:button] + NSWidth(button.frame);
break;
}
}
break;
}
case CNButtonBarAlignCentered: {
break;
}
}
[self addSubview:button];
}
UPDATE 2
The problem is solved. After some debugging I found out that it must be a problem of ARCs auto retain/release stuff. And I'm right. But the problem was my NSViewController subclass. It was leaking because of making an instance as a local variable (without a strong pointer). Thanks to Sean D., that pointed me to the right way. (-;
Looks like one of two things is happening. The first possibility is that your CNButtonBar object is getting released while the buttons are still around. If the buttons are still there, they'll try to send those selectors to whatever happens to occupy the memory the CNButtonBar used to be in. I'd say make sure it isn't autoreleasing itself anywhere, and that you're not accidentally autoreleasing it in the window controller's -awakeFromNib method. (If you're anything like me, that's the most likely culprit.)
The second possibility is that your -addButtonWithTitle:image:target:action:alignment: method is setting the buttons' actions but not their targets. Make sure your implementation of that method calls -setTarget: as well as -setAction: on the button.