0Pricing
Swift Academy · 강의

Layout 프로토콜 기초

sizeThatFits와 placeSubviews를 구현합니다.

Layout 프로토콜 기초은(는) CoddyKit의 무료 Swift Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Swift Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Swift Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

사용자 지정 레이아웃이 필요한 이유

SwiftUI에는 HStack, VStack, ZStack이 기본 제공되지만, 방사형 메뉴나 흐름 레이아웃, 석조식 격자처럼 이러한 구성 요소로 표현할 수 없는 배치 로직이 필요할 때가 있습니다.

레이아웃 프로토콜을 사용하면 일반적인 SwiftUI 뷰와 계속 조합하면서 이러한 로직을 작성할 수 있습니다.

레이아웃 프로토콜

Layout에는 두 메서드가 필요합니다. sizeThatFits는 레이아웃의 크기를 결정하고, placeSubviews는 각 자식 뷰의 위치를 결정합니다.

값 타입에 적합성을 추가하면 기본 제공 스택처럼 사용할 수 있습니다.

struct MyLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize,
                      subviews: Subviews,
                      cache: inout ()) -> CGSize { .zero }
    func placeSubviews(in bounds: CGRect,
                       proposal: ProposedViewSize,
                       subviews: Subviews,
                       cache: inout ()) { }
}

사용자 지정 레이아웃 사용하기

Layout 값은 컨테이너 역할을 합니다. 스택과 마찬가지로 후행 클로저에 자식 뷰를 전달합니다.

var body: some View {
    MyLayout {
        Text("One")
        Text("Two")
        Text("Three")
    }
}

ProposedViewSize

부모는 ProposedViewSize를 통해 레이아웃에 크기를 제안합니다. 이 값의 width와 height는 선택 사항이며, nil은 "지정되지 않았으므로 원하는 크기를 선택하라"는 뜻입니다.

.zero, .infinity, .unspecified라는 특수 값은 각각 최소 크기, 최대 크기, 이상적인 크기를 확인합니다.

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    let width = proposal.width ?? 0
    return CGSize(width: width, height: 44)
}

sizeThatFits 자세히 알아보기

sizeThatFits는 제안이 주어졌을 때 레이아웃이 원하는 크기를 반환합니다. SwiftUI는 레이아웃의 유연성을 파악하기 위해 서로 다른 제안으로 이 메서드를 여러 번 호출할 수 있습니다.

func sizeThatFits(proposal: ProposedViewSize,
                  subviews: Subviews,
                  cache: inout ()) -> CGSize {
    // Sum child ideal sizes for a simple horizontal stack
    let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let w = sizes.reduce(0) { $0 + $1.width }
    let h = sizes.map(\.height).max() ?? 0
    return CGSize(width: w, height: h)
}

하위 뷰 컬렉션

Subviews는 각 자식 뷰를 대신하는 프록시의 컬렉션입니다. 각 프록시는 sizeThatFits에 응답하고 레이아웃 우선순위와 사용자 지정 값을 제공합니다.

실제 뷰를 직접 볼 수는 없고 이러한 측정 프록시만 확인할 수 있습니다.

for subview in subviews {
    let size = subview.sizeThatFits(.unspecified)
    print(size)
}

placeSubviews

placeSubviews는 전달받은 bounds 사각형 안에서 각 자식의 위치를 지정합니다.

각 하위 뷰 프록시에서 place(at:anchor:proposal:)을 호출하십시오.

func placeSubviews(in bounds: CGRect,
                   proposal: ProposedViewSize,
                   subviews: Subviews,
                   cache: inout ()) {
    var x = bounds.minX
    for subview in subviews {
        let size = subview.sizeThatFits(.unspecified)
        subview.place(at: CGPoint(x: x, y: bounds.midY),
                      anchor: .leading,
                      proposal: .unspecified)
        x += size.width
    }
}

배치할 때 앵커 사용하기

anchor 매개변수는 자식 뷰의 어느 지점을 지정한 위치에 놓을지 나타냅니다. .topLeading은 왼쪽 위 모서리를 기준으로 배치하고, .center는 자식 뷰의 중심을 해당 위치에 맞춥니다.

subview.place(at: CGPoint(x: 0, y: 0),
              anchor: .topLeading,
              proposal: .unspecified)

Bounds가 항상 원점에 있는 것은 아닙니다

bounds 사각형은 (0,0)에서 시작하지 않을 수 있습니다. 항상 절대 좌표 0이 아니라 bounds.minX와 bounds.minY를 기준으로 위치를 지정하십시오.

let startX = bounds.minX // not 0
let startY = bounds.minY

간격을 동일하게 두는 최소 레이아웃

이제 지금까지의 내용을 결합해 보겠습니다. 다음은 자식 뷰를 간격 없이 왼쪽에서 오른쪽으로 배치하는 레이아웃입니다. 두 메서드는 일관되게 동작하며, 보고된 크기와 실제 배치가 일치합니다.

struct SimpleHStack: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
        return CGSize(width: sizes.reduce(0){ $0 + $1.width },
                      height: sizes.map(\.height).max() ?? 0)
    }
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        var x = bounds.minX
        for s in subviews {
            let size = s.sizeThatFits(.unspecified)
            s.place(at: CGPoint(x: x, y: bounds.minY), anchor: .topLeading, proposal: .unspecified)
            x += size.width
        }
    }
}

일관성은 개발자의 책임입니다

SwiftUI는 두 메서드가 일치한다고 가정합니다. placeSubviews가 sizeThatFits에서 보고한 크기보다 더 많은 공간을 사용하면 자식 뷰가 잘리거나 겹칠 수 있습니다.

두 메서드에서 기하 계산을 공유하여 일관성을 유지하십시오.

빠른 확인: 레이아웃 기초

레이아웃 프로토콜에 대한 이해도를 확인해 보십시오.

복습: 레이아웃 프로토콜 기초

Layout 프로토콜은 사용자 지정 배치 로직을 재사용 가능한 컨테이너로 바꿉니다. sizeThatFits는 ProposedViewSize에 대한 레이아웃 크기를 보고하고, placeSubviews는 place(at:anchor:proposal:)을 사용해 bounds 안에 각 자식 프록시를 배치합니다.

제안에는 선택적인 치수가 포함되고, bounds는 원점에서 이동되어 있을 수 있으며, 두 메서드는 기하학적으로 일관되어야 합니다. 다음에는 하위 뷰를 자세히 측정합니다.

자주 묻는 질문

“Layout 프로토콜 기초” 강의는 무료인가요?

네 — “Layout 프로토콜 기초” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Swift Academy 강의 전체를 잠금 해제할 수 있습니다. Swift Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“Layout 프로토콜 기초”에서 뭘 배우나요?

sizeThatFits와 placeSubviews를 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 Swift Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Swift Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Swift Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“Layout 프로토콜 기초” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Swift Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Swift Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Layout 프로토콜 기초
  2. 하위 뷰 측정하기
  3. 플로 레이아웃 만들기
  4. 레이아웃 캐시와 성능
← Swift Academy(으)로 돌아가기