🎍

Xcodeプロジェクトの新規作成時にやること個人的まとめ

に公開

はじめに

Xcodeプロジェクトの新規作成時に毎回やること(GitHubテンプレートを作成してもよい)が、個人的にも毎年少しずつ変わってきているので、とりあえず2025年1月現在にやっていることをここにまとめます。

.gitignore

GitHubから新規作成するなら、GitHubのデフォルトの .gitignore を使ってもいいです。
とはいえ個人的には相変わらず gitignore.io を使っています。

https://www.toptal.com/developers/gitignore

インストール方法

端末ごとに1回だけ、次のコマンドラインを実行すると、gitignoreを生成するためのツールがインストールされます。

echo "function gi() { curl -sLw \"\\\n\" https://www.toptal.com/developers/gitignore/api/\$@ ;}" >> \
~/.zshrc && source ~/.zshrc

https://docs.gitignore.io/install/command-line#macos より引用

使い方

プロジェクトのルートディレクトリで、次のコマンドラインを実行すると、Swift言語の設定が .gitignore ファイルに追加されます。

gi swift >> .gitignore

https://docs.gitignore.io/use/command-line より引用

.editorconfig

Xcode16からEditorConfigがサポートされました。
何がいいかというと、例えばインデント設定について、同時進行しているプロジェクトで、それぞれインデント設定が4つだったり、2つだったりバラバラの場合がありえますよね。そのときにこれまでは、プロジェクトを変えるごとに、Xcodeの環境設定から、インデント設定を手動でぽちぽち切り替えていたことと思いますが、その手作業が不要になります。 .editorconfig を各プロジェクトに置くことで、EditorConfigがXcodeの環境設定のエディター関係の設定を上書きしてくれるからです。

https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes#Source-Editor

引用

Source Editor
New Features
Added support for EditorConfig. The editor now respects indentation settings given in .editorconfig files. Xcode supports the following settings: indent_style, tab_width, indent_size, end_of_line, insert_final_newline, max_line_length, and trim_trailing_whitespace. See https://editorconfig.org for more information. Settings from .editorconfig files normally take precedence over settings given in the “Text Editing” section of View > Inspectors > File for a file or group. This can be disabled by unchecking the “Prefer Settings from EditorConfig” checkbox on the Indentation tab of Xcode > Settings > Text Editing. (20796230)

(日本語訳)EditorConfigのサポートが追加されました。エディターは、.editorconfigファイルで与えられたインデント設定を尊重するようになりました。Xcodeは、次の設定をサポートしています:indent_style、tab_width、indent_size、end_of_line、insert_final_newline、max_line_length、およびtrim_trailing_whitespace。詳細については、https://editorconfig.orgを参照してください。.Editorconfigファイルからの設定は、通常、ファイルまたはグループの[表示]>[インスペクタ]>[ファイルの[テキスト編集]]セクションで指定された設定よりも優先されます。これは、Xcode > 設定 > テキスト編集の [インデント] タブの [EditorConfig からの設定] チェックボックスをオフにすることで無効にできます。(20796230)

.editorconfigの例

# EditorConfig is awesome: https://editorconfig.org

[*.swift]
indent_style = space
tab_width = 4
indent_size = 4
end_of_line = lf
insert_final_newline = true
max_line_length = 120
trim_trailing_whitespace = true

参考資料

詳しくは次の記事が参考になります。
https://www.polpiella.dev/xcode-editor-config
https://editorconfig.org

プルリクエストテンプレート

個人開発の場合も、なんだかんだわかりやすいのでプルリクエストテンプレートを設定しています。
過去には詳細なテンプレートを作成していたこともありましたが、今はすごくシンプルなものに落ち着いています。

.github/pull_request_template.md

## 要件・仕様

## 処理概要

## 動作確認

## 特記

## 参考資料

参考資料

GitHub Docs > リポジトリ用のプルリクエストテンプレートの作成
https://docs.github.com/ja/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository

String Catalog

ローカリゼーションのため、及び文言管理のために、String Catalogを利用します。

デフォルト名のまま「Localizable.xcstrings」を生成します。

Info.plist用のString Catalog

アプリの表示名(Bundle Display Name / CFBundleDisplayName)をローカライズしたい場合、String Catalogを利用し、名称を「InfoPlist.xcstrings」として生成します。
ファイルを生成して、ビルドをすると次のようになります。

整理すると、次のスクショのように、 .xcstrings ファイルが2つ並ぶことになります。

README.md

タイトルと、少なくともソフトウェア要件を記載することにしています。

