본문으로 건너뛰기

SwiftUI에서 시스템 콘텐츠 여백 사용하기

How to use layoutMarginsGuide in SwiftUI

sarunw.com

핵심 요약

  1. 고정된 `.padding(.horizontal, 16)`은 기기와 방향에 따라 달라지는 시스템 여백에 맞지 않을 수 있으며, iPhone Duo에서는 좌우 여백이 비대칭이어서 화면 가장자리와 세로 바 위치를 모두 놓칠 수 있다.
  2. iOS 27.1의 `.contentMargins(for: .container)`는 현재 컨테이너와 뷰 위치에 맞춘 시스템 여백을 적용하며, `GeometryProxy.contentMargins(for:edges:)`는 같은 여백 값을 `EdgeInsets`로 반환한다.
  3. SwiftUI 여백 값은 UIKit의 `layoutMargins`와 달리 safe area를 포함하지 않는다. iOS 27.0을 지원할 때는 대체 modifier를 둘 수 있지만, `.padding(.horizontal)`도 기기별 차이를 정확히 반영하지 못한다.

도입

SwiftUI에서 화면 좌우에 16포인트 padding을 지정하면 일부 기기에서는 내비게이션 제목과 맞지만, 기기 크기와 방향이 달라지면 정렬이 어긋난다. iPhone Duo가 닫힌 상태에서는 세로 바 옆의 시스템 여백이 0이고 반대쪽은 20포인트일 수 있어 고정값으로는 대응하기 어렵다. iOS 27.1은 컨테이너의 시스템 여백을 적용하고 읽는 API를 제공한다.

상세 요약

고정 padding 대신 시스템 여백 적용

.padding(.horizontal, 16)은 기기마다 달라지는 여백을 반영하지 못한다. 원문에서 일반적인 iPhone의 시스템 여백은 16 또는 20포인트이며, 닫힌 iPhone Duo는 세로 바 옆 0포인트와 반대쪽 20포인트처럼 비대칭일 수 있다. 기기를 회전하면 0포인트가 적용되는 쪽도 바뀐다. 따라서 고정 숫자는 한쪽에서 여백이 부족하고 다른 쪽에서는 지나치게 커질 수 있다.

iOS 27.1에서 추가된 .contentMargins(for: .container, edges: .horizontal)는 시스템이 현재 기기와 컨테이너에 맞는 여백을 적용한다. ContentMarginGuide에는 현재 .container가 있으며, 여백은 플랫폼, 컨테이너 크기, 뷰의 위치에 따라 달라진다. edges로 적용할 방향을 지정하고 생략한 방향은 변경하지 않는다. 기본값은 .all이다.

.contentMargins(for: .container, edges: .horizontal)

이 API는 iOS 17의 .contentMargins(_:_:for:)와 다르다. 기존 API는 .scrollContent 같은 ContentMarginPlacement를 지정해 스크롤 뷰 안에 개발자가 정한 값을 적용한다. 새 API의 .container는 ContentMarginGuide이며 시스템 여백을 사용한다.

여백 값을 읽어 다른 곳에 적용

GeometryProxy에는 다음 메서드가 추가됐다.

proxy.contentMargins(for: .container, edges: .horizontal)

반환값은 EdgeInsets이며, 요청하지 않은 방향의 값은 0이다. 예를 들어 가로 캐러셀의 ScrollView를 화면 끝까지 두면서 카드의 정지 위치를 시스템 여백에 맞추려면, 이 값을 iOS 17의 .contentMargins(.horizontal, value, for: .scrollContent)에 전달할 수 있다. 그러면 첫 카드는 제목 시작 위치에 맞고, 마지막 카드는 반대편 바에서 멈춘다.

값은 GeometryReader의 위치에 따라 달라진다. 여백이 20포인트인 예에서 화면 상단에서는 20, .padding(8) 안에서는 남은 12, .padding(30) 안에서는 0을 읽는다. 이미 .contentMargins(for: .container)가 적용된 안쪽에서도 0이다. 따라서 값을 상위 위치에서 한 번 읽어 깊은 뷰에 전달하기보다 실제 사용하는 위치에서 읽어야 한다.

UIKit과의 차이 및 버전 대응

SwiftUI의 값은 safe area를 포함하지 않는다. UIKit의 layoutMargins가 safe area까지 포함하는 경우에도 SwiftUI 뷰는 이미 safe area 안에서 시작하므로 콘텐츠는 같은 위치에 놓일 수 있다. 원문 측정값에서 iPhone 18 Pro Max 가로 방향의 UIKit 여백은 78포인트였고 SwiftUI는 16포인트였다. 닫힌 iPhone Duo에서는 바 옆 값이 UIKit 84, SwiftUI 0이었다. UIKit의 insetsLayoutMarginsFromSafeArea에 대응하는 SwiftUI 설정은 없으며, 중첩 뷰는 남은 여백을 읽으므로 preservesSuperviewLayoutMargins도 필요하지 않다.

ContentMarginGuide, View modifier, GeometryProxy 메서드는 iOS 27.1 이상에서 제공된다. 문서에는 iPadOS, Mac Catalyst, macOS, visionOS, watchOS 27.1도 명시돼 있다. iOS 27.0을 지원한다면 #available로 새 API를 감싸고 이전 버전에서는 .padding(.horizontal)을 사용할 수 있다. 숫자를 생략한 padding은 원문에서 시험한 기기 모두 16포인트였으며, 20포인트 여백인 iPhone 18 Pro Max와 iPad Pro에서는 4포인트 부족했다. iPhone Duo는 iOS 27.1을 탑재하므로 이 fallback을 사용하지 않는다.

기기별 SwiftUI 레이아웃 검증 맥락은 iPhone Duo Simulator로 SwiftUI 앱의 레이아웃과 동작 테스트하기에서도 다룬다.

결론

뷰 자체를 시스템 여백에 맞추려면 `.contentMargins(for: .container)`를 사용하고, 여백 값을 스크롤 콘텐츠 등에 전달해야 한다면 해당 위치의 `GeometryProxy`에서 값을 읽는다. 이 값은 safe area를 포함하지 않으며, UIKit의 `layoutMargins`와 수치가 달라도 콘텐츠 위치는 safe area를 고려해 맞춰진다. 두 API는 iOS 27.1부터 제공되고, iOS 27.0에서는 별도 fallback이 필요하다.