Draggable NSView - objective-c

How can I programmatically make an NSView so that the user can move its position with the mouse? What properties do i need to assign to the view? thanks!
newView = [helpWindow contentView];
[contentView addSubview:newView];
//add properties of newView to be able to respond to touch and can be draggable

Unfortunately, there's no simple way like setMoveByWindowBackground: that you can do with a window. You have to override mouseDown:, mouseDragged:, and mouseUp: and use setFrameOrigin: based on the position of the mouse pointer. In order not have the view jump when you first click inside it, you also need to account for the offset between the view's origin and where the moue pointer is in the view when you first click. Here is an example that I made in a project to move "tiles" around in a parent view (this was for a computer version of the game "Upwords", which is like 3d scrabble).
-(void)mouseDown:(NSEvent *) theEvent{
self.mouseLoc = [theEvent locationInWindow];
self.movingTile = [self hitTest:self.mouseLoc]; //returns the object clicked on
int tagID = self.movingTile.tag;
if (tagID > 0 && tagID < 8) {
[self.viewsList exchangeObjectAtIndex:[self.viewsList indexOfObject:self.movingTile] withObjectAtIndex: 20]; // 20 is the highest index in the array in this case
[self setSubviews:self.viewsList]; //Reorder's the subviews so the picked up tile always appears on top
self.hit = 1;
NSPoint cLoc = [self.movingTile convertPoint:self.mouseLoc fromView:nil];
NSPoint loc = NSMakePoint(self.mouseLoc.x - cLoc.x, self.mouseLoc.y - cLoc.y);
[self.movingTile setFrameOrigin:loc];
self.kX = cLoc.x; //this is the x offset between where the mouse was clicked and "movingTile's" x origin
self.kY = cLoc.y; //this is the y offset between where the mouse was clicked and "movingTile's" y origin
}
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (self.hit == 1) {
self.mouseLoc = [theEvent locationInWindow];
NSPoint newLoc = NSMakePoint(self.mouseLoc.x - self.kX, self.mouseLoc.y - self.kY);
[self.movingTile setFrameOrigin:newLoc];
}
}
This example points out one more possible complication. As you move a view around, it may appear to move underneath other views, so I take care of that my making the moving view the top most view of the parent view's subviews (viewsList is the array gotten from self.subviews)

Related

Part of UIButton stops responding to touch after having its height increased

I have a view, embedded into a container that it itself inside a UIScrollView.
This view contains a text aligned to its edges as well as a button, again aligned to edges.
When launching the controller view, inside viewDidLayoutSubviews, I get the actual real height of the screen, then I call the view to be resized this way :
- (void)viewDidLayoutSubviews {
[self log:#"ViewDidLayoutSubviews"];
//::.. change scroll height according to main view ..::
float mainHeight = _subMainView.frame.size.height;
float scrollHeight = mainHeight - _scrollView.frame.origin.y;
_scrollView.frame = CGRectMake(_scrollView.frame.origin.x, _scrollView.frame.origin.y, _scrollView.frame.size.width, scrollHeight);
//::.. find buttons in child ..::
UIViewController *child = [self.childViewControllers lastObject];
//::.. Get only block views ::..
NSArray *blockViews = [myTools getAllSubviewsFromView:child.view ofClass:[UIView class] byTagFrom:1000 toTag:1010];
[self resizeViews:blockViews intoFrame:_scrollView.frame withVerticalPadding:16.0f andTopMargin:(31.0f+16.0f) andBottomMargin:0.0f andLeftMargin:16.0f andRightMargin:16.0f];
NSArray *buttons = [myTools getAllSubviewsFromView:child.view ofClass:[UIButton class]];
[self resizeViews:buttons intoFrame:_scrollView.frame withVerticalPadding:16.0f andTopMargin:(31.0f+16.0f) andBottomMargin:0.0f andLeftMargin:16.0f andRightMargin:16.0f];
for (UIButton *button in buttons) {
[button invalidateIntrinsicContentSize];
}
}
And here is the resize code in question :
+ (void)resizeViews:(NSArray *)views intoFrame:(CGRect)parentFrame withVerticalPadding:(float)verticalPadding andTopMargin:(float)topMargin andBottomMargin:(float)bottomMargin andLeftMargin:(float)leftMargin andRightMargin:(float)rightMargin {
float blockHeight = ((parentFrame.size.height - topMargin - bottomMargin) / views.count);
blockHeight -= verticalPadding;
float origin = topMargin;
for (UIView *aView in views) {
[aView layoutIfNeeded];
for (NSLayoutConstraint *constraint in aView.constraints) {
if (constraint.firstAttribute == NSLayoutAttributeHeight) {
constraint.constant = blockHeight;
break;
}
}
origin += blockHeight + verticalPadding;
[aView setNeedsUpdateConstraints];
[aView layoutIfNeeded];
}
}
The visual result is good because both views and there content (the buttons) are correctly resized (the button is aligned centered vertically and horizontally so it is easy to show the good alignment).
However, the first button has is whole area touchable where the second one, under it, can be touched only on its upper height, before its middle...
Can't understand how it is possible that a button, who's size is good, become untouchable on its whole part ?
Any help ? :)
Thanks a lot.

Moved annotation in PDFView but it moves to the opposite direction

