XcodeとPlaygroundsを共存させるプロジェクト構成
はじめに
iPadでのiOSアプリ開発
これはiOSエンジニアとしてロマンの一つだと思います。
Swift Playgrounds の機能も増えてきており、iPadで開発を完結させることも不可能ではなくなってきました。
実は Capabilities
などの設定もPlaygroundsで行えるため、本当に様々な機能を持ったアプリを作ることができます。
ただ残念なことに全ての Capabilities
を設定できるわけではありません。
例えば iCloud
関連の設定はできないようになっています。
他にも
- Info.plistで詳細な設定を行いたい
- Run Scriptを設定したい
- Xcode Cloudと連動させたい
など、Xcodeなら簡単に出来ることが、Playgroundsでは難しい場合があります。
そこで、本記事ではXcodeとPlaygroundsを共存させるプロジェクト構成を紹介します。
環境
- Xcode 15.4
- Swift Playgrounds 4.5
- iOS 17.5
- Xcode Cloud
全てが必須条件ではないですが、制約が厳しくない状況下という点が伝われば嬉しいです。
方針
Playgroundsプロジェクトのコードをパッケージ化し、Xcodeプロジェクトから利用する構成
を目指します。
手順
Xcodeプロジェクトの作成
- Xcodeプロジェクト
Sample
を作成します。
-
Hello, world!
の代わりにXcode
と表示するように変更します。 - Xcodeプロジェクトを実行して
Xcode
が表示されることを確認します。 - Xcodeプロジェクトを閉じます。
Playgroundsプロジェクトの作成
- Playgroundsプロジェクト
Sample
を作成します。
-
Hello, world
の代わりにPlaygrounds
と表示するように変更します。 - Playgroundsプロジェクトを実行して
Playgrounds
が表示されることを確認します。 - Playgroundsプロジェクトを閉じます。
ここで改めてとなりますが、
Xcodeプロジェクトを実行した時に Playgrounds
が表示される
が今回実現したいこととなります。
Xcodeワークスペースの作成
- Xcodeでワークスペース
Sample
を作成します。 - 空のサイドバーに
Sample.xcodeproj
をドラッグ&ドロップします。
パッケージの作成
- 空のパッケージ
MyPackage
を作成し、Add to
,Group
を設定します。
-
MyPackage
配下にSample.swiftpm
を配置します。 -
Sample.swiftpm
配下にSources
ディレクトリを作成し、ContentView.swift
をSources
に移動します。
Sample/
├── Sample.xcworkspace
├── Sample.xcodeproj
├── Sample/
│ ├── SampleApp.swift
│ └── ContentView.swift
└── MyPackage/
├── Package.swift
└── Sample.swiftpm
├── Package.swift
├── MyApp.swift
└── Sources/
└── ContentView.swift
-
MyPackage
のPackage.swift
を設定します。
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [.iOS(.v17)],
products: [.library(name: "MyPackage", targets: ["MyPackage"])],
targets: [.target(name: "MyPackage", path: "Sample.swiftpm/Sources")]
)
パッケージのインポート
- 作成した
MyPackage
をXcodeプロジェクトに組み込みます。
- Playgrounds側の
ContentView
のアクセスレベルをpublic
に変更します。
import SwiftUI
public struct ContentView: View {
public init() {}
public var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Playgrounds")
}
}
}
- Xcode側の
ContentView.swift
を削除します。
Sample/
├── Sample.xcworkspace
├── Sample.xcodeproj
├── Sample/
│ └── SampleApp.swift
└── MyPackage/
├── Package.swift
└── Sample.swiftpm
├── Package.swift
├── MyApp.swift
└── Sources/
└── ContentView.swift
- Xcode側の
SampleApp
を修正し、MyPackage
をインポートします。
import SwiftUI
import MyPackage
@main
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
- Xcodeプロジェクトを実行して
Playgrounds
が表示されることを確認します。
運用方法
開発時
普段の開発時 つまり Swiftを書く場合は Sample.swiftpm
を開いて作業します。
Xcode、Playgroundsどちらで開いても問題ありません。
設定変更時
プロジェクトの設定を変更したい場合は Sample.xcworkspace
を開いて作業します。
- Info.plistの設定
- Run Scriptの設定
- Xcode Cloudの設定
などがXcodeのGUI上で出来るようになります。
注意点
App.swift
と プロジェクトの設定ファイル がそれぞれ2つずつ存在する形になります。
その点に注意して運用する必要があります。
App.swift
出来る限りAppでは 何もせずにContentViewを呼び出すだけ の方針にすることをオススメします。
何かの処理が必要な場合もXcode, Playgroundsで同じ形になるようにしましょう。
プロジェクトの設定ファイル
Xcode CloudでArchiveする 方針にすることをオススメします。
PlaygroundsでArchiveすると、Xcode側で設定した内容が反映されない形でリリースすることになります。
今回の構成を活かすためには Xcode か Xcode Cloud でのArchiveが必要です。
iPadでの開発を叶えるためには Xcode Cloud 一択になりますよね!
補足
こだわる人向けに
iPadでの開発、というニッチな領域を攻めている人はこだわりたい部分も多いと思います。
何か役に立つかもしれないので、私が気づいたことをスクラップレベルで記載しておきます。
パッケージ関連の名前
Swift Packageやマルチモジュール化の話にはなりますが、Package.swift
で設定する名前について記載します。
MyPackageA, B, Cはそれぞれ別の名前でも問題ありません。
MyPackageCについては products
, targets
で同じ名前に揃える必要があります。
またXcodeプロジェクト側のターゲット名 Sample
と同じにすることはできません。
ここで設定した名前に応じて import MyPackageC
のようにインポートすることになります。
let package = Package(
name: "MyPackageA",
platforms: [.iOS(.v17)],
products: [.library(name: "MyPackageB", targets: ["MyPackageC"])],
targets: [.target(name: "MyPackageC", path: "Sample.swiftpm/Sources")]
)
Swiftコード用のディレクトリ
Sample.swiftpm
配下に作成する Sources
ディレクトリは任意の名前で問題ありません。
ディレクトリ構成
ルートディレクトリを綺麗に保ちたいという考えがあると思います。
特にPlaygroundsプロジェクト Sample.swiftpm
はルートにあった方が都合が良いでしょう。
一例として以下のようなディレクトリ構成はどうでしょうか。
Sample/
├── Package.swift
├── Sample.xcworkspace
├── Sample.swiftpm
│ ├── Package.swift
│ ├── MyApp.swift
│ └── Sources/
│ └── ContentView.swift
└── Sample/
├── Sample.xcodeproj
└── Sample
└── SampleApp.swift
ルートの Sample/
自体を MyPackage/
の代わりに利用する形です。
併せて、あまり利用しない Sample.xcodeproj
を Sample/Sample/
配下に移動しています。
これを実現するには Sample.xcworkspace/contents.xcworkspacedata
を下記のように修正します。
ルートを指定したい場合は空文字 location = "group:"
と記述することで実現することができます。
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:">
</FileRef>
<FileRef
location = "group:Sample/Sample.xcodeproj">
</FileRef>
</Workspace>
おわりに
Playgroundsプロジェクトのコードをパッケージ化し、Xcodeプロジェクトから利用する構成
を紹介しました。
詳細なプロジェクト設定が必要な場合、iPadのみで開発を完結させることはまだ難しい面があります。
ただし、設定のみをMacで行い、日々の開発をiPadで行うことは十分に可能です。
この構成を活かして、実務レベルのプロジェクトにもiPadでの開発体験を持ち込んでみてはいかがでしょうか?
WWDC24で Swift Playgrounds に更なるアップデートがあることを期待しています!
Discussion