How do you get NSScrollView to center the document

2019-02-01 10:50发布

问题:

There are lots of examples out there of how to get NSScrollView to center its document view. Here are two examples (which are so similar that somebody is copying somebody without attribution, but the point of how is there.) http://www.bergdesign.com/developer/index_files/88a764e343ce7190c4372d1425b3b6a3-0.html https://github.com/devosoft/avida/blob/master/apps/viewer-macos/src/main/CenteringClipView.h

This is normally done by subclassing NSClipView and overriding:

- (NSPoint)constrainScrollPoint:(NSPoint)newOrigin;

But this method is deprecated in Mac OS X 10.9 +

What can we do now? Oh noes~!

回答1:

Well, the answer is simple and nowhere near as over bloated as those are. Those do not work with double-tap magnification anyway.

This does. It just works. You can also customize your adjustments as needed.

In the @implementation you only need to implement an override of constrainBoundsRect:

- (NSRect)constrainBoundsRect:(NSRect)proposedClipViewBoundsRect {

    NSRect constrainedClipViewBoundsRect = [super constrainBoundsRect:proposedClipViewBoundsRect];

    // Early out if you want to use the default NSClipView behavior.
    if (self.centersDocumentView == NO) {
        return constrainedClipViewBoundsRect;
    }

    NSRect documentViewFrameRect = [self.documentView frame];

    // If proposed clip view bounds width is greater than document view frame width, center it horizontally.
    if (proposedClipViewBoundsRect.size.width >= documentViewFrameRect.size.width) {
        // Adjust the proposed origin.x
        constrainedClipViewBoundsRect.origin.x = centeredCoordinateUnitWithProposedContentViewBoundsDimensionAndDocumentViewFrameDimension(proposedClipViewBoundsRect.size.width, documentViewFrameRect.size.width);
    }

    // If proposed clip view bounds is hight is greater than document view frame height, center it vertically.
    if (proposedClipViewBoundsRect.size.height >= documentViewFrameRect.size.height) {

        // Adjust the proposed origin.y
        constrainedClipViewBoundsRect.origin.y = centeredCoordinateUnitWithProposedContentViewBoundsDimensionAndDocumentViewFrameDimension(proposedClipViewBoundsRect.size.height, documentViewFrameRect.size.height);
    }

    return constrainedClipViewBoundsRect;
}


CGFloat centeredCoordinateUnitWithProposedContentViewBoundsDimensionAndDocumentViewFrameDimension
(CGFloat proposedContentViewBoundsDimension,
 CGFloat documentViewFrameDimension )
{
    CGFloat result = floor( (proposedContentViewBoundsDimension - documentViewFrameDimension) / -2.0F );
    return result;
}

In the @interface just add one single property. This allows you to not use centering. As you can imagine, there may be conditional logic you want to turn centering off at times.

@property BOOL centersDocumentView;

Also, be sure to set this BOOL to YES or NO in your override of

initWithFrame and initWithCoder:

so you'll have a known default value to work from.

(remember kids, initWithCoder: allows you to do the needful and set a view's class in a nib. Don't forget to call super prior to your stuff!)

Of course if you need to support anything earlier than 10.9 you'll need to implement the other stuff.

(though probably not nearly as much as others have...)



回答2:

Here working class for swift

class CenteredClipView:NSClipView
{
    override func constrainBoundsRect(proposedBounds: NSRect) -> NSRect {

        var rect = super.constrainBoundsRect(proposedBounds)
        if let containerView = self.documentView as? NSView {

            if (rect.size.width > containerView.frame.size.width) {
                rect.origin.x = (containerView.frame.width - rect.width) / 2
            }

            if(rect.size.height > containerView.frame.size.height) {
                rect.origin.y = (containerView.frame.height - rect.height) / 2
            }
        }

        return rect
    }
}


回答3:

1) make sure the view (documentView) directly under the clip view has no constraints to the clip view! (if so, check, remove at build time)

2) subclass NSClipView

@implementation CenterClipView

- (NSRect)constrainBoundsRect:(NSRect)proposedClipViewBoundsRect {

    NSRect rect = [super constrainBoundsRect:proposedClipViewBoundsRect];
    NSView * view = self.documentView;

    if (view) {

        if (rect.size.width > view.frame.size.width) {
            rect.origin.x = (rect.size.width - view.frame.size.width) / 2.;
        }

        if(rect.size.height > view.frame.size.height) {
            rect.origin.y = (rect.size.height - view.frame.size.height) / 2.;
        }
    }

    return rect;
}

@end

3) change the NSClipView to your subclass



回答4:

The answer above by uchuugaka works very well and, as pointed out, it is very much simpler than older solutions.

The code above calls the function centeredCoordinateUnitWithProposedContentViewBoundsDimensionAndDocumentViewFrameDimension(), but the source for this isn't provided.

The function is trivial, but for completeness here's an an implementation:

CGFloat centeredCoordinateUnitWithProposedContentViewBoundsDimensionAndDocumentViewFrameDimension( CGFloat clipDimension, CGFloat docDimension)
{
    CGFloat newOrigin;
    newOrigin = roundf((docDimension - clipDimension) / 2.0);
    return newOrigin;
}