for my app, few month ago, i\'ve take the code from this site to use CTRubyAnnotation.
This code, with few changes to make work with swift 4, work perfectly.
I have same issue.
Your code as Swift4 works correctly on iOS10. It does not function correctly on iOS11.
It seems that there was a change in NSAttributedString of UILabel on iOS 11.
I wrote a general program to draw vertical writing characters in CoreText before. In CoreText, NSAttributedString works on iOS11 as well.
Although it is a provisional method, you can avoid this problem by using CoreText.
I written sample code. Since this draws characters directly to context, there is no need for UILabel, but draw with UILabel 's drawText for the moment.
import UIKit
protocol SimpleVerticalGlyphViewProtocol {
}
extension SimpleVerticalGlyphViewProtocol {
func drawContext(_ attributed:NSMutableAttributedString, textDrawRect:CGRect, isVertical:Bool) {
guard let context = UIGraphicsGetCurrentContext() else { return }
var path:CGPath
if isVertical {
context.rotate(by: .pi / 2)
context.scaleBy(x: 1.0, y: -1.0)
path = CGPath(rect: CGRect(x: textDrawRect.origin.y, y: textDrawRect.origin.x, width: textDrawRect.height, height: textDrawRect.width), transform: nil)
}
else {
context.textMatrix = CGAffineTransform.identity
context.translateBy(x: 0, y: textDrawRect.height)
context.scaleBy(x: 1.0, y: -1.0)
path = CGPath(rect: textDrawRect, transform: nil)
}
let framesetter = CTFramesetterCreateWithAttributedString(attributed)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, nil)
CTFrameDraw(frame, context)
}
}
And, Use it like below.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var furiganaLabel: CustomLabel!
override func viewDidLoad() {
super.viewDidLoad()
furiganaLabel.attributedText = Utility.sharedInstance.furigana(String: "|優勝《ゆうしょう》の|懸《か》かった|試合《しあい》。")
}
}
class CustomLabel: UILabel, SimpleVerticalGlyphViewProtocol {
//override func draw(_ rect: CGRect) { // if not has drawText, use draw UIView etc
override func drawText(in rect: CGRect) {
let attributed = NSMutableAttributedString(attributedString: self.attributedText!)
let isVertical = false // if Vertical Glyph, true.
attributed.addAttributes([NSAttributedStringKey.verticalGlyphForm: isVertical], range: NSMakeRange(0, attributed.length))
drawContext(attributed, textDrawRect: rect, isVertical: isVertical)
}
}
I added the CustomLabel class which adapted SimpleVerticalGlyphViewProtocol.
Please set the CustomLabel class as UILabel 's Custom Class on Storyboard.
Font size auto scale, and specify line number 0
Fixed font size, specify line number 4, baseline alignCenters, and last line truncated.
Font size auto scale, and specify line number 0 with Ruby
Update Swift 5.1
This solution is an update of preview answers and let you write Asian sentences with Phonetic Guide, using a pattern in the strings.
Let's start from handling string.
these 4 extension let you to inject in a string the ruby annotation.
the function createRuby() check the string a pattern, that it is: |word written in kanji《phonetic guide》.
Examples:
|紅玉《ルビー》
|成功《せいこう》するかどうかは、きみの|努力《どりょく》に|係《かか》る。 and so on.
the important thing is to follow the pattern.
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
}
}
Ruby Label: the big problem
As you maybe know, Apple has introduced in iOS 8 the ruby annotation like attribute for the attributedString, and if you did create the the attributed string with ruby annotation and did:
myLabel.attributedText = attributedTextWithRuby
the label did shows perfectly the string without problem.
From iOS 11, Apple unfortunately has removed this feature and, so, if want to show ruby annotation you have override the method draw, to effectively draw the text. To do this, you have to use Core Text to handle the text hand it's lines.
Let's show the code
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)
}
}
Code explanation:
1- Chinese and japanese text can be written in horizontal and vertical way. This enumeration let you switch in easy way between horizontal and vertical orietantation.
2- public variable with switch orientation text.
3- this method must be commented. the reason is that call it you see two overlapping strings:one without attributes, last your attributed string.
4- here call the method of String class extension in which you create the attributed string with ruby annotation.
5- This switch, rotate if need the context in which draw your text in case you want show vertical text. In fact in this switch you add the attribute NSAttributedString.Key.verticalGlyphForm that in case vertical is true, false otherwise.
6- This 'if' is particular important because, the label, cause we had commented the method 'super.draw()' doesn't know how to manage a long string. without this 'if', the label thinks to have only one line to draw. And so, you still to have a string with '...' like tail. In this 'if' the string is broken in more line and drawing correctly.
7- When you don't give to label some settings, the label knows to have more one line but because it can't calculate what is the showable last piece of string, give error in execution time and the app goes in crash. So be careful. But, don't worry! we talk about the right settings to give it later.
8- this is very important to fit the label to text's size.
How to use the RubyLabel
the use of the label is very simple:
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
}
}
Code Explanation:
1- connect label to xib if use storyboard or xib file or create label.
2- as I say, the use is very simple: here assign the string with ruby pattern like any other string
3- these setting are the setting to have to set to make work the label. You can set via code or via storyboard/xib
Be careful
if you use storyboard/xib, if you don't put correctly the constraints, the label give you the error at point n° 7.
Result
Works, but not perfect As you can see by screenshot, this label work well but still has some problem.
1- with vertical text the label still in horizontal shape;
2- if the string contains \n to split the string in more lines, the label shows only the number of lines that the string would have had if was without the '\n' character.
I'm working for fix these problem, but your help is appreciated.
The above code works perfectly but there is one problem. Overriding the method drawText(in rect: CGRect)
, it loses every benefit of managing of text that is done by UILabel. In fact, the text isn't cut when the text goes out of size of the label.
this is the result of code:
CustomTableViewCell
import UIKit
class CustomTableViewCell: UITableViewCell {
let userDefaults = UserDefaults.standard
var japaneseKanji = ""{
didSet{
if japaneseKanji != oldValue {
japaneseKanjiLabel.attributedText = Utility.sharedInstance.furigana(String: japaneseKanji)
}
}
}
var japaneseRomaji = ""{
didSet{
if japaneseRomaji != oldValue {
japaneseRomajiLabel.text = japaneseRomaji
}
}
}
var italianText = ""{
didSet{
if italianText != oldValue {
italianLabel.text = italianText
}
}
}
var englishText = ""{
didSet{
if englishText != oldValue {
englishLabel.text = englishText
}
}
}
private var japaneseImage = UIImageView()
private var romajiImage = UIImageView()
private var italianImage = UIImageView()
private var englishImage = UIImageView()
private var japaneseKanjiLabel = GQAsianLabel()
//private var japaneseKanjiLabel = UILabel()
private var japaneseRomajiLabel = UILabel()
private var italianLabel = UILabel()
private var englishLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
let japaneseImageRect = CGRect(x: 16, y: 14, width: 25, height: 25)
japaneseImage = UIImageView(frame: japaneseImageRect)
japaneseImage.translatesAutoresizingMaskIntoConstraints = false
japaneseImage.image = UIImage(named: "jp")
self.contentView.addSubview(japaneseImage)
let romajiImageRect = CGRect(x: 16, y: 50, width: 25, height: 25)
romajiImage = UIImageView(frame: romajiImageRect)
romajiImage.translatesAutoresizingMaskIntoConstraints = false
romajiImage.image = UIImage(named: "romaji")
self.contentView.addSubview(romajiImage)
let italianImageRect = CGRect(x: 16, y: 86, width: 25, height: 25)
italianImage = UIImageView(frame: italianImageRect)
italianImage.translatesAutoresizingMaskIntoConstraints = false
italianImage.image = UIImage(named: "it")
self.contentView.addSubview(italianImage)
let japaneseKanjiLabelRect = CGRect(x: 62, y: 8, width: 280, height: 46)
japaneseKanjiLabel = GQAsianLabel(frame: japaneseKanjiLabelRect)
japaneseKanjiLabel.isVertical = false
//japaneseKanjiLabel = UILabel(frame: japaneseKanjiLabelRect)
japaneseKanjiLabel.numberOfLines = 0
japaneseKanjiLabel.translatesAutoresizingMaskIntoConstraints = false
japaneseKanjiLabel.font = UIFont(name: "YuKyo_Yoko-Medium", size: 17)
japaneseKanjiLabel.sizeToFit()
japaneseKanjiLabel.backgroundColor = UIColor.brown
self.contentView.addSubview(japaneseKanjiLabel)
let japaneseRomajiLabelRect = CGRect(x: 62, y: 52, width: 280, height: 21)
japaneseRomajiLabel = UILabel(frame: japaneseRomajiLabelRect)
japaneseRomajiLabel.textAlignment = .left
japaneseRomajiLabel.numberOfLines = 0
japaneseRomajiLabel.translatesAutoresizingMaskIntoConstraints = false
japaneseRomajiLabel.font = UIFont(name: "HelveticaNeueLTPro-Lt", size: 14)
self.contentView.addSubview(japaneseRomajiLabel)
let italianLabelRect = CGRect(x: 62, y: 90, width: 280, height: 21)
italianLabel = UILabel(frame: italianLabelRect)
italianLabel.textAlignment = .left
italianLabel.numberOfLines = 0;
italianLabel.translatesAutoresizingMaskIntoConstraints = false
italianLabel.font = UIFont(name: "HelveticaNeueLTPro-Lt", size: 14)
self.contentView.addSubview(italianLabel)
let englishImageRect = CGRect(x: 16, y: 122, width: 25, height: 25)
englishImage = UIImageView(frame: englishImageRect)
englishImage.translatesAutoresizingMaskIntoConstraints = false
englishImage.image = UIImage(named: "en")
self.contentView.addSubview(englishImage)
let englishLabelRect = CGRect(x: 62, y: 138, width: 280, height: 21)
englishLabel = UILabel(frame: englishLabelRect)
englishLabel.textAlignment = .left
englishLabel.numberOfLines = 0
englishLabel.translatesAutoresizingMaskIntoConstraints = false
englishLabel.font = UIFont(name: "HelveticaNeueLTPro-Lt", size: 13)
self.contentView.addSubview(englishLabel)
let englishLanguage = userDefaults.object(forKey: "EnglishLang") as! String
let isThereEnglish = userDefaults.object(forKey: "isThereEnglish") as! String
let viewDictionary = ["japaneseImage":japaneseImage, "romajiImage":romajiImage, "italianImage":italianImage, "englishImage":englishImage, "kanjiLabel":japaneseKanjiLabel, "romajiLabel":japaneseRomajiLabel, "italianLabel":italianLabel, "englishLabel":englishLabel] as [String : AnyObject]
let japaneseImage_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[japaneseImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let japaneseImage_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[japaneseImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let japaneseImage_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[japaneseImage]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let japaneseKanji_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[japaneseImage]-21-[kanjiLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let japaneseKanji_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[kanjiLabel]-20-[romajiLabel]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraint(NSLayoutConstraint.init(item: japaneseImage, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: japaneseKanjiLabel, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0))
let romajiImage_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[romajiImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let romajiImage_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[romajiImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let romajiImage_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[romajiImage]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let romajiLabel_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[romajiImage]-21-[romajiLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let romajiLabel_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[romajiLabel]-20-[italianLabel]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraint(NSLayoutConstraint.init(item: romajiImage, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: japaneseRomajiLabel, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0))
let italianImage_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[italianImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let italianImage_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[italianImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let italianImage_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[italianImage]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let italianLabel_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[italianImage]-21-[italianLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraint(NSLayoutConstraint.init(item: italianImage, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: italianLabel, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0))
let englishImage_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[englishImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let englishImage_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[englishImage(25)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let englishImage_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[englishImage]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
let englishLabel_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:[englishImage]-21-[englishLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraint(NSLayoutConstraint.init(item: englishImage, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: englishLabel, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0))
japaneseImage.addConstraints(japaneseImage_H)
japaneseImage.addConstraints(japaneseImage_V)
self.contentView.addConstraints(japaneseImage_POS_H)
self.contentView.addConstraints(japaneseKanji_POS_H)
self.contentView.addConstraints(japaneseKanji_POS_V)
romajiImage.addConstraints(romajiImage_H)
romajiImage.addConstraints(romajiImage_V)
self.contentView.addConstraints(romajiImage_POS_H)
self.contentView.addConstraints(romajiLabel_POS_H)
self.contentView.addConstraints(romajiLabel_POS_V)
italianImage.addConstraints(italianImage_H)
italianImage.addConstraints(italianImage_V)
self.contentView.addConstraints(italianImage_POS_H)
self.contentView.addConstraints(italianLabel_POS_H)
englishImage.addConstraints(englishImage_H)
englishImage.addConstraints(englishImage_V)
self.contentView.addConstraints(englishImage_POS_H)
self.contentView.addConstraints(englishLabel_POS_H)
if englishLanguage == "ON" && isThereEnglish == "True" {
englishImage.alpha = 1.0
englishLabel.alpha = 1.0
let englishLabel_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[italianLabel]-20-[englishLabel]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraints(englishLabel_POS_V)
}else{
englishImage.alpha = 0.0
englishLabel.alpha = 0.0
let italianLabel_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[italianLabel]-10-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
self.contentView.addConstraints(italianLabel_POS_V)
}
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
GQAsianLabel
import UIKit
protocol SimpleVerticalGlyphViewProtocol {
}
extension SimpleVerticalGlyphViewProtocol {
func drawContext(_ attributed:NSMutableAttributedString, textDrawRect:CGRect, isVertical:Bool) {
guard let context = UIGraphicsGetCurrentContext() else { return }
var path:CGPath
if isVertical {
context.rotate(by: .pi / 2)
context.scaleBy(x: 1.0, y: -1.0)
path = CGPath(rect: CGRect(x: textDrawRect.origin.y, y: textDrawRect.origin.x, width: textDrawRect.height, height: textDrawRect.width), transform: nil)
}
else {
context.textMatrix = CGAffineTransform.identity
context.translateBy(x: 0, y: textDrawRect.height)
context.scaleBy(x: 1.0, y: -1.0)
path = CGPath(rect: textDrawRect, transform: nil)
}
let fontRef = UIFont(name: "Hiragino Sans", size: 17)
attributed.addAttribute(kCTFontAttributeName as NSAttributedStringKey, value: fontRef!, range:NSMakeRange(0, attributed.length))
let framesetter = CTFramesetterCreateWithAttributedString(attributed)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, nil)
CTFrameDraw(frame, context)
}
}
class GQAsianLabel: UILabel, SimpleVerticalGlyphViewProtocol {
var isVertical = false
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
let attributed = NSMutableAttributedString(attributedString: self.attributedText!)
//let isVertical = false // if Vertical Glyph, true.
attributed.addAttributes([NSAttributedStringKey.verticalGlyphForm: isVertical], range: NSMakeRange(0, attributed.length))
drawContext(attributed, textDrawRect: rect, isVertical: isVertical)
}
/*
override func drawText(in rect: CGRect) {
let attributed = NSMutableAttributedString(attributedString: self.attributedText!)
//let isVertical = false // if Vertical Glyph, true.
attributed.addAttributes([NSAttributedStringKey.verticalGlyphForm: isVertical], range: NSMakeRange(0, attributed.length))
drawContext(attributed, textDrawRect: rect, isVertical: isVertical)
}
*/
}
Utility class with furigana function
import UIKit
extension String {
func find(pattern: String) -> NSTextCheckingResult? {
do {
let re = try NSRegularExpression(pattern: pattern, options: [])
return re.firstMatch(
in: self,
options: [],
range: NSMakeRange(0, self.utf16.count))
} catch {
return nil
}
}
func replace(pattern: String, template: String) -> String {
do {
let re = try NSRegularExpression(pattern: pattern, options: [])
return re.stringByReplacingMatches(
in: self,
options: [],
range: NSMakeRange(0, self.utf16.count),
withTemplate: template)
} catch {
return self
}
}
}
class Utility: NSObject {
class var sharedInstance: Utility {
struct Singleton {
static let instance = Utility()
}
return Singleton.instance
}
func furigana(String:String) -> NSMutableAttributedString {
let attributed =
String
.replace(pattern: "(|.+?《.+?》)", template: ",$1,")
.components(separatedBy: ",")
.map { x -> NSAttributedString in
if let pair = x.find(pattern: "|(.+?)《(.+?)》") {
let string = (x as NSString).substring(with: pair.range(at: 1))
let ruby = (x as NSString).substring(with: pair.range(at: 2))
var text: [Unmanaged<CFString>?] = [Unmanaged<CFString>.passRetained(ruby as CFString) as Unmanaged<CFString>, .none, .none, .none]
let annotation = CTRubyAnnotationCreate(CTRubyAlignment.auto, CTRubyOverhang.auto, 0.5, &text[0])
return NSAttributedString(
string: string,
attributes: [kCTRubyAnnotationAttributeName as NSAttributedStringKey: annotation])
} else {
return NSAttributedString(string: x, attributes: nil)
}
}
.reduce(NSMutableAttributedString()) { $0.append($1); return $0 }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1
paragraphStyle.lineSpacing = 0
attributed.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, (attributed.length)))
return attributed
}
}