26
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUIで縦書きText

Last updated at Posted at 2020-04-18

SwiftUIで縦書きを実現するTategakiText

動作イメージ

  • textを渡すと縦書き表示されます。
  • カギかっこや、ビックリやハテナ記号、3点リーダーなども縦書きに合わせた表示になっています。
スクリーンショット 2020-04-18 19.12.49.png

ソースコード

SwiftUIで電子書籍っぽい動作

TategakiText

TategakiText

  • TategakiTextViewのswiftuiのwrapper
  • UIViewRepresentableを使って、UIViewのサブクラスをswiftuiで使っている
public struct TategakiText: UIViewRepresentable {
    public var text: String?

    public func makeUIView(context: Context) -> TategakiTextView {
        let uiView = TategakiTextView()
        uiView.isOpaque = false
        uiView.text = text
        return uiView
    }

    public func updateUIView(_ uiView: TategakiTextView, context: Context) {
        uiView.text = text
    }
}

TategakiTextView

  • UIViewのサブクラス。
  • CoreTextを使って縦書きでテキストを表示する。

CoreTextでテキストを表示する

  • CTFrameが上下反転表示されるので、CGContextで上下反転させて表示する。
public class TategakiTextView: UIView {    
    override public func draw(_ rect: CGRect) {
        guard let context:CGContext = UIGraphicsGetCurrentContext() else {
            return
        }
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: 0, y: -rect.height)

        ctFrame = textからCTFrameを作成する諸々
        
        CTFrameDraw(ctFrame, context)
    }
}

textからCTFrameを作成する諸々

  • verticalGlyphFormを有効にしたCTFramesetterを作成する。
        let baseAttributes: [NSAttributedString.Key : Any] = [
            .verticalGlyphForm: true,
        ]
        let attributedText = NSMutableAttributedString(string: text ?? "", attributes: baseAttributes)
        let setter = CTFramesetterCreateWithAttributedString(attributedText)
  • CTFrameProgression.rightToLeftの形式でCTFrameを作成する
        let path = CGPath(rect: rect, transform: nil)
        let frameAttrs = [
            kCTFrameProgressionAttributeName: CTFrameProgression.rightToLeft.rawValue,
        ]
        ctFrame = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs as CFDictionary)

全文

import UIKit
import CoreText
import SwiftUI

struct TategakiText_Previews: PreviewProvider {
    static var previews: some View {
        TategakiText(text: """
こんにちは、
わたしの名前はふじきです。
これはswiftuiで実装されています。
「どうやって実装してるのか」って?
それはQiitaの記事を読むとわかりますよ!
""").padding(40)
    }
}

public struct TategakiText: UIViewRepresentable {
    public var text: String?

    public func makeUIView(context: Context) -> TategakiTextView {
        let uiView = TategakiTextView()
        uiView.isOpaque = false
        uiView.text = text
        return uiView
    }

    public func updateUIView(_ uiView: TategakiTextView, context: Context) {
        uiView.text = text
    }
}

public class TategakiTextView: UIView {
    public var text: String? = nil {
        didSet {
            ctFrame = nil
        }
    }
    private var ctFrame: CTFrame? = nil
    
    override public func draw(_ rect: CGRect) {
        guard let context:CGContext = UIGraphicsGetCurrentContext() else {
            return
        }
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: 0, y: -rect.height)
        
        let baseAttributes: [NSAttributedString.Key : Any] = [
            .verticalGlyphForm: true,
            NSAttributedString.Key.font: UIFont.hiraMinProN_W6(size: 25),
        ]
        let attributedText = NSMutableAttributedString(string: text ?? "", attributes: baseAttributes)
        let setter = CTFramesetterCreateWithAttributedString(attributedText)
        let path = CGPath(rect: rect, transform: nil)
        let frameAttrs = [
            kCTFrameProgressionAttributeName: CTFrameProgression.rightToLeft.rawValue,
        ]
        let ct = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs as CFDictionary)
        ctFrame = ct
        
        CTFrameDraw(ct, context)
    }
}

終わりに

SwiftUIは、SwiftUIを書いていたはずが、気付いたらUIKitを書いていて、複雑な気持ちになりがちです。

26
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?