iOS resizability 디자인 사상: App이 자신이 어디서 도는지 알 필요가 없을 때

다섯 가지 설계 원칙을 각각 코드에 내려놓는다. 창 크기 조절 시대, App이 관심 갖는 것은 자신이 얼마나 큰 공간을 갖는지뿐. 씬 설정, 지오메트리 콜백, 두 단계 전략, Info.plist 레드라인, 테스트 매트릭스 포함.

iOS resizability란 App의 창을 데스크톱 앱처럼 사용자가 가장자리를 잡고 자유롭게 드래그해 크기를 바꿀 수 있는 것을 말한다. 이 변화에 대한 Apple의 디자인 사상은 한 줄로 압축된다——App은 자신이 어떤 기기에서, 어떤 방향으로, 어떤 화면 위에서 도는지 더 이상 신경 쓰지 않는다. 지금 이 순간 얼마나 큰 공간을 쓸 수 있는지에만 관심을 둔다. 여러분이 정하는 것은 "공간이 넓을 때 어떻게 배치하고, 좁을 때 어떻게 배치하는가"라는 두 가지 논리뿐이다. 시스템이 그 논리를 모든 기기, 모든 창 크기, 모든 접힘 상태에 적용한다.

이것은 흔한 API 업데이트가 아니다. iOS가 생긴 이래 레이아웃 패러다임이 바뀌는 자리이다. 이 글은 resizability의 디자인 사상을 다섯 가지 원칙으로 풀고, 각각을 UIKit과 SwiftUI 코드에 내려놓으며, 마지막으로 그대로 실행할 수 있는 마이그레이션 로드맵과 테스트 매트릭스를 준다.

글 속 시그니처는 Xcode 27.1의 iOS 27.1 SDK와 맞춰 확인했다. 동작의 근거는 Apple 공식 문서와 WWDC 세션이며, 출처는 글 끝에 적는다. 본문은 그 자료를 풀어쓴 것이며 공학적으로 정리한 것이지, 어느 한 페이지의 번역은 아니다.

1. iOS 26부터 27까지: resizability는 어떻게 기본값이 되었나

디자인 사상에 앞서 시간선을 본다. "예전의 그 방법"이 왜 이제 충분하지 않은지가 보인다.

단계 무슨 일이 있었나 개발자에게 주는 의미
iPadOS 15 Split View / Slide Over / Stage Manager iPad에 전체 화면이 아닌 크기가 나타난다
iPadOS 26 iPad가 자유 창 시대로. 임의로 드래그해 확대·축소 "iPad = regular" 가정이 흔들리기 시작
iOS 26 SDK UIRequiresFullScreen 폐기 예정 전체 화면 키로 크기 조절에서 빠져나갈 수 없음
iOS 26 SDK iPhone App도 iPad나 Mac 미러링에서 크기 조절 가능 "iPhone에서 도는 것"이 compact를 뜻하지 않음
iOS 27 최신 SDK로 빌드한 App은 UIScene 라이프사이클 필수 씬이 resizability의 밑바탕이 됨
iOS 27 탭 바·툴바 등 시스템 컴포넌트가 임의 종횡비에 전면 대응 시스템 부품이 적응의 주역이 됨

이 선을 한 마디로 요약하면: 창 크기가 "열거값"에서 "연속값"이 되었다. 예전에 App이 마주한 것은 375, 393, 414, 768, 1024 같은 몇 개의 이산 단이었다. 이제 사용자의 손가락이 창 가장자리를 움켜쥐고 한 번 드래그하면, 너비는 320부터 화면 상한까지 어떤 수가 될 수도 있다. 이산 단에 맞게 굳혀 쓴 레이아웃은 연속값 앞에서 반드시 깨진다.

2. 디자인 사상 하나: "기기 사고"에서 "공간 사고"로

오래된 레이아웃 지식은 거의 다 기기 사고 위에 서 있다. 이것은 iPhone이고 가로로 놓였으니 이렇게 놓는다. 이것은 iPad이므로 두 칸으로 나눈다. resizability는 이 사고를 통째로 바꿀 것을 요구한다.

