19

プロパティのみUILabelを使用して、最初の文字をドロップ キャップしたいと思います。attributedText NSAttributedStringこのような:


(出典:interpretationbydesign.com

最初の文字の範囲のベースラインを負の値に調整して実験しましたが、最初の文字の上部を最初の行の残りの上部に揃えるために機能します。しかし、他の行をドロップ キャップ文字の右側に流す方法が見つかりませんでした。

これは を使用して解決できますかNSAttributedString only? または、文字列を分割し、Core Text を使用して自分でレンダリングする必要がありますか?

4

5 に答える 5

15

他のみんなが言ったように、これだけでこれを行うことはできませんNSAttributedString。ニコライは、 を使用して正しいアプローチをしていCTFrameSettersます。ただし、フレームセッターに特定の領域 (つまり、CGPath によって定義される) にテキストをレンダリングするように指示することは可能です。

2 つのフレームセッターを作成する必要があります。1 つはドロップ キャップ用で、もう 1 つは残りのテキスト用です。

次に、ドロップ キャップのフレームをつかみ、ドロップ キャップCGPathRefのフレームのスペースの周りを走る を作成します。

次に、両方のフレームセッターをビューにレンダリングします。

UIView のサブクラスである DropCapView というオブジェクトを使用してサンプル プロジェクトを作成しました。このビューは最初の文字をレンダリングし、残りのテキストをその周りにラップします。

次のようになります。

iOSのドロップキャップ

かなりの数の手順があるため、例をホストしている 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からのインスピレーションがいくつかあります

于 2013-02-01T04:42:37.277 に答える
7

CoreText は、グリフ ランで構成される行で構成されているため、ドロップ キャップを実行できません。ドロップ キャップは、サポートされていない複数の行をカバーします。

この効果を実現するには、キャップを個別に描画してから、残りのテキストをパスで描画する必要があります。

簡単に言えば、UILabel では不可能ですが、可能ですが、CoreText ではかなりの作業が必要です。

CoreText でそれを行う手順は次のとおりです。

  • 単一のキャラクターのフレームセッターを作成します。
  • その限界を得る
  • ドロップ キャップのフレームを省くパスを作成する
  • このパスで残りの文字のフレームセッターを作成します
  • 最初のグリフを描く
  • 残りを描く
于 2013-01-15T06:42:34.717 に答える
4

UITextView を使用している場合は、Danny textView.textContainer.exclusionPathsP提案したようにここで使用できます。

スウィフトでの例:

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 の完全な例

于 2018-11-20T14:25:20.553 に答える
3

NSAttributedStringいいえ、これは標準の弦の描画のみでは実行できません。

ドロップ キャップは段落のプロパティであるため、ドロップ キャップCTParagraphStyleに関する情報を含める必要があります。CTParagraphStyle段落の先頭のインデントに影響するの唯一のプロパティはですkCTParagraphStyleSpecifierFirstLineHeadIndentが、最初の行のみに影響します。

CTFramesetter2 番目以降の行の先頭を計算する方法を伝える方法はありません。

唯一の方法は、独自の属性を定義し、このカスタム属性を使用CTFramesetterして文字列を描画するコードを記述することです。CTTypesetter

于 2013-01-25T11:31:58.480 に答える
1

完全な解決策ではありませんが、DTCoreTextを試して法線NSStringformatted HTML. HTML 内では、文字を「ドロップ キャップ」することができます。

于 2013-01-25T10:48:36.017 に答える