Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
12
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@fuziki

SwiftUIで縦書きText

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を書いていて、虚無な気持ちになりがちです。
電子書籍っぽいページめくりの話はこちら↓
https://fzkqi.hatenablog.com/entry/2020/04/18/230411

12
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
12
Help us understand the problem. What is going on with this article?