기기 사고(옛것) 공간 사고(새것)
판단 근거 기기 종류·방향·화면 크기 현재 컨테이너의 사용 가능 공간
레이아웃 수 기기마다 한 벌 한 벌의 규칙이 모든 공간을 커버
새 하드웨어 등장 코드 수정과 새 배포가 필요할 수 있음 자동 적용. 변경은 제로
접힘 화면 형태 자세마다 인터페이스를 하나 더 한 벌의 인터페이스가 너비에 따라 늘고 줄음
코드 속 냄새 if idiom == .pad, isLandscape horizontalSizeClass, 컨테이너 너비

공간 사고의 가장 평이한 표현은 WWDC에서 왔다. 플랫폼마다, 창 크기마다 따로 디자인할 필요가 없다. 내용이 "여유가 있을 때 어떻게 놓이고, 부족할 때 어떻게 놓이는가"를 한 번 정의하면, 시스템이 그 논리를 모든 종횡비에 적용한다. 화면을 돌리는 것은 그 논리의 한 번의 트리거일 뿐이며, 창을 드래그하고, 분할 화면에 들어가고, 화면을 펼치는 것과 본질적으로 다르지 않다——결국 모두 같은 한 가지로 귀결된다. 쓸 수 있는 공간의 크기가 변했다.

3. 디자인 사상 둘: size class는 새로운 공용어

기기 사고가 물러난 뒤 무엇이 그 바통을 받는가. size class이다. 그것은 공간에 대한 프레임워크의 정성 판단이다. 두 개의 축, 각 축에 두 글자——compact는 빡빡함을, regular는 여유로움을 나타낸다.

  • horizontal 축은 좌우를 본다. compact면 단일 칸 세로 쌓기여야 하고, regular가 되어서야 칸 나누기, 나란히 놓기, 사이드바가 떳떳해진다.

  • vertical 축은 상하를 본다. compact에서는 위아래 chrome(탭 바, 툴바)이 들어가지 않아 시스템이 가로 탭 바를 세로로 옮긴다. regular면 위아래 바를 함께 둘 수 있다.

SwiftUI에서 읽는 방법:

@Environment(\.horizontalSizeClass) private var hSize  // UserInterfaceSizeClass?
@Environment(\.verticalSizeClass) private var vSize

if hSize == .regular {
    // 좌우에 여유: 칸 나누기 / 사이드바 / 두 줄
} else {
    // 좌우가 빡빡함: 단일 칸 세로 쌓기
}

UIKit에서는 traitCollection.horizontalSizeClass가 같은 역할을 맡으며, registerForTraitChanges로 변화를 정확히 구독할 수 있다.

이 글에서 가장 중요한 표가 다음에 있다——size class는 기기·방향과 일대일이 아니다:

장면 horizontal vertical 설명
iPhone 세로 compact regular 모든 App의 기본 형태
iPhone 가로 compact compact 높이가 약 320pt뿐, 탭 바가 세로로
iPhone 창을 크게(iOS 26+) regular regular 실행 중 변함. 실행 시점에 확정되지 않음
iPad 전체 화면 regular regular 너비 약 1024pt
iPad 분할 화면 절반 regular regular 너비가 약 507pt뿐, 그래도 regular
iPad Slide Over compact compact iPad 위의 compact 형태
Duo 외부 화면 compact compact 외부 화면 너비 약 466pt
Duo 펼친 내부 화면 regular compact 접힘 상태 전환, 실행 중 변함

이 표를 세 번 읽으면 직관에 반하는 결론 두 개가 떠오른다. "iPhone에서 도는 것"은 더 이상 compact가 아니다(창을 크게 하면 regular다). "iPad에서 도는 것"도 더 이상 regular가 아니다(Slide Over는 compact다). 기기 종류도 화면 방향도 "지금 어떤 레이아웃을 써야 하는가"에는 답하지 못한다. 실행 중에 size class에게 물을 뿐이다.

