37
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIでプレゼンスライドが作れるSlideKit

Last updated at Posted at 2022-10-22

はじめに

プレゼンスライドを作る時、皆さんは何で作りますか?
Keynoteでしょうか?それともPowerPoint?
僕も以前はPowerPointを愛用していました。
ただエンジニアをしている身としてはやはりコードでスライドを実装してみたいものです。
そんな思いに駆られてSwiftUIでプレゼンスライドが作れるSlideKitというライブラリを作りました。
この記事ではSlideKitの使い方を解説します。

190956930-ea9ce4d0-0a19-4bb3-b43b-28dd2d73374a.png

基本的な使い方

セットアップ

さて、まずはスライドを表示するアプリを用意する必要があります。
ここではSlideKitを使ったスライドアプリを生成するために、SlideGenを使います。
SlideGenはmintで簡単に使えるようになっています。
(ちなみにこのSlideGenも自分で実装しました)

$ mint run mtj0928/SlideGen SlideKitPresentation --platform macOS

このコマンドを実行すると、スライドアプリのテンプレートがSlideKitPresentationというxcodeprojとして生成されます。
platformに渡しているmacOSiOS に変更するとiOS版のxcodeprojが生成されますが、この記事ではmacOSを中心に説明していきます。

さて早速できたプロジェクトをビルドしてみると、こんな感じの画面が表示されます。
Slide1.png

もしかしたら端に黒い部分があるかもしれませんが、ウインドウの大きさを変えると自動で適切なアスペクト比になるはずです。

これでセットアップは完了です。
ここから実際にスライドを作っていきます。

スライドの作り方

まずは簡単なスライドを作っていきます。
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で確認が可能です。
上のコードの場合、このようになるはずです。

SimpleSlide.png

コードの中にコメントを入れてあるので、大体内容はわかると思いますが、HeaderSlideItemで箇条書きのスライドを作っています。

スライドをアプリに追加する

さて、上で作った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)
                    }
                }
            }
        }
    }
}

これをビルドして、スライドを進めることで、段階的に表示されていることがわかると思います。

スクリーンショット 2022-10-22 11.09.34.png
スクリーンショット 2022-10-22 11.09.38.png
スクリーンショット 2022-10-22 11.09.42.png

発表者ツールを使う

発表をする時はスクリプトが見たいですよね。
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を押すことで確認できます。
presenterview.png

プレゼンしているウインドウとは別のウインドウとして表示されるので、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()
        }
    }
}

スクリーンショット 2022-10-22 17.29.05.png

また、ダークテーマも用意してあります。(背景色もそのうち足します😅)

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))

スクリーンショット 2022-10-22 17.37.42.png

スライドのテーマを変更する

HeaderSlideItemなど、便利な機能を持つ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()
        }
    }
}

このスライドだと次のようなデザインになります。
スクリーンショット 2022-10-22 21.09.38.png

まずは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()) // この行に追加する
    }
}

するとXcodePreviewはこうなります。
スクリーンショット 2022-10-22 21.13.14.png

無茶苦茶なデザインですね。大丈夫です。
ここで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)
    }
}

この実行結果は次のようになります。
スクリーンショット 2022-10-22 21.25.11.png

さっきとは全然違うデザインになりましたね。
ここではHeaderSlideのデザインを変更しましたが、同様にItemのデザインはItemStyleを、ページ番号のデザインはIndexStyleをカスタマイズしていきます。

ところでこのCustomHeaderSlideStyleを各スライドに対して適用していくのは大変ですよね。
スライド全体に適用するには、SlideGenが生成したSlideKitPresentationApppresentationContentViewというプロパティに適用するとスライド全体に適用されます。
(SlideKitPresentationAppの正確な名前はSlideGenの生成時に渡した引数によって変わります。)

@main
struct SlideKitPresentationApp: App {
    // 省略...
    var presentationContentView: some View {
        SlideRouterView(slideIndexController: Self.configuration.slideIndexController)
            .headerSlideStyle(CustomHeaderSlideStyle()) // この一行を追加する
            .background(.white)
            .foregroundColor(.black)
    }
    // 省略...
}

上の一行を追加するだけで全てのスライドに適用され、既存のスライドもこのようにデザインが変わります。
スクリーンショット 2022-10-22 21.33.08.png
ガラッと印象が変わりましたね。

タイトルスライドを実装する

ここまで、全て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周りの操作は大変ですが、このスライドはこのような感じになります。
スクリーンショット 2022-10-22 22.27.03.png
このようにHeaderSlideItemを使わずに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へのスター、コントリビュートもお待ちしています!

37
11
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
37
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?