Как сохранить положение прокрутки в NSScrollView при изменении масштаба?

У меня есть NSView (myView), завернутый в NSScrollView (myScrollView). Используя кнопки увеличения / уменьшения, пользователь может изменять масштаб myView. Если пользователь в настоящее время прокручивается до определенного места в myView, я хотел бы оставить эту часть представления на экране после того, как произошло масштабирование.

У меня есть код, который выглядит так:

    // preserve current position in scrollview
    NSRect oldVisibleRect = [[myScrollView contentView] documentVisibleRect];
    NSPoint oldCenter = NSPointFromCGPoint(CGPointMake(oldVisibleRect.origin.x + (oldVisibleRect.size.width  / 2.0),
                                                       oldVisibleRect.origin.y + (oldVisibleRect.size.height / 2.0)));

    // adjust my zoom
    ++displayZoom;
    [self scaleUnitSquareToSize:NSSizeFromCGSize(CGSizeMake(0.5, 0.5))];
    [self calculateBounds];  // make sure my frame & bounds are at least as big as the visible content view
    [self display];

    // Adjust scroll view to keep the same position.
    NSRect newVisibleRect = [[myScrollView contentView] documentVisibleRect];
    NSPoint newOffset = NSPointFromCGPoint(CGPointMake((oldCenter.x * 0.5) - (newVisibleRect.size.width  / 2.0),
                                                       (oldCenter.y * 0.5) - (newVisibleRect.size.height / 2.0)));
    if (newOffset.x < 0)
        newOffset.x = 0;
    if (newOffset.y < 0)
        newOffset.y = 0;

    [[myScrollView contentView] scrollToPoint: newOffset];
    [myScrollView reflectScrolledClipView: [myScrollView contentView]];

И это вроде как близко, но это не совсем правильно, и я не могу понять, что делаю не так. Мои два вопроса:

1) Нет ли встроенного чего-то вроде:

   [myView adjustScaleBy: 0.5 whilePreservingLocationInScrollview:myScrollView];

2) Если нет, может ли кто-нибудь увидеть, что я делаю неправильно в моем «долгом пути», описанном выше?

Спасибо!


person Olie    schedule 18.08.2009    source источник


Ответы (3)


Сохранить ту же позицию прокрутки после масштабирования непросто. Одна вещь, которую вам нужно решить, - это то, что вы подразумеваете под «одинаковым» - хотите ли вы, чтобы верх, середина или низ видимой области перед масштабированием оставались на месте после масштабирования?

Или, что более интуитивно, вы хотите, чтобы положение, которое оставалось на месте, было в процентах вниз по видимому прямоугольнику, равному проценту, который вы прокручиваете вниз по документу при запуске (например, чтобы центр большого пальца ползунка не перемещался вверх или вниз во время шкалы большой палец просто увеличивается или уменьшается).

Если вам нужен последний эффект, один из способов сделать это - получить verticalScroller и horizontalScroller NSScrollView, а затем прочитать их floatValue. Они нормализованы от 0 до 1, где «0» означает, что вы находитесь в верхней части документа, а 1 означает, что вы в конце. Хорошая вещь в том, чтобы спросить об этом у скроллера, заключается в том, что если документ короче, чем NSScrollView, скроллер по-прежнему возвращает разумный ответ во всех случаях для 'floatValue', поэтому вам не нужно делать это в особом случае.

После изменения размера установите положение прокрутки NSScrollView на тот же процент, который был до масштабирования, но, к сожалению, здесь я немного машу руками. Я давно не делал этого в своем коде, но, насколько я помню, вы не можете просто установить floatValue NSScrollers напрямую - они будут СМОТРЕТЬ прокрученными, но на самом деле они не повлияют на NSScrollView.

Итак, вам придется написать математику, чтобы вычислить новую верхнюю левую точку в вашем документе на основе процента, который вы хотите пройти через нее - например, по оси Y это будет выглядеть так: «Если документ теперь короче, чем contentView scrollView, прокрутите до точки 0, в противном случае прокрутите до точки, которая ((высота contentView - высота documentView) * oldVerticalPercentage) вниз по документу. " Ось X конечно аналогична.

Кроме того, я почти уверен, что вам здесь не нужен вызов -display, и вообще не следует его вызывать. (-setNeedsDisplay: максимум.)

-Wil