이 표는 오랜 생각 하나도 바로잡는다. 화면을 돌리는 것은 "기기가 변했다"가 아니다. 돌리는 것은 (H, V) 조합을 (compact, regular)에서 (compact, compact)로 바꾸고 bounds를 한 번 바꾸는 것뿐이다. 레이아웃 코드의 구조는 움직이지 않는다. 움직이는 것은 입력뿐이다. "방향"을 레이아웃 개념으로 쓰던 시대에 그것은 늘 대리 변수였다. 이제 대리는 없고, 남는 것은 공간뿐이다.

size class의 절벽: 왜 두 번째 단이 필요한가

size class는 이진이고, 충분히 거칠어서 "거짓말"을 한다. 절벽을 보라. iPad 전체 화면 1024pt와 분할 화면 절반 507pt는 둘 다 (regular, regular)다——그러나 전자에는 사이드바가 들어가고 후자에는 들어가지 않는다. 두 글자로는 "몇 칸인지, 얼마나 큰지, 너비를 제한할지" 같은 정도의 문제에 답할 수 없다.

그래서 성숙한 방식은 두 단계의 릴레이다. size class는 방향의 문제(단일 칸인지 분할인지)에 답하고, 공학 전략은 정도의 문제(몇 칸인지, Hero의 크기, 너비 제한)에 답한다. 두 번째 단은 보통 실측 컨테이너 너비로 나눈다. 예컨대 넓은 화면에서는 본문을 읽기 좋은 행 너비로 제한하거나, 내용의 최소 크기로 격자 칸 수를 정한다. iOS 17부터 SwiftUI에서는 onGeometryChange로 컨테이너 크기를 직접 실측해 이 판단에 쓸 수 있다. GeometryReader에 기댈 필요가 없다.

4. 디자인 사상 셋: 기능은 크기로 변하지 않는다. 변하는 것은 "보이는 양"뿐

이것은 Human Interface Guidelines에서 놓치기 쉽지만 지나칠 수 없이 중요한 원칙이다. size class가 변해도 기능의 집합은 그대로다. 변하는 것은 "한 번에 볼 수 있는 양"뿐이다.

넓은 화면에서는 탭 바가 사이드바로 펼쳐지며 2차 목적지를 드러낸다. 좁은 창에서는 우선순위가 낮은 도구 항목이 오버플로 메뉴로 들어가도 한 번 탭하면 닿는다. 수납은 재배치이지 삭제가 아니다. 넓은 레이아웃에서 쓸 수 있는 동작이 좁은 화면에서 홀연히 사라진다면, 그것은 적응이 아니라 기능 결손이다. 이와 마찬가지로 "이 화면은 한 방향으로만 완전히 조작할 수 있다"는 설계가 세로·가로 사이에 있어서는 안 된다. 세로에서 할 수 있는 것은 가로에서도 머리부터 꼬리까지 통해야 한다.

제품 설계자에게 주는 암묵의 요구는 이것이다. 정보 구조를 짓는 단계에서 모든 요소에 우선순위를 적어 넣는다. 공유·주 동작 같은 높은 우선순위 항목은 모든 크기에서 상주한다. 저장·2차 설정 같은 낮은 우선순위 항목은 넓을 때 상주하고 좁을 때 오버플로로 들어간다. 시스템 컴포넌트의 Visibility Priority 메커니즘이 하는 일이 바로 이것이다.

5. 디자인 사상 넷: min / max 크기는 겹침 방지이지, 크기 조절 거부가 아니다

UIKit은 씬에 최소·최대 크기를 선언할 수 있게 한다. 그러나 뜻을 분명히 해두자. 이 두 값이 존재하는 이유는 작은 크기에서 요소가 서로를 가리지 않게 하고, 큰 크기에서 느슨해지지 않게 하기 위해서이지, 사용자에게 "더 작아지지 마"라는 문턱을 세우기 위해서가 아니다. 창이 어떤 크기로든 드래그될 수 있게 된 이상, 여러분의 책임은 어떤 크기에서도 내용이 최소한 쓸 수 있고, 읽을 수 있고, 겹치지 않게 하는 것이다. 어떤 크기에서 반드시 깨진다면, 여러분의 최소 크기 선언이 너무 욕심스럽거나, 레이아웃 자체가 아직 참으로 적응하지 못했다는 뜻이다.

