12

上下文

我有一个普通的基于文档的 Cocoa Mac OS X 应用程序,它使用NSTextView富文本输入。用户可以在NSTextView.

基础 SDK:10.7
部署目标:10.6


问题:

我想NSTextView在用户编辑文本时以编程方式(包括 )实现整个 UI 的缩放。缩放框架NSTextView是没有问题的。但我不知道如何缩放视图内的可编辑文本,该文本可能在整个文本运行的不同子部分中包含多个不同的点大小。

如何将统一的比例因子应用于显示在 中的富文本NSTextView

这应该与“富文本”很好地配合使用,从而保留用户的字体系列、颜色,尤其是磅值(在文本运行的不同点可能不同),但统一/相对地缩放。

考虑到我的基本 SDK 和部署目标,这可能吗?是否可以使用更新的 Base SDK 或部署目标?

4

4 回答 4

12

如果目的是缩放视图(而不是实际更改字符串中的属性),我建议使用 scaleUnitSquareToSize: 方法:以及 ScalingScrollView (可通过 TextEdit 示例代码获得)以获得正确的滚动条行为。

ScalingScrollView 的核心部分是:

- (void)setScaleFactor:(CGFloat)newScaleFactor adjustPopup:(BOOL)flag
{
CGFloat oldScaleFactor = scaleFactor;
    if (scaleFactor != newScaleFactor)
    {
        NSSize curDocFrameSize, newDocBoundsSize;
        NSView *clipView = [[self documentView] superview];

        scaleFactor = newScaleFactor;

        // Get the frame.  The frame must stay the same.
        curDocFrameSize = [clipView frame].size;

        // The new bounds will be frame divided by scale factor
        newDocBoundsSize.width = curDocFrameSize.width / scaleFactor;
        newDocBoundsSize.height = curDocFrameSize.height / scaleFactor;
    }
    scaleFactor = newScaleFactor;
    [scale_delegate scaleChanged:oldScaleFactor newScale:newScaleFactor]; 
}

scale_delegate是您可以调整NSTextView对象的委托:

- (void) scaleChanged:(CGFloat)oldScale newScale:(CGFloat)newScale
{
    NSInteger     percent  = lroundf(newScale * 100);

    CGFloat scaler = newScale / oldScale;   
    [textView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)];

    NSLayoutManager* lm = [textView layoutManager];
    NSTextContainer* tc = [textView textContainer];
    [lm ensureLayoutForTextContainer:tc];
}

scaleUnitSquareToSize:方法相对于其当前状态进行缩放,因此您可以跟踪缩放因子,然后将绝对缩放请求 (200%) 转换为相对缩放请求。

于 2013-01-01T20:23:10.137 回答
7

适用于 iOS 和 Mac OS

@implementation NSAttributedString (Scale)

- (NSAttributedString *)attributedStringWithScale:(double)scale
{
    if(scale == 1.0)
    {
        return self;
    }

    NSMutableAttributedString *copy = [self mutableCopy];
    [copy beginEditing];

    NSRange fullRange = NSMakeRange(0, copy.length);

    [self enumerateAttribute:NSFontAttributeName inRange:fullRange options:0 usingBlock:^(UIFont *oldFont, NSRange range, BOOL *stop) {
        double currentFontSize = oldFont.pointSize;
        double newFontSize = currentFontSize * scale;

        // don't trust -[UIFont fontWithSize:]
        UIFont *scaledFont = [UIFont fontWithName:oldFont.fontName size:newFontSize];

        [copy removeAttribute:NSFontAttributeName range:range];
        [copy addAttribute:NSFontAttributeName value:scaledFont range:range];
    }];

    [self enumerateAttribute:NSParagraphStyleAttributeName inRange:fullRange options:0 usingBlock:^(NSParagraphStyle *oldParagraphStyle, NSRange range, BOOL *stop) {

        NSMutableParagraphStyle *newParagraphStyle = [oldParagraphStyle mutableCopy];
        newParagraphStyle.lineSpacing *= scale;
        newParagraphStyle.paragraphSpacing *= scale;
        newParagraphStyle.firstLineHeadIndent *= scale;
        newParagraphStyle.headIndent *= scale;
        newParagraphStyle.tailIndent *= scale;
        newParagraphStyle.minimumLineHeight *= scale;
        newParagraphStyle.maximumLineHeight *= scale;
        newParagraphStyle.paragraphSpacing *= scale;
        newParagraphStyle.paragraphSpacingBefore *= scale;

        [copy removeAttribute:NSParagraphStyleAttributeName range:range];
        [copy addAttribute:NSParagraphStyleAttributeName value:newParagraphStyle range:range];
    }];

    [copy endEditing];
    return copy;
}

@end
于 2013-11-07T20:06:19.477 回答
1

在这里。

我找到了一种可行的解决方案,而且实施起来并不难。但是,我不确定这是最佳/理想的解决方案。我仍然有兴趣寻找其他解决方案。但这是一种方法:

