Form control

입력 필드에 Label과 Message를 붙여 무엇을 입력해야 하는지 안내하는 컴포넌트입니다. Text field, Text area, Select와 같은 입력 필드를 감싸서 사용하며, 접근성 연결을 처리합니다.

Form control

입력 컨트롤에 제목(Label)과 도움말(Message)을 붙여 주고, 라벨 ↔ 입력 ↔ 메시지의 접근성 연결을 자동으로 처리하는 래퍼(wrapper) 컴포넌트입니다.

@MainActor struct FormControl

Overview

FormControl은 단독으로 값을 입력받지 않습니다. 내부 슬롯(input)에 실제 입력 컴포넌트를 조합해 사용하며, 라벨·필수 표시(*)·도움말/에러 메시지·액세서리(글자 수 카운트 등)를 일관된 레이아웃으로 감싸 줍니다.

FormControl의 .size(_:)·.status(_:)는 슬롯 안의 Montage 입력 컴포넌트(TextField·TextArea· Select)에 자동으로 전파됩니다. 입력 쪽에서 값을 명시하면 그 값이 우선하므로, 보통은 FormControl에 한 번만 설정하면 됩니다.

FormControl {
    TextField(text: $email)
        .placeholder("이메일을 입력하세요")
}
.label("이메일", required: true)
.message("회사 이메일을 입력해 주세요.")

// 에러 상태 — FormControl에만 .status(.negative)를 주면 메시지 색과 입력 상태가 함께 바뀐다.
FormControl {
    TextField(text: $email)
}
.size(.medium)
.status(.negative)
.label("이메일", required: true)
.message("올바른 이메일 형식이 아닙니다.")
.accessory {
    Text("\(email.count)/100")
        .typography(variant: .caption1, weight: .regular, semantic: .foregroundNeutralTertiary)
}

// 라벨을 입력 왼쪽에 배치
FormControl {
    TextField(text: $name)
}
.labelPlacement(.leading)
.label("이름")

Note

Montage 입력 컴포넌트는 label(_:required:) 등 같은 이름의 모디파이어를 직접 제공합니다. 단일 입력을 감쌀 때는 FormControl을 명시하지 않고 입력에 바로 붙이는 쪽이 간결하지만, 위 예제처럼 FormControl로 직접 감싸는 구성도 그대로 지원됩니다. 앱에서 만든 커스텀 입력을 감쌀 때나 입력 종류가 런타임에 바뀌어 래퍼 설정을 한곳에 모아 두고 싶을 때는 FormControl을 직접 쓰세요.

Topics

Structures

struct Context

입력 슬롯 클로저에 전달되는 FormControl의 현재 상태 컨텍스트입니다.

  • Overview

    슬롯 입력 컴포넌트가 FormControl의 FormControl.Size·FormControl.Status를 그대로 반영하도록 현재 값을 묶어 전달합니다. 향후 항목이 추가돼도 클로저 시그니처는 바뀌지 않습니다.

Instance Properties
let size: Size

현재 FormControl 크기.

let status: Status

현재 FormControl 상태.

Initializers

init<Input>(input: () -> Input)

입력 컴포넌트를 슬롯으로 받아 FormControl을 생성합니다.

  • Parameters

    ParameterDescription
    input감쌀 입력 컴포넌트를 반환하는 뷰 빌더
  • Discussion

    FormControl의 size(_:)·status(_:)는 슬롯 안의 Montage 입력 컴포넌트에 자동으로 전파되므로 호출부에서 다시 넘길 필요가 없습니다.

init<Input>(input: (Context) -> Input)

현재 FormControl.Context를 전달받는 슬롯으로 FormControl을 생성합니다.

  • Parameters

    ParameterDescription
    input현재 FormControl.Context를 받아 감쌀 입력 컴포넌트를 반환하는 뷰 빌더
  • Discussion

    크기·상태 전파는 Montage 입력 컴포넌트에만 자동 적용됩니다. 앱에서 만든 커스텀 입력이 FormControl의 크기·상태를 반영해야 할 때 이 초기화를 사용하세요.

    FormControl { context in
        MyCustomPicker(selection: $region)
            .compact(context.size == .medium)
            .invalid(context.status == .negative)
    }
    .status(.negative)
    .label("지역", required: true)
    

Instance Properties

var body: some View

뷰의 내용과 동작을 정의합니다.

Instance Methods

func accessory<Accessory>(() -> Accessory) -> FormControl

Footer 우측(trailing)에 표시할 액세서리 뷰를 설정합니다.

  • Parameters

    ParameterDescription
    accessory표시할 액세서리 뷰 빌더
  • Return Value

    수정된 FormControl 컴포넌트

  • Discussion

    글자 수 카운트, 타이머 등 입력 아래에 붙는 보조 요소를 자유롭게 구성할 수 있습니다. 스타일(타이포그래피·색)은 호출부에서 지정합니다.

func label(String?, required: Bool) -> FormControl

라벨 텍스트와 필수 표시 여부를 설정합니다.

  • Parameters

    ParameterDescription
    text라벨 텍스트. nil이거나 비어 있으면 라벨을 표시하지 않습니다.
    required필수 입력 표시(*) 여부. 생략하면 기본값으로 false 적용
  • Return Value

    수정된 FormControl 컴포넌트

func labelPlacement(LabelPlacement) -> FormControl

라벨 위치를 설정합니다.

  • Parameters

    ParameterDescription
    placement라벨 위치. 생략하면 기본값으로 .top 적용
  • Return Value

    수정된 FormControl 컴포넌트

func labelWidth(CGFloat) -> FormControl