씬이 연결될 때 선언한다:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
           options: UIScene.ConnectionOptions) {
    guard let windowScene = scene as? UIWindowScene else { return }

    // 하한: 이보다 작아지면 요소가 겹치기 시작한다
    // 상한: 이보다 커지면 레이아웃이 느슨해진다
    windowScene.sizeRestrictions?.minimumSize = CGSize(width: 320, height: 480)
}

이와 함께 짊어져야 할 오래된 빚이 있다. UIRequiresFullScreen은 폐기 예정이며,이를 유지하는 App은 크기 조절에서 빠져나갈 수 없다. 새로운 표시 크기를 자동으로 포기하는 셈이다. iOS 26 SDK부터 App은 새 화면을 위해 코드를 다시 쓰거나 재제출할 필요가 없다. 시스템이 자동으로 적응한다——여러분이 스스로 문에 자물쇠를 걸지 않는 한.

6. 디자인 사상 다섯: 시스템 컴포넌트가 일하게 하라

resizability 시대 가장 힘 안 드는 전략은 재배치의 일을 시스템 컴포넌트에 맡기는 것이다. 임의 종횡비에 대한 그들의 처리는 손으로 쓴 레이아웃보다 훨씬 튼튼하다.

  • 탭 바 / 툴바: 창이 넓어지면 탭 아이콘이 라벨과 같은 줄에 놓여 바를 간결하게 유지하고, 좁아지면 우선순위가 낮은 도구 항목이 오버플로 메뉴로 흘러간다. 뉴스류 App에서는 "공유"를 모든 크기에서 못 박아 두고 "저장"은 좁을 때만 오버플로로 보낼 수 있다——Visibility Priority로 선언하면 되며, 레이아웃 코드는 한 줄도 필요 없다.

  • 사이드바: iPadOS 26부터 iPhone App도 공간이 허락할 때 사이드바 레이아웃을 고를 수 있다. sidebar.isAvailable로 지금 공간에서 보여줄 수 있는지 확인한 뒤 요청한다.

  • NavigationSplitView: iOS 26부터 열이 공간에 따라 자동으로 나타났다 사라진다. 열 가시성을 손으로 관리할 필요가 없다.

  • Liquid Glass 시스템 머티리얼: iOS 27에서 개선된 확산과 가장자리 분리를 얻는다. 재컴파일하지 않아도 누린다.

여러분 자신의 UI에는 여기서 딱 한 가지 일만 남는다. 이 부품들과 싸우지 말라는 것이다. 직접 그린 상단 바는 창 왼쪽 위의 닫기·최소화 컨트롤을 피해야 한다(수평 코너 적응이 있는 레이아웃 가이드를 쓴다). 탭 바가 항상 아래에, 툴바가 항상 위에 있다고 가정하지 말라——그 형태 자체가 공간과 함께 변한다.

7. UIKit 구현: 씬 설정과 지오메트리 응답

씬은 resizability의 밑바탕이다. 각 씬은 자신의 UI 인스턴스와 상태를 독립적으로 관리하고, "이 UI가 어디에, 얼마나 큰지"라는 맥락을 공급한다. iOS 27부터 최신 SDK로 빌드하는 App은 UIScene 라이프사이클을 채택해야 한다. 이것은 선택이 아니다.

