我目前正在尝试在UITextView
. 是否可以在不使用 Web 视图的情况下以类似于 html 中的 < rt> 标记的方式在汉字上方显示假名(如下所示)?
涉及大量文本处理,因此我不能简单地使用 Web 视图。在 iOS8 中,CTRubyAnnotationRef
添加了但没有文档(我欢迎任何示例),而且我还担心与 iOS7 缺乏兼容性。我认为可以使用 显示上面的假名NSAttributedString
,但目前还不能。
我目前正在尝试在UITextView
. 是否可以在不使用 Web 视图的情况下以类似于 html 中的 < rt> 标记的方式在汉字上方显示假名(如下所示)?
涉及大量文本处理,因此我不能简单地使用 Web 视图。在 iOS8 中,CTRubyAnnotationRef
添加了但没有文档(我欢迎任何示例),而且我还担心与 iOS7 缺乏兼容性。我认为可以使用 显示上面的假名NSAttributedString
,但目前还不能。
更新 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' 字符的情况下将具有的行数。
我正在努力解决这些问题,但感谢您的帮助。
最后,我创建了一个获取汉字位置并每次在汉字上方创建标签的函数。它很脏,但它是确保与 iOS7 兼容的唯一方法。
但是我不得不提一下现在你可以在 GitHub 上找到的 CTRubyAnnotationRef 的非凡示例,例如:http ://dev.classmethod.jp/references/ios8-ctrubyannotationref/ 和 https://github.com/shinjukunian/SimpleFurigana
祝大家好运!