leading 배치에서 이 컨트롤의 라벨 폭을 명시적으로 고정합니다.

  • Parameters

    ParameterDescription
    width라벨 열 폭(pt).
  • Return Value

    수정된 FormControl 컴포넌트

  • Discussion

    주 용도는 두 가지입니다. (1) 단독 leading FormControl에서 라벨 폭을 스펙값으로 맞출 때, 그리고 (2) FormControlGroup 안에서 특정 행 하나만 다른 폭으로 둘 때(per-control 값이 컨테이너 폭보다 우선).

    컬럼 전체를 고정 폭으로 맞추려면 각 컨트롤에 반복하지 말고 FormControlGroup의 labelWidth를 쓰세요. FormControl.LabelPlacement.top 배치에는 영향이 없습니다.

func message(String?) -> FormControl

도움말/에러 메시지를 설정합니다.

  • Parameters

    ParameterDescription
    text메시지 텍스트. nil이거나 비어 있으면 메시지를 표시하지 않습니다.
  • Return Value

    수정된 FormControl 컴포넌트

  • Discussion

    Note

    메시지 색은 status(_:)에 따라 결정됩니다. .negative에서만 강조 색으로 표시됩니다.

func size(Size) -> FormControl

크기를 설정합니다.

  • Parameters

    ParameterDescription
    sizeFormControl 크기. 생략하면 기본값으로 .large 적용
  • Return Value

    수정된 FormControl 컴포넌트

func status(Status) -> FormControl

상태를 설정합니다.

  • Parameters

    ParameterDescription
    statusFormControl 상태. 생략하면 기본값으로 .normal 적용
  • Return Value

    수정된 FormControl 컴포넌트

Enumerations

enum LabelPlacement

라벨의 위치입니다.

Enumeration Cases
case leading

라벨을 입력의 leading 쪽에 가로로 배치하고, 입력 슬롯의 첫 줄 중앙에 맞춥니다. (단일 행 입력은 입력 세로 중앙과 같고, 다중 행 입력은 입력 전체가 아니라 첫 줄을 기준으로 정렬됩니다.)

case top

라벨을 입력 위에 세로로 배치합니다. (기본)

enum Size

FormControl의 크기입니다. 라벨 타이포그래피를 결정합니다.

Enumeration Cases
case large

큰 크기 (라벨 label1)

case medium

중간 크기 (라벨 label2)

enum Status

FormControl의 상태입니다. 메시지의 색을 결정합니다.

Enumeration Cases
case negative

에러 상태. 메시지는 강조 색(foregroundNegativePrimary)으로 표시됩니다.

case normal

기본 상태. 메시지는 도움말 색(foregroundNeutralTertiary)으로 표시됩니다.

case positive

성공 상태. 메시지는 기본 도움말과 동일한 색(foregroundNeutralTertiary)으로 표시됩니다.

Relationships

Conforms To

Swift.Sendable

SwiftUICore.View

Form control group

라벨이 붙은 입력을 세로로 쌓을 때 라벨 열 폭을 통일해 입력의 시작 위치를 정렬하는 컨테이너입니다.

@MainActor struct FormControlGroup<Content> where Content : View

Overview

Leading 배치(FormControl.LabelPlacement.leading)의 라벨들을 대상으로, 내부 라벨의 본연 폭 중 가장 긴 값으로 라벨 열이 맞춰집니다.

개별 행에 폭을 지정할 필요 없이 이 컨테이너로 감싸기만 하면, 내부 leading 라벨들의 본연 폭 중 최댓값으로 라벨 열이 맞춰집니다. Dynamic Type·다국어로 라벨 길이가 바뀌어도 자동으로 재정렬되므로 호출부에 고정 폭(매직 넘버)을 두지 않아도 됩니다.

FormControlGroup {
    TextField(text: $name)
        .labelPlacement(.leading)
        .label("이름")

    TextField(text: $email)
        .labelPlacement(.leading)
        .label("이메일 주소")
}
// → 두 입력의 leading이 정렬되고, 라벨 열은 "이메일 주소" 폭으로 통일된다.

앱에서 만든 커스텀 입력은 FormControl로 직접 감싸 같은 그룹에 섞어 넣을 수 있습니다.

자동 측정 대신 라벨 열 폭을 고정하고 싶으면 labelWidth를 지정합니다. 이 경우 측정을 건너뛰고 모든 행이 그 폭을 씁니다. (여러 화면에서 동일한 폭을 맞추거나, 측정으로 인한 1프레임 흔들림을 피하고 싶을 때)

FormControlGroup(labelWidth: .dimension64) { … }   // 전 행 라벨 폭을 64로 고정

Note

FormControl.LabelPlacement.top 배치에는 영향을 주지 않습니다. 특정 행 하나만 다른 폭으로 두려면 그 행에 labelWidth(_:)(또는 입력 컴포넌트의 같은 이름 모디파이어)를 사용하세요. per-control 값이 컨테이너 폭보다 우선합니다.

Topics

Initializers

init(labelWidth: CGFloat?, spacing: CGFloat, content: () -> Content)

컨테이너를 생성합니다.

  • Parameters

    ParameterDescription
    labelWidth라벨 열 폭을 고정할 값. 생략(nil)하면 내부 라벨 최댓값으로 자동 측정한다.
    spacingFormControl 사이의 세로 간격. 생략하면 기본값으로 .spacing16 적용
    content세로로 쌓을 입력 컴포넌트(또는 FormControl) 목록

Instance Properties

var body: some View

뷰의 내용과 동작을 정의합니다.

Relationships

Conforms To

Swift.Sendable

SwiftUICore.View

© 2026 Wanted Lab, Inc.