ios中的Ruby text/Furigana

ios中的Ruby text/Furigana,ios,core-text,ruby-characters,Ios,Core Text,Ruby Characters,我目前正试图在UITextView上显示一些日语文本。是否可以在不使用web视图的情况下,以类似于html中的标记的方式在汉字(如下所示)上方显示furigana 涉及到很多文本处理,因此我不能简单地使用web视图。在iOS8中,添加了CTRubyAnnotationRef,但没有文档(我欢迎任何示例),我还担心与iOS7不兼容。我原以为可以使用NSAttributedString来显示上面的furigana,但到目前为止还不行。最后我创建了一个函数,该函数可以获取汉字的位置,并每次在汉字上方

我目前正试图在
UITextView
上显示一些日语文本。是否可以在不使用web视图的情况下,以类似于html中的标记的方式在汉字(如下所示)上方显示furigana


涉及到很多文本处理,因此我不能简单地使用web视图。在iOS8中,添加了
CTRubyAnnotationRef
,但没有文档(我欢迎任何示例),我还担心与iOS7不兼容。我原以为可以使用
NSAttributedString
来显示上面的furigana,但到目前为止还不行。

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

但是,我必须提到,您可以在GitHub上找到CTRubyAnnotationRef的显著示例,例如: 及


祝你们好运

更新Swift 5.1

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

让我们从处理字符串开始

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

函数createRuby()检查字符串a模式,确认它是:\\用汉字书写的单词《语音指南》

示例:

/紅玉《ルビー》

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

重要的是要遵循这个模式

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
}
}
红宝石标签:大问题

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

myLabel.attributedText = attributedTextWithRuby
标签确实完美地显示了字符串,没有问题

不幸的是,苹果从iOS 11中删除了这一功能,因此,如果想要显示ruby注释,你必须重写draw方法,以有效地绘制文本。要做到这一点,你必须使用核心文本来处理文本及其线条

让我们展示代码

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-如果使用故事板或xib文件或创建标签,则将标签连接到xib

2-正如我所说,用法非常简单:这里使用ruby模式指定字符串,就像其他字符串一样

3-这些设置是必须设置的设置,以使标签正常工作。您可以通过代码或序列图像板/xib进行设置

小心点

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

结果

有效,但并不完美 正如您从截图中看到的,这个标签工作得很好,但仍然存在一些问题

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

2-如果字符串包含\n要将字符串拆分为更多行的内容,则标签仅显示该字符串在没有“\n”字符的情况下的行数


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

您解决了这个问题吗?我也需要一个答案。我已经浪费了很多时间让CtruyanNotationRef在UITextView或UITextField中工作。当我自己使用CoreText绘制时效果很好,但使用ruby文本注释字符串并设置UITextView/UITextField的注释它的ed文本不显示ruby文本。哦,好吧,是时候在iOS8中采用您的方法了……您介意分享您的解决方案吗?我面临的挑战与您最初的问题相同,我无法让ruby文本在UITextView中工作,但它似乎适用于UILabel
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
}
}