View in English

  • Apple Developer
    • 시작하기

    시작하기 탐색

    • 개요
    • 알아보기
    • Apple Developer Program

    알림 받기

    • 최신 뉴스
    • Hello Developer
    • 플랫폼

    플랫폼 탐색

    • Apple 플랫폼
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    피처링

    • 디자인
    • 배포
    • 게임
    • 액세서리
    • 웹
    • 홈
    • CarPlay
    • 기술

    기술 탐색

    • 개요
    • Xcode
    • Swift
    • SwiftUI

    피처링

    • 손쉬운 사용
    • 앱 인텐트
    • Apple Intelligence
    • 게임
    • 머신 러닝 및 AI
    • 보안
    • Xcode Cloud
    • 커뮤니티

    커뮤니티 탐색

    • 개요
    • Apple과의 만남 이벤트
    • 커뮤니티 주도 이벤트
    • 개발자 포럼
    • 오픈 소스

    피처링

    • WWDC
    • Swift Student Challenge
    • 개발자 이야기
    • App Store 어워드
    • Apple 디자인 어워드
    • 문서

    문서 탐색

    • 문서 라이브러리
    • 기술 개요
    • 샘플 코드
    • 휴먼 인터페이스 가이드라인
    • 비디오

    릴리즈 노트

    • 피처링 업데이트
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • 다운로드

    다운로드 탐색

    • 모든 다운로드
    • 운영 체제
    • 애플리케이션
    • 디자인 리소스

    피처링

    • Xcode
    • TestFlight
    • 서체
    • SF Symbols
    • Icon Composer
    • 지원

    지원 탐색

    • 개요
    • 도움말
    • 개발자 포럼
    • 피드백 지원
    • 문의하기

    피처링

    • 계정 도움말
    • 앱 심사 지침
    • App Store Connect 도움말
    • 새로 추가될 요구 사항
    • 계약 및 지침
    • 시스템 상태
  • 빠른 링크

    • 이벤트
    • 뉴스
    • 포럼
    • 샘플 코드
    • 비디오
 

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 전체 비디오
  • 소개

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • AppKit 앱 현대화하기

    AppKit 앱을 최신 macOS 규칙에 맞게 최신 상태로 업데이트하세요. 제어 이벤트와 제스처 인식기를 사용한 입력 처리에 대해 자세히 살펴보고, 기존의 추적 방식을 향상해 보세요. 앱에서 키보드 탐색을 개선하고, 재시동 후 원활한 상태 복원을 구현하며, 인터페이스가 macOS 미감과 매끄럽게 어우러지도록 해 주는 새로운 모서리 동심성 API를 활용하세요.

    챕터

    • 0:00 - Introduction
    • 1:06 - Modern input
    • 1:27 - Modern event handling with gesture recognizers
    • 2:25 - Selection, context menus, and drag and drop
    • 3:52 - Text selection in custom views
    • 4:26 - Control events and gesture recognizers
    • 5:51 - Keyboard navigation and status items
    • 8:57 - Continuity across launches
    • 9:08 - Graceful app termination
    • 9:55 - State restoration
    • 14:09 - Design updates
    • 14:24 - Liquid Glass updates in macOS 27
    • 15:41 - Concentricity
    • 16:59 - Next steps

    리소스

    • Use SwiftUI with AppKit
    • Restoring your app’s state with AppKit
    • Gestures
    • TN3212: Adopting gesture recognizers for Sidecar touch support
    • NSControl.Events
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC26

    • AppKit 및 UIKit과 함께 SwiftUI 사용하기
  • 비디오 검색…

    안녕하세요! 저는 Mac UI 프레임워크 엔지니어 Ujjaini이고 "Modernize Your AppKit App"을 소개합니다 현대적인 앱은 AppKit이 Mac과 상호작용하는 방식을 활용합니다 앱의 형태와 기능이 시스템 전체와 조화를 이룰 수 있도록요 그 조화는 세 가지 측면에서 나타납니다 사용자가 앱을 조작하는 방식과 시스템이 앱을 관리하는 방식 그리고 화면에서의 모양과 느낌입니다 오늘은 세 가지 모두에 대한 팁을 공유하겠습니다 먼저 앱이 적응해야 할 현대적인 정밀 입력 방식부터 시작하겠습니다 다음으로, 실행 간 연속성 유지의 중요성에 대해 살펴보겠습니다 즉, 시스템이 필요할 때 앱이 정상적으로 종료되고 중단된 지점으로 돌아오는 방법입니다 마지막으로, macOS 27의 모양과 느낌 업데이트를 살펴볼 텐데 앱을 다시 빌드하지 않아도 확인할 수 있는 것들이 많습니다 Mac이 시작된 곳, 즉 입력 장치부터 시작하겠습니다 정밀 입력 장치는 처음부터 Mac의 핵심이었습니다 최초의 Mac에는 키보드와 마우스가 함께 제공되었습니다!

    시간이 지나면서 다양한 입력을 처리하는 API가 발전해 훨씬 더 편리해졌습니다 mouseDown과 추적 루프는 핵심 패턴이었습니다 AppKit 앱에서 인터랙티브 동작을 구현하는 데 사용되었죠 하지만 AppKit이 Mac의 유일한 프레임워크는 아닙니다 SwiftUI, Mac Catalyst를 통한 UIKit 그리고 AppKit이 함께 Mac 경험을 만들어냅니다 이들은 Gesture Recognizer에 의존하며 세 프레임워크 모두에서 공통 이벤트 처리 언어를 제공합니다 Gesture recognizer를 사용하면 AppKit에서 고급 동작을 구현할 수 있습니다 직접 구현하지 않아도 됩니다 이벤트를 처리하는 현대적인 방식은 gesture recognizer입니다 이와 잘 연동되는 세 가지 솔루션을 살펴보겠습니다 view 기반 API, control event, 그리고 커스텀 gesture recognizer입니다 이것들은 추적 루프와 mouseDown 오버라이드와 동일한 커스터마이징 기능을 제공하며 프레임워크 간 호환성을 지원합니다 각각을 언제 사용해야 할지 알려드리겠습니다 일반적인 mouseDown 오버라이드로 선택을 추적하고 컨텍스트 메뉴 표시, 드래그 앤 드롭 텍스트 선택을 구현합니다 현재 mouseDown을 오버라이드하고 있다면 AppKit에는 이러한 동작을 더 안정적으로 처리하는 전용 API가 있으며 최신 Mac 플랫폼을 최대한 활용합니다

    mouseDown은 선택을 추적하기 위해 자주 오버라이드됩니다 대신에 NSCollectionViewItem, NSTableRowView 같은 타입의 selected 프로퍼티를 관찰하세요 또는 선택 변경 시 알림을 받는 델리게이트 콜백을 사용하세요 NSTableViewDelegate나 NSOutlineViewDelegate 등이 있습니다 뷰에서 컨텍스트 메뉴를 표시하는 방법은 몇 가지가 있습니다

    NSView의 클래스 프로퍼티 .defaultMenu를 사용하면 뷰의 모든 인스턴스가 동일한 메뉴를 표시합니다 NSResponder의 인스턴스 프로퍼티 .menu를 사용하면 각 리스폰더마다 다른 메뉴를 제공할 수 있습니다 또는 NSView의 인스턴스 메서드 .menuForEvent를 사용하면 이벤트를 기반으로 메뉴를 동적으로 생성합니다 앱에서 컬렉션 컨테이너 뷰를 사용한다면 tableView pasteboardWriterForRow 같은 최신 드래그 델리게이트 메서드를 사용하세요 pasteboardItem을 만들고 데이터를 설정한 다음 반환하세요 NSCollectionView, NSOutlineView, NSBrowser에도 유사한 메서드가 있습니다

    NSTextView 외부에서 텍스트 선택 동작이 필요하다면 NSTextSelectionManager를 사용하세요 macOS 27의 새로운 API로 gesture recognizer를 활용하며 클래식 macOS 텍스트 선택 동작을 모든 뷰에 제공합니다 뷰에 연결하고 텍스트 선택 데이터 소스를 설정하세요 그러면 양방향 선택을 지원하고 텍스트 드래그 앤 드롭, 토글 등 다양한 기능을 사용할 수 있습니다

    다음 솔루션은 약간 익숙하게 느껴질 수 있습니다 UIKit의 control event를 안다면요 Control event가 이제 AppKit에도 있습니다! Control event는 표준 Mac 컨트롤에 추가할 수 있습니다 버튼이나 슬라이더 같은 것들에요 코드가 사용자 주도 추적 상태 변경에 반응하도록 해줍니다 복잡한 mouseDown 추적 로직을 직접 구현하지 않아도 됩니다 control event가 트리거되면 AppKit이 등록된 target과 action을 호출합니다 대부분의 control event는 OS 10.11부터 사용 가능합니다!

    NSControlEvents 예시를 살펴보겠습니다 버튼을 인스턴스화하고 control event에 target과 action을 등록하세요 NSButton을 서브클래싱하지 않아도 작동한다는 점을 참고하세요! 뷰의 인터랙션을 더 세밀하게 제어하려면 표준 gesture recognizer를 추가하세요 더 높은 유연성을 위해 커스텀 gesture recognizer 서브클래스를 만드세요 "Gestures" 문서에서 더 자세히 알아보세요 gesture recognizer는 뷰와 그 서브 뷰에서 작동하므로 겹치는 형제 뷰가 마우스 이벤트를 차단할 수 있습니다 컨트롤이 클릭에 반응하지 않는 것 같다면 버튼과 겹치는 형제 뷰가 없는지 확인하세요 이 문제를 해결하려면 뷰의 크기를 조정하여 겹치지 않도록 하세요 오버레이이기 때문에 뷰 크기를 조정할 수 없다면 hitTest를 오버라이드하고 nil을 반환하세요 히트 테스트가 아래 콘텐츠까지 전달됩니다

    이제 키보드 탐색에 집중하겠습니다 앱이 키보드 입력에 원활하게 반응해야 합니다 속도와 접근성을 지원하기 위해서입니다 컨트롤에 대한 키보드 탐색은 시스템 설정에서 활성화할 수 있습니다 활성화되면 탭 또는 shift-tab으로 컨트롤 간에 포커스가 이동합니다 키 뷰 루프는 컨트롤이 순환하는 순서입니다 Tab 키를 눌렀을 때 적용됩니다 루프를 자동으로 재계산하려면 뷰가 계층 구조에 추가되거나 제거될 때마다 window에서 .autorecalculatesKeyViewLoop를 활성화하세요 이 값을 설정하지 않으면 직접 키 뷰 루프를 만들고 유지 관리해야 합니다

    키보드 탐색은 앱의 윈도우를 넘어서 메뉴 바와 상태 항목까지 확장됩니다 상태 항목 탐색은 메인 메뉴 항목과 약간 다릅니다 클릭 시 메뉴를 표시하는 상태 항목은 이미 메뉴 바의 메뉴처럼 동작합니다 하지만 상태 항목은 트리거가 될 수도 있습니다 액션을 위한 트리거나 임시 UI를 표시하기도 합니다 액션을 트리거하려면 NSStatusItem의 button 프로퍼티를 수정하여 target과 action을 포함하고 선택적으로 이미지도 추가하세요 일반 버튼처럼 동작하며 액션이 자동으로 실행됩니다 키보드 탐색 중 Return 키를 눌렀을 때요 상태 메뉴 항목에 커스텀 뷰를 사용하려면 상태 항목의 view 프로퍼티로 뷰를 설정하세요 그런 다음 상태 항목에 target과 action을 추가하여 해당 액션을 실행할 수 있도록 하세요 상태 항목은 커스텀 윈도우를 표시하는 트리거가 될 수도 있습니다! 상태 항목이 윈도우를 표시할 때 AppKit은 해당 UI가 활성화된 시점을 알아야 합니다 키보드 포커스가 올바르게 동작하도록요 확장 인터페이스 세션 API를 사용하여 커스텀 UI의 생명 주기를 추적하세요 먼저 델리게이트를 설정하세요 항목이 생성될 때 시작 및 종료 호출을 받을 윈도우를 표시하거나 닫기 위한 것입니다 델리게이트에서 statusItem didBegin ExpandedInterfaceSession을 구현하고 statusItemDidEnd_ ExpandedInterfaceSession도 구현하세요 이 메서드들은 AppKit이 호출합니다 확장 인터페이스 세션의 생명 주기를 관리하기 위해서입니다 didBegin 호출에서 윈도우를 표시하세요 didEnd 호출에서 윈도우를 닫으세요

    세션을 닫아야 할 때 예를 들어 액션이 선택되었을 때 .expandedInterfaceSession?에서 .cancel을 호출하세요 세션이 자동으로 취소될 수도 있습니다 포커스가 자연스럽게 다른 곳으로 이동하면요 SwiftUI 메뉴 바 엑스트라가 이 작업을 대신 처리해 줍니다! WWDC26 영상 "Use SwiftUI with AppKit and UIKit"을 확인하세요 AppKit 앱에서 SwiftUI 메뉴 바 엑스트라를 사용하는 방법을 알아보세요 앱이 마우스만큼 키보드에서도 잘 작동하도록 하는 것은 Mac을 선택하는 파워 유저들에게 특히 중요합니다 원활한 전환을 제공하는 것은 앱 내외부에서 사용자를 지원하는 또 다른 방법입니다 원활한 전환 이야기가 나온 김에 훌륭한 Mac 앱은 원활하게 종료되고 빠르게 복원됩니다 불필요한 저항 없이 종료되고 마치 종료된 적이 없었던 것처럼 돌아옵니다!

    사용자는 언제든 앱을 종료할 수 있어야 합니다 원할 때도 있고 시스템이 재부팅해야 할 때도 있습니다 야간 소프트웨어 업데이트 중에 발생할 수 있죠 따라서 앱은 정말 필요할 때만 종료를 차단해야 합니다 앱에서 시트를 표시 중이면 윈도우를 닫지 못할 수 있습니다 윈도우를 닫을 수 없으면 앱도 종료할 수 없습니다 NSWindow 프로퍼티 preventsApplicationTerminationWhenModal의 기본값은 true입니다 그럴 만한 이유가 있습니다! 앱이 데이터를 잃지 않도록 하는 것이 중요합니다 예를 들어 문서를 저장해야 할 때요 이 프로퍼티를 false로 설정하세요 반드시 개입이 필요하지 않은 모달이나 시트 모두에서 앱이 더 정상적으로 종료될 수 있도록요 정상 종료 처리가 끝났으면 다음 단계는 복원입니다 NSWindowRestoration을 사용하여 앱 복원 방식을 커스터마이징하세요 상태 복원에는 3단계가 필요합니다: 상태 복원 선택 UI 상태 인코딩, 그리고 윈도우와 UI를 복원하기 위한 상태 디코딩 NSWindowRestoration을 사용하는 코드를 살펴보겠습니다 먼저 윈도우 컨트롤러에서 윈도우 식별자를 설정하세요 메인 윈도우나 환경설정 윈도우 같은 공통 윈도우에는 autosave 이름을 설정하세요 동일한 프레임으로 활성 공간에 윈도우를 복원하는 데 도움이 됩니다 문서 윈도우에는 autosave 이름을 설정할 필요가 없습니다 그런 다음 window.isRestorable이 true로 설정되어 있는지 확인하세요 AppKit이 윈도우에서 encodeRestorableState와 restoreState를 호출할 수 있도록요 이를 통해 AppKit이 윈도우 상태를 자동으로 복원하고 어떤 윈도우가 최소화되었는지 어떤 것이 맨 앞에 있었는지 어떤 것이 전체 화면이었는지 알 수 있습니다 또한 window.restorationClass를 설정하세요 앱이 재실행될 때 호출되며 윈도우 자체를 복원합니다 encodeRestorableState를 사용하여 필요한 모든 것을 보존하세요 윈도우 상태를 재생성하기 위해서입니다 super 구현도 호출하여 상태가 올바르게 복원되도록 하세요 이 예시에서는 선택된 항목의 식별자가 productIdentifier 키로 인코딩됩니다

    문서나 데이터베이스에 있는 데이터는 인코딩하지 마세요 상태 복원의 목표는 UI 상태를 재구성하는 것입니다 앱 전체를 다시 직렬화하는 것이 아니라요 모든 NSResponder에는 오버라이드할 수 있는 encodeRestorableState 메서드가 있습니다 따라서 뷰의 상태도 관리하세요

    .encodeRestorableState는 객체의 상태가 무효화된 경우에만 호출됩니다 뷰 계층 구조에 변경이 있을 때마다 저장된 상태를 변경해야 한다면 .invalidateRestorableState()를 호출하세요 이 예시에서는 사이드바에서 다른 제품이 선택될 때 이 메서드가 호출됩니다 나중에 무효화된 모든 것에 대해 encodeRestorableState가 호출됩니다

    앱이 종료되기 전에 UI 상태를 저장하는 방법입니다 앱이 재실행되면 해당 정보를 모두 디코딩하여 UI를 복원해야 합니다 먼저 윈도우를 복원하고 그런 다음 해당 윈도우의 상태를 복원하세요! 윈도우 복원 클래스에서 restoreWindow withIdentifier 메서드를 구현하여 앱의 윈도우를 재생성하세요 이 메서드는 복원되는 모든 윈도우에 대해 호출됩니다 파라미터에는 윈도우 식별자가 포함되며 해당 윈도우와 함께 호출해야 하는 completionHandler도 포함됩니다 식별자를 사용하여 윈도우 컨트롤러와 윈도우를 재생성하세요 .mainWindow는 앱 델리게이트의 .mainWindowController에서 이미 사용 가능합니다 기존 .window와 함께 completionHandler를 호출하세요 다른 윈도우의 경우 윈도우 컨트롤러를 인스턴스화하고 .window를 completionHandler에 전달하세요 윈도우 생성이 실패해도 error와 함께 completionHandler를 호출하세요 AppKit은 모든 복원 가능한 윈도우를 기다리므로 반드시 completionHandler를 호출하세요 이 메서드 내에서 호출할 수 없다면 핸들러를 저장하고 나중에 호출하세요 반드시 호출해야 합니다! 윈도우가 복원되면 마지막 단계는 각 윈도우의 UI를 복원하는 것입니다 윈도우 컨트롤러의 restoreState 메서드에서 AppKit이 이전에 인코딩한 키가 포함된 동일한 coder 객체를 전달합니다 앱 상태를 재구성하는 데 필요한 데이터를 가져오는 곳입니다 식별자를 디코딩하고 해당 뷰 컨트롤러에 전달하세요 완료되면 윈도우가 이전과 동일한 상태가 됩니다 종료와 재실행이 끊김 없이 느껴지도록 개선하면 사용자가 중단된 지점에서 바로 이어갈 수 있습니다 앱을 종료하거나 Mac을 재시작한 경우 모두요 실제 상태 복원을 알아보려면 코드 샘플 "Restoring your app's state with AppKit"을 확인하세요

    입력과 복원을 파악했으니 앱과 Mac이 만나는 또 하나의 영역이 있습니다: UI입니다 macOS 26에 도입된 Liquid Glass 소재가 계속 발전하고 있습니다 앱이 많은 업데이트를 자동으로 활용할 수 있습니다 macOS 26에서 Liquid Glass를 도입했다면 macOS 27에서 실행할 때 몇 가지 변경 사항이 적용됩니다 자동 NSScrollEdgeEffectStyle이 하드 엣지 효과로 적용됩니다 타이틀 바의 윈도우 제목처럼 자유롭게 부동하는 텍스트가 있을 때요

    사이드바가 윈도우 가장자리까지 확장되고 사이드바에서 선택 시 강조를 위해 세미볼드 텍스트 스타일을 사용합니다

    그리고 콘텐츠는 여전히 뒤에서 흐릅니다

    사이드바 위의 테두리 있는 툴바 항목도 Liquid Glass를 적용합니다 macOS 27의 새로운 기능으로 glass에 추가할 수 있는 효과가 있습니다 클릭 시 glass가 미묘하게 튕기며 컨트롤이 인터랙션에 반응한다는 느낌을 줍니다 Maps는 몇 가지 커스텀 컨트롤에 이것을 사용합니다

    이것이 앱의 모든 glass 사용에 적용되는 것은 아닙니다 컨트롤과 버튼에 이 효과를 사용하거나 인터랙티브 컨트롤의 glass 컨테이너에 사용하세요 조금만 사용해도 효과가 큽니다!

    둥근 직사각형은 수십 년간 Apple 생태계의 특징이었습니다 하드웨어 베젤부터 macOS 전반의 컨트롤과 컨테이너까지요 AppKit에 동심성을 위한 새로운 API가 생겼습니다 코너용 콘텐츠가 컨테이너의 모양에 맞게 조정됩니다 나머지 윈도우와 어울리지 않는 느낌 없이요 예를 들어 Maps의 로컬 날씨 뷰는 윈도우와 동심을 이룹니다

    뷰가 컨테이너의 코너 근처에 있을 때 자체 둥근 모서리가 해당 컨테이너의 곡선을 따라야 합니다 뷰가 컨테이너 코너에 가까울수록 반지름이 더 많이 일치해야 합니다

    버튼이나 뷰를 동심으로 만들려면 cornerConfiguration API를 사용하세요 먼저 커스텀 뷰 서브클래스를 만드세요 커스텀 뷰에서 cornerConfiguration을 오버라이드하여 NSViewCornerConfiguration?을 반환하도록 하세요

    반지름의 경우 NSViewCornerRadius의 .containerConcentric을 사용하세요 컨테이너 뷰를 기반으로 반지름을 계산합니다 최솟값도 설정하세요 모든 모서리가 항상 둥글게 유지되도록요

    구성에 다양한 종류의 팩토리 메서드를 선택할 수 있습니다 네 모서리 모두에서 동일한 반지름의 roundedRect를 유지하려면 .uniformCorners를 사용하세요

    최신 macOS에서 앱을 조화롭게 만들 수 있는 몇 가지 지침입니다 시작하는 곳에 대한 간단한 요약을 전달하겠습니다 앱에서 mouseDown을 오버라이드하는 부분을 파악하고 대신 view API, control event 또는 gesture recognizer를 사용하세요 추적 루프보다 사용자 의도를 우선시하세요 앱이 마우스만큼 키보드에서도 잘 작동하도록 하세요 종료와 재실행이 원활하게 느껴지도록 하여 앱이 사용자가 중단한 정확한 지점에서 이어지도록 하세요 뷰와 버튼에 동심성을 적용하기 위해 뷰 계층 구조를 평가하세요

    시청해 주셔서 감사합니다 앱이 컴퓨터 사용법을 배우는 학생들을 위한 것이든 세계에서 가장 중요한 도구와 예술을 만드는 파워 유저들을 위한 것이든 여러분의 앱은 이 경험에서 중심적인 역할을 해왔습니다 계속 만들어 가세요!

    • 3:35 - Modern dragging delegate

      // Modern dragging delegate methods
      func tableView(_ tableView: NSTableView,
              pasteboardWriterForRow row: Int) -> (any NSPasteboardWriting)? {
          let pasteboardItem = NSPasteboardItem()
          pasteboardItem.setString(..., forType: .string)
          return pasteboardItem
      }
    • 4:55 - Control events

      // Use control events
      let button = NSButton()
      button.addTarget(
          self,
          action: #selector(trackingEndedOutsideHandler),
          for: .trackingEndedOutside
      )
    • 5:44 - hitTest override

      override func hitTest(_ point: NSPoint) -> NSView? {
          return nil
      }
    • 6:24 - autorecalculatesKeyViewLoop

      window.autorecalculatesKeyViewLoop = true
    • 7:37 - Expanded interface delegate — setup

      // Set the expanded interface delegate
      @main class LightAppDelegate: NSObject, NSApplicationDelegate {
          lazy var lightStatusItem: NSStatusItem = { ... }()
      
          func applicationDidFinishLaunching(_ notification: Notification) {
              // ...
              lightStatusItem.expandedInterfaceDelegate = self
          }
      }
    • 7:52 - Expanded interface delegate — methods

      // Implement the delegate methods
      extension LightAppDelegate: NSStatusItemExpandedInterfaceDelegate {
          // ...
          func statusItem(_ statusItem: NSStatusItem, didBegin session:
                          NSStatusItemExpandedInterfaceSession) {
              // Show window
          }
          func statusItemDidEndExpandedInterfaceSession(
              _ statusItem: NSStatusItem, animated: Bool) {
              // Hide window
          }
          func selectedAction() {
              // Take the action
              // Cancel session to request window dismissal
              lightStatusItem.expandedInterfaceSession?.cancel()
          }
      }
    • 8:16 - Expanded interface delegate — cancel

      // Cancel the session when dismissing
      extension LightAppDelegate: NSStatusItemExpandedInterfaceDelegate {
          // ...
          func statusItem(_ statusItem: NSStatusItem, didBegin session:
                          NSStatusItemExpandedInterfaceSession) {
              // Show window
          }
          func statusItemDidEndExpandedInterfaceSession(
              _ statusItem: NSStatusItem, animated: Bool) {
              // Hide window
          }
          func selectedAction() {
              // Take the action
              // Cancel session to request window dismissal
              lightStatusItem.expandedInterfaceSession?.cancel()
          }
      }
    • 9:45 - preventsApplicationTerminationWhenModal

      window.preventsApplicationTerminationWhenModal = false
    • 10:18 - Set window identifiers for state restoration

      // Set window identifiers for state restoration
      @MainActor class MainWindowController: NSWindowController, NSWindowDelegate {
          // ...
          convenience init() {
              let window = NSWindow( ... )
              // ...
              window.identifier = NSUserInterfaceItemIdentifier(WindowIdentifiers.mainWindow)
              window.setFrameAutosaveName(WindowIdentifiers.mainWindow)
              window.isRestorable = true
              window.restorationClass = WindowRestorationHandler.self
              // ...
          }
      }
    • 11:04 - encodeRestorableState

      // Preserve state to recreate the UI
      @MainActor class MainWindowController: NSWindowController, NSWindowDelegate {
          // ...
          override func encodeRestorableState(with coder: NSCoder) {
              super.encodeRestorableState(with: coder)
              // ...
              coder.encode(selectedProduct?.identifier.uuid,
                          forKey: RestorationKeys.productIdentifier)
              // ...
          }
          // ...
      }
    • 11:50 - invalidateRestorableState

      // Invalidate restorable state when the view hierarchy changes
      @MainActor class MainWindowController: NSWindowController, NSWindowDelegate {
          // ...
          convenience init() {
              // ...
              splitViewController.onProductSelected = { [weak self] product in
                  self?.invalidateRestorableState()
              }
          }
      }
    • 12:26 - restoreWindow(withIdentifier:)

      // Restore windows
      class WindowRestorationHandler: NSObject, NSWindowRestoration {
          static func restoreWindow(
              withIdentifier identifier: NSUserInterfaceItemIdentifier,
              state: NSCoder,
              completionHandler: @escaping (NSWindow?, Error?) -> Void
          ) {
              //...
              if identifier == .mainWindow, let window = appDelegate.mainWindowController?.window {
                  completionHandler(window, nil)
              } else if identifier == .imageWindow {
                  let controller = ImageWindowController()
                  appDelegate.imageWindowControllers.append(controller)
                  completionHandler(controller.window, nil)
              } else {
                  completionHandler(nil, error)
              }
          }
      }
    • 13:29 - restoreState

      // Restore window UI
      @MainActor class MainWindowController: NSWindowController, NSWindowDelegate {
          //...
          override func restoreState(with coder: NSCoder) {
              super.restoreState(with: coder)
              if let productId = coder.decodeObject(
                  of: [NSString.self],
                  forKey: RestorationKeys.productIdentifier) as? String {
                  splitViewController?.selectedProductId = productId
              }
              //...
          }
      }
    • 16:11 - cornerConfiguration

      // Subclass NSView to override cornerConfiguration
      class LocalWeatherView: NSView {
          // ...
          override var cornerConfiguration: NSViewCornerConfiguration? {
              let radius: NSViewCornerRadius = .containerConcentric(minimumCornerRadius)
              return .uniformCorners(radius: radius)
          }
          // ...
      }
    • 0:00 - Introduction
    • A modern app takes advantage of how AppKit interfaces with Mac so that its form and function feel in harmony with the rest of the system. That harmony shows up in precision input, continuity across launches, and look and feel.

    • 1:06 - Modern input
    • Precision input devices have been at the heart of the Mac since the very beginning. Learn modern APIs for handling mouse events, keyboard navigation, and status items.

    • 1:27 - Modern event handling with gesture recognizers
    • Gesture recognizers are the modern way to handle mouse events in AppKit. mouseDown: overrides and tracking loops must be replaced with modern APIs.

    • 2:25 - Selection, context menus, and drag and drop
    • AppKit has dedicated view-based APIs for the most common mouseDown: use cases: observe selected on collection and table types for selection, use menuForEvent: or .menu for context menus, and use modern pasteboard delegate methods for drag and drop.

    • 3:52 - Text selection in custom views
    • NSTextSelectionManager brings classic macOS text selection behaviors to any view outside of NSTextView. Attach it to a view to get bidirectional selection, drag and drop, and toggling.

    • 4:26 - Control events and gesture recognizers
    • Control events let you react to user-driven tracking state changes on standard controls. For custom interactions, use NSGestureRecognizer.

    • 5:51 - Keyboard navigation and status items
    • Enable autorecalculatesKeyViewLoop on your window to keep Tab navigation correct as views change. For status items with custom UI, use the expanded interface session API so AppKit can manage keyboard focus correctly.

    • 8:57 - Continuity across launches
    • A great Mac app seamlessly quits and quickly restores. Learn how to handle graceful app termination and state restoration using NSWindowRestoration.

    • 9:08 - Graceful app termination
    • Apps should quit without blocking, especially during system reboots. Set preventsApplicationTerminationWhenModal to false on any sheet or modal that does not strictly require user intervention.

    • 9:55 - State restoration
    • Use NSWindowRestoration to save and recover your app's UI across launches, so it picks up exactly where people left off.

    • 14:09 - Design updates
    • There's one more area where your app and the Mac meet: the UI. Learn about Liquid Glass updates in macOS 27 and the new concentricity API.

    • 14:24 - Liquid Glass updates in macOS 27
    • Liquid Glass continues to evolve in macOS 27, and many updates apply automatically. Sidebars, scroll edge effects, and toolbar items all receive refinements, and a new interactive glass effect gives controls a physical sense of response when clicked.

    • 15:41 - Concentricity
    • The NSViewCornerConfiguration API lets views near a container's corners automatically match the container's corner radius using .containerConcentric, so views adapt to the shape of their container instead of feeling at odds with the rest of the window.

    • 16:59 - Next steps
    • Prioritize gesture recognizers and view-based APIs over mouseDown:, ensure your app is fully keyboard-navigable, make quit and relaunch feel seamless, and adopt concentricity in your view hierarchies.

Developer Footer

  • 비디오
  • WWDC26
  • AppKit 앱 현대화하기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • Apple Intelligence
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • Mini Apps Partner Program
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    최신 뉴스 읽기.
    Apple Developer 앱 받기.
    Copyright © 2026 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침