Hello, Swift ラバーなみなさん。これからラバーのみなさん。
2022 年の iOSDC2022 の発表の中で、まつじさんという方の発表がありました。
なんとその発表スライドが SwiftUI で作られているというではありませんか。
発表を見た私の感想は・・「なにそれすごい(小並感)」というものでした。
しかもすでにSlideKitというライブラリにされているではないですか!!
これは使ってみるしかねぇ!!
ということでやってまいります。
SlideKit とは
SlideKit は、SwiftUI でプレゼンテーション スライドを作成するのに役立ちます。
すべてのコンポーネントが SwiftUI の View であるため、簡単にプレゼンテーション スライドを作成し、デザインを完全にカスタマイズできます。
SlideKit README
なるほどー。
なんかすごいぞ(小並感)
とりあえず使ってみる
まずは、公式のチュートリアルに沿って、作ってみましょう!
(全部を紹介すると、なかなかの量になるので、気になる方は試してみてください!)
環境構築
まずは Swift 製のパッケージ管理ツールの「mint」を導入しましょう。
(homebrew が入っている前提です、他にも導入方法があるようなのでお好みのものを選択してください)
今回使うバージョン(2022 年 11 月時点)
- MacOSX: 12.6.1
- Xcode: 14.1
- Homebrew: 3.6.7
$ brew install mint
$ mint --version
Version: 0.17.2
インストールができましたら、SlideKit のプロジェクトファイルを作成しましょう。
まずは作成先のディレクトリに移動します。
$ cd <プロジェクトのディレクトリ>
今回は Home ディレクトリで「~/Develop/SlideKit_Demo」というディレクトリを作成したので、下記のようになりました。
$ cd ~/Develop/SlideKit_Demo
実際にプロジェクトを作成します。
$ mint run mtj0928/SlideGen <保存名> --platform <対象 OS>
(mtj0928/SlideGen はまつじさんが作られたスライドアプリを作るライブラリ)
今回は、
- プロジェクト名を「SlideKitDemo」
- 対象 OS はチュートリアルを参考に「macOS」
としました。
$ mint run mtj0928/SlideGen SlideKitDemo --platform macOS
完了したら、スライドの Xcode プロジェクトができていると思うので、開いてみましょう。
開けたら、まず実行します(⌘+R
)!
こんな感じにサンプルのスライドが表示されました!
(以下のサンプルはスクショが見やすいようにbackgroundColorを設定しています。通常は白です。)

ここからスライドを追加していきます。
最初のスライド
Slides ディレクトリ配下にIntroductionSlide.swift
という SwiftUI ファイルを作成します。
開いたら、Slide を作成する前に 3 つ、やるべきことがあります。
- SlideKit を import する
- Slide protocol に準拠する
- SlidePreview で表示する Preview内のView を囲う
こちらコードです
import SwiftUI
import SlideKit // ①
struct IntroductionSlide: Slide { // ②
var body: some View {
Text("Hello, World!")
}
}
struct Slide_Previews: PreviewProvider {
static var previews: some View {
SlidePreview { // ③
IntroductionSlide()
}
}
}
さぁ、準備は整いました!
次にスライドをカスタムしてきましょう。
body 部分にHeaderSlide() {}
を記述します。
いわゆるタイトルテキストですね!
var body: some View {
HeaderSlide("SlideKit") {}
}
Preview がこんな感じになりましたでしょうか?

Simulatorで確認するには、SlideConfiguration.swift
内のslideIndexController
変数内にIntroductionSlide()
を追加してください。
let slideIndexController = SlideIndexController {
SampleSlide()
IntroductionSlide()
}
次にIntroductionSlide.swift
のHeaderSlide()
のコールバック内にItem()
を記述します。
HeaderSlide("SlideKit") {
Item("SlideKit helps you make presentation slides by SwiftUI.")
Item("The followings are provided.")
}
いわゆる箇条書きですね!
こんな簡単にかけるなら、作成も楽そうです!

このように、想像より(?)簡単だったのではないでしょうか?
では、チュートリアルの外に出て、通常のスライド作成ソフトでは作りづらいようなものを作ってみましょう。
デモスライドを作ってみる
今回は以下のようなスライドを作りました。
6枚のスライドですが、SwiftUI の機能をしっかり盛り込んでいます。
構成
1. 段階表示
2. アプリ埋め込み
3. カスタムデザイン
4. WebView
5. Charts API
6. コード表示
(*注意:以下、記事の都合上、一部本筋と関係のないコードは省略しています。実際に動作確認する場合は、そのままだと動かないので、適宜置き換えてください。)
また、趣向を変えて、iPad用にプロジェクト作成を行ったため、少し構築手順が異なります。
環境構築
任意のディレクトリにて、以下を実行しました。
$ mint run mtj0928/SlideGen SlidekitDemoProject --platform iOS
前述のチュートリアルでは、作成したスライドの追加をSlideConfiguration.swift
に追加をしましたが、iOSプロジェクトの場合、SceneDelegate.swift
のslideIndexController
変数に追加していきます。
static let slideIndexController = SlideIndexController(index: 0) {
TitleSlide()
AppDemoSlide() // このように作成したスライドを表示させたい順で追加する
}
プロジェクトが作成されましたら、実際に作っていきましょう!
1. 段階表示
1 ページ目はタイトルスライドです。
世のスライドではよくある、順番にコンテンツを表示させる機能です。

