40

I have a super view, which has 2 subviews. These subviews are overlapped.

Whenever i choose a view from a menu, corresponding view should become the front view and handle actions. i.e., it should be the front most subview.

acceptsFirstResponder resigns all work fine. But the mouse down events are sent to the topmost sub view which was set.

Regards, Dhana

Muhammad Ali
  • 2,173
  • 15
  • 20
Dhanaraj
  • 987
  • 1
  • 10
  • 26
  • beware that: "Note: For performance reasons, Cocoa does not enforce clipping among sibling views ..." And furthermore! "Cocoa does not ... guarantee correct invalidation and drawing behavior when sibling views overlap." As it says in the manual: "If you want a view to be drawn in front of another view, you should make the front view a subview (or descendant) of the rear view." (from the View Programming Guide in the OSX doco) – Fattie Jun 25 '16 at 17:11

7 Answers7

41

Here's another way to accomplish this that's a bit more clear and succinct:

[viewToBeMadeForemost removeFromSuperview];
[self addSubview:viewToBeMadeForemost positioned:NSWindowAbove relativeTo:nil];

Per the documentation for this method, when you use relativeTo:nil the view is added above (or below, with NSWindowBelow) all of its siblings.

Adam Preble
  • 2,162
  • 17
  • 28
  • 5
    There is a problem with the above approach. First, when you send **removeFromSuperview** to a view, it is released. So you first need to retain the view, then later balance the original retain with a release. Second, you have the overhead of removing and adding the view to the hierarchy (which may be costly depending on the complexity of your view hierarchy). There is another way of doing this which avoids having to remove and add views from the view hierarchy using NSView's built-in subview sorting function. See below. – Dalmazio Dec 21 '11 at 13:17
  • I think the two lines of code still in the same event loop, so viewToBeMadeForemost won't be released before the second line. – Li Fumin Sep 15 '15 at 06:07
  • 1
    @LiFumin, that is incorrect. You are assuming that removeFromSuperview autoreleases the view, or that other code autoreleased it. From the doc for -removeFromSuperview: "The view is also released; if you plan to reuse it, be sure to retain it before sending this message and to release it as appropriate when adding it as a subview of another NSView." Dalmazio is correct, the view must be retained. This solution also has problems regarding the breaking of constraints, etc., I would imagine. It should not be the accepted answer. – bhaller May 21 '17 at 03:54
  • 1
    another issue: remove from superview releases constraints – Confused Vorlon Jan 22 '19 at 14:33
  • If you remove view that is being a first responder it will lose focus. – Andriy Mar 31 '20 at 19:41
21

using @Dalmazio's answer in a Swift 4 category that mimics the equivalent UIView method gives the following:

extension NSView {

    func bringSubviewToFront(_ view: NSView) {
            var theView = view
            self.sortSubviews({(viewA,viewB,rawPointer) in
                let view = rawPointer?.load(as: NSView.self)

                switch view {
                case viewA:
                    return ComparisonResult.orderedDescending
                case viewB:
                    return ComparisonResult.orderedAscending
                default:
                    return ComparisonResult.orderedSame
                }
            }, context: &theView)
    }

}

so, to bring subView to the front in containerView

containerView.bringSubviewToFront(subView)

unlike solutions which remove and re-add the view, this keeps constraints unchanged

Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
17

Another way is to use NSView's sortSubviewsUsingFunction:context: method to re-order a collection of sibling views to your liking. For example, define your comparison function:

static NSComparisonResult myCustomViewAboveSiblingViewsComparator( NSView * view1, NSView * view2, void * context )
{    
    if ([view1 isKindOfClass:[MyCustomView class]])    
        return NSOrderedDescending;    
    else if ([view2 isKindOfClass:[MyCustomView class]])    
        return NSOrderedAscending;    

    return NSOrderedSame;
}

Then when you want to ensure your custom view remains above all sibling views, send this message to your custom view's superview:

[[myCustomView superview] sortSubviewsUsingFunction:myCustomViewAboveSiblingViewsComparator context:NULL];

Alternatively, you can move this code to the superview itself, and send the message sortSubviewsUsingFunction:context: to self instead.

Dalmazio
  • 1,835
  • 2
  • 23
  • 40
  • 2
    @Jonathan. That's for UIViews which are part of the iOS framework. The question is about NSViews which are used for OS X apps. – Mathias Aug 21 '14 at 15:02
  • This works, but appears to make the order of the rest of the subviews random, since the doc says "NSOrderedSame if their ordering isn’t important". A solution that preserves the ordering of the rest of the views would be much better. Also, the use of IsKindOfClass: here is weird; the question does not state that custom view subclasses are being used, or that the sibling subviews to be reordered are different classes. – bhaller May 21 '17 at 04:02
3

I was able to get this to work without calling removeFromSuperView

// pop to top
[self addSubview:viewToBeMadeForemost positioned:NSWindowAbove relativeTo:nil];
user511037
  • 147
  • 2
  • 3
1

You can achieve this by just adding the view again; it will not create another instance of it.

[self addSubview:viewToBeMadeForemost];

You can Log the number of subviews before and after this line of code is executed.

Sangram Shivankar
  • 3,535
  • 3
  • 26
  • 38
  • 1
    If this works, it is just by accident; the docs make no guarantees as to the behavior of adding a view that is already added. – bhaller May 21 '17 at 03:56
0

Below given code should work fine..

    NSMutableArray *subvies = [NSMutableArray arrayWithArray:[self subviews]];//Get all subviews..

    [viewToBeMadeForemost retain]; //Retain the view to be made top view..

    [subvies removeObject:viewToBeMadeForemost];//remove it from array

    [subvies addObject:viewToBeMadeForemost];//add as last item

    [self setSubviews:subvies];//set the new array..
Dhanaraj
  • 987
  • 1
  • 10
  • 26
  • This should be the accepted answer, as it is closer to being correct than the others. However, note that it ought to do a [viewToBeMadeForemost release]; at the end, otherwise there is a leak. – bhaller May 21 '17 at 04:03
-11

You might try this:

[viewToBeMadeFirst.window makeKeyAndOrderFront:nil];
Yuval
  • 7,987
  • 12
  • 40
  • 54
Guy
  • 308
  • 2
  • 11