1

我目前正在尝试在UITextView. 是否可以在不使用 Web 视图的情况下以类似于 html 中的 < rt> 标记的方式在汉字上方显示假名(如下所示)?

在此处输入图像描述

涉及大量文本处理,因此我不能简单地使用 Web 视图。在 iOS8 中,CTRubyAnnotationRef添加了但没有文档(我欢迎任何示例),而且我还担心与 iOS7 缺乏兼容性。我认为可以使用 显示上面的假名NSAttributedString,但目前还不能。

4

2 回答 2

3

更新 Swift 5.1

此解决方案是预览答案的更新,让您可以使用拼音指南编写亚洲句子,使用字符串中的模式。

让我们从处理字符串开始。

这 4 个扩展允许您在字符串中注入 ruby​​ 注释。

createRuby() 函数检查字符串的一个模式,它是:|用汉字写的单词《拼音指南》。

例子:

|红玉《ルビー》</p>

|成功《せいこう》するかどうかは、きみの|努力《どりょく》に|系《かか》る。等等。

重要的是遵循模式。

extension String {
// 文字列の範囲
private var stringRange: NSRange {
    return NSMakeRange(0, self.utf16.count)
}

// 特定の正規表現を検索
private func searchRegex(of pattern: String) -> NSTextCheckingResult? {
    do {
        let patternToSearch = try NSRegularExpression(pattern: pattern)
        return patternToSearch.firstMatch(in: self, range: stringRange)
    } catch { return nil }
}

// 特定の正規表現を置換
private func replaceRegex(of pattern: String, with templete: String) -> String {
    do {
        let patternToReplace = try NSRegularExpression(pattern: pattern)
        return patternToReplace.stringByReplacingMatches(in: self, range: stringRange, withTemplate: templete)
    } catch { return self }
}

// ルビを生成
func createRuby() -> NSMutableAttributedString {
    let textWithRuby = self
        // ルビ付文字(「|紅玉《ルビー》」)を特定し文字列を分割
        .replaceRegex(of: "(|.+?《.+?》)", with: ",$1,")
        .components(separatedBy: ",")
        // ルビ付文字のルビを設定
        .map { component -> NSAttributedString in
            // ベース文字(漢字など)とルビをそれぞれ取得
            guard let pair = component.searchRegex(of: "|(.+?)《(.+?)》") else {
                return NSAttributedString(string: component)
            }
            let component = component as NSString
            let baseText = component.substring(with: pair.range(at: 1))
            let rubyText = component.substring(with: pair.range(at: 2))

            // ルビの表示に関する設定
            let rubyAttribute: [CFString: Any] =  [
                kCTRubyAnnotationSizeFactorAttributeName: 0.5,
                kCTForegroundColorAttributeName: UIColor.darkGray
            ]
            let rubyAnnotation = CTRubyAnnotationCreateWithAttributes(
                .auto, .auto, .before, rubyText as CFString, rubyAttribute as CFDictionary
            )


            return NSAttributedString(string: baseText, attributes: [kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation])
        }
        // 分割されていた文字列を結合
        .reduce(NSMutableAttributedString()) { $0.append($1); return $0 }
    return textWithRuby
}
}

红宝石标签:大问题

您可能知道,Apple 在 iOS 8 中引入了 ruby​​ 注释,如属性字符串的属性,如果您确实使用 ruby​​ 注释创建了属性字符串并且做了:

myLabel.attributedText = attributedTextWithRuby

标签确实完美地显示了字符串没有问题。

不幸的是,Apple 从 iOS 11 中删除了此功能,因此,如果要显示 ruby​​ 注释,您必须覆盖方法 draw,以有效地绘制文本。为此,您必须使用 Core Text 来处理文本的行。

让我们显示代码

import UIKit

public enum TextOrientation { //1
    case horizontal
    case vertical
}

