# UIToolkit Property System UIToolkit 기반의 Property 편집 시스템입니다. 데이터(Item)와 뷰(View)를 분리하는 MVVM 아키텍처를 적용하여, 20가지 속성 타입을 지원합니다. ## 📋 목차 - [주요 기능](#주요-기능) - [아키텍처](#아키텍처) - [파일 구조](#파일-구조) - [빠른 시작](#빠른-시작) - [사용 예제](#사용-예제) - [지원 타입 목록](#지원-타입-목록) - [API 문서](#api-문서) - [메모리 관리](#메모리-관리) - [성능 최적화](#성능-최적화) --- ## 주요 기능 ✅ **데이터/뷰 완전 분리 (MVVM)** - Item: 순수 데이터 레이어 (UI 코드 없음) - View: 순수 UI 레이어 (비즈니스 로직 없음) - Bind/Unbind 패턴으로 양방향 데이터 동기화 ✅ **20가지 속성 타입 지원** - 기본: String, Int, Float, Bool, Vector2, Vector3, Color - 날짜: Date, DateTime, DateRange, DateTimeRange - 선택: Dropdown, Enum, Radio, MultiSelectDropdown - 범위: IntRange, FloatRange - 특수: ColorState, FloatDropdown, Button ✅ **ReadOnly 모드** - 모든 View가 ReadOnly 상태 전환 지원 - ReadOnly 시 편집 컨트롤이 읽기 전용 InputField로 전환 - 그룹 단위 일괄 ReadOnly 설정 가능 ✅ **그룹(Group) 관리** - 속성 아이템을 그룹으로 묶어 관리 - 그룹 접기/펼치기 지원 - 아이템 추가/제거 이벤트 ✅ **Factory 패턴** - `UTKPropertyItemViewFactory`로 데이터 타입에 맞는 View 자동 생성 - 커스텀 View 등록/교체 가능 ✅ **완벽한 메모리 관리** - 모든 클래스 IDisposable 구현 - Bind/Unbind 대칭 이벤트 관리 - 정적 UXML/USS 캐싱으로 반복 로드 방지 --- ## 아키텍처 ``` Data Layer (Items) View Layer (Views) ───────────────── ────────────────── IUTKPropertyEntry IUTKPropertyItemView ├─ IUTKPropertyGroup IUTKPropertyItemView └─ IUTKPropertyItem UTKPropertyItemViewBase (abstract) └─ UTKPropertyItemBase └─ UTK{Type}PropertyItemView └─ UTK{Type}PropertyItem Factory Bridge ────────────── UTKPropertyItemViewFactory CreateView(IUTKPropertyItem) → new View → View.Bind(item) → return VisualElement ``` ### 데이터 흐름 ``` 1. Item 생성 (데이터) var item = new UTKFloatPropertyItem("speed", "이동 속도", 1.0f); 2. Factory가 View 생성 + 바인딩 var view = UTKPropertyItemViewFactory.CreateView(item); 3. 양방향 동기화 Item.Value 변경 → OnTypedValueChanged → View.RefreshUI() View 사용자 입력 → OnValueChanged → Item.Value 업데이트 ``` --- ## 파일 구조 ``` Assets/Scripts/UVC/UIToolkit/Property/ ├── Core/ # 인터페이스 & 핵심 타입 │ ├── IUTKPropertyEntry.cs # 트리 엔트리 베이스 인터페이스 │ ├── IUTKPropertyGroup.cs # 그룹 인터페이스 │ ├── IUTKPropertyItem.cs # 속성 아이템 인터페이스 (비제네릭/제네릭) │ ├── UTKPropertyGroup.cs # 그룹 구현체 │ ├── UTKPropertyType.cs # 속성 타입 열거형 (20종) │ └── UTKPropertyValueChangedEventArgs.cs # 값 변경 이벤트 인자 ├── Data/ # 복합 데이터 구조체 │ ├── UTKColorState.cs # 상태-색상 쌍 │ ├── UTKRangeValue.cs # 범위 값 (Int/Float/Date/DateTime) │ └── UTKFloatDropdownValue.cs # Float + Dropdown 복합 값 ├── Items/ # 데이터 레이어 (Item 클래스) │ ├── Base/ │ │ └── UTKPropertyItemBase.cs # Item 추상 베이스 클래스 │ ├── UTKStringPropertyItem.cs │ ├── UTKIntPropertyItem.cs │ ├── UTKFloatPropertyItem.cs │ ├── UTKBoolPropertyItem.cs │ ├── UTKVector2PropertyItem.cs │ ├── UTKVector3PropertyItem.cs │ ├── UTKColorPropertyItem.cs │ ├── UTKColorStatePropertyItem.cs │ ├── UTKDatePropertyItem.cs │ ├── UTKDateRangePropertyItem.cs │ ├── UTKDateTimePropertyItem.cs │ ├── UTKDateTimeRangePropertyItem.cs │ ├── UTKDropdownPropertyItem.cs │ ├── UTKEnumPropertyItem.cs │ ├── UTKRadioPropertyItem.cs │ ├── UTKIntRangePropertyItem.cs │ ├── UTKFloatRangePropertyItem.cs │ ├── UTKMultiSelectDropdownPropertyItem.cs │ ├── UTKFloatDropdownPropertyItem.cs │ └── UTKButtonItem.cs └── Views/ # 뷰 레이어 (View 클래스) ├── IUTKPropertyItemView.cs # View 인터페이스 (비제네릭/제네릭) ├── UTKPropertyItemViewBase.cs # View 추상 베이스 클래스 ├── UTKPropertyItemViewFactory.cs # View 팩토리 (타입별 자동 생성) ├── UTKStringPropertyItemView.cs ├── UTKIntPropertyItemView.cs ├── UTKFloatPropertyItemView.cs ├── UTKBoolPropertyItemView.cs ├── UTKVector2PropertyItemView.cs ├── UTKVector3PropertyItemView.cs ├── UTKColorPropertyItemView.cs ├── UTKColorStatePropertyItemView.cs ├── UTKDatePropertyItemView.cs ├── UTKDateRangePropertyItemView.cs ├── UTKDateTimePropertyItemView.cs ├── UTKDateTimeRangePropertyItemView.cs ├── UTKDropdownPropertyItemView.cs ├── UTKEnumPropertyItemView.cs ├── UTKRadioPropertyItemView.cs ├── UTKIntRangePropertyItemView.cs ├── UTKFloatRangePropertyItemView.cs ├── UTKMultiSelectDropdownPropertyItemView.cs ├── UTKFloatDropdownPropertyItemView.cs ├── UTKButtonItemView.cs └── CLAUDE.md Assets/Resources/UIToolkit/Property/Views/ ├── UTKPropertyItemViewCommonUss.uss # 공통 스타일 (모든 View 공유) ├── UTKStringPropertyItemView.uxml ├── UTKStringPropertyItemViewUss.uss ├── UTKIntPropertyItemView.uxml ├── UTKIntPropertyItemViewUss.uss ├── UTKFloatPropertyItemView.uxml ├── UTKFloatPropertyItemViewUss.uss ├── UTKBoolPropertyItemView.uxml ├── UTKBoolPropertyItemViewUss.uss ├── UTKVector2PropertyItemView.uxml ├── UTKVector2PropertyItemViewUss.uss ├── UTKVector3PropertyItemView.uxml ├── UTKVector3PropertyItemViewUss.uss ├── UTKColorPropertyItemView.uxml ├── UTKColorPropertyItemViewUss.uss ├── UTKColorStatePropertyItemView.uxml ├── UTKColorStatePropertyItemViewUss.uss ├── UTKDatePropertyItemView.uxml ├── UTKDatePropertyItemViewUss.uss ├── UTKDateRangePropertyItemView.uxml ├── UTKDateRangePropertyItemViewUss.uss ├── UTKDateTimePropertyItemView.uxml ├── UTKDateTimePropertyItemViewUss.uss ├── UTKDateTimeRangePropertyItemView.uxml ├── UTKDateTimeRangePropertyItemViewUss.uss ├── UTKDropdownPropertyItemView.uxml ├── UTKDropdownPropertyItemViewUss.uss ├── UTKEnumPropertyItemView.uxml ├── UTKEnumPropertyItemViewUss.uss ├── UTKRadioPropertyItemView.uxml ├── UTKRadioPropertyItemViewUss.uss ├── UTKIntRangePropertyItemView.uxml ├── UTKIntRangePropertyItemViewUss.uss ├── UTKFloatRangePropertyItemView.uxml ├── UTKFloatRangePropertyItemViewUss.uss ├── UTKMultiSelectDropdownPropertyItemView.uxml ├── UTKMultiSelectDropdownPropertyItemViewUss.uss ├── UTKFloatDropdownPropertyItemView.uxml ├── UTKFloatDropdownPropertyItemViewUss.uss ├── UTKButtonItemView.uxml └── UTKButtonItemViewUss.uss ``` --- ## 빠른 시작 ### 1. 단일 속성 아이템 생성 ```csharp // 1. 데이터(Item) 생성 var nameItem = new UTKStringPropertyItem("name", "이름", "홍길동"); // 2. Factory로 View 생성 + 바인딩 var nameView = UTKPropertyItemViewFactory.CreateView(nameItem); // 3. UI에 추가 rootVisualElement.Add(nameView); // 4. 값 변경 감지 nameItem.OnTypedValueChanged += (item, oldVal, newVal) => { Debug.Log($"이름 변경: {oldVal} → {newVal}"); }; ``` ### 2. 그룹으로 묶어서 관리 ```csharp // 그룹 생성 var transformGroup = new UTKPropertyGroup("transform", "Transform"); // 아이템 추가 transformGroup.AddItem(new UTKVector3PropertyItem("position", "Position", Vector3.zero)); transformGroup.AddItem(new UTKVector3PropertyItem("rotation", "Rotation", Vector3.zero)); transformGroup.AddItem(new UTKVector3PropertyItem("scale", "Scale", Vector3.one)); // 그룹 전체 ReadOnly 설정 transformGroup.SetAllItemsReadOnly(true); ``` --- ## 사용 예제 ### 다양한 속성 타입 생성 ```csharp // 기본 타입 var stringItem = new UTKStringPropertyItem("name", "이름", "홍길동"); var intItem = new UTKIntPropertyItem("count", "개수", 10); var floatItem = new UTKFloatPropertyItem("speed", "속도", 1.5f); var boolItem = new UTKBoolPropertyItem("active", "활성화", true); // 벡터 타입 var posItem = new UTKVector2PropertyItem("pos", "위치", Vector2.zero); var rotItem = new UTKVector3PropertyItem("rot", "회전", Vector3.zero); // 색상 타입 var colorItem = new UTKColorPropertyItem("color", "색상", Color.white); colorItem.UseAlpha = true; // 알파 채널 사용 // 날짜 타입 var dateItem = new UTKDatePropertyItem("start", "시작일", DateTime.Today); dateItem.DateFormat = "yyyy-MM-dd"; // 드롭다운 타입 var dropdownItem = new UTKDropdownPropertyItem("type", "유형", "TypeA"); dropdownItem.Choices = new List { "TypeA", "TypeB", "TypeC" }; // Enum 타입 var enumItem = new UTKEnumPropertyItem("align", "정렬", TextAlignment.Center); ``` ### 슬라이더/스테퍼 활용 ```csharp // Int 슬라이더 var intItem = new UTKIntPropertyItem("volume", "볼륨", 50); intItem.UseSlider = true; intItem.MinValue = 0; intItem.MaxValue = 100; // Float 스테퍼 var floatItem = new UTKFloatPropertyItem("opacity", "투명도", 1.0f); floatItem.UseStepper = true; floatItem.Step = 0.1f; floatItem.MinValue = 0f; floatItem.MaxValue = 1f; ``` ### 범위(Range) 타입 사용 ```csharp // Int 범위 var intRange = new UTKIntRangePropertyItem("level", "레벨 범위", new UTKIntRange { Min = 1, Max = 10 }); intRange.UseStepper = true; intRange.StepperStep = 1; // Float 범위 var floatRange = new UTKFloatRangePropertyItem("temp", "온도 범위", new UTKFloatRange { Min = -10f, Max = 40f }); // 날짜 범위 var dateRange = new UTKDateRangePropertyItem("period", "기간", new UTKDateRange { Start = DateTime.Today, End = DateTime.Today.AddDays(30) }); ``` ### 멀티셀렉트 드롭다운 ```csharp var multiSelect = new UTKMultiSelectDropdownPropertyItem( "tags", "태그", new List { "UI", "3D" }); multiSelect.Choices = new List { "UI", "3D", "Network", "Audio", "Physics" }; // 프로그래밍 방식 선택 multiSelect.SelectAll(); multiSelect.ClearSelection(); multiSelect.SetSelectedValues(new List { "UI", "Audio" }); ``` ### ColorState 사용 ```csharp var colorState = new UTKColorStatePropertyItem("status", "상태", new UTKColorState { State = "Normal", Color = Color.green }); // 상태/색상 개별 변경 colorState.SetState("Warning"); colorState.SetColor(Color.yellow); ``` ### FloatDropdown 복합 타입 ```csharp var floatDropdown = new UTKFloatDropdownPropertyItem("size", "크기", new UTKFloatDropdownValue { FloatValue = 100f, DropdownValue = "px" }); floatDropdown.Choices = new List { "px", "%", "em", "rem" }; floatDropdown.UseStepper = true; ``` ### Button 아이템 ```csharp var buttonItem = new UTKButtonItem("save", "저장"); buttonItem.Text = "저장하기"; buttonItem.Icon = UTKMaterialIcons.Save; buttonItem.Variant = UTKButton.ButtonVariant.Primary; buttonItem.ActionName = "save_action"; ``` ### String + ActionButton 조합 ```csharp var pathItem = new UTKStringPropertyItem("path", "파일 경로", ""); // ActionButton 설정 (찾아보기 버튼) var browseButton = new UTKButtonItem("browse", "찾아보기"); browseButton.Icon = UTKMaterialIcons.FolderOpen; browseButton.IconOnly = true; pathItem.ActionButton = browseButton; ``` ### 커스텀 View 등록 ```csharp // 기본 View를 커스텀 View로 교체 UTKPropertyItemViewFactory.RegisterCustomView( UTKPropertyType.String, () => new MyCustomStringView() ); // 해제 UTKPropertyItemViewFactory.UnregisterCustomView(UTKPropertyType.String); ``` ### 값 변경 이벤트 처리 ```csharp // 제네릭 이벤트 (타입 안전) floatItem.OnTypedValueChanged += (item, oldVal, newVal) => { Debug.Log($"{item.Name}: {oldVal} → {newVal}"); }; // 비제네릭 이벤트 (범용) floatItem.OnValueChanged += (item, oldVal, newVal, notify) => { Debug.Log($"{item.Id} changed"); }; // 상태 변경 이벤트 (ReadOnly, ShowLabel 등) floatItem.OnStateChanged += (item) => { Debug.Log($"{item.Id} state changed, ReadOnly={item.IsReadOnly}"); }; ``` --- ## 지원 타입 목록 | UTKPropertyType | Item 클래스 | View 클래스 | 데이터 타입 | 주요 컨트롤 | |-----------------|------------|------------|------------|------------| | `String` | UTKStringPropertyItem | UTKStringPropertyItemView | string | UTKInputField + ActionButton | | `Int` | UTKIntPropertyItem | UTKIntPropertyItemView | int | UTKIntegerField + Slider/Stepper | | `Float` | UTKFloatPropertyItem | UTKFloatPropertyItemView | float | UTKFloatField + Slider/Stepper | | `Bool` | UTKBoolPropertyItem | UTKBoolPropertyItemView | bool | UTKToggle | | `Vector2` | UTKVector2PropertyItem | UTKVector2PropertyItemView | Vector2 | UTKVector2Field | | `Vector3` | UTKVector3PropertyItem | UTKVector3PropertyItemView | Vector3 | UTKVector3Field | | `Color` | UTKColorPropertyItem | UTKColorPropertyItemView | Color | ColorPreview + UTKColorPicker | | `ColorState` | UTKColorStatePropertyItem | UTKColorStatePropertyItemView | UTKColorState | Label + ColorPreview + Picker | | `Date` | UTKDatePropertyItem | UTKDatePropertyItemView | DateTime | UTKInputField + UTKDatePicker | | `DateTime` | UTKDateTimePropertyItem | UTKDateTimePropertyItemView | DateTime | UTKInputField + UTKDatePicker | | `DateRange` | UTKDateRangePropertyItem | UTKDateRangePropertyItemView | UTKDateRange | InputField×2 + DatePicker×2 | | `DateTimeRange` | UTKDateTimeRangePropertyItem | UTKDateTimeRangePropertyItemView | UTKDateTimeRange | InputField×2 + DatePicker×2 | | `Enum` | UTKEnumPropertyItem | UTKEnumPropertyItemView | Enum | UTKEnumDropDown | | `DropdownList` | UTKDropdownPropertyItem | UTKDropdownPropertyItemView | string | UTKDropdown | | `MultiSelectDropdownList` | UTKMultiSelectDropdownPropertyItem | UTKMultiSelectDropdownPropertyItemView | List\ | UTKMultiSelectDropdown | | `RadioGroup` | UTKRadioPropertyItem | UTKRadioPropertyItemView | int (index) | UTKRadioButton × N | | `IntRange` | UTKIntRangePropertyItem | UTKIntRangePropertyItemView | UTKIntRange | IntegerField×2 + Stepper×2 | | `FloatRange` | UTKFloatRangePropertyItem | UTKFloatRangePropertyItemView | UTKFloatRange | FloatField×2 + Stepper×2 | | `FloatDropdown` | UTKFloatDropdownPropertyItem | UTKFloatDropdownPropertyItemView | UTKFloatDropdownValue | FloatField/Stepper + Dropdown | | `Button` | UTKButtonItem | UTKButtonItemView | string (actionName) | UTKButton | --- ## API 문서 ### IUTKPropertyItem (데이터 인터페이스) #### Properties | 속성 | 타입 | 설명 | |------|------|------| | `Id` | string | 고유 식별자 | | `Name` | string | 표시 이름 | | `DisplayName` | string | 표시 이름 (Name과 동일) | | `Description` | string | 설명 | | `Tooltip` | string | 툴팁 텍스트 | | `IsReadOnly` | bool | 읽기 전용 여부 | | `IsVisible` | bool | 표시 여부 | | `ShowLabel` | bool | 라벨 표시 여부 | | `PropertyType` | UTKPropertyType | 속성 타입 열거형 | | `GroupId` | string | 소속 그룹 ID | #### Methods | 메서드 | 설명 | 반환 | |--------|------|------| | `GetValue()` | 현재 값 가져오기 (object?) | object? | | `SetValue(object?, bool)` | 값 설정 (notify로 이벤트 발생 제어) | void | #### Events | 이벤트 | 타입 | 설명 | |--------|------|------| | `OnValueChanged` | Action\ | 값 변경 (비제네릭) | | `OnTypedValueChanged` | Action\, T, T\> | 값 변경 (제네릭, 타입 안전) | | `OnStateChanged` | Action\ | 상태 변경 (ReadOnly, ShowLabel 등) | ### IUTKPropertyGroup (그룹 인터페이스) #### Properties | 속성 | 타입 | 설명 | |------|------|------| | `GroupId` | string | 그룹 고유 ID | | `GroupName` | string | 그룹 표시 이름 | | `IsExpanded` | bool | 접기/펼치기 상태 | | `Items` | IReadOnlyList\ | 하위 아이템 목록 | | `ItemCount` | int | 아이템 수 | #### Methods | 메서드 | 설명 | 파라미터 | |--------|------|----------| | `AddItem(item)` | 아이템 추가 | IUTKPropertyItem | | `RemoveItem(itemId)` | 아이템 제거 | string → bool | | `GetItem(itemId)` | 아이템 검색 | string → IUTKPropertyItem? | | `Clear()` | 전체 아이템 제거 | - | | `SetAllItemsReadOnly(isReadOnly)` | 일괄 ReadOnly 설정 | bool | #### Events | 이벤트 | 타입 | 설명 | |--------|------|------| | `OnItemAdded` | Action\ | 아이템 추가 | | `OnItemRemoved` | Action\ | 아이템 제거 | | `OnExpandedChanged` | Action\ | 접기/펼치기 변경 | ### UTKPropertyItemViewFactory (팩토리) #### Public Methods | 메서드 | 설명 | 반환 | |--------|------|------| | `CreateView(data)` | View 생성 + 바인딩 후 VisualElement 반환 | VisualElement? | | `CreateViewInstance(data)` | View 인스턴스만 생성 (데이터 기반 초기화) | IUTKPropertyItemView? | | `CreateViewInstance(type)` | View 인스턴스만 생성 (타입 기반) | IUTKPropertyItemView? | | `GetViewType(data)` | 데이터에 맞는 View Type 조회 | Type? | | `RegisterCustomView(type, factory)` | 커스텀 View 등록 | void | | `UnregisterCustomView(type)` | 커스텀 View 해제 | void | | `ClearCustomViews()` | 모든 커스텀 View 해제 | void | ### UTKPropertyItemViewBase (View 베이스) #### UxmlAttributes | 속성 | UXML 어트리뷰트 | 타입 | 설명 | |------|----------------|------|------| | `Label` | `label` | string | 라벨 텍스트 | | `IsReadOnly` | `is-read-only` | bool | 읽기 전용 | | `IsVisible` | `is-visible` | bool | 표시 여부 | | `TooltipText` | `tooltip-text` | string | 툴팁 | | `ShowLabel` | `show-label` | bool | 라벨 표시 여부 | #### Public Methods | 메서드 | 설명 | |--------|------| | `Bind(data)` | 데이터 바인딩 (양방향 동기화 시작) | | `Unbind()` | 바인딩 해제 (이벤트 구독 해제) | | `RefreshUI()` | UI 갱신 | | `Dispose()` | 리소스 정리 | | `ClearAllCache()` | 정적 UXML/USS 캐시 전체 정리 (static) | --- ## 메모리 관리 ### Bind/Unbind 패턴 모든 View는 `Bind()`에서 이벤트를 구독하고 `Unbind()`에서 해제합니다: ```csharp // View 내부 구현 패턴 public void Bind(IUTKPropertyItem data) { _boundData = data; data.OnTypedValueChanged += OnDataValueChanged; data.OnStateChanged += OnDataStateChanged; RefreshUI(); } public void Unbind() { if (_boundData != null) { _boundData.OnTypedValueChanged -= OnDataValueChanged; _boundData.OnStateChanged -= OnDataStateChanged; _boundData = null; } } ``` ### IDisposable 구현 모든 Item과 View 클래스가 IDisposable을 구현합니다: ```csharp // Item Dispose var item = new UTKFloatPropertyItem("speed", "속도", 1.0f); // ... 사용 ... item.Dispose(); // 모든 이벤트 구독 해제 // Group Dispose var group = new UTKPropertyGroup("transform", "Transform"); group.AddItem(item); // ... 사용 ... group.Dispose(); // 하위 아이템 전부 Dispose + 이벤트 해제 // View Dispose var view = UTKPropertyItemViewFactory.CreateView(item); // ... 사용 ... if (view is IDisposable disposable) disposable.Dispose(); // Unbind + 테마 구독 해제 + 콜백 해제 ``` ### 테마 이벤트 관리 View 베이스 클래스에서 `AttachToPanelEvent`/`DetachFromPanelEvent`로 테마 구독을 관리합니다: ```csharp // UTKPropertyItemViewBase 내부 private void OnAttachToPanel(AttachToPanelEvent evt) { UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged; UTKThemeManager.Instance.ApplyThemeToElement(this); } private void OnDetachFromPanel(DetachFromPanelEvent evt) { UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; } ``` --- ## 성능 최적화 ### 정적 UXML/USS 캐싱 View 베이스 클래스에서 UXML/USS를 정적 Dictionary로 캐싱하여 `Resources.Load` 호출을 최소화합니다: ```csharp // UTKPropertyItemViewBase 내부 private static readonly Dictionary _uxmlCache = new(); private static readonly Dictionary _ussCache = new(); private static StyleSheet? _commonUssCache; // 최초 1회만 Resources.Load 호출, 이후 캐시에서 반환 // 결과: 20개 View 타입 × 다수 인스턴스 → 각 타입당 Resources.Load 1회 ``` ### Fallback UI 패턴 UXML 로드 실패 시 코드로 UI를 생성합니다: ```csharp // UXML 존재 → UXML 기반 UI 생성 if (CreateUIFromUxml()) { // _labelElement, _valueContainer 자동 쿼리 } else { // UXML 없음 → 코드로 Fallback UI 생성 CreateUIFallback(); } ``` ### ReadOnly 전환 최적화 컨트롤을 생성/파괴하지 않고 DisplayStyle 토글로 전환합니다: ```csharp // ❌ 나쁜 예: 매번 생성/파괴 if (isReadOnly) { Remove(_editControl); Add(new UTKInputField(...)); } // ✅ 좋은 예: 미리 생성 후 DisplayStyle 전환 _editControl.style.display = isReadOnly ? DisplayStyle.None : DisplayStyle.Flex; _readOnlyField.style.display = isReadOnly ? DisplayStyle.Flex : DisplayStyle.None; ``` ### 성능 요약 | 항목 | 설명 | |------|------| | **UXML/USS 로드** | 정적 Dictionary 캐싱, 타입당 1회만 로드 | | **공통 USS** | `UTKPropertyItemViewCommonUss.uss` 1회 로드 후 전체 View 공유 | | **쿼리 캐싱** | `Q()` 결과를 필드에 캐싱 (초기화 시 1회) | | **ReadOnly 전환** | DisplayStyle 토글 (생성/파괴 없음) | | **이벤트 관리** | Bind/Unbind로 정확한 구독/해제 대칭 | | **GC Alloc** | LINQ 미사용, 클로저 캡처 최소화 | --- ## 문제 해결 ### View가 표시되지 않는 경우 1. **Factory 사용 확인** ```csharp var view = UTKPropertyItemViewFactory.CreateView(item); if (view == null) { Debug.LogError($"지원하지 않는 PropertyType: {item.PropertyType}"); } ``` 2. **Bind 호출 확인** - `CreateView()`는 내부적으로 Bind까지 수행 - `CreateViewInstance()`는 Bind를 별도로 호출해야 함 3. **UXML/USS 경로 확인** - 리소스 경로: `UIToolkit/Property/Views/{ViewTypeName}` - USS 접미사: `{ViewTypeName}Uss` ### 값이 동기화되지 않는 경우 1. **Bind 상태 확인**: `Unbind()` 후 다시 `Bind()` 필요 2. **이벤트 확인**: `OnTypedValueChanged` 구독이 정상적으로 등록되었는지 확인 3. **SetValue notify 파라미터**: `SetValue(value, false)`는 이벤트를 발생시키지 않음 --- ## 유지보수 가이드 > **소스 코드를 수정할 때 반드시 CLAUDE.md와 코드 주석(XML 문서)도 함께 업데이트해야 합니다.** | 변경 사항 | 업데이트 대상 | |-----------|--------------| | 클래스/메서드 추가·삭제·이름 변경 | CLAUDE API 문서 + 파일 구조 + 해당 클래스 XML 주석 | | 생성자 파라미터 변경 | CLAUDE API 문서 + 사용 예제 + XML `` 태그 | | 공개 속성(Property) 추가·삭제 | CLAUDE API 테이블 + 지원 타입 목록 + XML `` 태그 | | 이벤트 추가·삭제 | CLAUDE 이벤트 테이블 + 메모리 관리 섹션 | | 새로운 PropertyType 추가 | CLAUDE 지원 타입 목록 + Factory 문서 + 파일 구조 | | UXML/USS 파일 추가·삭제 | CLAUDE 파일 구조 섹션 | | 새로운 기능 추가 | CLAUDE 주요 기능 + 사용 예제 섹션 | --- ## 라이선스 이 프로젝트는 UVC 프레임워크의 일부입니다. --- ## 작성자 - **작성일**: 2026-02-19 - **작성자**: Claude Code Assistant - **버전**: 1.0.0