klioop for iOS

SwiftUI layout 시스템 1, 레이아웃 원칙 본문

SwiftUI

SwiftUI layout 시스템 1, 레이아웃 원칙

klioop2@gmail.com 2021. 5. 19. 17:37

안녕하세요

 

WWDC 2019 부터 시작해서 SwiftUI layout 시스템에 대해서 공부하고 있는데요. 

처음에는 UI 를 너무 쉽게 슥슥 그리는 모습에 SwiftUI 레이아웃 시스템이 정말 쉽게 느껴졌습니다. 

그러나 레이아웃 시스템에 대해서 알게 될수록 쉽지 않네요 ㅜㅜ

특히 디테일하게 레이아웃을 그려야 할 때 SwiftUI 레이아웃은 결코 쉽게 느껴지지 않습니다.

 

그래서 튜토리얼을 따라서 마냥 만들어 보는 것을 멈추고 SwiftUI 레이아웃 시스템의 원리를 공부 해놔야 겠다는 생각이 들었습니다.

아주 기초적인 부분부터 정리해 놓으려고 합니다.

 

먼저 SwiftUI 가 그리는 레이아웃 방식의 대원칙을 살펴보면 다음과 같습니다.

 

  1. 부모 뷰는 자식 뷰에게 자신의 사이즈를 그대로 제안한다.
  2. 자식 뷰는 부모의 사이즈와 함께 자신의 display contents 를 고려해서 자신의 사이즈를 정한다. 그리고 부모에게 알려준다.
  3. 부모는 자식이 결정한 사이즈를 가지고 자식 뷰를 자신의 좌표공간에 놓는다. 기본적으로 가운데에 놓는다.
  4. SwiftUI rounds the corners of your view to the nearest pixel.

WWDC 에서 4번은 SwiftUI 가 알아서 해주지만 아는 것은 중요하다고 나오는데요.

정확히 무슨 말인지는 아직 잘 모르겠습니다.

 

레이아웃이 그려지는 과정을 4 번을 제외하고 살펴보려고 합니다.

 

매우 중요하고 항상 명심해야 할 것은 자식 뷰가 자신의 사이즈를 정한다는 것입니다.

 

부모 뷰는 자식 뷰가 정한 사이즈 그대로 자신의 좌표공간에 자식 뷰를 놓을 뿐, 자식 뷰의 사이즈를 결정하지 않습니다.

 

이제 매우 간단한 한 가지 예를 살펴보겠습니다.

 

 

위 결과는 다음 코드의 프리뷰 레이아웃 프레임을 300x300 으로 고정한 것 입니다.

 

struct HelloLayout: View {
    
    var body: some View {
     
        Text("Hello, World")
       
    }
        
}

struct HelloLayout_Previews: PreviewProvider {
    static var previews: some View {
        HelloLayout()
            .previewLayout(.fixed(width: 300, height: 300))
    }
}

 

 

뷰 hierarchy or tree

먼저 뷰의 구조를 살펴보면 왼쪽과 같습니다.

여기에서 RootView 는 300x300 의 뷰를 나타냅니다. 

 

 그런데 커스텀 뷰인 HelloLayout 의 body 와 같이 다른 뷰들을 그저 감싸서 반환하는 뷰는 layout neutral 이라고 하는 성질을 가지고 있습니다. 

 

부모로부터 제안받은 사이즈를 그대로 자식뷰에게 전달하고 자식 뷰들의 사이즈가 정해지면 자식 뷰들을 정확히 감싸는 사이즈로 자신의 사이즈를 결정하는 특성입니다. 

 

 

 

 

따라서 실질적인 뷰의 구조는 다음과 같게 됩니다. 

실질적인 뷰 계층구조

 

이 실질적인 뷰의 계층 구조와 함께 SwiftUI 레이아웃 원칙을 생각하면서 레이아웃 과정을 살펴보겠습니다.

 

  1.  RootView 는 자식 뷰인 Text 뷰에게 자신의 크기인 300x300 을 크기로 제안합니다.
  2.  Text 뷰는 자신의 contents 인 "Hello, World" 가 차지하는 정도만 사이즈로 결정합니다.
  3.  부모 뷰인 RootView 는 Text 가 정한 사이즈로 Text 를 자신의 좌표공간 가운데에 위치 시킵니다.

 

매우 간단하죠?

 

이제부터 Text 뷰를 꾸미는 modifier 뷰도 함께 고려해볼게요.

 

프리뷰를 300x300 으로 고정함

 

struct HelloLayout: View {
    
    var body: some View {
     
        Text("Hello, World")
            .padding(10)
            .background(Color.red)
            .foregroundColor(.white)
    }
        
}

