下沉与NSAttributedString(Drop cap with NSAttributedSt

2019-07-17 23:32发布

我愿做一个下沉第一个字符UILabel使用attributedText NSAttributedString唯一的财产。 像这样:

http://www.interpretationbydesign.com/wp-content/uploads/2009/02/drop_caps.gif

我已经试验了调整的第一个字符到一个负值的范围内的基线,并且它适用于与所述第一行的其余部分的顶部对准第一字符的顶部。 但我没有发现任何方式让其他线路流向下降封顶字符的右侧。

可以这样使用来解决NSAttributedString only ,还是我来分割字符串,并使用核心文本渲染它自己?

Answer 1:

CoreText不能做的下降帽,因为它包含一个由字形运行的线路。 将一滴帽将覆盖不被支持多行。

为了达到这种效果,你将不得不单独绘制帽,然后绘制文本的其余部分,围绕它去的路径。

长话短说:在的UILabel,可能不可能的,但工作与CoreText公平一点。

与CoreText做的步骤如下:

  • 创建一个字符a framesetter。
  • 得到它的边界
  • 创建备件出下沉的帧中的路径
  • 创建一个framesetter与此路径中剩余的字符
  • 绘制第一个字形
  • 借鉴休息


Answer 2:

正如其他人所提到的,这是不可能的,只有做到这一点NSAttributedString 。 尼古拉有正确的方法,使用CTFrameSetters 。 但是, 可以告诉framesetter呈现在特定区域(即由CGPath定义的)文本。

你必须创建2个framesetters,一个用于下沉和其他文本的其余部分。

然后,你抢下沉的框架,并建立一个CGPathRef围绕下沉的帧的空间中运行。

然后,您呈现两个framesetters到您的视图。

我创建了一个名为DropCapView一个对象,它是UIView的子类的样本项目。 这种观点使第一个字符和环绕它剩余的文本。

它看起来像这样:

有相当多的步骤,所以我添加了一个链接到托管例如GitHub的项目。 有项目,这将帮助你前进的意见。

在GitHub上DropCap项目

你必须玩的形状textBox元素(即CGPathRef)周围的视图的边缘填充,并且它拧紧下沉字母为好。

下面是绘制方法的胆量:

- (void)drawRect:(CGRect)rect {
    //make sure that all the variables exist and are non-nil
    NSAssert(_text != nil, @"text is nil");
    NSAssert(_textColor != nil, @"textColor is nil");
    NSAssert(_fontName != nil, @"fontName is nil");
    NSAssert(_dropCapFontSize > 0, @"dropCapFontSize is <= 0");
    NSAssert(_textFontSize > 0, @"textFontSize is <=0");

    //convert the text aligment from NSTextAligment to CTTextAlignment
    CTTextAlignment ctTextAlignment = NSTextAlignmentToCTTextAlignment(_textAlignment);

    //create a paragraph style
    CTParagraphStyleSetting paragraphStyleSettings[] = { {
            .spec = kCTParagraphStyleSpecifierAlignment,
            .valueSize = sizeof ctTextAlignment,
            .value = &ctTextAlignment
        }
    };

    CFIndex settingCount = sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings;
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, settingCount);

    //create two fonts, with the same name but differing font sizes
    CTFontRef dropCapFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _dropCapFontSize, NULL);
    CTFontRef textFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _textFontSize, NULL);

    //create a dictionary of style elements for the drop cap letter
    NSDictionary *dropCapDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                (__bridge id)dropCapFontRef, kCTFontAttributeName,
                                _textColor.CGColor, kCTForegroundColorAttributeName,
                                style, kCTParagraphStyleAttributeName,
                                @(_dropCapKernValue) , kCTKernAttributeName,
                                nil];
    //convert it to a CFDictionaryRef
    CFDictionaryRef dropCapAttributes = (__bridge CFDictionaryRef)dropCapDict;

    //create a dictionary of style elements for the main text body
    NSDictionary *textDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                 (__bridge id)textFontRef, kCTFontAttributeName,
                                 _textColor.CGColor, kCTForegroundColorAttributeName,
                                 style, kCTParagraphStyleAttributeName,
                                 nil];
    //convert it to a CFDictionaryRef
    CFDictionaryRef textAttributes = (__bridge CFDictionaryRef)textDict;

    //clean up, because the dictionaries now have copies
    CFRelease(dropCapFontRef);
    CFRelease(textFontRef);
    CFRelease(style);

    //create an attributed string for the dropcap
    CFAttributedStringRef dropCapString = CFAttributedStringCreate(kCFAllocatorDefault,
                                                                   (__bridge CFStringRef)[_text substringToIndex:1],
                                                                   dropCapAttributes);

    //create an attributed string for the text body
    CFAttributedStringRef textString = CFAttributedStringCreate(kCFAllocatorDefault,
                                                                (__bridge CFStringRef)[_text substringFromIndex:1],
                                                                   textAttributes);

    //create an frame setter for the dropcap
    CTFramesetterRef dropCapSetter = CTFramesetterCreateWithAttributedString(dropCapString);

    //create an frame setter for the dropcap
    CTFramesetterRef textSetter = CTFramesetterCreateWithAttributedString(textString);

    //clean up
    CFRelease(dropCapString);
    CFRelease(textString);

    //get the size of the drop cap letter
    CFRange range;
    CGSize maxSizeConstraint = CGSizeMake(200.0f, 200.0f);
    CGSize dropCapSize = CTFramesetterSuggestFrameSizeWithConstraints(dropCapSetter,
                                                                      CFRangeMake(0, 1),
                                                                      dropCapAttributes,
                                                                      maxSizeConstraint,
                                                                      &range);

    //create the path that the main body of text will be drawn into
    //i create the path based on the dropCapSize
    //adjusting to tighten things up (e.g. the *0.8,done by eye)
    //to get some padding around the edges of the screen
    //you could go to +5 (x) and self.frame.size.width -5 (same for height)
    CGMutablePathRef textBox = CGPathCreateMutable();
    CGPathMoveToPoint(textBox, nil, dropCapSize.width, 0);
    CGPathAddLineToPoint(textBox, nil, dropCapSize.width, dropCapSize.height * 0.8); 
    CGPathAddLineToPoint(textBox, nil, 0, dropCapSize.height * 0.8);
    CGPathAddLineToPoint(textBox, nil, 0, self.frame.size.height);
    CGPathAddLineToPoint(textBox, nil, self.frame.size.width, self.frame.size.height);
    CGPathAddLineToPoint(textBox, nil, self.frame.size.width, 0);
    CGPathCloseSubpath(textBox);

    //create a transform which will flip the CGContext into the same orientation as the UIView
    CGAffineTransform flipTransform = CGAffineTransformIdentity;
    flipTransform = CGAffineTransformTranslate(flipTransform,
                                               0,
                                               self.bounds.size.height);
    flipTransform = CGAffineTransformScale(flipTransform, 1, -1);

    //invert the path for the text box
    CGPathRef invertedTextBox = CGPathCreateCopyByTransformingPath(textBox,
                                                                   &flipTransform);
    CFRelease(textBox);

    //create the CTFrame that will hold the main body of text
    CTFrameRef textFrame = CTFramesetterCreateFrame(textSetter,
                                                    CFRangeMake(0, 0),
                                                    invertedTextBox,
                                                    NULL);
    CFRelease(invertedTextBox);
    CFRelease(textSetter);

    //create the drop cap text box
    //it is inverted already because we don't have to create an independent cgpathref (like above)
    CGPathRef dropCapTextBox = CGPathCreateWithRect(CGRectMake(_dropCapKernValue/2.0f,
                                                               0,
                                                               dropCapSize.width,
                                                               dropCapSize.height),
                                                    &flipTransform);
    CTFrameRef dropCapFrame = CTFramesetterCreateFrame(dropCapSetter,
                                                       CFRangeMake(0, 0),
                                                       dropCapTextBox,
                                                       NULL);
    CFRelease(dropCapTextBox);
    CFRelease(dropCapSetter);

    //draw the frames into our graphic context
    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextConcatCTM(gc, flipTransform);
        CTFrameDraw(dropCapFrame, gc);
        CTFrameDraw(textFrame, gc);
    } CGContextRestoreGState(gc);
    CFRelease(dropCapFrame);
    CFRelease(textFrame);
}

