调整窗口大小时,使缩放后的NSScrollView的内容居中并可见

时间:2019-05-02 10:13:05

标签: objective-c cocoa nsview nstextview

我正在尝试放大包含NSScrollView的{​​{1}},并始终保持其内容居中。 NSTextView具有左/右插入物,以使自动换行保持一致,并使段落很好地保持在视图的中心。

NSTextView[NSScrollView scaleUnitSquareToSize:...]都有自己的怪癖和问题,但就目前而言,setMagnification似乎是一个更好的选择,因为它不是相对的。

发生了什么(其他奇怪的事情): enter image description here

在调整大小时,我会更新插图:

setMagnification:...

放大:

CGFloat inset = self.textScrollView.frame.size.width / 2 - _documentWidth / 2;
self.textView.textContainerInset = NSMakeSize(inset, TEXT_INSET_TOP);
self.textView.textContainer.size = NSMakeSize(_documentWidth, self.textView.textContainer.size.height);

一切工作一段时间。有时,取决于从哪个窗口角调整窗口大小,ScrollView会丢失其中心,而我还没有找到解决方案来重新定居放大的CGFloat magnification = [self.textScrollView magnification]; NSPoint center = NSMakePoint(self.textScrollView.frame.size.width / 2, self.textScrollView.frame.size.height / 2); if (zoomIn) magnification += .05; else magnification -= .05; [self.textScrollView setMagnification:magnification centeredAtPoint:center]; 的视图。

放大后,在调整窗口大小时,布局约束也会被破坏,尤其是当NSScrollView被裁剪到视图之外时,应用程序崩溃并显示以下错误: textContainer

一个问题可能是我根据*** Assertion failure in -[NSISLinearExpression addVariable:coefficient:], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1349.91/Layout.subproj/IncrementalSimplex/NSISLinearExpression.m:716帧大小来设置插图,因为所包含的NSTextView的坐标在放大之后似乎不是相对的,而是绝对的。

是否有任何安全的方法可以始终放大这种视图并将其始终保持在其内容的中心?为什么我的约束被打破了?

1 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,不幸的是我最终还是自己对中。这是我的解决方案的一些亮点。

  1. 需要递归预防! (否则stackoverflow:)
  2. 创建一个不可绘制的NSView作为documentView,然后将您的可绘制视图添加为手动居中的子视图,然后将框架手动设置为父级的visibleRect。
  3. 覆盖visibleRect,如果无效则再次调用它,并进行调试以确保其有效!
  4. 缩放分层后备视图sux。您可以尝试使用NSTiledLayer,但我已尝试并多次放弃该解决方案。

以下代码:

@interface FlippedParentView : NSView
@end

@implementation FlippedParentView
- (BOOL) isFlipped { return YES; }
@end




- (void)awakeFromNib
{
    [self resetMouseInfo];
    [[self window] setAcceptsMouseMovedEvents:YES];
    needsFullRedraw = YES;
    [self setAcceptsTouchEvents:YES];

    // problem: when zoomed-in, CALayer backed NSOpenGLView becomes too large
    // and hurts performance.
    // solution: create a fullsizeView for the NSScrollView to resize,
    // and make NSOpenGLView a subview.  Keep NSOpenGLView size the same as visibleRect,
    // positioning it as needed on the fullsizeView.
    NSScrollView *scrollvw = [self enclosingScrollView];
    [scrollvw setBackgroundColor:[NSColor darkStrokeColor]];
    fullsizeView = [[FlippedParentView alloc] initWithFrame: [self frame]];
    [scrollvw setDocumentView:fullsizeView];
    [fullsizeView setAutoresizesSubviews:NO];
    //printf("mask %d\n", [self autoresizingMask]);
    [fullsizeView setAutoresizingMask: NSViewHeightSizable | NSViewWidthSizable | NSViewMinYMargin | NSViewMaxYMargin | NSViewMaxXMargin | NSViewMinXMargin];
    [self setAutoresizingMask: NSViewNotSizable];
    [fullsizeView addSubview:self];
}

- (NSRect) visibleRect
{
    NSRect visRect = [super visibleRect];
    if ( visRect.size.width == 0 )
    {
        visRect = [[self superview] visibleRect];
        if ( visRect.size.width == 0 )
        {
            // this jacks up everything
            DUMP( @"bad visibleRect" );
        }
        visRect.origin = NSZeroPoint;
    }
    return visRect;
}

