TCA로 프로젝트를 진행하면서 정말 지긋지긋했던 순간이 있어요.
새 Feature를 추가할 때마다:
// 1. Feature 파일 만들고
@Reducer
public struct UserLoginFeature {
@ObservableState
public struct State: Equatable {
// ...
}
// 이하 생략
}
// 2. View 파일 만들고
public struct UserLoginView: View {
@Bindable var store: StoreOf<UserLoginFeature>
// ...
}
// 3. 파일명을 UserLogin으로 일일이 바꾸고...
음 벌써 파일을 두개 만들었죠..??

10번째쯤 되니까 너무 귀찮더라고요. 그래서 찾아본 게 Xcode 커스텀 템플릿이었어요.
그래서 템플릿이 뭔데?
"File → New → File" 메뉴

Xcode에서 ⌘N 누르면 나오는 화면, 자주 보셨죠?
이게 다 템플릿이에요. Apple이 미리 만들어둔 파일 생성 틀이죠.
그럼 🙋♀️: 왜 Apple은 이런 걸 만들어뒀을까요?
답은 간단해요. 반복적인 작업을 자동화하기 위해서죠.
예를 들어 "SwiftUI View"를 선택하면:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
#Preview {
ContentView()
}
그럼 또또 🙋: 우리가 직접 만들 수는 없을까요?
당연히 가능합니다. 그게 바로 커스텀 템플릿이에요!
Xcode 템플릿은 사실 특별한 구조를 가진 폴더예요.
# 이런 경로에 이런 폴더를 만들면
~/Library/Developer/Xcode/Templates/MyTemplates/TCA.xctemplate/
# Xcode가 자동으로 인식해서
File → New → File → TCA Feature (SwiftUI)
# 이렇게 메뉴에 나타나요
왜 여기에 만들어야 할까요?
Xcode가 시작할 때 이 경로를 스캔하기 때문이에요. 마치 macOS가 /Applications 폴더를 스캔해서 앱 목록을 보여주는 것처럼요.
실제로 Xcode 내부를 보면:
/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/
├── File Templates/
│ ├── Source/
│ │ ├── Swift File.xctemplate/
│ │ ├── SwiftUI View.xctemplate/
│ │ └── ...
│ └── ...
└── ...
Apple의 기본 템플릿들이 이미 여기 있어요. 우리도 똑같은 방식으로 만들면 되는 거죠.
템플릿 파일 구조 뜯어보기