그대로 베낄 수 있는 씬 델리게이트 뼈대:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }

        // 1) 크기 능력 선언: 겹침 방지. 크기 조절 거부가 아니다
        windowScene.sizeRestrictions?.minimumSize = CGSize(width: 320, height: 480)

        // 2) 창은 씬에 건다. UIWindow(frame: UIScreen.main.bounds)는 금물
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = AdaptiveRootController()
        self.window = window
        window.makeKeyAndVisible()
    }

    // 3) 지오메트리 변화 콜백: 드래그, 분할 화면, 접힘 전개, 외부 화면 전환이 모두 여기로 온다
    func windowScene(_ windowScene: UIWindowScene,
                     didUpdateEffectiveGeometry previous: UIWindowScene.Geometry) {
        let geometry = windowScene.effectiveGeometry
        let size = geometry.coordinateSpace.bounds.size

        // isInteractivelyResizing가 true면 사용자가 아직 가장자리를 움켜쥐고 드래그 중이라는 뜻이다
        // 무거운 일(자원 재구축, 차트 재계산)은 손을 뗀 뒤 한 번에 한다. 프레임마다 렌더링하지 않는다
        if !geometry.isInteractivelyResizing,
           size != previous.coordinateSpace.bounds.size {
            AssetStore.shared.rebuild(for: size)
        }
    }

    // 4) iPadOS 26+ 창 버튼(닫기 / 최소화) 스타일 선호
    func preferredWindowingControlStyle(for scene: UIWindowScene)
        -> UIWindowScene.WindowingControlStyle {
        .unified
    }
}

펼쳐 볼 만한 요점이 세 개 있다.

isInteractivelyResizing는 드래그 성능의 생명선이다. 사용자가 창을 드래그하는 동안 시스템은 지오메트리 변화를 연속으로 보고한다. 게임이나 App이 중간 크기마다 렌더링 자원을 다시 만들면 프레임률이 그 자리에서 무너진다. 표준 자세는 드래그 중에는 가벼운 갱신(제약, 글자 크기 변경)만 하고, 손을 뗀 뒤 무거운 일을 한 번에 끝내는 것이다.

화면이나 크기를 얻을 때 UIScreen.main을 통하지 말라. iPhone 미러링과 외부 화면 상황에서 main screen은 틀린 답이다. 올바른 출처는 view.window?.windowScene?.screen, 씬의 effectiveGeometry, 또는 뷰 로컬의 view.bounds다.

창 컨트롤 피하기. iPadOS 26부터 씬의 왼쪽 위 모서리에는 닫기·최소화 컨트롤이 놓인다. 직접 그린 상단 바는 containerView.layoutGuide(for: .margins(cornerAdaptation: .horizontal))에 제약해야 한다. 그렇지 않으면 사용자가 여러분의 첫 번째 버튼을 누를 수 없다.

8. 뷰 레이어: 낡은 모드 셋을 지운다

적응형 UIKit 뷰 코드의 본질은 한 번의 "세 가지 교체"이다.

지우는 것 바꾸는 것 이유
UIScreen.main.bounds view.bounds / effectiveGeometry / windowScene?.screen 분할과 미러링 아래서 main screen은 여러분의 창이 아니다
userInterfaceIdiom == .pad 분기 traitCollection.horizontalSizeClass 기기 종류는 레이아웃 뜻을 담지 않는다
interfaceOrientation 관련 분기 size class + 로컬 bounds 방향은 레이아웃의 결과이지 원인이 아니다

교체 뒤에는 trait 변화가 레이아웃을 몰아간다:

class AdaptiveRootController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // size class가 변하면 재배치. 자동 추적이 못 덮는 캐시 무효화도 등록이 맡는다
        registerForTraitChanges([UITraitHorizontalSizeClass.self,
                                 UITraitVerticalSizeClass.self]) { (vc: Self, _) in
            vc.applyLayout()
        }
    }

    private func applyLayout() {
        switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass) {
        case (.regular, _):   useSidebarLayout()
        case (.compact, _):   useStackedLayout()
        default:              useStackedLayout()
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        // size class보다 섬세한 판단이 필요할 때는 로컬의 쓸 수 있는 크기를 쓴다
        fineTune(for: view.bounds.size)
    }
}