- (void) _my_zoom: (double)newZoom
{
    mouseFocusPt = [self focusPt];
    NSRect oldVisRect = [[self superview] visibleRect];
    if ( newZoom < 1.0 )
        newZoom = 1.0;
    if ( newZoom > kZoomFactorMax ) newZoom = kZoomFactorMax;

    float xpct = (mouseFocusPt.x - oldVisRect.origin.x) /
    ( NSMaxX(oldVisRect) - oldVisRect.origin.x );

    float ypct = (mouseFocusPt.y  - oldVisRect.origin.y) /
    ( NSMaxY(oldVisRect) - oldVisRect.origin.y );

    float oldZoom = zoomFactor;

    zoomFactor = newZoom;

    /////////////////////////////////////////////////////////////////////////////////////////////////////
    // Stay locked on users' relative mouse location, so user can zoom in and back out without
    // the view scrolling out from under the mouse location.
    NSPoint newFocusPt = NSMakePoint (mouseFocusPt.x * newZoom/oldZoom,
                                      mouseFocusPt.y * newZoom/oldZoom) ;

    NSRect myFrame = fullsizeFrame; // [self frame];
    float marginPercent = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;

    [self updateContext];

    NSRect newVisRect;
    newVisRect.size = [self visibleRect].size;
    newVisRect.origin.x = (newFocusPt.x) - (xpct * newVisRect.size.width);
    //DLog( @"xpct %0.2f, zoomFactor %0.2f, newVisRect.origin.x %0.2f", xpct, zoomFactor, newVisRect.origin.x);

    myFrame = fullsizeFrame; // [self frame];
    float marginPercent2 = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;
    float marginDiff = (marginPercent - marginPercent2) * drawableSizeWithMargins.height;
    newVisRect.origin.y = (newFocusPt.y ) - (ypct * newVisRect.size.height) - marginDiff;
    //DLog( @"ypct %0.2f, zoomFactor %0.2f, newVisRect.origin.y %0.2f", ypct, zoomFactor, newVisRect.origin.y);
    //DLog( @"marginPercent %0.2f newVisRect %@", marginPercent, NSStringFromRect(newVisRect) );
    if ( newVisRect.origin.x < 1 ) newVisRect.origin.x = 1;
    if ( newVisRect.origin.y < 1 ) newVisRect.origin.y = 1;


     //   NSLog( @"zoom scrollRectToVisible %@ bounds %@", NSStringFromRect(newVisRect), NSStringFromRect([[self superview] bounds]) );
    // if ( iUseMousePt || isSlider )
        [[self superview] scrollRectToVisible:newVisRect];
}

// - zoomFactor of 1.0 is defined as the zoomFactor needed to show entire selected context within visibleRect,
//   including margins of 5% of the context size
// - zoomFactor > 1.0 will make pixels look bigger (view a subsection of a larger total drawableSize)
// - zoomFactor < 1.0 will make pixels look smaller (selectedContext size will be less than drawableSize)
-(void)updateContext
{
    static BOOL sRecursing = NO;
    if ( sRecursing ) return; // prevent recursion
    sRecursing = YES;

    //NSRect scrollRect = [[self superview]  frame];
    NSRect clipViewRect = [[[self enclosingScrollView] contentView] frame];
    NSRect visRect = [[self superview] visibleRect]; // careful... visibleRect is sometimes NSZeroRect

    float layoutWidth = clipViewRect.size.width;
    float layoutHeight = clipViewRect.size.height;



    marginPct = layoutHeight / (layoutHeight - (overlayViewMargin*2) );

    // Satisfy the constraints fully-zoomed-out case:
    //  1) the drawable rect is centered in the view with at margins.
    //     Allow for 5% margins (1.025 = 2.5% left, right, top, bottom)
    //  2) guarantee the drawable rect does not overlap the mini-map in upper right corner.
    NSRect baseRect = NSZeroRect;
    baseRect.size = visRect.size;
    NSRect drawableBaseRect = getCenteredRectFloat(baseRect, metaUnionRect.size );

    //drawableSizeWithMargins = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor ) );
    drawableSizeWithMargins = nsScaleSize( drawableBaseRect.size, zoomFactor );

    // drawableSize will NOT include the margins.  We loop until we've satisfied
    // the constraints above.
    drawableSize = drawableSizeWithMargins;

    do
    {
        NSSize shrunkSize;
        shrunkSize.width = layoutWidth / marginPct;
        shrunkSize.height = layoutHeight /  marginPct;
        //drawableSize = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct ));
        drawableSize = nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct );

        [self calculateMiniMapRect]; // get approx. size.  Will calculate once more below.

        NSRect shrunkRect = getCenteredRectNoScaling(baseRect, shrunkSize );

        // DLog( @"rough miniMapRect %@ shrunk %@", NSStringFromRect(miniMapRect), NSStringFromRect(shrunkRect));

        // make sure minimap doesn't overlap drawable when you scroll to top-left
        NSRect topMiniMapRect = miniMapRect;
        topMiniMapRect.origin.x -= visRect.origin.x;
        topMiniMapRect.origin.y = 0;
        if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
        {
            topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y)  / baseRect.size.height;
            break;
        }

        float topMarginOffset = shrunkRect.size.height + (baseRect.size.height * 0.025);
        shrunkRect.origin.y = NSMaxY(baseRect) - topMarginOffset;

        if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
        {
            topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y)  / baseRect.size.height;
            break;
        }

        marginPct *= 1.025;
    } while (1);

    fullsizeFrame.origin = NSZeroPoint;
    fullsizeFrame.size.width  = fmax(drawableSizeWithMargins.width, layoutWidth);
    fullsizeFrame.size.height = fmax(drawableSizeWithMargins.height, layoutHeight);

    [fullsizeView setFrame:fullsizeFrame];

    NSRect myNewFrame = [fullsizeView visibleRect];
    if (myNewFrame.size.width > 0)
       [self setFrame: myNewFrame]; //NSView

    sRecursing = NO;
}