67

カスタムの角丸と文字背景色について質問UIViewです。

基本的に、カスタムUIViewで次のような効果を実現する必要があります(画像が添付されています-片側の角が丸くなっていることに注意してください): 背景のハイライト

私は使用するアプローチは次のように考えています:

  • Core Text を使用して、グリフ ランを取得します。
  • ハイライト範囲を確認します。
  • 現在のランがハイライト範囲内にある場合は、グリフ ランを描画する前に、角が丸く目的の塗りつぶし色で背景の四角形を描画します。
  • グリフ ランを描画します。

ただし、これが唯一の解決策であるかどうかはわかりません (さらに言えば、これが最も効率的な解決策であるかどうかもわかりません)。

を使用することUIWebViewはオプションではないため、カスタムで行う必要がありますUIView

私の質問は、これが使用するための最良のアプローチですか、そして私は正しい方向に進んでいますか? それとも、重要なことを見逃しているか、間違った方法で行っていますか?

4

4 に答える 4

69

上記の効果をなんとか達成できたので、同じ回答を投稿すると思いました。

これをより効果的にするための提案があれば、お気軽に投稿してください。あなたの答えを正しいものとしてマークします。:)

これを行うには、「カスタム属性」を に追加する必要がありますNSAttributedString

つまり、インスタンスに追加できるものである限り、任意のキーと値のペアを追加できるということですNSDictionary。システムがその属性を認識しない場合、何もしません。その属性のカスタム実装と動作を提供するのは、開発者の責任です。

この回答の目的のために@"MyRoundedBackgroundColor"、値が[UIColor greenColor].

以下の手順では、 がどのように処理を行うかについての基本的な理解が必要ですCoreText。フレーム/ライン/グリフ ラン/グリフなどとは何かを理解するには、Apple の Core Text Programming Guideを参照してください。

手順は次のとおりです。

  1. カスタム UIView サブクラスを作成します。
  2. を受け入れるためのプロパティを持っていますNSAttributedString
  3. そのインスタンスCTFramesetterを使用して を作成します。NSAttributedString
  4. drawRect:メソッドをオーバーライドする
  5. CTFrameからインスタンスを作成しますCTFramesetter
    1. CGPathRefを作成するには、 を指定する必要がありますCTFrame。テキストCGPathを描画するフレームと同じになるようにします。
  6. 現在のグラフィック コンテキストを取得し、テキスト座標系を反転します。
  7. を使用して、作成CTFrameGetLines(...)した のすべての行を取得しCTFrameます。
  8. を使用してCTFrameGetLineOrigins(...)、 のすべての線の起点を取得しますCTFrame
  9. ...for loopの配列内の各行に対して - を開始します。CTLine
  10. テキスト位置をCTLine使用の開始位置に設定しますCGContextSetTextPosition(...)
  11. CTLineGetGlyphRuns(...)からすべての Glyph Runs ( CTRunRef)を取得しCTLineます。
  12. 別の開始for loop- の配列内の各 glyphRun に対してCTRun...
  13. を使用してランの範囲を取得しますCTRunGetStringRange(...)
  14. を使用してタイポグラフィの境界を取得しCTRunGetTypographicBounds(...)ます。
  15. を使用してランの x オフセットを取得しCTLineGetOffsetForStringIndex(...)ます。
  16. runBounds前述の関数から返された値を使用して 、境界矩形を計算します (これを と呼びましょう)。
    1. 覚えておいてください-CTRunGetTypographicBounds(...)テキストの「上昇」と「下降」を格納するには、変数へのポインターが必要です。ランの高さを取得するには、それらを追加する必要があります。
  17. を使用して実行の属性を取得しますCTRunGetAttributes(...)
  18. 属性ディクショナリに属性が含まれているかどうかを確認します。
  19. アトリビュートが存在する場合は、ペイントする必要がある長方形の境界を計算します。
  20. コア テキストには、ベースラインに線の始点があります。テキストの最下点から最上点まで描画する必要があります。したがって、降下に合わせて調整する必要があります。
  21. したがって、ステップ 16 で計算した境界矩形から降下を減算します ( runBounds)。
  22. を取得したのでrunBounds、どの領域をペイントするかがわかりました。これで、CoreGraphis/UIBezierPathメソッドのいずれかを使用して、特定の丸みを帯びた角を持つ四角形を描画して塗りつぶすことができます。
    1. UIBezierPathbezierPathWithRoundedRect:byRoundingCorners:cornerRadii:特定の角を丸めることができる便利なクラス メソッドが呼び出されます。2 番目のパラメーターでビット マスクを使用してコーナーを指定します。
  23. 四角形を塗りつぶしたので、 を使用してグリフ ランを描画しますCTRunDraw(...)
  24. カスタム属性を作成したことで勝利を祝いましょう - ビールか何かを飲みましょう! :D