viewDidLayoutSubviews / layoutSubviews가 계속 효력을 낸다는 점에 유의하라. size class가 발화시키는 것은 "구조 재배치"이고, bounds 변화가 발화시키는 것은 "세밀 조정"이다. 한 번뿐인 onAppear / viewDidLoad에서 크기를 읽어 두면, 크기 조절이 시작된 뒤 가장 먼저 낡아버린다.

9. SwiftUI 대조: 대부분 자동, 요처만 받쳐준다

SwiftUI로 쓴 App은 resizability에서 이익을 본다. Xcode 27로 재컴파일하면 창 크기 조절 지원이 자동으로 효력을 얻고, 받아들일 새 API가 없으며, Liquid Glass 새 머티리얼도 자동으로 계승한다. 그러나 "자동"은 "점검 불요"가 아니다. 다음 메커니즘이 SwiftUI 쪽의 받침 도구상자를 이룬다.

  • @Environment(\.horizontalSizeClass) / verticalSizeClass: UIKit trait에 대응하며, 방향의 문제에 답한다.

  • onGeometryChange(for:of:action:): 컨테이너 크기를 실측해 정도의 문제에 답한다. GeometryReader의 탐욕스러운 점유를 일으키지 않고 읽으며, 파생 값이 진짜로 변할 때만 액션을 부른다. 드래그 도중의 중간 상태를 자연스럽게 걸러낸다.

  • ViewThatFits: "들어가는 것을 처음부터 순서대로 쓴다"는 선언적 작성법. 너비에 따라 배치가 바뀌는 내용에 맞는다.

  • containerRelativeFrame(): 절대 점수가 아니라 컨테이너 비율로 치수를 정한다. 배경 확장 영역을 쓸 때 특히 마음을 놓을 수 있다.

  • NavigationSplitView: iOS 26부터 열 너비와 열 가시성이 스스로 적응한다. 그 바깥에 손으로 열 관리를 한 겹 더 씌우지 말라.

  • .windowResizability(.contentMinSize): SwiftUI 씬이 최소 크기를 선언하는 입구로, UIKit의 sizeRestrictions?.minimumSize와 동치다.

전형적인 두 단계 릴레이 작성법:

struct DashboardView: View {
    @Environment(\.horizontalSizeClass) private var hSize
    @State private var columnCount = 1

    var body: some View {
        ScrollView {
            MetricGrid(columns: columnCount)
        }
        // 첫 단계: size class가 단일 칸인지 분할인지의 세계관을 정한다
        .onChange(of: hSize) { _, new in
            usesSidebar = (new == .regular)
        }
        // 둘째 단계: 실측 너비가 격자 칸 수를 정한다. 둘 다 regular인 507과 1024를 커버한다
        .onGeometryChange(for: CGFloat.self) { $0.size.width } action: { _, width in
            columnCount = AdaptiveWindowLayoutPolicy.tier(for: width).columns
        }
    }
}

10. Info.plist와 Launch Screen의 레드라인

마이그레이션 목록에는 공학 설정에서 비롯된 두 줄의 레드라인이 있다. 어느 하나라도 놓치면 터지는 때는 출시 직전이다.

  1. UIRequiresFullScreen은 반드시 처리해야 한다. 폐기 예정이며, 이를 유지하는 App은 크기 조절에 참여할 수 없다. 지우거나, 역사적 호환성이 정말 필요하면 UIRequiresFullScreenIgnoredStartingWithVersion으로 몇 번째 버전부터 그 키를 무시할지 밝힌다. 게임류 App은 그 결과로 discrete resizing에 들어간다——드래그를 끝낼 때마다 크기에 맞는 새 화면 구성으로 전환돼 full-quality 렌더링을 지킨다.

  2. Launch Screen이 의무가 되었다. iOS 27 SDK부터 Launch Screen을 구성하지 않은 App은 App Store 제출을 통과할 수 없다(TN3208). SwiftUI 프로젝트에서 이것은 보통 최소한의시작 화면 구성을 채우는 것을 뜻한다. 맨땅으로 계속 달린다는 뜻이 아니다.

11. 안티 패턴 속람표

