しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

コードからXcodeのプロジェクトルートを取得する

シミュレータでのデバック時やXcode previewsを利用する際に、差分管理の都合上、プロジェクトにリソースを追加せずにデバッグしたいケースがあります。
そのような場合に、リソースのロードにローカルのパスを参照する必要があるため、Xcodeのプロジェクトルートをコードから探る必要が生じます。

ということでDEBUG onlyで、プロジェクトルートを辿る方法を考えてみます。
(今回はSRCROOTを使っていますが、PROJECT_DIRなどでも同様です。)

ビルド時にスクリプトでInfo.plistに書き込む

PlistBuddyを使って、Info.plistに書き込む方法です。
Build PhasesのCopy Bundle Resourcesの後にスクリプトを追加して、.app配下のInfo.plistに項目を追加します。
(プロジェクト内のInfo.plistには項目は追加されません)

if [ "${CONFIGURATION}" = "Debug" ]; then
  /usr/libexec/PlistBuddy -c "Add :RootDir string ${SRCROOT}" "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
fi

PlistBuddyで書き込む際に、ENABLE_USER_SCRIPT_SANDBOXINGNOにする必要があります。

Swiftからは、Bundle.main.infoDictionaryで取得できます。

let projectRootPath = Bundle.main.infoDictionary?["RootDir"] as? String

SRCROOT以外にも、gitで管理している場合はそちらを使うことができます。

filePathマクロを使う

#filePathマクロを使うことで、あるファイルのパスからプロジェクトルートを探す方法です。
ファイルの置き場所とプロジェクトルートの相対パスに処理が依存するので、ファイルの移動の際に注意する必要があります。

let fileUrl = URL(filePath: #filePath)
let components = fileUrl.pathComponents.dropLast().dropFirst() // 相対パスに応じて加工
let projectRootPath = components.joined(separator: "/")

developer.apple.com

リソース置き場に上記の内容を含んだファイルを置いておけば、相対パスの考慮が必要なくなります。

ObjC経由でpreprocessor macroを使う

Swiftからは直接preprocessor macroの値を参照することはできないですが、Objective-Cで定数として定義し直して利用する方法もあります。

Build SettingsのApple Clang - Preprocessingから、Preprocessor MacrosでROOTDIR=$(SRCROOT)のように定義します。

Objective-Cの適当なヘッダーに定数を定義して、Bridging-Headerを介してSwiftから扱います。

#import <Foundation/Foundation.h>

#ifdef DEBUG
static NSString *const projectRootPath = @OS_STRINGIFY(ROOTDIR);
#endif

SwiftからはprojectRootPathで参照できます。