{
"$type": "site.standard.document",
"content": [
{
"$type": "app.myblog.markdown",
"lang": "ja",
"markdown": "おはようございます。waturaです。新しいmac miniがほしいなぁと思っているんですが,やっぱり、独立した画面ほしいよね。机の上にもう1セットキーボードとかおくのいやだよね。とかって考えると、ほしいのはノートパソコンでは?となっています。\n\nnote Mobile Tech Talk #1で発表した内容になります。\n\n[**note Mobile Tech Talk #1 (2024/11/20 12:00〜)** _\\# 📝概要 note株式会社 AppチームによるモバイルアプリエンジニアLTイベントです。 noteのiOS / An_ _pieceofcake.connpass.com_](https://pieceofcake.connpass.com/event/335723/)[](https://pieceofcake.connpass.com/event/335723/)\n\n \n\n## ルビをふりたい\n\n**ルビをふりたい** noteでルビってどうやってふるんだろう?って検索しないといけないくらい、ルビの使い方がわからなかったんですが、noteでもちゃんとルビはふれるようです。\n\n[ルビ(ふりがな)をふる](https://www.help-note.com/hc/ja/articles/4406430353817-%E3%83%AB%E3%83%93-%E3%81%B5%E3%82%8A%E3%81%8C%E3%81%AA-%E3%82%92%E3%81%B5%E3%82%8B)\n\nアプリに入力補助がほしいな!と思いました。が閑話休題。\n\n## iOSアプリ上ではルビはふれるのだろうか?\n\n**結論:ふれるけど、完璧とはいいがたい。**さらに、**SwiftUIのみ**でルビをふる機能は2024年11月時点ではなさそうです。 \n表題が「SwiftUIでルビをふる」という記事なので、これで終了ですといきたいところですが,SwiftUIからUIKitなどを呼び出せるので,それをつかってルビをふるという方法を説明します。\n\nまず、AttributedStringにはルビAttributeはないようです。NSAttributedStringにもルビAttributeはないようです。CoreTextまでおりてくるとルビAttributeがあります。\n\n[**CTRubyAnnotationCreateWithAttributes(\\_:\\_:\\_:\\_:\\_:) | Apple Developer Documentation** _Creates an immutable ruby annotation object with the specifie_ _developer.apple.com_](https://developer.apple.com/documentation/coretext/1642000-ctrubyannotationcreatewithattrib)[](https://developer.apple.com/documentation/coretext/1642000-ctrubyannotationcreatewithattrib)\n\nCTRubyAnnotationCreateWithAttributes(::::\\_:)をつかうと\n\n* ルビとして表示する文字\n \n* 表示される位置や表示方法\n \n * ベースの文字幅に合わせて表示とか\n \n* ルビが長すぎるときの処理方法\n \n* ルビの表示位置(上下とか)\n \n\nなどが設定でき、ベースのフォントサイズに対する相対サイズでのサイズ指定や文字色の指定などができます。\n\nこのCTRubyAnnotationCreateの戻り値をNSAttributedStringのAttributeに指定してあげると、ルビ表示ができるようになります。\n\n```\nlet rubyAttribute: [CFString: Any] = [\n kCTRubyAnnotationSizeFactorAttributeName: 0.5\n]\n\nlet rubyAnnotation = CTRubyAnnotationCreateWithAttributes(\n .auto,\n .auto,\n .before,\n rubyText as CFString,\n rubyAttribute as CFDictionary\n)\n\nNSAttributedString(string: baseText, attributes: [\n kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation\n])\n```\n\n**簡単!これでUITextViewとかでルビが表示できます!**\n\nこれを、AttributedStringに変換してTextに渡してあげれば、SwiftUIでもルビが!って少し期待したのですが、だめでした。 \nAttributedStringにした時点で、AttributeからkCTRubyAnnotationAttributeNameが消失してしまうようでした。\n\n## SwiftUIアプリでルビを表示する\n\nざっくり、以下の4つの方法を試してみました。\n\n* SwiftUIは諦めてUIViewControllerでUITextView/UILabelを使う\n \n* UITextViewをUIViewRepresentableでくるんでSwiftUIから呼び出す\n \n* Canvasを使う\n \n* TextRendererを使う\n \n\n残念なことにどの方法もちゃんとSwiftUIだよ!といいきれる実装ではなく、実際に実装している内容はUIKitだったりCoreTextだったりします。 \n残念です。\n\n### 表示する文章\n\n**だれもが創作をはじめ、 \n続けられるようにする。**\n\n本来であれば、このnoteルビ記法をパースして表示できるようにしたほうがよかったのですが、簡単のためはぶいています。\n\n```\nfunc rubyAnnotation(text: String, ruby: String) -> NSAttributedString {\n let rubyAttribute: [CFString: Any] = [\n kCTRubyAnnotationSizeFactorAttributeName: 0.5\n ]\n let rubyAnnotation = CTRubyAnnotationCreateWithAttributes(\n .auto,\n .auto,\n .before,\n ruby as CFString,\n rubyAttribute as CFDictionary\n )\n\n return NSAttributedString(\n string: text,\n attributes:\n [\n kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation\n ]\n )\n}\n\nvar dummyAttributedString: NSAttributedString {\n let txt = NSMutableAttributedString(string: \"だれもが\")\n txt.append(rubyAnnotation(text: \"創作\", ruby: \"そうさく\"))\n txt.append(.init(string: \"をはじめ、\\n\"))\n txt.append(rubyAnnotation(text: \"続\", ruby: \"つづ\"))\n txt.append(.init(string: \"けられるようにする。\"))\n \n // ルビが範囲外に表示されてしまうので行間を広げる\n let paragraphStyle = NSMutableParagraphStyle()\n paragraphStyle.lineHeightMultiple = 1.5\n\n txt.addAttributes([\n .font: UIFont.systemFont(ofSize: 16),\n .paragraphStyle: paragraphStyle,\n ], range: .init(location: 0, length: txt.length))\n \n return txt\n}\n```\n\n### 1\\. SwiftUIは諦めてUIViewControllerでUITextView/UILabelを使う\n\nSwiftUIとUIKitは共存できます。なので、UIViewControllerで表示するというのも1手です。表示は以下のコードをAutoLayoutごにょごにょしたりして表示するだけです。 \nしかし、残念なことにただそのまま表示するだけではだめでした。\n\n```\nlet view = UITextView()\nview.attributedText = dummyAttributedString\n```\n\n \n\n\n\n単純に表示したパターン\n\n1行目はともかく2行目が1行目とかぶってしまい読めなくなっています。 \nまた、背景色を指定するとわかりやすいのですが1行目のふりがなも範囲外になってしまっています。\n\nなので、NSAttributedStringを作るときに、lineHeightMultipleを指定するといい感じになります。\n\n```\nlet paragraphStyle = NSMutableParagraphStyle()\nparagraphStyle.lineHeightMultiple = 1.5\n```\n\n1行しかない場合は、TextView自体のcontentInsetやtextContainerInsetなどをいい感じに設定してあげると表示できます。こちらの指定だけだと、2行目以降でかぶってしまうので、ある程度以上のlineHeightMultipleが設定されている前提でのデザイン組がいいと思われます。\n\n\n\nlineHeightMultipleを1.6に指定\n\n### 2\\. UITextViewをUIViewRepresentableでくるんでSwiftUIから呼び出す\n\n基本的にはUIViewControllerから呼び出すのと変わりません。\n\n```\nstruct RubyTextView: UIViewRepresentable {\n func makeUIView(context _: Context) -> UITextView {\n let view = UITextView()\n view.attributedText = dummyAttributedString\n return view\n }\n\n func updateUIView(_: UITextView, context _: Context) {}\n}\n```\n\nこれをSwiftUIのScrollViewにいれるなどしたりするとおもいます。そのさいに表示される領域のサイズを考えたりとかっていうのが必要になってくるかとおもいます。\n\n \n\n\n\n一応、別画像だよ\n\n### 3\\. Canvas\n\nCanvasはSwiftUIでGraphicsContextを扱うためのViewです。GraphicsContextは2Dのお絵書きをするためのstructです。GraphicsContextはCoreTextをつかった描画ができます。\n\n[**Canvas | Apple Developer Documentation** _A view type that supports immediate mode drawing._ _developer.apple.com_](https://developer.apple.com/documentation/swiftui/canvas)[](https://developer.apple.com/documentation/swiftui/canvas)\n\n[**GraphicsContext | Apple Developer Documentation** _An immediate mode drawing destination, and its current state._ _developer.apple.com_](https://developer.apple.com/documentation/swiftui/graphicscontext)[](https://developer.apple.com/documentation/swiftui/graphicscontext)\n\nすなわち、kCTRubyAnnotationAttributeNameがちゃんと仕事をするのです!また、Textをつかった描画もできます。\n\n注意点としては、 \nCore Graphicsの座標系(左上が原点)をCore Textの座標系(左下が原点)なので、座標を変換する必要があります。\n\n```\ncontext.scaleBy(x: 1, y: -1)\ncontext.translateBy(x: 0, y: -size.height)\n```\n\nまた、Canvasをスクロールする場合にはScrollViewでframeをいい感じにする必要があったりするので,めっちゃ楽っす!!!っていうかんじまではいきません。\n\nkCTRubyAnnotationがCoreTextの機能であるため,1や2のようにLineHeightを調製しないと表示がくずれるとかはありません。\n\n \n\n\n\nCanvasだとLineHeightの調整がいらない\n\n### 4\\. TextRenderer\n\nTextRendererはiOS 18から使えるようになったTextRendererというものがあります。Textのレンダリング時に介入できる機能になります。\n\n[**TextRenderer | Apple Developer Documentation** _A value that can replace the default text view rendering beha_ _developer.apple.com_](https://developer.apple.com/documentation/SwiftUI/TextRenderer)[](https://developer.apple.com/documentation/SwiftUI/TextRenderer)\n\nレンダリングに介入できる→本来表示する文字の上にルビもついでにレンダリングしてあげたらいいんじゃない???という考えでやってみました。\n\nTextRendererは別記事を書いています。アドベントカレンダーとかそういう系で出したいなぁって思っています。\n\n## その他思いついた方法\n\n* UIViewControllerの中でCoreTextを使う\n \n* 気合いのText地獄\n \n\n一つ目はSwiftUIを信じていくとい点で、SwiftUIでも同じようなことができるしまあ、いいかっていうかんじでやっていません。\n\n### 気合いのText地獄\n\n```\nLazyVStack {\n HStack(alignment: .bottom, spacing: 0) {\n Text(\"だれもが\")\n VStack(spacing: -2) {\n Text(\"そうさく\")\n .font(.caption2)\n Text(\"創作\")\n }\n Text(\"をはじめ、\")\n }\n HStack(alignment: .bottom, spacing: 0) {\n VStack(spacing: -2) {\n Text(\"つづ\")\n .font(.caption2)\n Text(\"続\")\n }\n Text(\"けられるようにする。\")\n }\n}\n```\n\nこのコードをClaudeになげていろいろ相談していたら、CoreTextもUIKitもつかわないで、それっぽく動くものが出来てしまいました。\n\n\n\n改行ができなかった\n\n↑の雑Textの組みあわせたくらいで、ほとんどClaudeがつくって私はコピペ・デバッグ係に徹した出力結果です。表示している文言もClaudeがつくったよ!普通に嘘かいてあるよ!!!\n\n\n\nClaudeさんがつくったnote活用ガイド\n\n## まとめ\n\n### UIViewController, UIViewRepresentable\n\n* SwiftUIじゃないのがいやだよね\n \n* LineHeightとかの調整が必要\n \n* SwiftUIアプリだったら、SwiftUI向けにつくった資産が使えない\n \n* UITextView/UILabelだからみんないろいろ知見もってるよね!\n \n* UIKit画面としてつくるなら一番いいのでは?\n \n* ScrollViewに埋め込むときの対応\n \n\n### Canvas\n\n* CoreGraphics由来なので高度な描画機能が使える\n \n* CoreGraphics由来なのでルビ表示がきれいにできる\n \n* 座標系がかわってしまうので、ややこしい時がある\n \n* ScrollViewに埋め込むときの対応\n \n* CoreGraphicsようわからん問題\n \n\n### TextRenderer\n\n* 他にも面白いことができそう\n \n* 下書きにはTextRendererの記事が控えている\n \n\n新しく作るならば,SwiftUIをメインでやっていきたいなぁと思っているのでUIViewControllerやUIViewRepresentableではなくCanvasかClaude先生が生み出したものをより深掘っていこうかと思っています。\n\n## ソースコード\n\n最後のClaude先生に作ってもらったもの以外のサンプルコードです。\n\n[](https://gist.github.com/watura/08b06adffd79cab32be76eb0804306c1)\n"
}
],
"path": "/blog/3mmcuykkybket",
"publishedAt": "2024-11-20T00:00:00Z",
"site": "https://wtr.app",
"tags": [
"note"
],
"textContent": "おはようございます。waturaです。新しいmac miniがほしいなぁと思っているんですが,やっぱり、独立した画面ほしいよね。机の上にもう1セットキーボードとかおくのいやだよね。とかって考えると、ほしいのはノートパソコンでは?となっています。\n\nnote Mobile Tech Talk #1で発表した内容になります。\n\nnote Mobile Tech Talk #1 (2024/11/20 12:00〜) \\# 📝概要 note株式会社 AppチームによるモバイルアプリエンジニアLTイベントです。 noteのiOS / An pieceofcake.connpass.com\n\nルビをふりたい\n\nルビをふりたい noteでルビってどうやってふるんだろう?って検索しないといけないくらい、ルビの使い方がわからなかったんですが、noteでもちゃんとルビはふれるようです。\n\nルビ(ふりがな)をふる\n\nアプリに入力補助がほしいな!と思いました。が閑話休題。\n\niOSアプリ上ではルビはふれるのだろうか?\n\n**結論:ふれるけど、完璧とはいいがたい。**さらに、SwiftUIのみでルビをふる機能は2024年11月時点ではなさそうです。表題が「SwiftUIでルビをふる」という記事なので、これで終了ですといきたいところですが,SwiftUIからUIKitなどを呼び出せるので,それをつかってルビをふるという方法を説明します。\n\nまず、AttributedStringにはルビAttributeはないようです。NSAttributedStringにもルビAttributeはないようです。CoreTextまでおりてくるとルビAttributeがあります。\n\nCTRubyAnnotationCreateWithAttributes(\\_:\\_:\\_:\\_:\\_:) | Apple Developer Documentation Creates an immutable ruby annotation object with the specifie developer.apple.com\n\nCTRubyAnnotationCreateWithAttributes(::::\\_:)をつかうと\n\nルビとして表示する文字\n\n表示される位置や表示方法\n\nベースの文字幅に合わせて表示とか\n\nルビが長すぎるときの処理方法\n\nルビの表示位置(上下とか)\n\nなどが設定でき、ベースのフォントサイズに対する相対サイズでのサイズ指定や文字色の指定などができます。\n\nこのCTRubyAnnotationCreateの戻り値をNSAttributedStringのAttributeに指定してあげると、ルビ表示ができるようになります。\n\nlet rubyAttribute: [CFString: Any] = [\n kCTRubyAnnotationSizeFactorAttributeName: 0.5\n]\n\nlet rubyAnnotation = CTRubyAnnotationCreateWithAttributes(\n .auto,\n .auto,\n .before,\n rubyText as CFString,\n rubyAttribute as CFDictionary\n)\n\nNSAttributedString(string: baseText, attributes: [\n kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation\n])\n\n簡単!これでUITextViewとかでルビが表示できます!\n\nこれを、AttributedStringに変換してTextに渡してあげれば、SwiftUIでもルビが!って少し期待したのですが、だめでした。AttributedStringにした時点で、AttributeからkCTRubyAnnotationAttributeNameが消失してしまうようでした。\n\nSwiftUIアプリでルビを表示する\n\nざっくり、以下の4つの方法を試してみました。\n\nSwiftUIは諦めてUIViewControllerでUITextView/UILabelを使う\n\nUITextViewをUIViewRepresentableでくるんでSwiftUIから呼び出す\n\nCanvasを使う\n\nTextRendererを使う\n\n残念なことにどの方法もちゃんとSwiftUIだよ!といいきれる実装ではなく、実際に実装している内容はUIKitだったりCoreTextだったりします。残念です。\n\n表示する文章\n\nだれもが創作をはじめ、続けられるようにする。\n\n本来であれば、このnoteルビ記法をパースして表示できるようにしたほうがよかったのですが、簡単のためはぶいています。\n\nfunc rubyAnnotation(text: String, ruby: String) -> NSAttributedString {\n let rubyAttribute: [CFString: Any] = [\n kCTRubyAnnotationSizeFactorAttributeName: 0.5\n ]\n let rubyAnnotation = CTRubyAnnotationCreateWithAttributes(\n .auto,\n .auto,\n .before,\n ruby as CFString,\n rubyAttribute as CFDictionary\n )\n\n return NSAttributedString(\n string: text,\n attributes:\n [\n kCTRubyAnnotationAttributeName as NSAttributedString.Key: rubyAnnotation\n ]\n )\n}\n\nvar dummyAttributedString: NSAttributedString {\n let txt = NSMutableAttributedString(string: \"だれもが\")\n txt.append(rubyAnnotation(text: \"創作\", ruby: \"そうさく\"))\n txt.append(.init(string: \"をはじめ、\\n\"))\n txt.append(rubyAnnotation(text: \"続\", ruby: \"つづ\"))\n txt.append(.init(string: \"けられるようにする。\"))\n \n // ルビが範囲外に表示されてしまうので行間を広げる\n let paragraphStyle = NSMutableParagraphStyle()\n paragraphStyle.lineHeightMultiple = 1.5\n\n txt.addAttributes([\n .font: UIFont.systemFont(ofSize: 16),\n .paragraphStyle: paragraphStyle,\n ], range: .init(location: 0, length: txt.length))\n \n return txt\n}\n\n1\\. SwiftUIは諦めてUIViewControllerでUITextView/UILabelを使う\n\nSwiftUIとUIKitは共存できます。なので、UIViewControllerで表示するというのも1手です。表示は以下のコードをAutoLayoutごにょごにょしたりして表示するだけです。しかし、残念なことにただそのまま表示するだけではだめでした。\n\nlet view = UITextView()\nview.attributedText = dummyAttributedString\n\n単純に表示したパターン\n\n1行目はともかく2行目が1行目とかぶってしまい読めなくなっています。また、背景色を指定するとわかりやすいのですが1行目のふりがなも範囲外になってしまっています。\n\nなので、NSAttributedStringを作るときに、lineHeightMultipleを指定するといい感じになります。\n\nlet paragraphStyle = NSMutableParagraphStyle()\nparagraphStyle.lineHeightMultiple = 1.5\n\n1行しかない場合は、TextView自体のcontentInsetやtextContainerInsetなどをいい感じに設定してあげると表示できます。こちらの指定だけだと、2行目以降でかぶってしまうので、ある程度以上のlineHeightMultipleが設定されている前提でのデザイン組がいいと思われます。\n\nlineHeightMultipleを1.6に指定\n\n2\\. UITextViewをUIViewRepresentableでくるんでSwiftUIから呼び出す\n\n基本的にはUIViewControllerから呼び出すのと変わりません。\n\nstruct RubyTextView: UIViewRepresentable {\n func makeUIView(context _: Context) -> UITextView {\n let view = UITextView()\n view.attributedText = dummyAttributedString\n return view\n }\n\n func updateUIView(_: UITextView, context _: Context) {}\n}\n\nこれをSwiftUIのScrollViewにいれるなどしたりするとおもいます。そのさいに表示される領域のサイズを考えたりとかっていうのが必要になってくるかとおもいます。\n\n一応、別画像だよ\n\n3\\. Canvas\n\nCanvasはSwiftUIでGraphicsContextを扱うためのViewです。GraphicsContextは2Dのお絵書きをするためのstructです。GraphicsContextはCoreTextをつかった描画ができます。\n\nCanvas | Apple Developer Documentation A view type that supports immediate mode drawing. developer.apple.com\n\nGraphicsContext | Apple Developer Documentation An immediate mode drawing destination, and its current state. developer.apple.com\n\nすなわち、kCTRubyAnnotationAttributeNameがちゃんと仕事をするのです!また、Textをつかった描画もできます。\n\n注意点としては、Core Graphicsの座標系(左上が原点)をCore Textの座標系(左下が原点)なので、座標を変換する必要があります。\n\ncontext.scaleBy(x: 1, y: -1)\ncontext.translateBy(x: 0, y: -size.height)\n\nまた、Canvasをスクロールする場合にはScrollViewでframeをいい感じにする必要があったりするので,めっちゃ楽っす!!!っていうかんじまではいきません。\n\nkCTRubyAnnotationがCoreTextの機能であるため,1や2のようにLineHeightを調製しないと表示がくずれるとかはありません。\n\nCanvasだとLineHeightの調整がいらない\n\n4\\. TextRenderer\n\nTextRendererはiOS 18から使えるようになったTextRendererというものがあります。Textのレンダリング時に介入できる機能になります。\n\nTextRenderer | Apple Developer Documentation A value that can replace the default text view rendering beha developer.apple.com\n\nレンダリングに介入できる→本来表示する文字の上にルビもついでにレンダリングしてあげたらいいんじゃない???という考えでやってみました。\n\nTextRendererは別記事を書いています。アドベントカレンダーとかそういう系で出したいなぁって思っています。\n\nその他思いついた方法\n\nUIViewControllerの中でCoreTextを使う\n\n気合いのText地獄\n\n一つ目はSwiftUIを信じていくとい点で、SwiftUIでも同じようなことができるしまあ、いいかっていうかんじでやっていません。\n\n気合いのText地獄\n\nLazyVStack {\n HStack(alignment: .bottom, spacing: 0) {\n Text(\"だれもが\")\n VStack(spacing: -2) {\n Text(\"そうさく\")\n .font(.caption2)\n Text(\"創作\")\n }\n Text(\"をはじめ、\")\n }\n HStack(alignment: .bottom, spacing: 0) {\n VStack(spacing: -2) {\n Text(\"つづ\")\n .font(.caption2)\n Text(\"続\")\n }\n Text(\"けられるようにする。\")\n }\n}\n\nこのコードをClaudeになげていろいろ相談していたら、CoreTextもUIKitもつかわないで、それっぽく動くものが出来てしまいました。\n\n改行ができなかった\n\n↑の雑Textの組みあわせたくらいで、ほとんどClaudeがつくって私はコピペ・デバッグ係に徹した出力結果です。表示している文言もClaudeがつくったよ!普通に嘘かいてあるよ!!!\n\nClaudeさんがつくったnote活用ガイド\n\nまとめ\n\nUIViewController, UIViewRepresentable\n\nSwiftUIじゃないのがいやだよね\n\nLineHeightとかの調整が必要\n\nSwiftUIアプリだったら、SwiftUI向けにつくった資産が使えない\n\nUITextView/UILabelだからみんないろいろ知見もってるよね!\n\nUIKit画面としてつくるなら一番いいのでは?\n\nScrollViewに埋め込むときの対応\n\nCanvas\n\nCoreGraphics由来なので高度な描画機能が使える\n\nCoreGraphics由来なのでルビ表示がきれいにできる\n\n座標系がかわってしまうので、ややこしい時がある\n\nScrollViewに埋め込むときの対応\n\nCoreGraphicsようわからん問題\n\nTextRenderer\n\n他にも面白いことができそう\n\n下書きにはTextRendererの記事が控えている\n\n新しく作るならば,SwiftUIをメインでやっていきたいなぁと思っているのでUIViewControllerやUIViewRepresentableではなくCanvasかClaude先生が生み出したものをより深掘っていこうかと思っています。\n\nソースコード\n\n最後のClaude先生に作ってもらったもの以外のサンプルコードです。",
"title": "SwiftUIでルビをふる",
"updatedAt": "2026-05-20T21:47:12Z"
}