코드를 고치기 전에 먼저 전문을 이 패턴들로 검색하라. 찾아낸 한 곳 한 곳이 갚아야 할 빚이다.

안티 패턴 왜 틀린가 바꾸는 것
UIScreen.main.bounds 분할·미러링 아래서 여러분의 창이 아니다 로컬 view.bounds 또는 씬 지오메트리
UIDevice.current.userInterfaceIdiom 분기 기기 종류에는 레이아웃 뜻이 없다 horizontalSizeClass
interfaceOrientation / 방향 알림 기기 방향을 보고, 창 모양을 보고하지 않는다 size class + 로컬 bounds
size.width > 600 같은 수제 브레이크포인트 trait 뜻을 우회하고 분할 중간 상태를 놓친다 공학 분단 전략 + onGeometryChange
onAppear에서 한 번 읽기 크기 조절은 계속돼 캐시가 곧 쓸모없어진다 trait / 지오메트리 콜백
iPad에만 넓은 레이아웃을 씀 Slide Over의 iPad는 스마트폰처럼 군다 넓고 좁은 두 논리를 모두 공간으로 발화
지도·히어로에 .frame(height: 480)이라 굳혀 씀 낮은 창에서 본문이 첫 화면 밖으로 밀린다 높이를 컨테이너 비율 또는 적응형으로

12. 마이그레이션 로드맵

리스크와 수익으로 배열한 네 걸음:

1단계 · 뇌전 제거(먼저. 출시를 막는다): UIRequiresFullScreen 삭제, Launch Screen 채우기, UIScene 라이프사이클이 쓰이고 있음 확인. 이 세 항목은 설계 품질과 무관한 하드 문턱이다. 빠지면 심사나 빌드에서 바로 막힌다.

2단계 · 안티 패턴 정리: 11절의 표로 UIScreen.main, idiom / 방향 분기, 수제 너비 브레이크포인트를 전문 교체한다. 교체의 목표는 "돌아간다"가 아니라 레이아웃의 입력 원천을 공간 뜻으로 통일하는 것이다.

3단계 · 두 단계 분단 채우기: 내용형 화면에 "size class가 방향을 정하고 + 실측 너비가 정도를 정한다"는 전략 층을 세우고, 모든 있는 그대로의 임계값을 한곳에 모은다. 넓은 화면에서는 본행 너비 제한에 유의하라. 한 줄이 85 글자를 넘는 것은 가독성 버그다.

4단계 · 테스트 체계화: 13절 매트릭스로 크기마다 검증하고, 결과를 회귀 항목으로 굳힌다.

13. 테스트 매트릭스

resizability 테스트는 기기 명세로 하지 않는다. 공간 형태로 한다. 어떤 형태든 물음에 답해야 한다. 기능이 완전한지, 내용이 잘리지 않는지, chrome을 누를 수 있는지.

공간 형태 중점 검증
가장 좁은 쓸 수 있는 너비(minimumSize에 가까운) 요소가 겹치지 않는지, 주요 버튼을 누를 수 있는지
표준 스마트폰 너비 기본 레이아웃 기선
스마트폰 가로 vertical compact 아래 탭 바가 세로가 되고 첫 화면 내용이 완전한지
스마트폰 창을 regular까지 크게 칸 나누기가 이어받는지, 상태가 유지되는지
분할 화면 절반 / Slide Over 같은 iPad 위의 두 가지 공간 행동
또 넓고 납작한 / 또 높고 좁은 극단 비율 배경 확장이 드러나지 않는지, 내용이 찢어지지 않는지
Duo 외부 화면과 펼친 내부 화면 접힘 전환 시 레이아웃과 상태가 연속인지
iPhone 미러링 / 외부 화면 화면과 크기를 얻는 것이 올바른지

도구 쪽에서는 Xcode 27의 Device Hub와 Previews에 창 resize 모드가 있어, 가장자리를 드래그하면 연속으로 크기를 바꿀 수 있다. 미러링과 외부 화면은 실기로 검증한다.

14. 자주 묻는 질문

