🕊️

XcodeとPlaygroundsを共存させるプロジェクト構成

2024/05/20に公開

はじめに

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プロジェクトの作成

  1. Xcodeプロジェクト Sample を作成します。
  1. Hello, world! の代わりに Xcode と表示するように変更します。
  2. Xcodeプロジェクトを実行して Xcode が表示されることを確認します。
  3. Xcodeプロジェクトを閉じます。

Playgroundsプロジェクトの作成

  1. Playgroundsプロジェクト Sample を作成します。
  1. Hello, world の代わりに Playgrounds と表示するように変更します。
  2. Playgroundsプロジェクトを実行して Playgrounds が表示されることを確認します。
  3. Playgroundsプロジェクトを閉じます。

ここで改めてとなりますが、
Xcodeプロジェクトを実行した時に Playgrounds が表示される
が今回実現したいこととなります。

Xcodeワークスペースの作成

  1. Xcodeでワークスペース Sample を作成します。
  2. 空のサイドバーに Sample.xcodeproj をドラッグ&ドロップします。

パッケージの作成

  1. 空のパッケージ MyPackage を作成し、Add to , Group を設定します。
  1. MyPackage 配下に Sample.swiftpm を配置します。
  2. Sample.swiftpm 配下に Sources ディレクトリを作成し、ContentView.swiftSources に移動します。
Sample/
├── Sample.xcworkspace
├── Sample.xcodeproj
├── Sample/
│   ├── SampleApp.swift
│   └── ContentView.swift
└── MyPackage/
    ├── Package.swift
    └── Sample.swiftpm
        ├── Package.swift
        ├── MyApp.swift
        └── Sources/
            └── ContentView.swift
  1. MyPackagePackage.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")]
)

パッケージのインポート

  1. 作成した MyPackage をXcodeプロジェクトに組み込みます。
  1. Playgrounds側の ContentView のアクセスレベルを public に変更します。
MyPackage/Sample.swiftpm/Sources/ContentView.swift
import SwiftUI

public struct ContentView: View {
    public init() {}

    public var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Playgrounds")
        }
    }
}
  1. Xcode側の ContentView.swift を削除します。
Sample/
├── Sample.xcworkspace
├── Sample.xcodeproj
├── Sample/
│   └── SampleApp.swift
└── MyPackage/
    ├── Package.swift
    └── Sample.swiftpm
        ├── Package.swift
        ├── MyApp.swift
        └── Sources/
            └── ContentView.swift
  1. Xcode側の SampleApp を修正し、MyPackage をインポートします。
Sample/SampleApp.swift
import SwiftUI
import MyPackage

@main
struct SampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
  1. 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側で設定した内容が反映されない形でリリースすることになります。
今回の構成を活かすためには XcodeXcode Cloud でのArchiveが必要です。

iPadでの開発を叶えるためには Xcode Cloud 一択になりますよね!

補足

こだわる人向けに

iPadでの開発、というニッチな領域を攻めている人はこだわりたい部分も多いと思います。
何か役に立つかもしれないので、私が気づいたことをスクラップレベルで記載しておきます。

パッケージ関連の名前

Swift Packageやマルチモジュール化の話にはなりますが、Package.swift で設定する名前について記載します。

MyPackageA, B, Cはそれぞれ別の名前でも問題ありません。

MyPackageCについては products , targets で同じ名前に揃える必要があります。
またXcodeプロジェクト側のターゲット名 Sample と同じにすることはできません。
ここで設定した名前に応じて import MyPackageC のようにインポートすることになります。

Package.swift
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.xcodeprojSample/Sample/ 配下に移動しています。

これを実現するには Sample.xcworkspace/contents.xcworkspacedata を下記のように修正します。
ルートを指定したい場合は空文字 location = "group:" と記述することで実現することができます。

Sample.xcworkspace/contents.xcworkspacedata
<?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 に更なるアップデートがあることを期待しています!

GitHubで編集を提案

Discussion