🪜

SwiftUIでUIViewRepresentableを使わずCGContextに描く

2023/02/19に公開

SwiftUIでCGContextに描く方法はCanvas

CGContextを使ってCoreGraphics系の処理だったりCoreText系の処理だったりをしたいとき、UIViewRepresentableを使ってUIViewをホスティングすることがあります。

UIViewベースで既に作ってあるならそれで良いですが、これから作ったり、いっそSwiftUIだけで作り直したりするならCanvasがいいですね。
CanvasのwithCGContextに渡すクロージャではCGContextが使えます。

var body: some View {
    Canvas { context, size in
        context.withCGContext(content: { cgContext in
	    draw(context: cgContext, size: size)
	})
    }
}

func draw(context: CGContext, size: CGSize) {
    // 描く処理
}

人のコードを勝手にCanvas化

試しに、UIViewRepresentableを使って書かれた以下の記事のコードを勝手にCanvas化してみます。

https://qiita.com/fuziki/items/b31055a69330a3ce55a5

ContentView.swift
import SwiftUI
import UIKit

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

struct TategakiText: View {
    var text: String
    
    var body: some View {
        Canvas { context, size in
            context.withCGContext(content: { cgContext in
                draw(context: cgContext, size: size)
            })
        }
    }
    
    func draw(context: CGContext, size: CGSize) {
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: 0, y: -size.height)
        let font = UIFont(name: "HiraMinProN-W6", size: 25)
        let baseAttributes: [NSAttributedString.Key : Any] = [
            .verticalGlyphForm: true,
            NSAttributedString.Key.font: font!]
        let attributedText = NSMutableAttributedString(string: text , attributes: baseAttributes)
        let setter = CTFramesetterCreateWithAttributedString(attributedText)
        let path = CGPath(rect: CGRect(origin:CGPointZero, size: size), transform: nil)
        let frameAttrs = [
            kCTFrameProgressionAttributeName: CTFrameProgression.rightToLeft.rawValue,
        ]
        let ct = CTFramesetterCreateFrame(setter, CFRangeMake(0, 0), path, frameAttrs as CFDictionary)
        
        CTFrameDraw(ct, context)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

できあがりイメージは以下のようになります。

Discussion