struct HelloLayout_Previews: PreviewProvider {
    static var previews: some View {
        HelloLayout()
            .previewLayout(.fixed(width: 300, height: 300))
    }
}

 

SwiftUI 에서 modifier 또한 뷰 입니다. (https://klioop.tistory.com/27)

padding 은 Text 를 기반으로 자신의 뷰를 만듭니다.

background 와 foregroundColor 도 마찬가지 입니다.

 

modifer 를 고려한 뷰의 계층 구조는 다음과 같습니다.

 

Text 의 modifiers 고려한 뷰 tree

 

이제 레이아웃 원칙을 생각하며 레이아웃 과정을 살펴보겠습니다.

 

  1. RootView 는 자식 뷰인 foreGroundColor 에게 자신의 사이즈를 제안합니다.

  2. foreGroundColor 뷰는 layout neutral 특성을 가지고 있는 뷰 입니다.
    따라서 제안받은 사이즈를 자신의 자식 뷰인 background 뷰에게 그대로 제안합니다.

  3. background 뷰도 마찬가지로 layout neutral 뷰 입니다. padding 뷰에게 제안받은 사이즈를 넘깁니다.

  4. padding 뷰는 Text 뷰 모든 면에 패딩을 10 씩 추가해야 합니다.
    따라서 Text 뷰에게 자신이 제안받은 사이즈의 각 면(edge)에서 10 씩 뺀 사이즈를 제안합니다.
    여기에서는 280x280 이 되겠네요.

  5. Text 뷰는 "Hello, World" 만 채울정도로 자신의 사이즈를 정하고 부모뷰인 padding 에게 전달합니다. 

  6. padding 뷰는 Text 뷰가 전달한 사이즈에다가 모든 면에 10 을 더해서 자신의 사이즈를 결정합니다.
    그리고 부모 뷰에 전달합니다.

  7. backgroud 뷰는 layout neutral 로 자식뷰가 전달한 사이즈를 자신의 사이즈로 정하고 그대로 부모뷰에 전달합니다.
    하지만 전달하기 전에 자신의 두 번째 자식 뷰인 Color 뷰에게 자신의 사이즈를 제안합니다.

    Color 뷰는 제안 받은 사이즈를 그대로 채우는(fill) 성질을 가지고 있습니다.

    따라서 Color 뷰는 background 의 사이즈를 자신의 색으로 전부 채웁니다. 

  8. foreGroundColor 뷰도 layout neutral 이므로 background 의 사이즈를 자신의 사이즈로 정하고
    부모 뷰인 RootView 에 전달합니다.
    추가적으로 foreGroundColor 뷰는 SwiftUI 에게 Text 의 색을 자신이 정한 색으로 렌더링 하도록 말합니다.

  9. RootView 는 전달받은 사이즈로 자신의 좌표공간에 자식 뷰를 가운데에 위치시킵니다.

원래 foreGroundColor 의 부모 뷰는 HelloLayout body 가 반환하는 wrapper 뷰가 되지만

layout neutral 이므로 생략했습니다.

 

modifier 가 4 개 밖에 추가되지 않았는데 과정이 길어지네요. 

하지만 원칙에서 벗어나지 않는 것을 알 수 있습니다. 

 

부모 뷰나 자식뷰의 사이즈에 상관없이 자신의 크기를 고정시키는 frame 을 고려하기 시작하면

레이아웃을 contents 크기에 맞도록 다이나믹하게 설정하는 일이 쉬어지지는 않는 것 같습니다. 

차차 공부해보도록 하고 이번 포스트에서는 다음의 원칙만 기억하면 됩니다.

 

  1. 부모 뷰는 자식 뷰에게 자신의 사이즈를 그대로 제안한다.
  2. 자식 뷰는 부모의 사이즈와 함께 자신의 display contents 를 고려해서 자신의 사이즈를 정한다. 
  3. 부모는 자식이 결정한 사이즈를 가지고 자식을 자신의 좌표공간에 놓는다. 

다음에는 Stack (VStack, HStack) 과 같은 Container 뷰가 어떻게 자식 뷰들을 정렬하는 지 알아보겠습니다.

'SwiftUI' 카테고리의 다른 글

SwiftUI AlignmentGuide 2, Custom AlignmentGuide  (0) 2021.05.27
SwiftUI AlignmentGuide 1  (1) 2021.05.27
SwiftUI 레이아웃 시스템 2, Stack  (0) 2021.05.22
SwiftUI, 좌표공간 각도방향  (0) 2021.05.21
SwiftUI, 선언적 뷰의 의미  (0) 2021.05.17