# タイトル

## ソフトウェア要件

- Xcode 16.2(16C5032a)
- Compiler Version: Swift 6
- Language Mode: Swift 6
- Minimum Deployments: iOS 18.2 〜
- Supported Destinations: iPhone, iPad

Build Settings

Versioning

[Marketing Version](アプリバージョンに相当) と [Current Project Version](ビルド番号に相当)を更新します。
特に要件がなければ複数TARGETSがあったとしても、同一のアプリバージョンで管理すればいいかと思いますので、私の場合は、TARGETSレベルに設定されている値を削除し、PROJECTレベルに記入しなおしています。

before
デフォルト値

after
変更後

やり方

TARGETSレベルに設定されている値を削除する方法は次のとおりです。
[Build Settings]タブで、TARGETSレベルを選び、対象の項目([Marketing Version]など)にフォーカスを当てた状態でdeleteキーを押します。


この状態でdeleteキーを押します


TARGETSレベルに設定されている値が削除されます

PROJECTレベルに記入する方法は、PROJECTレベルを選び、対象の項目([Marketing Version]など)の値の欄をダブルクリックして入力ポップアップを開き、値を入力します。


ダブルクリックして入力ポップアップを開き、値を入力します

Swift Language Version

[Swift Language Version](Swift Language モードに相当)を、新規プロジェクトの場合はSwift6に最新化します。デフォルトでSwift5なのですよね。
これも私の場合は、TARGETSレベルに設定されている値を削除し、PROJECTレベルに記入しなおしています。


デフォルト値


変更後

.swift-format

Xcode 16 からXcodeのメニューから swiftlang/swift-format に則ったコードフォーマットが可能になりました。

https://github.com/swiftlang/swift-format

