아 지겹다 복붙! Xcode 커스텀 템플릿 만들기

2025. 11. 25. 21:11·SWIFT개발일지

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)") }
}

핵심 포인트:

  1. Xcode 시작할 때 딱 한 번만 스캔해요
  2. 그래서 새 템플릿 추가하면 Xcode 재시작해야 해요
  3. 사용자 템플릿이 나중에 스캔되니까 시스템 템플릿을 덮어쓸 수 있어요

실전: 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     # ← 설치 스크립트

왜 이렇게 하나요?

  1. Git으로 버전 관리: 템플릿 변경 이력 추적
  2. 자동 배포: git pull → ./install_templates.sh 끝
  3. 온보딩 간소화: 신규 팀원도 스크립트 한 줄로 설치

설치 스크립트 작성

프로젝트 루트에 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
'SWIFT개발일지' 카테고리의 다른 글
  • ProtoBuf - 이게 뭔데 사람들은 환호성을 지를까?
  • BLE 완전 기초: CoreBluetooth를 이해하기 위한 필수 개념
  • 이미지 URL 저장 시 마주하는 함정 문제들
  • Metal3편 - 메모리 사용량 급증 버그 수정
2료일
2료일
좌충우돌 모든것을 다 정리하려고 노력하는 J가 되려고 하는 세미개발자의 블로그입니다. 편하게 보고 가세요
  • 2료일
    GPT에게서 살아남기
    2료일
  • 전체
    오늘
    어제
    • 분류 전체보기 (137) N
      • SWIFT개발일지 (31) N
        • ARkit (1)
        • Vapor-Server with swift (3)
        • UIkit (2)
      • 알고리즘 (25)
      • Design (6)
      • iOS (42)
        • 반응형프로그래밍 (12)
      • 디자인패턴 (6)
      • CS (3)
      • 도서관 (2)
  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
2료일
아 지겹다 복붙! Xcode 커스텀 템플릿 만들기
상단으로

티스토리툴바