class RubyLabel: UILabel {

public var orientation:TextOrientation = .horizontal //2

// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
// ルビを表示
override func draw(_ rect: CGRect) {
    //super.draw(rect) //3


    // context allows you to manipulate the drawing context (i'm setup to draw or bail out)
    guard let context: CGContext = UIGraphicsGetCurrentContext() else {
        return
    }
    guard let string = self.text else { return }
    let attributed = NSMutableAttributedString(attributedString: string.createRuby()) //4

    let path = CGMutablePath()
    switch orientation { //5
        case .horizontal:
        context.textMatrix = CGAffineTransform.identity;
        context.translateBy(x: 0, y: self.bounds.size.height);
        context.scaleBy(x: 1.0, y: -1.0);
        path.addRect(self.bounds)
        attributed.addAttribute(NSAttributedString.Key.verticalGlyphForm, value: false, range: NSMakeRange(0, attributed.length))
        case .vertical:
        context.rotate(by: .pi / 2)
        context.scaleBy(x: 1.0, y: -1.0)
        //context.saveGState()
        //self.transform = CGAffineTransform(rotationAngle: .pi/2)
        path.addRect(CGRect(x: self.bounds.origin.y, y: self.bounds.origin.x, width: self.bounds.height, height: self.bounds.width))
        attributed.addAttribute(NSAttributedString.Key.verticalGlyphForm, value: true, range: NSMakeRange(0, attributed.length))
    }

    attributed.addAttributes([NSAttributedString.Key.font : self.font], range: NSMakeRange(0, attributed.length))

    let frameSetter = CTFramesetterCreateWithAttributedString(attributed)

    let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,attributed.length), path, nil)

    // Check need for truncate tail
    //6
    if (CTFrameGetVisibleStringRange(frame).length as Int) < attributed.length {
        // Required truncate
        let linesNS: NSArray  = CTFrameGetLines(frame)
        let linesAO: [AnyObject] = linesNS as [AnyObject]
        var lines: [CTLine] = linesAO as! [CTLine]

        let boundingBoxOfPath = path.boundingBoxOfPath

        let lastCTLine = lines.removeLast() //7

        let truncateString:CFAttributedString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, CTFrameGetFrameAttributes(frame))
        let truncateToken:CTLine = CTLineCreateWithAttributedString(truncateString)

        let lineWidth = CTLineGetTypographicBounds(lastCTLine, nil, nil, nil)
        let tokenWidth = CTLineGetTypographicBounds(truncateToken, nil, nil, nil)
        let widthTruncationBegins = lineWidth - tokenWidth
        if let truncatedLine = CTLineCreateTruncatedLine(lastCTLine, widthTruncationBegins, .end, truncateToken) {
            lines.append(truncatedLine)
        }

        var lineOrigins = Array<CGPoint>(repeating: CGPoint.zero, count: lines.count)
        CTFrameGetLineOrigins(frame, CFRange(location: 0, length: lines.count), &lineOrigins)
        for (index, line) in lines.enumerated() {
            context.textPosition = CGPoint(x: lineOrigins[index].x + boundingBoxOfPath.origin.x, y:lineOrigins[index].y + boundingBoxOfPath.origin.y)
            CTLineDraw(line, context)
        }
    }
    else {
        // Not required truncate
        CTFrameDraw(frame, context)
    }
}

//8
override var intrinsicContentSize: CGSize {
    let baseSize = super.intrinsicContentSize

    return CGSize(width: baseSize.width, height: baseSize.height * 1.0)

}

}

代码说明:

1-中日文字可以横写和竖写。此枚举使您可以在水平和垂直方向之间轻松切换。

2- 带有开关方向文本的公共变量。

3-此方法必须注释。原因是调用它你会看到两个重叠的字符串:一个没有属性,最后一个是你的属性字符串。

4-在这里调用 String 类扩展的方法,在该方法中使用 ruby​​ 注释创建属性字符串。

5-这个开关,如果需要在其中绘制文本的上下文,以防你想显示垂直文本。实际上,在此开关中,您添加了属性 NSAttributedString.Key.verticalGlyphForm ,以防垂直为真,否则为假。

6-这个'if'特别重要,因为标签,因为我们已经评论了方法'super.draw()'不知道如何管理长字符串。没有这个“如果”,标签认为只有一条线可以画。所以,你仍然有一个带有'...'的字符串,就像尾巴一样。在这个“如果”中,字符串被打破了更多的线条并正确绘制。

7-当你不给标签一些设置时,标签知道有更多的一行,但因为它无法计算出最后一段字符串是什么,执行时间出错,应用程序崩溃。所以要小心。但是,别担心!我们讨论正确的设置,以便稍后提供。

8-这对于使标签适合文本大小非常重要。

如何使用 RubyLabel

标签的使用非常简单:

import UIKit

class ViewController: UIViewController {
@IBOutlet weak var rubyLabel: RubyLabel! //1

override func viewDidLoad() {
    super.viewDidLoad()
    setUpLabel()
}

private func setUpLabel() {
    rubyLabel.text = "|成功《せいこう》するかどうかは、きみの|努力《どりょく》に|係《かか》る。|人々《ひとびと》の|生死《せいし》に|係《かか》る。"  //2
    //3
    rubyLabel.textAlignment = .left
    rubyLabel.font = .systemFont(ofSize: 20.0)
    rubyLabel.orientation = .horizontal
    rubyLabel.lineBreakMode = .byCharWrapping
}
}

代码说明:

1- 如果使用故事板或 xib 文件或创建标签,则将标签连接到 xib。

2-正如我所说,使用非常简单:这里像任何其他字符串一样使用 ruby​​ 模式分配字符串

3-这些设置是为使工作标签必须设置的设置。您可以通过代码或故事板/xib 设置

当心

如果您使用故事板/xib,如果您没有正确放置约束,则标签会在第 7 点为您提供错误。

结果

水平字符串

竖线

有效,但并不完美 正如您在屏幕截图中看到的那样,这个标签运作良好,但仍然存在一些问题。

1-垂直文本标签仍为水平形状;

2- 如果字符串包含 \n 以将字符串拆分为更多行,则标签仅显示字符串在没有 '\n' 字符的情况下将具有的行数。

我正在努力解决这些问题,但感谢您的帮助。

于 2019-01-21T16:55:41.153 回答
1

最后,我创建了一个获取汉字位置并每次在汉字上方创建标签的函数。它很脏,但它是确保与 iOS7 兼容的唯一方法。

但是我不得不提一下现在你可以在 GitHub 上找到的 CTRubyAnnotationRef 的非凡示例,例如:http ://dev.classmethod.jp/references/ios8-ctrubyannotationref/ 和 https://github.com/shinjukunian/SimpleFurigana

祝大家好运!

于 2014-11-23T00:02:11.810 回答