왜 제 iPhone App이 갑자기 창을 크게 할 수 있게 되었나요?

버그가 아니다. iOS 26 SDK 이후의 새 행동이다. iPad 위와 Mac의 iPhone 미러링 안에서 도는 iPhone App은 자유롭게 크기를 바꿀 수 있는 창을 가진다. 그에 맞는 레이아웃 신호는 horizontal size class가 compact에서 regular로 뒤집히는 것이다. 예전의 if idiom == .phone 코드는 이런 창 안에서 잘못된 레이아웃을 낸다. size class에게 묻는 것으로 바꾸라.

size class로 충분합니까. 왜 너비도 실측합니까?

충분하지 않다. 그리고 절벽은 실재한다. iPad 전체 화면과 분할 화면 절반은 둘 다 (regular, regular)지만 너비는 갑절 차이 난다. 한쪽에는 사이드바가 들어가고 다른 한쪽에는 들어가지 않는다. size class는 방향(단일 칸 / 분할)을 정하고, 실측 너비는 정도(몇 칸 / 얼마나)를 정한다. 둘은 릴레이이지 둘 중 하나가 아니다.

minimumSize를 선언하면 사용자가 더 작게 드래그할 수 없습니까?

minimumSize는 "여러분의 내용이 어지럽기 시작하는" 하한 설정이며, 사용자는 이 하한까지 오면 더 이상 드래그하지 못한다——이것은 겹침 방지 메커니즘이며, 크기 조절을 거부하는 기능 문턱이 아니다. 참으로 보장해야 할 것은 minimumSize부터 maximumSize까지 어떤 크기에서도 App이 쓸 수 있다는 것이다. 그것이 안 되면 적응을 다 쓰지 못했다는 뜻이지, 최소값을 키우면 된다는 이야기가 아니다.

UIRequiresFullScreen을 지우면 어떻게 됩니까?

지우면 App이 창 크기 조절에 참여한다. 이것은 iOS 26+의 기본 예상이다. 낡은 버전과의 호환이 걱정이면 UIRequiresFullScreenIgnoredStartingWithVersion으로 무시 시작 버전을 지정할 수 있다. 게임 렌더링 품질이 걱정이면 discrete resizing에 들어가, 크기에 맞는 화면 구성으로 전환된다. 오히려 화질을 지키기에 낫다.

SwiftUI 앱은 무엇을 해야 합니까?

재컴파일하면 크기 조절 지원을 얻고, 받아들일 새 API가 없다. 점검할 것은 이것이다. UIScreen 시대의 가정에 기대는 코드가 없는지, 크기 판단이 size class만 쓰고 실측 받침이 없는지, 고정 frame 부품이 극단 종횡비에서 찢어지지 않는지. 프레임워크가 관할하는 것은 거시다. 찢어지는 부품은 대개 여러분이 스스로 굳혀 쓴 그것이다.

참고 자료와 더 읽을거리

공식 출처: WWDC25 session 282「Make your UIKit app more flexible」, WWDC26 session 278「Modernize your UIKit app」, Human Interface Guidelines의 Layout과 Designing for iPhone Duo, UISceneSizeRestrictions와 UIWindowScene.Geometry 문서, 기술 노트 TN3192(지원 중단된 UIRequiresFullScreen 키에서 마이그레이션)와 TN3208(Launch Screen 요구사항).

사이트 안의 더 읽을거리: 이 사상이 구체적인 기기 위에 처음부터 끝까지 내려앉는 모습을 보려면 iPhone Duo의 화면: 레이아웃에서 코드까지를 읽으면 좋다. "데이터도 공간에 따라 줄어든다"는 또 다른 면을 이해하고 싶으면 개인 베이스라인 가이드를 보라. 제품과 적응에 대한 더 많은 물음은 자주 묻는 질문에 있다.

더 읽기

Stress HRV에서 이 아이디어를 경험하기

App Store에서 다운로드

이 노트는 개인 웰니스를 위한 제품 생각이며 의료 조언이 아닙니다.