.xctemplate 폴더?
템플릿 폴더는 반드시 .xctemplate 확장자를 가져야 해요.
왜 그럴까요?
Xcode가 템플릿을 찾을 때 확장자로 필터링하기 때문이에요:
// Xcode 내부 (의사코드)
func scanTemplates(in directory: URL) -> [Template] {
return directory
.contents()
.filter { $0.pathExtension == "xctemplate" } // ← 여기!
.compactMap { parseTemplate(at: $0) }
}
.xctemplate이 아니면 아예 안 읽어요. 그냥 무시해버립니다.
TemplateInfo.plist: 템플릿의 설계도
모든 .xctemplate 폴더 안에는 TemplateInfo.plist가 반드시 있어야 해요.
왜 plist를 쓸까요? 선언적으로 템플릿을 정의하기 위해서죠.
실제 plist를 봅시다:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Kind</key>
<string>Xcode.IDEKit.TextSubstitutionFileTemplateKind</string>
<key>Title</key>
<string>TCA Feature (SwiftUI)</string>
<key>Nodes</key>
<array>
<string>___FILEBASENAME___Feature.swift</string>
<string>___FILEBASENAME___View.swift</string>
</array>
</dict>
</plist>
하나하나 뜯어봅시다.
Kind: 템플릿의 종류
<key>Kind</key>
<string>Xcode.IDEKit.TextSubstitutionFileTemplateKind</string>
이게 뭘까요?
TextSubstitutionFileTemplateKind를 분해하면:
- TextSubstitution: 텍스트 치환 방식
- FileTemplate: 파일 템플릿
- Kind: 종류
즉, "텍스트 치환 방식의 파일 템플릿"이라는 뜻이에요
왜 "텍스트 치환"일까요?
템플릿 파일 안에 있는 ___XXX___ 같은 특수 문자열(매크로)을 실제 값으로 바꾸는 방식이기 때문이에요:
// 템플릿 파일
struct ___FILEBASENAME___Feature { }
// 사용자가 "UserLogin" 입력
↓ 텍스트 치환
// 생성된 파일
struct UserLoginFeature { }
```
다른 Kind도 있나요?
있어요! Apple 내부 템플릿을 보면:
Xcode.IDEKit.TextSubstitutionFileTemplateKind
→ 텍스트 치환 (가장 많이 씀)
Xcode.Xcode3.ProjectTemplateUnitKind
→ 프로젝트 템플릿 (File → New → Project)
Xcode.IDEFoundation.TargetTemplateUnitKind
→ 타겟 템플릿 (Framework, Test Target 등)
우리는 파일만 만들 거니까 TextSubstitutionFileTemplateKind를 쓰는 거예요.
Title: 메뉴에 표시될 이름
<key>Title</key>
<string>TCA Feature (SwiftUI)</string>
Nodes: 생성할 파일 목록
<key>Nodes</key>
<array>
<string>___FILEBASENAME___Feature.swift</string>
<string>___FILEBASENAME___View.swift</string>
</array>
이게 핵심이에요. 어떤 파일들을 만들지 정의하는 거예요.
Xcode는 이 배열을 순회하면서 각 파일을 생성해요:
매크로 시스템
___XXX___ 형태의 특수 키워드를 매크로라고 해요. Xcode가 실제 값으로 바꿔줘요.
자주 쓰는 매크로
___FILEBASENAME___ // "UserLogin"
___FILEBASENAMEASIDENTIFIER___ // "UserLogin" (Swift identifier로 변환)
___FILENAME___ // "UserLogin.swift"
___PROJECTNAME___ // "MyApp"
___DATE___ // "11/24/25"
___YEAR___ // "2025"
___ORGANIZATIONNAME___ // "My Company"
차이점: FILEBASENAME vs FILEBASENAMEASIDENTIFIER
// 입력: "User Login" (공백 포함)
___FILEBASENAME___
→ "User Login" (그대로)
___FILEBASENAMEASIDENTIFIER___
→ "UserLogin" (공백 제거, Swift 규칙에 맞게)
타입명, 변수명에는 항상 ASIDENTIFIER 버전을 쓰세요:
// ✅ 올바름
struct ___FILEBASENAMEASIDENTIFIER___Feature { }
// ❌ 잘못됨 - 공백 들어가면 에러
struct ___FILEBASENAME___Feature { }
조직명 설정하기
기본값은 "Your Company"예요. 전문적으로 바꿔봅시다:
# 설정
defaults write com.apple.dt.Xcode IDETemplateOptions.organizationName "My Company"
# 확인
defaults read com.apple.dt.Xcode IDETemplateOptions.organizationName
Xcode 내부 동작: 템플릿이 실제로 어떻게 작동하나
지금까지는 "무엇을" 만드는지 봤어요. 이제 "어떻게" 작동하는지 파헤쳐봅시다.
템플릿 스캔 과정
Xcode를 켜면 제일 먼저 뭘 할까요?
// Xcode 시작 시 (의사코드)
func launch() {
// ... 다른 초기화 작업들 ...
let templates = scanTemplates()
self.templateRegistry.register(templates)
// ...
}
func scanTemplates() -> [Template] {
var templates: [Template] = []
// 1. 시스템 템플릿 스캔
let systemPath = "/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/"
templates += scanDirectory(systemPath)
// 2. 사용자 템플릿 스캔
let userPath = "~/Library/Developer/Xcode/Templates/"
templates += scanDirectory(userPath)
return templates
}
func scanDirectory(_ path: String) -> [Template] {
return FileManager.default
.contentsOfDirectory(atPath: path)
.filter { $0.hasSuffix(".xctemplate") }
.compactMap { loadTemplate(at: "\(path)/\($0)") }
}
핵심 포인트:
- Xcode 시작할 때 딱 한 번만 스캔해요
- 그래서 새 템플릿 추가하면 Xcode 재시작해야 해요
- 사용자 템플릿이 나중에 스캔되니까 시스템 템플릿을 덮어쓸 수 있어요
실전: TCA 템플릿 만들기
1. 폴더 구조 만들기
mkdir -p ~/Library/Developer/Xcode/Templates/CustomTemplates/TCA.xctemplate
cd ~/Library/Developer/Xcode/Templates/CustomTemplates/TCA.xctemplate
2. TemplateInfo.plist 작성

3. Feature 템플릿 파일
___FILEBASENAME___Feature.swift 파일:


4. View 템플릿 파일
___FILEBASENAME___View.swift 파일:
5. Xcode 재시작

중요한것!! 그런데 팀원마다 Library 폴더에 직접 설치하면 불편하잖아요??
- 업데이트 전파가 어려움
- 버전 관리 안 됨
- 신규 팀원이 설치 못 함
=> 프로젝트 저장소에 템플릿을 보관하고 스크립트로 설치해버리자!!
MyProject/
├── App/
├── Sources/
├── XcodeTemplates/ # ← 템플릿을 여기 보관!
│ └── TCA.xctemplate/
│ ├── TemplateInfo.plist
│ ├── ___FILEBASENAME___Feature.swift
│ └── ___FILEBASENAME___View.swift
└── install_templates.sh # ← 설치 스크립트
왜 이렇게 하나요?
- Git으로 버전 관리: 템플릿 변경 이력 추적
- 자동 배포: git pull → ./install_templates.sh 끝
- 온보딩 간소화: 신규 팀원도 스크립트 한 줄로 설치
설치 스크립트 작성
프로젝트 루트에 install_templates.sh 파일을 만드세요:
#!/bin/bash
# TCA Template Installation Script
# 프로젝트의 XcodeTemplates를 Xcode에 설치합니다
set -e # 에러 발생 시 중단
# 색상 정의
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# 경로 설정
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATE_SOURCE="$SCRIPT_DIR/XcodeTemplates"
TEMPLATE_DEST="$HOME/Library/Developer/Xcode/Templates/CustomTemplates"
echo -e "${YELLOW}🚀 TCA 템플릿 설치 중...${NC}"
echo ""
# 템플릿 소스 확인
if [ ! -d "$TEMPLATE_SOURCE" ]; then
echo -e "${RED}❌ 템플릿 폴더를 찾을 수 없습니다: $TEMPLATE_SOURCE${NC}"
exit 1
fi
# 대상 폴더 생성
if [ ! -d "$TEMPLATE_DEST" ]; then
echo -e "📁 템플릿 폴더 생성: $TEMPLATE_DEST"
mkdir -p "$TEMPLATE_DEST"
fi
# 템플릿 복사
echo -e "📋 템플릿 복사 중..."
echo -e " From: $TEMPLATE_SOURCE"
echo -e " To: $TEMPLATE_DEST"
cp -R "$TEMPLATE_SOURCE"/* "$TEMPLATE_DEST/"
echo ""
echo -e "${GREEN}✅ 설치 완료!${NC}"
echo ""
echo -e "${YELLOW}📌 다음 단계:${NC}"
echo " 1. Xcode를 재시작하세요"
echo " 2. 새 파일 생성 (⌘N)"
echo " 3. 'TCA Feature (SwiftUI)'를 찾아보세요"
echo ""
echo "💡 Xcode 재시작 명령어:"
echo " killall Xcode && open /Applications/Xcode.app"
echo ""
사용 방법
처음 설치할 때
# 프로젝트 clone
git clone https://github.com/company/myproject.git
cd myproject
# 템플릿 설치
./install_templates.sh
# Xcode 재시작
killall Xcode && open /Applications/Xcode.app
템플릿 업데이트했을 때
# 최신 코드 받기
git pull
# 템플릿 재설치
./install_templates.sh
# Xcode 재시작
killall Xcode
'SWIFT개발일지' 카테고리의 다른 글
| ProtoBuf - 이게 뭔데 사람들은 환호성을 지를까? (1) | 2025.11.29 |
|---|---|
| BLE 완전 기초: CoreBluetooth를 이해하기 위한 필수 개념 (0) | 2025.11.16 |
| 이미지 URL 저장 시 마주하는 함정 문제들 (0) | 2025.09.11 |
| Metal3편 - 메모리 사용량 급증 버그 수정 (0) | 2025.04.20 |
| 이미지 최적화 3탄(kingFisher를 삭제하고 Custom) (0) | 2025.04.16 |