在显示之前手动缩放源文本的字体点大小行高多个属性NSAttributedString,然后在存储为源之前取消缩放显示的文本。

此解决方案的问题在于,在缩放时,系统字体面板将在编辑时显示所选文本的实际缩放显示点大小(而不是“真实”源点大小)。这是不可取的。


这是我的实现:

- (void)scaleAttributedString:(NSMutableAttributedString *)str by:(CGFloat)scale {
    if (1.0 == scale) return;

    NSRange r = NSMakeRange(0, [str length]);
    [str enumerateAttribute:NSFontAttributeName inRange:r options:0 usingBlock:^(NSFont *oldFont, NSRange range, BOOL *stop) {
        NSFont *newFont = [NSFont fontWithName:[oldFont familyName] size:[oldFont pointSize] * scale];

        NSParagraphStyle *oldParaStyle = [str attribute:NSParagraphStyleAttributeName atIndex:range.location effectiveRange:NULL];
        NSMutableParagraphStyle *newParaStyle = [[oldParaStyle mutableCopy] autorelease];

        CGFloat oldLineHeight = [oldParaStyle lineHeightMultiple];
        CGFloat newLineHeight = scale * oldLineHeight;
        [newParaStyle setLineHeightMultiple:newLineHeight];

        id newAttrs = @{
            NSParagraphStyleAttributeName: newParaStyle,
            NSFontAttributeName: newFont,
        };
        [str addAttributes:newAttrs range:range];
    }];    
}

这需要在显示之前缩放源文本:

// scale text
CGFloat scale = getCurrentScaleFactor();
[self scaleAttributedString:str by:scale];

然后在存储为源之前反向缩放显示的文本:

// un-scale text
CGFloat scale = 1.0 / getCurrentScaleFactor();
[self scaleAttributedString:str by:scale];
于 2013-01-01T19:45:20.330 回答
0

我要感谢 Mark Munz 的回答,因为它让我免于在充满 NSScrollView 放大疯狂和 NSLayoutManagers 的黑暗森林中徘徊。

对于仍在寻找的人,这是我的方法。此代码位于 NSDocument 中。所有文本都被插入到一个固定宽度和居中的容器中,并且这里的缩放使自动换行等保持不变。它创建了一个漂亮的“页面视图”外观,而不需要复杂的布局管理。

你需要在你的类中设置CGFloat _documentSize和常量,这个例子才能工作。NSTextView textView

- (void) initZoom {
    // Call this when the view has loaded and is ready
    // I am storing a separate _scaleFactor and _magnification for my own purposes, mainly to have the initial scale to be higher than 1.0
    _scaleFactor = 1.0;
    _magnification = 1.1;
    [self setScaleFactor:_magnification adjustPopup:false];

    [self updateLayout];
    // NOTE: You might need to call updateLayout after the content is set and we know the window size etc.
}

- (void) zoom: (bool) zoomIn {
    if (!_scaleFactor) _scaleFactor = _magnification;

    // Arbitrary maximum levels of zoom
    if (zoomIn) {
        if (_magnification < 1.6) _magnification += 0.1;
    } else {
        if (_magnification > 0.8) _magnification -= 0.1;
    }

    [self setScaleFactor:_magnification adjustPopup:false];
    [self updateLayout];
}

- (void)setScaleFactor:(CGFloat)newScaleFactor adjustPopup:(BOOL)flag
{
    CGFloat oldScaleFactor = _scaleFactor;
    if (_scaleFactor != newScaleFactor)
    {
        NSSize curDocFrameSize, newDocBoundsSize;
        NSView *clipView = [[self textView] superview];

        _scaleFactor = newScaleFactor;

        // Get the frame.  The frame must stay the same.
        curDocFrameSize = [clipView frame].size;

        // The new bounds will be frame divided by scale factor
        //newDocBoundsSize.width = curDocFrameSize.width / _scaleFactor;
        newDocBoundsSize.width = curDocFrameSize.width;
        newDocBoundsSize.height = curDocFrameSize.height / _scaleFactor;

        NSRect newFrame = NSMakeRect(0, 0, newDocBoundsSize.width, newDocBoundsSize.height);
        clipView.frame = newFrame;
    }
    _scaleFactor = newScaleFactor;
    [self scaleChanged:oldScaleFactor newScale:newScaleFactor];
}
- (void) scaleChanged:(CGFloat)oldScale newScale:(CGFloat)newScale
{
    CGFloat scaler = newScale / oldScale;
    [self.textView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)];

    NSLayoutManager* lm = [self.textView layoutManager];
    NSTextContainer* tc = [self.textView textContainer];
    [lm ensureLayoutForTextContainer:tc];
}

- (void) updateLayout {
    CGFloat width = (self.textView.frame.size.width / 2 - _documentWidth * _magnification / 2) / _magnification;    self.textView.textContainerInset = NSMakeSize(width, TEXT_INSET_TOP);
    self.textView.textContainer.size = NSMakeSize(_documentWidth, self.textView.textContainer.size.height);
}
于 2019-09-06T06:48:21.107 回答