(Xcode 26.0.1の時点)
Xcodeのメニューから [Editor] > [Structure]を選択すると、コードフォーマットに関するアクションが表示されます。
選択範囲のフォーマットは、[Format Selection with 'swift-format` ](^⌥⇧I)で、
表示中のファイルのフォーマットは、 [Format File with 'swift-format'](^⇧I)で行います。
(だんだん手が覚えます)

.swift-format ファイルでフォーマットルールを定義することができます。
リリース 602.0.0 時点でのルールを使って説明を進めます。

デフォルトの定義とカスタマイズした定義

次のコマンドでデフォルトの定義が出力できます。

swift format dump-configuration

.swift-formatファイルとして保存したい場合はこちら。

swift format dump-configuration > .swift-format
.swift-format の出力結果(デフォルト値)
{
  "fileScopedDeclarationPrivacy" : {
    "accessLevel" : "private"
  },
  "indentConditionalCompilationBlocks" : true,
  "indentSwitchCaseLabels" : false,
  "indentation" : {
    "spaces" : 2
  },
  "lineBreakAroundMultilineExpressionChainComponents" : false,
  "lineBreakBeforeControlFlowKeywords" : false,
  "lineBreakBeforeEachArgument" : false,
  "lineBreakBeforeEachGenericRequirement" : false,
  "lineBreakBetweenDeclarationAttributes" : false,
  "lineLength" : 100,
  "maximumBlankLines" : 1,
  "multiElementCollectionTrailingCommas" : true,
  "noAssignmentInExpressions" : {
    "allowedFunctions" : [
      "XCTAssertNoThrow"
    ]
  },
  "prioritizeKeepingFunctionOutputTogether" : false,
  "reflowMultilineStringLiterals" : "never",
  "respectsExistingLineBreaks" : true,
  "rules" : {
    "AllPublicDeclarationsHaveDocumentation" : false,
    "AlwaysUseLiteralForEmptyCollectionInit" : false,
    "AlwaysUseLowerCamelCase" : true,
    "AmbiguousTrailingClosureOverload" : true,
    "AvoidRetroactiveConformances" : true,
    "BeginDocumentationCommentWithOneLineSummary" : false,
    "DoNotUseSemicolons" : true,
    "DontRepeatTypeInStaticProperties" : true,
    "FileScopedDeclarationPrivacy" : true,
    "FullyIndirectEnum" : true,
    "GroupNumericLiterals" : true,
    "IdentifiersMustBeASCII" : true,
    "NeverForceUnwrap" : false,
    "NeverUseForceTry" : false,
    "NeverUseImplicitlyUnwrappedOptionals" : false,
    "NoAccessLevelOnExtensionDeclaration" : true,
    "NoAssignmentInExpressions" : true,
    "NoBlockComments" : true,
    "NoCasesWithOnlyFallthrough" : true,
    "NoEmptyLinesOpeningClosingBraces" : false,
    "NoEmptyTrailingClosureParentheses" : true,
    "NoLabelsInCasePatterns" : true,
    "NoLeadingUnderscores" : false,
    "NoParensAroundConditions" : true,
    "NoPlaygroundLiterals" : true,
    "NoVoidReturnOnFunctionSignature" : true,
    "OmitExplicitReturns" : false,
    "OneCasePerLine" : true,
    "OneVariableDeclarationPerLine" : true,
    "OnlyOneTrailingClosureArgument" : true,
    "OrderedImports" : true,
    "ReplaceForEachWithForLoop" : true,
    "ReturnVoidInsteadOfEmptyTuple" : true,
    "TypeNamesShouldBeCapitalized" : true,
    "UseEarlyExits" : false,
    "UseExplicitNilCheckInConditions" : true,
    "UseLetInEveryBoundCaseVariable" : true,
    "UseShorthandTypeNames" : true,
    "UseSingleLinePropertyGetter" : true,
    "UseSynthesizedInitializer" : true,
    "UseTripleSlashForDocumentationComments" : true,
    "UseWhereClausesInForLoops" : false,
    "ValidateDocumentationComments" : false
  },
  "spacesAroundRangeFormationOperators" : false,
  "spacesBeforeEndOfLineComments" : 2,
  "tabWidth" : 8,
  "version" : 1
}

自分好みにカスタマイズしたものも載せておきます。

"spaces" : 2"spaces" : 4 に変えるかどうかは、プロジェクトの方針に合わせます。

.swift-format の出力結果(カスタマイズしたもの)
{
  "fileScopedDeclarationPrivacy" : {
    "accessLevel" : "private"
  },
  "indentConditionalCompilationBlocks" : true,
  "indentSwitchCaseLabels" : false,
  "indentation" : {
    "spaces" : 2
  },
  "lineBreakAroundMultilineExpressionChainComponents" : false,
  "lineBreakBeforeControlFlowKeywords" : false,
  "lineBreakBeforeEachArgument" : false,
  "lineBreakBeforeEachGenericRequirement" : false,
  "lineBreakBetweenDeclarationAttributes" : false,
  "lineLength" : 300,
  "maximumBlankLines" : 1,
  "multiElementCollectionTrailingCommas" : true,
  "noAssignmentInExpressions" : {
    "allowedFunctions" : [
      "XCTAssertNoThrow"
    ]
  },
  "prioritizeKeepingFunctionOutputTogether" : false,
  "reflowMultilineStringLiterals" : "never",
  "respectsExistingLineBreaks" : true,
  "rules" : {
    "AllPublicDeclarationsHaveDocumentation" : false,
    "AlwaysUseLiteralForEmptyCollectionInit" : false,
    "AlwaysUseLowerCamelCase" : true,
    "AmbiguousTrailingClosureOverload" : true,
    "AvoidRetroactiveConformances" : true,
    "BeginDocumentationCommentWithOneLineSummary" : false,
    "DoNotUseSemicolons" : true,
    "DontRepeatTypeInStaticProperties" : true,
    "FileScopedDeclarationPrivacy" : true,
    "FullyIndirectEnum" : true,
    "GroupNumericLiterals" : true,
    "IdentifiersMustBeASCII" : true,
    "NeverForceUnwrap" : true,
    "NeverUseForceTry" : true,
    "NeverUseImplicitlyUnwrappedOptionals" : true,
    "NoAccessLevelOnExtensionDeclaration" : false,
    "NoAssignmentInExpressions" : true,
    "NoBlockComments" : true,
    "NoCasesWithOnlyFallthrough" : true,
    "NoEmptyLinesOpeningClosingBraces" : false,
    "NoEmptyTrailingClosureParentheses" : true,
    "NoLabelsInCasePatterns" : true,
    "NoLeadingUnderscores" : false,
    "NoParensAroundConditions" : true,
    "NoPlaygroundLiterals" : true,
    "NoVoidReturnOnFunctionSignature" : true,
    "OmitExplicitReturns" : true,
    "OneCasePerLine" : true,
    "OneVariableDeclarationPerLine" : true,
    "OnlyOneTrailingClosureArgument" : true,
    "OrderedImports" : true,
    "ReplaceForEachWithForLoop" : false,
    "ReturnVoidInsteadOfEmptyTuple" : true,
    "TypeNamesShouldBeCapitalized" : true,
    "UseEarlyExits" : false,
    "UseExplicitNilCheckInConditions" : true,
    "UseLetInEveryBoundCaseVariable" : false,
    "UseShorthandTypeNames" : true,
    "UseSingleLinePropertyGetter" : true,
    "UseSynthesizedInitializer" : true,
    "UseTripleSlashForDocumentationComments" : true,
    "UseWhereClausesInForLoops" : false,
    "ValidateDocumentationComments" : false
  },
  "spacesAroundRangeFormationOperators" : false,
  "spacesBeforeEndOfLineComments" : 2,
  "tabWidth" : 8,
  "version" : 1
}

参考までに、カスタマイズしたルールの説明

ルールのドキュメントは次の2つが役に立ちます。

ルール名 説明 フォーマットかLintか デフォルト値 カスタマイズした値 備考
lineLength The maximum allowed length of a line, in characters. フォーマットのみ 100 300 まとまりや見やすさなどの都合で1行が長い場合、自動フォーマットかけられてもあまり嬉しくない場合があるので
NeverForceUnwrap Force-unwraps are strongly discouraged and must be documented. Linterのみ false true テストコードは自動で対象外にしてくれる
NeverUseForceTry Force-try (try!) is forbidden. Linterのみ false true テストコードは自動で対象外にしてくれる
NeverUseImplicitlyUnwrappedOptionals Implicitly unwrapped optionals (e.g. var s: String!) are forbidden. Linterのみ false true テストコードは自動で対象外にしてくれる
NoAccessLevelOnExtensionDeclaration Specifying an access level for an extension declaration is forbidden. Format: The access level is removed from the extension declaration and is added to each declaration in the extension; Linterもフォーマットもする true false extensionの前にアクセサーつけたいので、falseに変更した
OmitExplicitReturns Single-expression functions, closures, subscripts can omit return statement. Format: func <name>() { return ... } constructs will be replaced with equivalent func <name>() { ... } constructs. Linterもフォーマットもする false true return 不要なら除去したいので、trueに変更した
UseLetInEveryBoundCaseVariable Every variable bound in a case pattern must have its own let/var . For example, case let .identifier(x, y) is forbidden. Use case .identifier(let x, let y) instead. Format: case let .identifier(x, y) will be replaced by case .identifier(let x, let y) . Linterもフォーマットもする true false let を前にまとめて出したいので、falseに変更した

注意事項

README に留意点が記載されています。

運用

Xcodeビルド時にswift-formatを自動実行するスクリプトを追加してみました。

設定手順

  1. Xcodeの [Build Phases] タブを開く
  2. + ボタンをクリック → [New Run Script Phase] を選択
  3. Run Scriptを [Compile Sources] の前にドラッグして移動
  4. 以下のスクリプトを入力
echo "Formatting Swift code..."
cd "$SRCROOT/.."

# HEADから変更されたSwiftファイルを取得
CHANGED_FILES=$(git diff --name-only --diff-filter=ACM HEAD "*.swift" 2>/dev/null | sort -u)

if [ -n "$CHANGED_FILES" ]; then
  echo "$CHANGED_FILES" | while read file; do
    if [ -f "$file" ]; then
      swift format --in-place "$file"
      echo "Formatted: $file"
    fi
  done
  echo "✅ Code formatted"
else
  echo "No Swift files to format"
fi

スクリプトを正常に実行するための設定

1. "Based on dependency analysis" のチェックを外す

Run Scriptフェーズの設定で "Based on dependency analysis" のチェックを外してください。

理由:
git diffで変更されたファイルを動的に取得するため、事前にInput/Output Filesを指定できません。

2. "User Script Sandboxing" を無効化

[Build Settings] タブで "User Script Sandboxing" を検索し、Debug構成のみ NO に設定してください。

設定手順:

  1. "User Script Sandboxing" の左側の矢印をクリックして展開
  2. Debug: NO
  3. Release: YES (デフォルトのまま)

理由:
サンドボックスが有効だと、スクリプトからソースファイルへの書き込みが制限され、フォーマットが適用されません。

セキュリティ:
Debug構成のみ無効化することで、開発時は自動フォーマットが機能し、Release/CI環境では安全性が保たれます。

参考)コマンド

全ディレクトリを対象にした場合のコマンドを載せます。

フォーマット

swift format format --in-place --recursive ./

Linter

swift format lint --recursive .

Linterはまだ https://github.com/realm/SwiftLint と共存させるのもおすすめです。

今後

今後も随時更新していきます。
かつて SwiftFormat を使っていたところもswift-formatに差し替えて取り入れたい。 → DONE!

Discussion