属性の範囲が複数回の実行にまたがっていることの検出に関しては、最初の実行で属性が検出されたときに、カスタム属性の有効範囲全体を取得できます。アトリビュートの最大有効範囲の長さが実行の長さよりも大きいことがわかった場合は、右側に鋭い角をペイントする必要があります (左から右へのスクリプトの場合)。さらに計算すると、次の行のハイライト コーナー スタイルも検出できます。:)

エフェクトのスクリーンショットを添付します。上部のボックスはUITextView、attributedText を設定した標準の です。下部のボックスは、上記の手順を使用して実装されたものです。両方の textView に同じ属性文字列が設定されています。 角が丸いカスタム属性

繰り返しますが、私が使用したものよりも優れたアプローチがある場合は、お知らせください。:D

これがコミュニティに役立つことを願っています。:)

乾杯!

于 2013-05-07T15:52:07.807 に答える
10

Apple API ドキュメントをカスタマイズNSLayoutManagerしてオーバーライドするだけですdrawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)

この方法では、自分で下線を引いたり、Swiftコード、

override func drawUnderline(forGlyphRange glyphRange: NSRange,
    underlineType underlineVal: NSUnderlineStyle,
    baselineOffset: CGFloat,
    lineFragmentRect lineRect: CGRect,
    lineFragmentGlyphRange lineGlyphRange: NSRange,
    containerOrigin: CGPoint
) {
    let firstPosition  = location(forGlyphAt: glyphRange.location).x

    let lastPosition: CGFloat

    if NSMaxRange(glyphRange) < NSMaxRange(lineGlyphRange) {
        lastPosition = location(forGlyphAt: NSMaxRange(glyphRange)).x
    } else {
        lastPosition = lineFragmentUsedRect(
            forGlyphAt: NSMaxRange(glyphRange) - 1,
            effectiveRange: nil).size.width
    }

    var lineRect = lineRect
    let height = lineRect.size.height * 3.5 / 4.0 // replace your under line height
    lineRect.origin.x += firstPosition
    lineRect.size.width = lastPosition - firstPosition
    lineRect.size.height = height

    lineRect.origin.x += containerOrigin.x
    lineRect.origin.y += containerOrigin.y

    lineRect = lineRect.integral.insetBy(dx: 0.5, dy: 0.5)

    let path = UIBezierPath(rect: lineRect)
    // let path = UIBezierPath(roundedRect: lineRect, cornerRadius: 3) 
    // set your cornerRadius
    path.fill()
}

次に、あなたを構築し、属性とNSAttributedStringを追加します。.underlineStyle.underlineColor

addAttributes(
    [
        .foregroundColor: UIColor.white,
        .underlineStyle: NSUnderlineStyle.single.rawValue,
        .underlineColor: UIColor(red: 51 / 255.0, green: 154 / 255.0, blue: 1.0, alpha: 1.0)
    ],
    range: range
)

それでおしまい!

結果

于 2020-04-29T02:44:25.780 に答える