PS这个带有一些灵感源自: https://stackoverflow.com/a/9272955/1218605



Answer 3:

不,这不能用做NSAttributedString和标准字符串只绘制。

由于下沉是一个段落的财产CTParagraphStyle必须包含有关下沉的信息。 在唯一的财产CTParagraphStyle影响段落的开始缩进kCTParagraphStyleSpecifierFirstLineHeadIndent ,但只影响第一行。

只是有没有办法告诉CTFramesetter如何计算开始第二多行。

唯一的办法就是定义自己的属性,并编写代码来绘制使用字符串CTFramesetterCTTypesetter是承认这一点自定义属性。



Answer 4:

不是一个完美的解决方案,但你应该给DTCoreText一个尝试,使您的正常NSString作为一个formatted HTML 。 在HTML就可以“下沉”的信。



Answer 5:

如果你使用一个UITextView可以使用textView.textContainer.exclusionPaths作为丹妮P建议在这里 。

实施例中夫特:

class WrappingTextVC: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    let textView = UITextView()
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.text = "ropcap example. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris aliquam vulputate ex. Fusce interdum ultricies justo in tempus. Sed ornare justo in purus dignissim, et rutrum diam pulvinar. Quisque tristique eros ligula, at dictum odio tempor sed. Fusce non nisi sapien. Donec libero orci, finibus ac libero ac, tristique pretium ex. Aenean eu lorem ut nulla elementum imperdiet. Ut posuere, nulla ut tincidunt viverra, diam massa tincidunt arcu, in lobortis erat ex sed quam. Mauris lobortis libero magna, suscipit luctus lacus imperdiet eu. Ut non dignissim lacus. Vivamus eget odio massa. Aenean pretium eget erat sed ornare. In quis tortor urna. Quisque euismod, augue vel pretium suscipit, magna diam consequat urna, id aliquet est ligula id eros. Duis eget tristique orci, quis porta turpis. Donec commodo ullamcorper purus. Suspendisse et hendrerit mi. Nulla pellentesque semper nibh vitae vulputate. Pellentesque quis volutpat velit, ut bibendum magna. Morbi sagittis, erat rutrum  Suspendisse potenti. Nulla facilisi. Praesent libero est, tincidunt sit amet tempus id, blandit sit amet mi. Morbi sed odio nunc. Mauris lobortis elementum orci, at consectetur nisl egestas a. Pellentesque vel lectus maximus, semper lorem eget, accumsan mi. Etiam semper tellus ac leo porta lobortis."
    textView.backgroundColor = .lightGray
    textView.textColor = .black
    view.addSubview(textView)

    textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
    textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
    textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
    textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40).isActive = true

    let dropCap = UILabel()
    dropCap.text = "D"
    dropCap.font = UIFont.boldSystemFont(ofSize: 60)
    dropCap.backgroundColor = .lightText
    dropCap.sizeToFit()
    textView.addSubview(dropCap)

    textView.textContainer.exclusionPaths = [UIBezierPath(rect: dropCap.frame)]
  }
}

结果:

在github完整的示例



文章来源: Drop cap with NSAttributedString