はじめに
プレゼンスライドを作る時、皆さんは何で作りますか?
Keynoteでしょうか?それともPowerPoint?
僕も以前はPowerPointを愛用していました。
ただエンジニアをしている身としてはやはりコードでスライドを実装してみたいものです。
そんな思いに駆られてSwiftUIでプレゼンスライドが作れるSlideKitというライブラリを作りました。
この記事ではSlideKitの使い方を解説します。
基本的な使い方
セットアップ
さて、まずはスライドを表示するアプリを用意する必要があります。
ここではSlideKitを使ったスライドアプリを生成するために、SlideGenを使います。
SlideGenはmintで簡単に使えるようになっています。
(ちなみにこのSlideGenも自分で実装しました)
$ mint run mtj0928/SlideGen SlideKitPresentation --platform macOS
このコマンドを実行すると、スライドアプリのテンプレートがSlideKitPresentation
というxcodeprojとして生成されます。
platform
に渡しているmacOS
をiOS
に変更するとiOS版のxcodeprojが生成されますが、この記事ではmacOSを中心に説明していきます。
さて早速できたプロジェクトをビルドしてみると、こんな感じの画面が表示されます。
もしかしたら端に黒い部分があるかもしれませんが、ウインドウの大きさを変えると自動で適切なアスペクト比になるはずです。
これでセットアップは完了です。
ここから実際にスライドを作っていきます。
スライドの作り方
まずは簡単なスライドを作っていきます。
IntroductionSlide.swift
というファイルを追加して、以下のようにします。
import SwiftUI
import SlideKit
struct IntroductionSlide: Slide { // ① 準拠するのはViewではなくSlide
var body: some View {
// ② HeaderSlideは一番上にタイトルがあるスライドのテンプレート
HeaderSlide("SlideKit") {
// ③ Itemを使うと楽に箇条書きができる
Item("SlideKitはSwiftUIでスライドが作れるライブラリです")
Item("ネストしたり、数字で箇条書きにもしたりもできます") {
Item("1つ目のアイテム", accessory: 1) {
Item("ネストのネストもできます", accessory: "a")
}
Item("2つ目のアイテム", accessory: 2)
Item("3つ目のアイテム", accessory: 3)
}
}
}
}
struct IntroductionSlide__Previews: PreviewProvider {
static var previews: some View {
// ④ XcodePreviewを使う時はSlidePreviewをで囲むと適切な形になります
SlidePreview {
IntroductionSlide()
}
}
}
SlideKitで作成しているスライドはXcodePreviewで確認が可能です。
上のコードの場合、このようになるはずです。
コードの中にコメントを入れてあるので、大体内容はわかると思いますが、HeaderSlide
とItem
で箇条書きのスライドを作っています。
スライドをアプリに追加する
さて、上で作ったIntroductionSlide
はXcodePreviewでは確認できますが、まだアプリを実行しても表示されません。
アプリでスライドとして表示するにはSlideIndexController
に登録する必要があります。
SlideGenで生成されたプロジェクトの中に、SlideConfiguration.swift
というファイルがあります。
そのファイルの中でSlideIndexController
の生成をしているので、そこに先ほど追加したIntroductionSlide
を追加します。
import SwiftUI
import SlideKit
struct SlideConfiguration {
// ...省略
let slideIndexController = SlideIndexController {
IntroductionSlide() // ← この一行を追加する
SampleSlide()
}
}
アプリをビルドすると、先ほど作ったIntroductionSlide
がアプリに表示されます。
このSlideIndexController
に追加したスライドは上から順番に表示されます。
Returnキーを押したり右矢印キーを押したりすることで、スライドを次のスライドに遷移させることができます。
逆に左矢印キーを押すことで一つ前のスライドに戻ることができます。
スライドを段階的に表示する
スライドで発表をする時に段階的にスライドを表示したくなることがあります。
SlideKitはそのようなユースケースをカバーしています。
先ほど作成したIntroductionSlide
に戻って、次のように編集します。
struct IntroductionSlide: Slide {
enum SlidePhasedState: Int, PhasedState { // 型名が違うとコンパイルエラーになるので注意
case initial, secondItem, all
}
@Phase var phasedStateStore: PhasedStateStore<SlidePhasedState> // 変数名が違うとコンパイルエラーになるので注意
var body: some View {
//省略
}
}
表示する段階をSlidePhasedState
というenumで表現しています。
SlidePhasedState
が準拠しているPhasedState
は.initial
を要求しています。
これはスライドが表示される際の最初の段階になります。
そして、phasedStateStore
には現在のSlidePhasedState
があります。
phasedStateStore
にはafter(_)
やwhen(_)
といったBoolを返すメソッドがあるので、これらを用いてbody
の中身を現在の状態に合わせて分岐することができます。
var body: some View {
if phasedStateStore.when(.secondItem) {
// 現在の状態がsecondItemの時だけここに入る
}
if phasedStateStore.after(.secondItem) {
// 現在の状態がsecondItem以降の時にここに入る
}
}
さて、先ほど追加した IntroductionSlide
に追加します。
struct IntroductionSlide: Slide {
// ... 省略
var body: some View {
HeaderSlide("SlideKit") {
Item("SlideKitはSwiftUIでスライドが作れるライブラリです")
if phasedStateStore.after(.secondItem) {
Item("ネストしたり、数字で箇条書きにもしたりもできます") {
if phasedStateStore.after(.all) {
Item("1つ目のアイテム", accessory: 1) {
Item("ネストのネストもできます", accessory: "a")
}
Item("2つ目のアイテム", accessory: 2)
Item("3つ目のアイテム", accessory: 3)
}
}
}
}
}
}
これをビルドして、スライドを進めることで、段階的に表示されていることがわかると思います。
発表者ツールを使う
発表をする時はスクリプトが見たいですよね。
SlideKitはこのようなユースケースもサポートしています。
Slide
プロトコルにはscript
というプロパティがあります。
デフォルトでは空の文字列なのですが、カスタムすることで発表中にスクリプトを見ることができます。
struct IntroductionSlide: Slide {
// ... 省略
var script: String {
switch phasedStateStore.current {
case .initial:
return """
SlideKitについて紹介します
SlideKitはSwiftUIでスライドが作れるライブラリです
今皆さんが見ているようにヘッダーを持つスライドを作れ、箇条書きにも対応しています。
"""
case .secondItem:
return """
さらに実はネストしたり、数字の箇条書きしたりできます。
"""
case .all:
return """
こんな感じですね
"""
}
}
}
そしてこのスクリプトはアプリを起動して⌘ + P
を押すことで確認できます。
プレゼンしているウインドウとは別のウインドウとして表示されるので、Zoomなどで発表する時は発表スライドのウインドウだけを画面共有すれば、スクリプトが見られる心配はありません。
ページ番号を隠す
表示されているスライドの右下にページ番号があることに気づきましたか?
このページ番号は自動でSlideKitが表示しています。
しかし、タイトルスライドのような、ページ番号を表示したくないこともあると思います。
Slide
プロトコルにはshouldHideIndex
プロパティがあります。
このプロパティはデフォルトではfalse
ですが、この値をtrue
にすることで、ページ番号を隠すことができます。
struct IntroductionSlide: Slide {
// ... 省略
var shouldHideIndex: Bool {
true
}
}
ソースコードを表示する
発表スライドにソートコードを表示したいときもあると思います。
SlideKitにはCode
というViewが用意されています。
Swiftのみですが、Code
はシンタックスハイライトをサポートしています。
(他の言語をサポートするPR待っています!)
struct CodeSlide: Slide {
var body: some View {
HeaderSlide("Code Slide") {
Text("シンタックスハイライトもサポート!")
Code(code, fontSize: 32)
}
}
}
private let code = """
struct CodeSlide: Slide {
var body: some View {
HeaderSlide("Code Slide") {
Text("シンタックスハイライトもサポート!")
Code(code, fontSize: 32)
}
}
}
"""
struct CodeSlide_Previews: PreviewProvider {
static var previews: some View {
SlidePreview {
CodeSlide()
}
}
}
また、ダークテーマも用意してあります。(背景色もそのうち足します😅)
Code(code, colorTheme: .defaultDark, fontSize: 32)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(48)
.background(Color.init(red: 41 / 255, green: 42 / 255, blue: 47 / 255))
スライドのテーマを変更する
HeaderSlide
やItem
など、便利な機能を持つSlideKitですが、やはりずっと同じデザインだと飽きますし、オリジナリティもないです。
SlideKitでは以下の3つのデザインを自由に変更できる機能があります。
HeaderSlide
Item
- ページ番号
ここではHeaderSlide
のデザインを変更していきます。
まずは簡単なスライドを用意します。
struct CustomThemeSlide: Slide {
var body: some View {
HeaderSlide("スライドのテーマ") {
Item("SlideKitはカスタムテーマに対応しています")
Item("HeaderSlideStyleを使います")
}
}
}
struct CustomThemeSlide_Previews: PreviewProvider {
static var previews: some View {
SlidePreview {
CustomThemeSlide()
}
}
}
まずはCustomHeaderSlideStyle
を追加します。
struct CustomHeaderSlideStyle: HeaderSlideStyle {
func makeBody(configuration: Configuration) -> some View {
VStack {
configuration.header
configuration.content
}
}
}
そしてこれを先ほどのCustomThemeSlide_Previews
に追加します。
struct CustomThemeSlide_Previews: PreviewProvider {
static var previews: some View {
SlidePreview {
CustomThemeSlide()
}
.headerSlideStyle(CustomHeaderSlideStyle()) // この行に追加する
}
}
無茶苦茶なデザインですね。大丈夫です。
ここでCustomHeaderSlideStyle
について解説します。
struct CustomHeaderSlideStyle: HeaderSlideStyle {
func makeBody(configuration: Configuration) -> some View {
VStack {
configuration.header // ① スライドのヘッダー
configuration.content // ② スライドのコンテンツ
}
}
}
SwiftUIのLabelStyle
と同じ発想で実装されています。
configuration
にはHeaderSlide
のヘッダーとコンテンツが入っています。
これを元にスライドのデザインをいじっていきます。
struct CustomHeaderSlideStyle: HeaderSlideStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center) {
configuration.header
.font(.system(size: 60, weight: .black))
.frame(width: 640)
.frame(maxHeight: .infinity, alignment: .leading)
.foregroundColor(.white)
.background(.red)
VStack(alignment: .leading, spacing: 60) {
configuration.content
.font(.system(size: 48))
}
.padding(60)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
さっきとは全然違うデザインになりましたね。
ここではHeaderSlide
のデザインを変更しましたが、同様にItem
のデザインはItemStyle
を、ページ番号のデザインはIndexStyle
をカスタマイズしていきます。
ところでこのCustomHeaderSlideStyle
を各スライドに対して適用していくのは大変ですよね。
スライド全体に適用するには、SlideGenが生成したSlideKitPresentationApp
のpresentationContentView
というプロパティに適用するとスライド全体に適用されます。
(SlideKitPresentationApp
の正確な名前はSlideGenの生成時に渡した引数によって変わります。)
@main
struct SlideKitPresentationApp: App {
// 省略...
var presentationContentView: some View {
SlideRouterView(slideIndexController: Self.configuration.slideIndexController)
.headerSlideStyle(CustomHeaderSlideStyle()) // この一行を追加する
.background(.white)
.foregroundColor(.black)
}
// 省略...
}
上の一行を追加するだけで全てのスライドに適用され、既存のスライドもこのようにデザインが変わります。
ガラッと印象が変わりましたね。
タイトルスライドを実装する
ここまで、全てHeaderSlide
を使ってスライドを実装してきましたが、別にHeaderSlide
を使用することは必須ではありません。
SlideKitではSwiftUIが提供している様々なViewを組み合わせてスライドを作ることができます。
最後にタイトルスライドを実装していきます。
struct TitleSlide: Slide {
var body: some View {
VStack {
titleText
.frame(maxWidth: .infinity, alignment: .leading)
.padding(90)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(.red)
.foregroundColor(.white)
}
var titleText: Text {
Text("SwiftUI")
.font(.system(size: 90, weight: .heavy))
+ Text(" で\n")
.font(.system(size: 70, weight: .heavy))
+ Text("プレゼンスライド")
.font(.system(size: 90, weight: .heavy))
+ Text(" が ")
.font(.system(size: 70, weight: .heavy))
+ Text("作れる\n")
.font(.system(size: 90, weight: .heavy))
+ Text("SlideKit")
.font(.system(size: 190, weight: .heavy))
}
var shouldHideIndex: Bool { true }
}
Text周りの操作は大変ですが、このスライドはこのような感じになります。
このようにHeaderSlide
やItem
を使わずにSwiftUIのViewを使うことでより自由にスライドのデザインができます。
SlideKitの良いところ
1. スライドの使い回しができる!
PowerPointやKeynoteなどは一度見せたスライドをもう一度表示するには、スライドのコピペをする必要がありました。
ただ、そうするとどちらかのスライドを変更した時にもう一方も編集する必要が出てきて同期し続けるのが大変でした。(実際はできるのに自分が知らないだけかもですが...)
しかし、SlideKitのスライドは単なるstructなので、何回でも好きなだけ使いまわせる上、変更も一箇所ですみます。
また、スライドと同様に色の管理なども簡単にできます。
2. SwiftUIだから、実装してしまえばなんでもできる
単なるSwiftUIなので、実装さえしてしまえば、PowerPointやKeynoteではできないことも色々できます。
例えば以下のようなことができます
- アプリのデモをスライドの中に埋め込む
- 紹介したいWebページがあればWebViewで表示する
- Swift Chartsを使ったグラフの描画
- イベントのハッシュタグがあればそれをTwitterから拾ってきてスライド中に表示する
- Firebaseと連携してリアルタイムなアンケートなど
僕ではこの程度しか思いつきませんが、まだまだ皆さんのアイデア次第では色々なことをスライドの中に入れることができます。
SlideKitの微妙なところ
1. 細かいデザインの調整が大変
SlideKitにはふき出しや矢印といったKeynoteやPowerPointには当たり前のように入っているデザインがまだありません。
また、そういったものを表示する時の位置の調整も直感的にはできないので、なかなか苦労します。
2. PDFに変換できない
PDFにしてスライドを共有することも多いと思いますが、まだSlideKitにはスライドをPDFにする機能がありません。
iOS16 / macOS13 から新しく追加されたImageRenderer
を使えば、SwiftUIのViewを画像にして、PDFに生成することもできそうですが、文字情報などがどうしても欠落してしまいます。
何か良い方法があれば教えてください。
最後に
この記事ではSwiftUIでスライドが作れるSlideKitについて紹介しました。
実はこの記事と同様のことをDocCのチュートリアルとしても公開しているので、もしより細かいステップで知りたい方がいましたらそちらも参考にしてください。
「SlideKitを使ってスライド作ってみた」という方がいましたらぜひ、教えてください!
SlideKitへのスター、コントリビュートもお待ちしています!