person Wil Shipley    schedule 20.09.2009
comment
Я хочу получить следующий эффект: предположим, я смотрю в середину экрана. Когда я увеличиваю (или отдаляю) масштаб, держите эту вещь посередине экрана. Спасибо! - person Olie; 23.09.2009
comment
Отличный ответ! Вместо того, чтобы устанавливать значение floatValue на скроллерах после того, как я прокрутил свое представление до вычисленного источника, я вызвал в моем представлении прокрутки рефлектскролледедклипвиев:. Кажется, полосы прокрутки находятся в правильном положении. Не уверен, что это правильно, так как это самое первое приложение для Mac, которое я создал, но оно работает для меня. - person Aaron; 13.08.2013

Мне кажется, тебе слишком нравится печатать… ;-)

// instead of this:
NSPoint oldCenter = NSPointFromCGPoint(CGPointMake(oldVisibleRect.origin.x +
    (oldVisibleRect.size.width  / 2.0),

// use this:
NSPoint oldCenter = NSMakePoint(NSMidX(oldVisibleRect), NSMaxY(oldVisibleRect));

// likewise instead of this:
[self scaleUnitSquareToSize:NSSizeFromCGSize(CGSizeMake(0.5, 0.5))];

// use this:
[self scaleUnitSquareToSize:NSMakeSize(0.5, 0.5)];

// and instead of this
NSPoint newOffset = NSPointFromCGPoint(CGPointMake(
    (oldCenter.x * 0.5) - (newVisibleRect.size.width  / 2.0),
    (oldCenter.y * 0.5) - (newVisibleRect.size.height / 2.0)));

// use this:
NSPoint newOffset NSMakePoint(
    (oldCenter.x - NSWidth(newVisibleRect)) / 2.f,
    (oldCenter.y - NSHeight(newVisibleRect)) / 2.f);
person geowar    schedule 30.09.2009
comment
Да, я узнал о них вскоре после публикации. Я прихожу из мира iPhone, и угадывать имена NS для вещей для меня все еще немного медленно. Спасибо! :) - person Olie; 01.10.2009

Это старый вопрос, но я надеюсь, что кто-то, ищущий его, найдет мой ответ полезным ...

float zoomFactor = 1.3;

-(void)zoomIn
{
    NSRect visible = [scrollView documentVisibleRect];
    NSRect newrect = NSInsetRect(visible, NSWidth(visible)*(1 - 1/zoomFactor)/2.0, NSHeight(visible)*(1 - 1/zoomFactor)/2.0);
    NSRect frame = [scrollView.documentView frame];

    [scrollView.documentView scaleUnitSquareToSize:NSMakeSize(zoomFactor, zoomFactor)];
    [scrollView.documentView setFrame:NSMakeRect(0, 0, frame.size.width * zoomFactor, frame.size.height * zoomFactor)];

    [[scrollView documentView] scrollPoint:newrect.origin];
}

-(void)zoomOut
{
    NSRect visible = [scrollView documentVisibleRect];
    NSRect newrect = NSOffsetRect(visible, -NSWidth(visible)*(zoomFactor - 1)/2.0, -NSHeight(visible)*(zoomFactor - 1)/2.0);

    NSRect frame = [scrollView.documentView frame];

    [scrollView.documentView scaleUnitSquareToSize:NSMakeSize(1/zoomFactor, 1/zoomFactor)];
    [scrollView.documentView setFrame:NSMakeRect(0, 0, frame.size.width / zoomFactor, frame.size.height / zoomFactor)];

    [[scrollView documentView] scrollPoint:newrect.origin];
}
person whooops    schedule 16.06.2011
comment
Я нашел это весьма полезным. Это сработало после того, как я попробовал несколько других вещей, которые не помогли. Спасибо, что разместили! - person jbillfinger; 29.10.2013
comment
Это прекрасно работает! Единственная проблема заключается в изменении размера окна (и, следовательно, просмотра прокрутки): содержимое снова прокручивается в нижний левый угол. - person Nicolas Miari; 22.11.2013
comment
@NicolasMiari У меня также возникают проблемы с прокруткой содержимого в нижний левый угол. Вы нашли решение? - person jblixr; 16.11.2016
comment
Что ж, я все еще пытаюсь решить эту проблему, и хотя это хорошо помогает масштабировать вещи и сохранять их масштабированные позиции, тестирование нажатия мыши для объектов CALayer не масштабируется, поэтому оно не работает. - person Duncan Groenewald; 26.05.2020