2009-08-18 3 views
14

J'ai un NSView (myView) enveloppé dans un NSScrollView (myScrollView). En utilisant les boutons de zoom avant/arrière, l'utilisateur peut modifier l'échelle de myView. Si l'utilisateur est en train de défiler jusqu'à un endroit particulier dans myView, j'aimerais conserver cette partie de la vue à l'écran après le zoom.Comment maintenir la position de défilement dans NSScrollView lors du changement d'échelle?

J'ai le code qui ressemble à ceci:

// 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]]; 

Et il semble sorte de près, mais ce n'est pas tout à fait raison et je ne peux pas comprendre ce que je fais mal. Mes deux questions sont:

1) est-il pas intégré quelque chose le long des lignes de:

[myView adjustScaleBy: 0.5 whilePreservingLocationInScrollview:myScrollView]; 

2) Dans le cas contraire, peut-on voir ce que je fais mal dans mon « chemin autour "approche, ci-dessus?

Merci!

Répondre

11

Garder la même position de défilement après la mise à l'échelle n'est pas facile. Une chose que vous devez décider est ce que vous entendez par "le même" - voulez-vous que le haut, le milieu ou le bas de la zone visible avant la mise à l'échelle restent en place après la mise à l'échelle?

Ou, plus intuitivement, voulez-vous que la position qui reste en place soit un pourcentage descendant du rectangle visible égal au pourcentage de défilement du document lorsque vous commencez (par exemple, le centre du pouce du défileur ne fait pas t monter ou descendre pendant une échelle, le pouce grandit ou se rétrécit). Si vous voulez ce dernier effet, vous pouvez obtenir verticalScroller et horizontalScroller de NSScrollView, puis lire leur 'floatValue'. Ceux-ci sont normalisés de 0 à 1, où «0» signifie que vous êtes en haut du document et 1 signifie que vous êtes à la fin. Ce qu'il y a de bien à demander au scroller, c'est que si le document est plus court que NSScrollView, le scroller renvoie toujours une réponse sensée dans tous les cas pour 'floatValue', donc vous n'avez pas besoin de cas particulier.

Après avoir redimensionné, définissez la position de défilement de NSScrollView comme étant le même pourcentage qu'avant l'échelle, mais, malheureusement, c'est ici que je fais un léger mouvement de la main. Je n'ai pas fait cela depuis un moment dans mon code, mais si je me souviens bien, vous ne pouvez pas simplement définir les floatValue des NSScrollers directement - ils vont LOOK défiler, mais ils n'affecteront pas réellement NSScrollView. Donc, vous devrez écrire des maths pour calculer le nouveau point en haut à gauche de votre document en fonction du pourcentage que vous voulez y passer - sur l'axe des y, par exemple, ça va ressembler, "Si le document est maintenant plus court que contentView de scrollView, faites défiler jusqu'au point 0, sinon faites défiler jusqu'à un point qui est ((hauteur de contentView - height of documentView) * oldVerticalPercentage) dans le document." L'axe X est bien sûr similaire.

Aussi, je suis presque sûr que vous n'avez pas besoin d'un appel à -display ici, et en général ne devrait jamais l'appeler, jamais. (-setNeedsDisplay:. Au plus)

-Wil

+0

L'effet que je veux est: supposons que je regarde au milieu de l'écran. Comme je zoom-in (ou -out), gardez cette chose au milieu de l'écran. Merci! – Olie

+0

Excellente réponse! Au lieu de définir floatValue sur les scrollers après avoir fait défiler ma vue vers l'origine calculée, j'ai appelé reflectScrolledClipView: dans ma vue déroulante. Cela semble obtenir les barres de défilement dans la bonne position. Je ne sais pas si c'est correct car c'est la toute première application Mac que j'ai créée, mais ça marche pour moi. – afarnham

7

Me pense que vous aimez taper trop ... ;-)

// 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); 
+0

Oui, j'ai appris à propos de ceux peu de temps après l'affichage. Je viens du monde de l'iPhone, et deviner les noms des NS pour les choses est encore un peu lent pour moi. Merci! :) – Olie

6

C'est une vieille question, mais j'espère que quelqu'un qui cherche cette trouve ma réponse utile ...

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]; 
} 
+0

Je l'ai trouvé très utile. Cela a fonctionné, après avoir essayé plusieurs autres choses qui ne l'ont pas fait. Merci de l'avoir posté! – jbillfinger

+1

Cela fonctionne très bien! Le seul problème est lors du redimensionnement de la fenêtre (et donc de la vue déroulante): le contenu est défilé en bas à gauche à nouveau. –

+0

@NicolasMiari J'ai aussi des problèmes pour que le contenu défile en bas à gauche, avez-vous trouvé une solution? –

Questions connexes