I have written the code to move the annotation while dragging the mouse, however the result shows me the annotation moved to the opposite direction of my mouse move trace. It moves with center at the mouse first click point, centrosymmetric opposite... I draw a Rect for the annotaion's bound, but the Rect position is correct...
There is two images show you what happened:
-Before I drag:
After I moved:
The annotation goes away ....
Below is my code:
- (void) mouseDragged: (NSEvent *) theEvent
{
// Move annotation.
// Hit test, is mouse still within page bounds?
if (NSPointInRect([self convertPoint: mouseLoc toPage: activePage],
[[_activeAnnotation page] boundsForBox: [self displayBox]]))
{
// Calculate new bounds for annotation.
newBounds = currentBounds;
newBounds.origin.x = roundf(endPt.x - _clickDelta.x);
newBounds.origin.y = roundf(endPt.y - _clickDelta.y);
}
else
{
// Snap back to initial location.
newBounds = _wasBounds;
}
// Change annotation's location.
[_activeAnnotation setBounds: newBounds];
// Call our method to handle updating annotation geometry.
[self annotationChanged];
// Force redraw.
dirtyRect = NSUnionRect(currentBounds, newBounds);
dirtyRect.origin.x -= 10;
dirtyRect.origin.y -= 10;
dirtyRect.size.width += 20;
dirtyRect.size.height += 20;
[super setNeedsDisplay:YES];
[self setNeedsDisplayInRect:RectPlusScale([self convertRect: dirtyRect fromPage: [_activeAnnotation page]], [self scaleFactor])];
[super mouseDragged: theEvent];
The weird thing is, if the annotation is a image or text, the moved would be correct. It only happens with PDFAnnotationInk class....

Make NSWindow with transparent titlebar partly unmovable

I have a NSWindow with a splitted screen like in Reminders. Therefore I use this code:
self.window.titlebarAppearsTransparent = true
self.window.styleMask |= NSFullSizeContentViewWindowMask
This works perfectly. But inside the window I have a SplitView (like in the Reminders App) and a NSOutlineView at the right side. The OutlineView goes up to the top of the window corner.
The problem now is: Clicking and dragging at the top of the OutlineView makes the window movable. Any way, I can disable this but still keeping the moving ability at the left side of the app?
Ok, there are two things you need to do:
First you need to set your window to be not movable. To do so, subclass your Window and override isMovable and return no. Or you call setMovable: and set it to no.
After that, you have to manually reenable dragging by adding a view that has the exact size and position of the area you want to draggable. Alternatively you can set up a NSTrackingArea.
Either way, you need to override mouseDown: and insert some code to move the window.
My words in code:
Objective-C
[self.window setMovable:false];
// OR (in NSWindow subclass)
- (BOOL)isMovable {
return false;
}
//Mouse Down
- (void)mouseDown:(NSEvent *)theEvent {
_initialLocation = [theEvent locationInWindow];
NSPoint point;
while (1) {
theEvent = [[self window] nextEventMatchingMask: (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
point =[theEvent locationInWindow];
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self.window frame];
NSPoint newOrigin = windowFrame.origin;
// Get the mouse location in window coordinates.
NSPoint currentLocation = point;
// Update the origin with the difference between the new mouse location and the old mouse location.
newOrigin.x += (currentLocation.x - _initialLocation.x);
newOrigin.y += (currentLocation.y - _initialLocation.y);
// Don't let window get dragged up under the menu bar
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
// Move the window to the new location
[self.window setFrameOrigin:newOrigin];
if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
}
initialLocation is a NSPoint property
Note: I looked up some things here and here

When showing the real time resizing of a pane, it struggles while dragging and top of the pane is hidden after released

I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.

Dragging a UIView created after touchesBegan

I have a few UIView subclasses that exist in a kind of inventory. When you tap one, another draggable version is placed over top of it. I want the inventory versions to stay put while you drag others around. The code to make a duplicate works but dragging your finger around doesn't move it.
If I release and then start dragging the newly created version it moves as expected. I think this is because the original touches (that made the dupe) didn't have the draggable version in the responder chain.
A little code...
in my stationary "icon"...
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[self.viewController placeDraggableItem:self.item WithPoint:self.frame.origin];
}
in my viewController...
- (void)placeDraggableItem:(Item *)item WithPoint:(CGPoint)point
{
DraggableItem *draggableItem = [[DraggableItem alloc] initWithImage:[UIImage imageNamed:item.graphic]];
draggableItem.frame = CGRectMake(point.x, scrollView.frame.origin.y + point.y, 64.0f, 64.0f);
[self.view addSubview:draggableItem];
[draggableItem release];
}
in my DraggableItem...
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
currentPoint = [[touches anyObject] locationInView:self];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint activePoint = [[touches anyObject] locationInView:self];
CGPoint newPoint = CGPointMake(self.center.x + (activePoint.x - currentPoint.x), self.center.y + (activePoint.y - currentPoint.y));
float midPointX = CGRectGetMidX(self.bounds);
if (newPoint.x > self.superview.bounds.size.width - midPointX)
newPoint.x = self.superview.bounds.size.width - midPointX;
else if (newPoint.x < midPointX) // If too far left...
newPoint.x = midPointX;
float midPointY = CGRectGetMidY(self.bounds);
if (newPoint.y > self.superview.bounds.size.height - midPointY)
newPoint.y = self.superview.bounds.size.height - midPointY;
else if (newPoint.y < midPointY) // If too far up...
newPoint.y = midPointY;
self.center = newPoint;
}
Now again creating the draggable version works. And the draggable version is able to be moved after you first release your first touch. But I think I need to get the newly create UIView to respond to touches that were originally made for the "icon".
Any ideas?
I'm aware this question is kind of similar to this one: How to "transfer" first responder from one UIView to another? but in that case the view which should receive the touches is already there whereas I need to pass touches onto a newly created view.
I don't know of any way to hand touches off to a newly created view, though that doesn't mean there isn't a way to do it. It looks like you really only want your draggable view to handle the touches, I would probably create each draggable view with with an alpha of 0 at the same time you create the non draggable views, and in touchesBegan set it to 1. If your non-draggable views don't need to handle touches, then it doesn't make sense to make them handle touches just to pass them along.