チュートリアルにもありますが、Int
とPhasedState
に準拠した enum を使用します。
// 列挙体名は固定、IntとPhasedStateに準拠する
enum SlidePhasedState: Int, PhasedState {
case initial, second, third // ここの列挙子名はなんでもいい
}
@Phase var phasedStateStore // ここの変数名も固定
enum の列挙子名については任意ですが、列挙体名はSlidePhasedState
である必要があります。
また、@Phase
の propertyWrapper の付いた変数名もphasedStateStore
固定になります。
スライドをクリックするごとに enum の値が増えていくようなイメージです。
クリック時に動作する段階表示制御にもいくつか種類あります。
- 今回使っている
.after()
だと、そのケース以降は表示する
.when()
だと、そのケース時だけ表示する
他にもありますので、ソースを見てみてください!
これらの使い方は、実際のソースを見るとわかりやすいかもしれません。
struct TitleSlide: Slide {
enum SlidePhasedState: Int, PhasedState {
case initial, second, third
}
@Phase var phasedStateStore
var body: some View {
VStack {
Text("SwiftUI")
.font(.system(size: 90, weight: .heavy))
// 画面を一回クリックすると表示されるようになる。それ以降表示される
if phasedStateStore.after(.second) {
Text(" で作るn")
.font(.system(size: 70, weight: .heavy))
}
// 画面を二回クリックすると表示されるようになる。(今回それ以上ないけど)それ以降表示される
if phasedStateStore.after(.third) {
Text("SlideKit")
.font(.system(size: 190, weight: .heavy))
.padding(.top, -100)
}
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(90)
}
}
画面をクリックするたびにphasedStateStore
の値が、initial
→second
→third
と増えていくイメージです。
2. アプリ埋め込み
2 ページ目はアプリ埋め込みを行ったスライドです。
やり方は簡単、好きなアプリを埋め込むだけです・・・
それだとお話にならないので、今回は簡単なチャット風アプリを仕込んでみました。

struct AppDemoSlide: Slide {
var body: some View {
HeaderSlide("デデモアプリを埋め込んでみた") {
HStack(alignment: .top) {
DemoApp()
.padding(.trailing, 100)
DescriptionView()
.padding(.top)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal, 40)
}
}
@ViewBuilder
func DemoApp() -> some View {
ChatView() // 埋め込んだサンプルアプリ
.frame(width: 500)
.overlay {
RoundedRectangle(cornerRadius: 50)
.stroke(Color.black, lineWidth: 10)
}
.clipShape(RoundedRectangle(cornerRadius: 50))
}
@ViewBuilder
func DescriptionView() -> some View {
VStack(alignment: .leading) {
Item("なんとアプリを埋め込める")
Item("アプリなので、動かせちゃう")
}
}
}
普段の SwiftUI プロジェクトと同じように、宣言するだけですね!
本筋ではないですが、Chat画面のコードはこちらです。(一部のコードは省略)
struct ChatView: View {
@State private var messages: [Message] = []
@State private var currentMessageNumber: Int = 0
var body: some View {
ZStack {
VStack(spacing: 0) {
Spacer()
.frame(height: 40)
GeometryReader { proxy in
ScrollView {
LazyVStack(spacing: 20) {
ForEach(messages) { message in
MessageItem(message: message, viewWidth: proxy.size.width)
.padding(.horizontal)
}
}
}
}
}
VStack {
Spacer()
Button {
messages.append(sampleMessages[currentMessageNumber])
if currentMessageNumber == sampleMessages.count - 1 {
currentMessageNumber = 0
return
}
currentMessageNumber += 1
} label: {
Text("Message Add")
.font(.title.bold())
.foregroundColor(.white)
.padding(20)
.background(.blue)
.cornerRadius(12)
.padding(.bottom, 20)
}
}
}
.background(.green.opacity(0.2))
}
@ViewBuilder
func MessageItem(message: Message, viewWidth: CGFloat) -> some View {
let isReceived = message.type == .received
Text(message.text)
.font(.system(size: 24))
.padding(.horizontal)
.padding(.vertical, 12)
.background(isReceived ? .black.opacity(0.2) : .green.opacity(0.9))
.cornerRadius(13)
.frame(width: viewWidth * 0.7, alignment: isReceived ? .leading : .trailing)
.padding(.vertical)
.frame(maxWidth: .infinity, alignment: isReceived ? .leading : .trailing)
}
実際のアプリを埋め込むなら、package とかで読み込むのがいいかもしれないですね!
3. カスタムデザイン
デフォルトでもいい感じのスライドですが、スライドデザインをカスタムすることもできます。
今回は縦書きでスライドを作ってみました。

TategakiText
は、NSAttributedString
を UIViewRepresentable
でラップしてゴニョゴニョしています。
実は先程までのHeaderSlide
と違い、String
ではなく、View
の構造体を Header タイトルに設定しています。
String以外を使うにはライブラリを拡張する必要があるかなーと思っていたら、すでに用意されていました!!
デザインカスタムするには、HeaderSlideStyle
に準拠した構造体でデザインを実装します。
今回はCustomHeader
という構造体を実装しました。
HeaderSlideStyle
で宣言されているfunc makeBody(configuration:)
を使用して実際に実装します。
header と content は configuration
という引数で渡されてくるので、そちらに対し、効果を与えていきます。
そして、実際に使用する際には、スライドビューに対し、.headerSlideStyle(CustomHeader())
のように適用します。
すべてのスライドに適用したい場合は、main ファイルのvar presentationContentView
内にあるSlideRouterView()
に適用すると反映されます。
では、実際のコードです。
struct CustomHeaderView: Slide {
var body: some View {
HeaderSlide {
TategakiText(text: "縦書きのヘッダーもいいね", fontSize: 80, fontColor: .white)
} content: {
HStack(spacing: 0) {
TategakiText(text: description, fontSize: 44)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.headerSlideStyle(CustomHeader())
}
let description = "..." //省略
}
struct CustomHeader: HeaderSlideStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .top, spacing: 100) {
configuration.content
.padding(100)
configuration.header
.frame(width: 400)
.frame(maxHeight: .infinity)
.padding(.top, 40)
.background(.red)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
簡単にスライドの世界観を統一できそうですね!
4. WebView
次に WebView です。
こちらも先ほどと同様にただ画面内に宣言するだけです。
WebView自体もただ表示するだけのサンプルです。

実装で特に難しいものはないですね!!
struct WebViewSlide: Slide {
var body: some View {
HeaderSlide("WebViewだって表示できます") {
WebView(url: "https://up-frontier.jp/")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
struct WebView: UIViewRepresentable {
let url: String
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
let request = URLRequest(url: URL(string: url)!)
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
}
5. Charts API
次に Charts API を使ったものです。
こちらは Xcode14 からのものなので、注意してください。

とはいえ、スライドとしてやれることは、他の画面と変わりません。
まずはスライド部分
struct ChartDemoSlide2: Slide {
var body: some View {
HeaderSlide("チャートも書けちゃう") {
VStack(alignment: .leading) {
Item("Xcode14からのChartsAPIを使いました")
Item("2000年から2022年の平均ドル円相場です")
ChartView()
}
}
}
}
シンプルですね〜
本筋とは関係ないですが、Chart のサンプルも貼っておきます。
struct ChartView: View {
@State private var rates: [DollarYen] = sampleRate
var body: some View {
Chart {
ForEach(rates) { rate in
BarMark(
x: .value("Date", dateFromString(string: rate.date), unit: .year),
y: .value("Yen", rate.isAnimate ? rate.yen : 0)
)
.foregroundStyle(.blue.gradient)
.interpolationMethod(.catmullRom)
AreaMark(
x: .value("Date", dateFromString(string: rate.date), unit: .year),
y: .value("Yen", rate.isAnimate ? rate.yen : 0)
)
.foregroundStyle(.green.opacity(0.3).gradient)
}
}
.onAppear {
for (index, _) in rates.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.05) {
withAnimation(.interactiveSpring(response: 0.8,
dampingFraction: 0.8,
blendDuration: 0.8)) {
rates[index].isAnimate = true
}
}
}
}
}
}
onAppear()
の部分は、ただビヨーンとアニメーションをしたかっただけなので、
チャート自体には関係ありません。
6. コード表示
やっぱり、プログラマがスライド作るなら、コードを載せたいですよね!

簡単です!
Code
という構造体に String 形式でコードを渡すだけです!
var body: some View {
ScrollView {
Code(code, colorTheme: .defaultDark, fontSize: 36)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(48)
.background(Color.init(red: 41 / 255, green: 42 / 255, blue: 47 / 255))
}
.padding()
}
let code = "..." // 省略
Code の引数にある、colorTheme:
は、スニペットの文字色が dark モード対応になります。
現状、背景色には適用できないようなので、通常通り背景色を設定します。
まとめ
このようにすごい簡単にオリジナルスライドを作成することができました!
なかなか登壇などの発表の機会は少ないですが、チャンスがあれば使ってみたいと思います!
それでは、素敵な Swift ライフを!!