스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
SwiftUI 핵심 기능
Apple의 선언형 UI 프레임워크인 SwiftUI를 살펴보고, 뷰, 상태 변수, 레이아웃 등 SwiftUI에서 앱을 빌드하기 위한 핵심 개념을 알아보세요. 광범위한 기능을 사용할 수 있는 경험과 고유한 맞춤형 요소를 제작하기 위한 다양한 API를 알아보세요. SwiftUI를 처음 접하는 초보자나 숙련된 개발자 등 누구나 멋진 앱을 빌드할 수 있도록 SwiftUI가 제공하는 기능을 활용하는 방법을 학습해 보세요.
챕터
- 0:00 - Introduction
- 1:34 - Fundamentals of views
- 13:06 - Bulit-in capability
- 17:36 - Across all platforms
- 20:30 - SDK interoperability
리소스
관련 비디오
WWDC24
WWDC20
-
다운로드
안녕하세요, 저는 Taylor입니다 ‘SwiftUI 기초’를 시작하겠습니다 SwiftUI는 Apple 플랫폼 전반에서 앱 빌드에 사용되는 Apple의 선언적 UI 프레임워크입니다 Apple에서는 최신 앱에 이 프레임워크를 광범위하게 사용했으며 기본 앱에도 점진적으로 채택했습니다 새로운 앱이나 기능을 빌드하는 경우 SwiftUI가 최적의 도구입니다 몇 가지 이유가 있는데요 먼저 SwiftUI에는 다양한 기능이 있습니다 앱은 이 기능을 통해 기기 고유의 이점을 활용하고 Apple 플랫폼에 자연스럽게 조화되며 풍부한 상호작용을 더할 수 있습니다 그리고 적은 코드만으로 이러한 기능을 추가할 수 있죠 프로토타입에서 프로덕션으로의 진행이 더 빨라지며 앱의 고유한 매력 요소에 집중할 수 있게 됩니다 SwiftUI는 점진적인 채택을 수용하므로 필요한 순간에 적절하게 활용할 수 있습니다 SwiftUI의 이점을 활용하기 위해 전체 앱이 SwiftUI 기반일 필요는 없습니다 이러한 특성 덕분에 누구나 SwiftUI를 사용하여 앱을 빌드하는 방법을 쉽게 배울 수 있습니다 또한 SwiftUI가 이러한 특성을 어떻게 지원하는지 이해하면 그 이점을 최대한 활용하는 방법을 더 쉽게 파악할 수 있습니다 먼저 뷰가 작동하는 방식에 대한 기본적인 설명부터 시작하겠습니다 그런 다음 SwiftUI에 내장된 몇 가지 기능과 해당 기능이 Apple 플랫폼 전체에서 어떻게 작동하는지 보여 드리겠습니다 마지막으로 다른 프레임워크와 통합되는 SwiftUI의 역량을 설명하겠습니다
시작하기 전에 SwiftUI와 관련 작업에 대해 한 가지 더 알아보겠습니다 우리는 반려동물을 사랑하며 어떤 동물이 최고인지 열띤 토론을 벌이곤 합니다 저는 어느 반려동물이 최고의 재주를 부릴 수 있는지를 가장 객관적인 방법으로 해결하기로 했습니다 경쟁이 치열하겠죠
이 비디오를 진행하는 동안 SwiftUI를 사용하여 반려동물과 반려동물의 재주, 비교 방법을 추적하는 앱을 빌드해 보려 합니다 SwiftUI에서 이 과정을 시작하려면 뷰를 사용해야 합니다 뷰는 사용자 인터페이스의 기본 구성 단위이며 SwiftUI에서 수행하는 모든 작업에 중요한 역할을 합니다 화면에 표시되는 모든 픽셀은 어떤 식으로든 뷰에 의해 정의됩니다 SwiftUI 뷰를 특별하게 만드는 세 가지 특징이 있습니다 선언적이고 구성적이며 상태 중심의 특성을 가집니다 뷰는 선언적으로 표현됩니다
개발자가 사용자 인터페이스에서 원하는 뷰를 설명하면 SwiftUI에서 결과가 생성됩니다 텍스트 SF Symbols를 사용한 이미지 버튼과 같은 컨트롤을 만들 수 있습니다
이 코드는 아이콘과 제목의 조합인 레이블로 구성된 수평 스택을 생성합니다 끝에 스페이서 및 텍스트가 있습니다 이 선언적 구문은 스크롤 가능한 목록과 같은 다른 컨테이너에 적용됩니다
이 목록에 반려동물 컬렉션이 주어지며 각 반려동물에 대해 수평 스택이 생성됩니다 모든 반려동물의 이름이 Whiskers는 아니죠 각 반려동물의 속성을 사용하도록 스택의 뷰를 업데이트하겠습니다
목록에 행을 추가하거나 제거하는 작업 등 이 UI를 생성하기 위해 필요한 작업을 저는 설명할 필요가 없었습니다
이것이 선언적 프로그래밍과 명령형 프로그래밍의 차이입니다 반려동물에게 재주 부리기를 가르쳐 본 사람이라면 명령형 명령에 익숙할 것입니다
명령형 프로그래밍과 마찬가지로 홈런을 치는 각 단계를 이 Rufus에게 지시할 수 있습니다 Rufus 이리 와 Rufus 배트 물어 Rufus 홈 플레이트로 이런 식으로 과정의 각 단계를 설명합니다
이에 비해 선언적 반려동물 재주의 경우 원하는 작업을 설명하고 미리 준비된 강아지가 그러한 작업을 수행하게끔 합니다 원하는 작업, 즉 Rufus가 홈런을 쳐서 득점하는 것을 보고 싶다고 말하면 됩니다 그리고 재주의 어떤 측면을 맞춤화할 수 있습니다 이를테면 Rufus가 입을 맞춤형 셔츠를 가져오라 할 수 있죠 선언적 프로그래밍과 명령형 프로그래밍은 상호 배타적이지 않습니다 선언적 코드를 사용하면 달성하기 위한 단계 대신 예상되는 결과에 집중할 수 있습니다 그리고 명령형 코드는 상태를 변경할 때나 기존의 선언적 구성요소가 없을 때 유용합니다 SwiftUI는 이 두 가지를 모두 수용합니다 버튼이 좋은 예시입니다 버튼은 선언적으로 사용자 인터페이스에 추가되며 버튼 선언의 일부는 탭할 때 수행할 작업을 나타냅니다 이 작업은 명령형 코드를 사용하여 변경을 수행합니다 이 경우에는 목록에 새 반려동물을 추가합니다
SwiftUI 뷰는 UI의 현재 상태가 어떤지 기술하는 설명이며 시간이 지남에 따라 명령형 명령을 받는 오래 지속되는 객체 인스턴스가 아닙니다 그래서 SwiftUI 뷰가 값 유형이며 클래스 대신 구조체로 정의되어 있습니다
SwiftUI는 이러한 설명을 취하고 이를 표현하기 위한 효율적인 데이터 구조를 생성합니다 SwiftUI는 백그라운드에서 자동으로 이 데이터 구조를 유지합니다 그리고 이는 다양한 출력에 사용됩니다 예를 들면 화면에 표시되는 내용 뷰의 제스처와 인터랙티브 측면 그리고 접근성 표현 등이 있습니다 뷰는 단지 선언적 설명이므로 뷰 하나를 여러 개로 나누어도 앱 성능은 저하되지 않습니다 최고의 성능을 확보하기 위해 원하는 코드 구성 방식을 변경할 필요가 없습니다
구성은 SwiftUI 전체에서 사용되며 모든 사용자 인터페이스의 필수적인 부분입니다 앞서 빌드한 HStack은 컨테이너 뷰로 레이아웃을 위한 것이며 하위 항목을 수평 스택에 배치합니다 SwiftUI에서 컨테이너 뷰를 재배열하고 실험하는 것은 정말 쉽습니다 코드 자체가 생성된 뷰의 계층 구조와 유사합니다
수평 스택은 이미지, 세로 스택 스페이서라는 세 가지 뷰를 가집니다 세로 스택 자체에는 레이블과 텍스트라는 두 가지 자체 뷰가 있습니다
이 구문은 ViewBuilder 클로저를 사용하여 컨테이너의 하위 항목을 선언합니다 이 예에서는 ViewBuilder 콘텐츠 매개변수가 있는 HStack 초기화 프로그램을 사용하고 있습니다 이것은 SwiftUI의 모든 컨테이너 뷰에서 사용되는 패턴입니다
구성은 뷰 한정자라고 불리는 또 다른 SwiftUI 패턴에서 중요한 역할을 합니다 뷰 한정자는 기본 뷰에 수정 사항을 적용하고 해당 뷰의 모든 측면을 변경할 수 있습니다 Whiskers 사진으로 시작하죠 먼저 원으로 자르고 그림자를 추가한 다음 그 위에 녹색 테두리를 오버레이합니다 이 고양이가 가장 좋아하는 색이죠
구문 측면에서 이는 컨테이너 뷰와 매우 다르게 보이지만 유사한 계층 구조가 됩니다 효과의 계층 구조와 순서는 한정자의 정확한 순서를 기반으로 정의됩니다 한정자를 함께 연결하면 결과가 생성되는 방식과 결과를 맞춤화하는 방법이 명확해집니다 모두 읽기 쉬운 구문으로 되어 있죠
뷰 계층은 사용자 설정 뷰와 뷰 한정자로 캡슐화될 수 있습니다 사용자 설정 뷰는 View 프로토콜을 준수하며 자신이 나타내는 뷰를 반환하는 본문 속성을 가집니다 본문에서 반환된 뷰는 지금까지 보여 드린 것과 동일한 뷰 작성 구문을 사용하여 동일한 구성 기능과 빠른 반복을 지원합니다 코드를 원하는 대로 정리하는 데 도움이 되는 뷰 속성을 추가로 만들 수 있습니다 프로필 이미지 구성을 개인 뷰 속성으로 리팩토링했습니다
이러한 종류의 증분 단계를 통해 원하는 방식으로 행 뷰를 계속 반복하고 빌드할 수 있습니다
사용자 설정 뷰에는 본문이 생성되는 방식을 변경하는 입력이 있을 수 있습니다 이 행이 나타낼 반려동물에 대한 속성을 추가했으며 본문에서 반환된 뷰에 해당 속성을 사용했습니다
이 변경을 통해 동일한 뷰를 재사용하여 Whiskers는 물론 Roofus와 Bubbles에 대한 정보를 표시할 수 있습니다
사용자 설정 뷰는 다른 뷰처럼 사용할 수 있습니다 여기에서는 각 반려동물에 대해 생성할 뷰로 목록에서 이를 사용했습니다
목록은 뷰 구성의 효과를 잘 보여 줍니다 이 목록 초기화 프로그램에는 컬렉션 매개변수가 있습니다 ForEach 뷰를 만드는 것이 편리합니다 ForEach는 컬렉션의 각 요소에 대한 뷰를 생성하고 해당 뷰를 해당 컨테이너에 제공합니다 이 뷰 기반 목록 초기화 프로그램을 사용하면 섹션으로 구성된 여러 데이터 컬렉션과 같은 고급 구성을 생성할 수 있습니다 하나는 제 반려동물용이며 하나는 다른 모든 반려동물에 사용합니다
목록은 뷰 한정자를 사용하여 맞춤화할 수도 있습니다 예를 들어 각 행에 쓸어넘기기 동작을 추가합니다
추가 컨테이너와 한정자의 구성을 통해 전체 앱을 점진적으로 빌드할 수 있습니다
SwiftUI 뷰의 세 번째 특징은 상태 중심이라는 것입니다 시간이 지남에 따라 뷰의 상태가 변경되면 SwiftUI는 자동으로 상용구 및 업데이트 버그를 제거하여 UI를 최신 상태로 유지합니다 SwiftUI는 백그라운드에서 자동으로 사용자 인터페이스 표현을 유지합니다 데이터가 변경되면 새로운 뷰 값이 생성되어 SwiftUI에 제공됩니다 SwiftUI는 해당 값을 사용하여 출력을 업데이트하는 방법을 결정합니다 이제 저의 앱에서 반려동물과 반려동물의 재주 목록을 볼 수 있습니다 하지만 반려동물 대회에서 가장 중요한 부분은 최고의 재주를 부리는 반려동물에게 보상을 주는 것입니다 이 친구는 Sheldon입니다 보상으로 딸기를 받는 것을 좋아하죠 각 행에 쓸어넘기기 동작을 추가한 후 잘 준비되었습니다
보상 버튼을 탭하면 해당 작업이 호출됩니다 이렇게 하면 연결된 반려동물 객체가 수정되고 hasAward가 이제 true로 변경됩니다
SwiftUI는 행 뷰와 같이 이 반려동물에 의존하는 모든 뷰를 추적합니다
여기에는 반려동물에 대한 참조가 있으며 본문에서는 반려동물이 보상을 받았는지 또는 종속성이 설정되지 않았는지 판독합니다
SwiftUI는 업데이트된 반려동물로 이 뷰의 본문을 다시 호출합니다
이제 Sheldon의 보상을 반영하는 이미지가 포함된 결과를 반환합니다
SwiftUI는 이 결과를 기반으로 출력을 업데이트하여 화면에 새 이미지를 표시합니다
뷰가 본문에서 사용하는 모든 데이터는 해당 뷰의 종속성입니다 제 경우에는 앱에서 Observable 반려동물 클래스를 생성했습니다 SwiftUI는 뷰 본문에 사용되는 특정 속성에 대한 종속성을 생성합니다 SwiftUI에는 상태 관리를 위한 여러 도구가 있습니다 다른 두 가지 중요한 도구는 State와 Binding입니다 State는 뷰에 대한 새로운 내부 데이터 소스를 생성합니다 뷰 속성을 @State로 표시하면 SwiftUI는 저장 공간을 관리하고 뷰가 읽고 쓸 수 있도록 다시 제공합니다
Binding은 다른 뷰의 상태에 대한 양방향 참조를 생성합니다
저는 이를 활용하는 또 다른 뷰를 작성했습니다 이 뷰를 통해 반려동물의 재주를 평가할 수 있습니다 State를 사용하여 현재 등급을 추적하고 시간이 지남에 따라 변경합니다 값은 중앙에 눈에 띄게 표시되며 값을 늘리거나 줄이는 두 개의 버튼이 있습니다
SwiftUI는 백그라운드에서 자동으로 이 상태의 값을 유지합니다
이전 예와 유사하게 버튼을 탭하면 버튼의 작업이 호출됩니다 이번에는 뷰의 내부 State가 증가합니다
SwiftUI가 이 변화를 인지하고 RatingView에서 본문을 호출하여 새로운 텍스트 값을 반환합니다 그러면 결과가 화면에 업데이트됩니다
상태 변경이 이루어지는 본문의 뷰에 초점을 맞출 예정입니다
지금까지는 애니메이션 없이 즉시 변경이 이루어졌습니다 SwiftUI의 애니메이션은 지금까지 설명한 내용과 같은 데이터 기반 업데이트의 위에 빌드됩니다
이 상태 변경을 withAnimation으로 래핑하면 결과 뷰 업데이트가 기본 애니메이션과 함께 적용됩니다
SwiftUI는 텍스트에 기본 크로스페이드 전환 효과를 적용했습니다 하지만 전환 효과를 맞춤화할 수도 있습니다
이 경우에는 숫자 텍스트 콘텐츠 전환 효과를 사용하면 완벽하게 맞습니다
상태와 애니메이션을 사용하여 원하는 상호작용이 포함된 캡슐화된 뷰 구성요소를 빌드했습니다 최종적으로 앱의 나머지 부분에서 이 뷰를 구성할 것입니다
여기에 Gauge와 RatingView를 본문에 결합한 또 다른 보기인 RatingContainerView가 있습니다
현재 이러한 보기에는 각각 고유한 상태가 있으며 이는 등급이 무엇인지에 대한 별도의 정보 소스 역할을 합니다 그러나 이는 등급 뷰의 자체 상태가 증가할 때 컨테이너 뷰의 상태와 Gauge가 변경되지 않음을 의미합니다
Binding을 입력으로 사용하여 컨테이너 뷰에서 양방향 참조를 제공할 수 있도록 RatingView를 업데이트했습니다
이제 컨테이너 뷰 상태가 유일한 정보 소스가 되며 Gauge에 값을 제공하고 RatingView에 Binding을 제공합니다 이제 동기화되어 업데이트되고 애니메이션 상태 변경이 Gauge에도 적용됩니다
SwiftUI는 다양한 수준의 내장 기능을 제공하여 앱 빌드를 위한 훨씬 더 높은 시작점을 제공합니다
반려동물의 재주를 추적하는 앱을 저는 이제 막 시작했지만 그 결과는 만족스럽습니다 SwiftUI는 여러 차원에 따라 자동으로 적응성을 제공합니다
제가 만든 앱은 다크 모드에서 괜찮아 보입니다 유동적 글자 크기 조절과 같은 여러 손쉬운 사용 기능을 지원합니다 또한 현지화도 가능합니다 예를 들어 히브리어나 아랍어에서 어떻게 보이는지 확인하기 위해 오른쪽에서 왼쪽으로 쓰는 언어의 시뮬레이션을 사용해 미리 보겠습니다
이 기능은 Xcode 미리보기의 장점 중 하나입니다 다양한 컨텍스트가 포함된 뷰가 어떻게 보이는지 빠르게 확인할 수 있죠 그리고 앱을 반복해서 실행할 필요 없이 코드를 작성할 때 이 작업을 수행합니다
미리보기는 기기에서 직접 대화식으로 진행할 수 있습니다 작업 중인 기능을 빌드하면서 어떤 느낌이 드는지 정확히 이해할 수 있습니다
SwiftUI 선언적 뷰의 한 가지 이점은 적응성입니다 SwiftUI에서 제공하는 뷰는 정확한 시각적 구성이 아닌 기능의 목적을 설명하는 경우가 많습니다
앞서 쓸어넘기기 액션이 버튼과 같은 뷰로 구성되는 방식을 보여 드렸습니다 버튼은 적응형 뷰의 좋은 예입니다 버턴의 두 가지 기본적인 속성은 동작 그리고 해당 동작을 설명하는 레이블입니다
버튼은 다양한 상황에서 사용할 수 있지만 항상 레이블이 지정된 동작의 목적을 수행합니다
버튼은 Borderless, Bordered Prominent와 같은 다양한 스타일에 적응합니다 그리고 Swipe actions, Menus, Forms 등 다양한 상황에 자동으로 적응합니다 이 패턴은 토글을 포함하여 SwiftUI의 모든 컨트롤에 적용됩니다 토글에는 스위치, 체크상자 토글 버튼과 같은 고유한 스타일이 있습니다 그리고 다양한 맥락에서 켜지고 꺼지는 것을 나타내는 관용구 스타일로 나타납니다
SwiftUI의 많은 뷰는 이와 동일한 적응형 품질을 가지며 구성을 활용하여 동작에 영향을 주고 맞춤화를 지원합니다 이는 일부 뷰 한정자에도 적용됩니다 가장 좋아하는 예시인 searchable을 반려동물 목록에 적용하겠습니다
searchable 한정자를 추가하면 해당 한정자가 적용된 뷰는 검색이 가능하다고 설명하는 것입니다 SwiftUI는 모든 세부 사항을 처리하여 관용적인 방식으로 이를 수행합니다 그리고 다른 한정자를 점진적으로 채택함으로써 경험을 맞춤화할 수 있습니다 제안, 범위, 토큰 추가 등의 한정자가 예입니다
SwiftUI의 선언적 뷰와 적응형 뷰는 단 몇 줄의 코드에 많은 기능을 담고 있습니다
Button, Toggle, Picker와 같은 컨트롤이 있습니다 NavigationSplitView 같은 컨테이너 뷰 또는 맞춤화 지원 다중 열 테이블도 있습니다 sheet 및 Inspector와 같은 프레젠테이션과 문서에서 더 많은 예를 볼 수 있습니다
고유한 맞춤형 경험을 만들 준비가 되었다면 SwiftUI에는 하위 수준 제어를 제공하는 또 다른 API 레이어도 준비되어 있습니다 자신만의 컨트롤 스타일을 빌드할 수 있습니다 높은 성능의 명령형 그리기에는 Canvas를 사용하세요 완전히 맞춤화된 레이아웃을 생성하세요 사용자 설정 Metal 셰이더를 SwiftUI 뷰에 직접 적용할 수도 있습니다
이 앱에서는 점수판이 이러한 하위 수준의 도구를 사용하여 고전적인 플립보드를 연상시키는 특별한 경험을 선사하는 완벽한 위치입니다 애니메이션, 그래픽 트릭과 약간의 Metal 셰이더를 사용했습니다
마지막 재주에서 제대로 서지 못한 Sheldon에게 7점을 주어야 할 것 같습니다 다음번에는 행운을 빌어요
SwiftUI의 기능에 뷰만 있는 것이 아닙니다 전체 앱 정의는 뷰가 따르는 동일한 원칙을 기반으로 빌드됩니다 앱은 장면별로 정의된 선언적 구조입니다 WindowGroup은 장면의 한 종류이며 화면에 표시할 콘텐츠 뷰로 생성됩니다 장면을 함께 구성할 수도 있습니다
macOS와 같은 멀티 윈도우 플랫폼에서는 추가 장면이 앱 기능과 상호작용하는 다양한 방법을 제공합니다
이 패턴은 맞춤형 위젯 빌드로도 확장됩니다 위젯은 홈 화면과 데스크탑에 표시되며 여러 뷰로 구성됩니다 점수판 뷰 중 일부를 재사용하여 Sheldon의 최근 등급을 표시했습니다 SwiftUI의 기능은 사용되는 모든 플랫폼으로 확장되므로 한 플랫폼에서 투자하고 다른 플랫폼에서 네이티브 앱을 빌드할 수 있습니다 SwiftUI는 모든 Apple 플랫폼에서 앱을 빌드할 때 사용할 수 있습니다
SwiftUI는 여러분의 노력을 몇 배로 줄여줍니다 하나의 플랫폼에 대해 SwiftUI를 사용하여 빌드한 사용자 인터페이스가 있으면 해당 UI를 모든 플랫폼에 적용할 수 있는 훌륭한 시작이 됩니다
제가 빌드한 iOS용 앱이 좋은 예입니다
적응형 뷰와 장면은 모든 Apple 플랫폼에서 관용적인 디자인을 제공합니다 macOS에서는 키보드 탐색이나 여러 윈도우 생성과 같은 기능을 자동으로 지원합니다
검색 제안을 동일하게 사용하면 macOS에서는 표준 드롭다운 메뉴가 생성되고 iOS에서는 오버레이 목록이 생성됩니다
하위 수준 API에서 맞춤형으로 제작된 뷰는 플랫폼 전체에서 동일한 결과를 생성하며 필요할 때 동일한 뷰를 재사용할 수 있는 또 다른 좋은 장소입니다
점수판 애니메이션을 완벽하게 만들려는 제 노력은 모든 플랫폼에서 멋지게 보입니다
SwiftUI는 이러한 방식으로 코드 공유를 가능하게 하지만 “한 번 작성하면 어디서나 실행”되는 것은 아닙니다 한 번만 익히면 모든 상황에서 또는 모든 Apple 플랫폼에서 사용할 수 있는 도구 모음입니다
SwiftUI에는 플랫폼 전반에 이러한 상하위 수준 구성요소의 공통 세트가 있지만 각 플랫폼에 특화된 API도 있습니다
모든 플랫폼은 사용 방식과 설계 방식이 고유합니다
휴먼 인터페이스 가이드라인에서는 구성요소, 패턴 해당 플랫폼의 고려 사항을 설명합니다
NavigationSplitView는 세부 사항을 푸시하는 소스 목록의 watchOS 디자인에 자동으로 적응합니다
점수판과 같은 맞춤형 뷰를 재사용했지만 watchOS에만 적용하고 싶은 변경 사항이 하나 있습니다 터치 또는 키보드를 사용하는 대신 Digital Crown을 사용하여 등급을 빠르게 선택하기를 기대합니다
그래서 동일한 점수판 뷰를 기반으로 watchOS용 추가 한정자인 digitalCrownRotation을 추가했습니다
이제 Digital Crown을 돌리면 원하는 점수로 전환됩니다
Mac에서 반려동물의 재주를 검토할 때 이전 데이터를 자세히 검토하여 반려동물 간에 비교하려면 다양한 장면 유형에서 macOS의 유연한 윈도우 모델을 활용할 수 있습니다 또는 macOS의 익숙한 컨트롤 라이브러리와 정보 밀도 및 정확한 입력을 최대한 활용하는 뷰를 사용하세요
또한 제 앱을 visionOS로 가져와서 다른 플랫폼의 뷰를 활용하고 입체 콘텐츠를 추가할 수 있습니다 SwiftUI는 어디에서나 멋진 앱을 만들도록 도와줍니다 이 또한 본질적으로 점진적입니다 SwiftUI에서는 개발자가 여러 플랫폼을 지원할 필요가 없습니다 하지만 준비가 되었을 때 유리한 출발을 제공합니다 마지막 영역은 SwiftUI에 내장된 기능이 아니라 다른 프레임워크의 기능과 상호 운용되는 SwiftUI의 기능입니다 SwiftUI는 각 플랫폼의 SDK와 함께 제공되는 프레임워크입니다 SDK에는 다른 많은 프레임워크도 포함되어 있으며 각각 고유한 흥미로운 기능을 제공합니다 앱에는 이러한 프레임워크 중 일부가 사용되지만 필요한 기술을 제공하는 프레임워크 중에서 선택할 수 있습니다
SwiftUI는 이 모든 기능에 대한 상호 운용성을 제공하며 대부분의 경우 앱에 다른 뷰 또는 속성을 추가하는 것만큼 쉽습니다
UIKit 및 AppKit은 필수적인 객체 지향 사용자 인터페이스 프레임워크입니다 SwiftUI와 유사한 빌딩 블록을 제공하지만 뷰 생성 및 업데이트에 다른 패턴을 사용합니다 그리고 SwiftUI의 기반이 되는 오래되고 풍부한 기능을 갖추고 있습니다
SwiftUI의 핵심적인 기능은 이들과의 원활한 상호 운용성입니다
SwiftUI에서 사용하려는 UIKit 또는 AppKit의 뷰 또는 뷰 컨트롤러가 있는 경우 ViewRepresentable을 생성할 수 있습니다
이는 명령형 코드를 사용하여 연관된 UIKit 또는 AppKit 뷰를 생성하고 업데이트하기 위한 특수 SwiftUI 뷰 프로토콜입니다
결과는 SwiftUI의 선언적 뷰 빌더 내에서 사용할 수 있고 HStack에서 사용하는 것과 같이 다른 뷰처럼 사용할 수 있는 뷰입니다 그 반대도 마찬가지입니다 SwiftUI 뷰를 UIKit 또는 AppKit 뷰 계층 구조에 포함하려면 Hosting View Controller와 같은 클래스를 사용할 수 있습니다 이는 루트 SwiftUI 뷰로 생성되며 UIKit 또는 AppKit 뷰 컨트롤러 계층 구조에 추가될 수 있습니다 Apple의 자체 앱은 SwiftUI를 기존 앱에 도입하거나 새로운 SwiftUI 앱을 빌드하고 Kit 뷰를 통합할 때 이러한 도구를 사용하여 SwiftUI를 점진적으로 채택합니다 도구 상자에 준비된 이 모든 도구로 뛰어난 앱을 제작할 수 있습니다 SwiftUI의 이점을 얻기 위해 앱이 전체적으로 SwiftUI일 필요는 없습니다
SDK의 모든 프레임워크는 고유한 기능을 제공합니다 SwiftData를 사용하면 앱에 영구적인 모델을 빠르게 추가할 수 있으며 SwiftUI 뷰에서 해당 모델을 연결하고 쿼리할 수 있는 API가 함께 제공됩니다
Swift Charts는 SwiftUI를 기반으로 구축된 맞춤화가 가능한 차트 프레임워크로 멋진 정보 시각화를 쉽게 만들 수 있습니다
이러한 프레임워크는 모두 멋진 앱을 빌드하는 데 사용할 수 있습니다 SwiftUI는 선언적이고 구성적이며 상태 중심 뷰를 기반으로 구축되었습니다 뿐만 아니라 플랫폼 관용적 기능과 광범위한 SDK와의 통합을 제공합니다 이러한 모든 기능은 더 적은 코드로 앱을 고유하게 만드는 요소에 집중하도록 도와줍니다 관용적이고 매력적인 애플리케이션을 생성하는 광범위한 구성요소를 제공합니다 그리고 모든 단계에서 점진적인 채택을 지원합니다
이제 SwiftUI를 시작할 때입니다 Xcode를 실행하고 첫 앱 제작을 시작하거나 SwiftUI를 기존 앱에 통합하기 시작하세요 SwiftUI에 대한 다른 유용한 비디오를 확인해 보세요 다음에는 SwiftUI 소개 비디오를 시청해 보시기 바랍니다
다양한 앱 빌드 과정을 안내하는 SwiftUI 튜토리얼을 따라해 보세요 그리고 문서에는 더 많은 유용한 정보가 있습니다
반려동물 대회의 경우 단지 앱만 이용해서는 어떤 동물이 최고인지 알 수 없을 겁니다 지금 제가 말씀드리고 싶은 결론은 모든 반려동물이 사랑스럽다는 것입니다
-
-
2:30 - Declarative views
Text("Whiskers") Image(systemName: "cat.fill") Button("Give Treat") { // Give Whiskers a treat }
-
2:43 - Declarative views: layout
HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") }
-
2:56 - Declarative views: list
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { List(pets) { pet in HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
3:07 - Declarative views: list
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { List(pets) { pet in HStack { Label(pet.name, systemImage: pet.kind.systemImage) Spacer() Text(pet.trick) } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
4:24 - Declarative and imperative programming
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { Button("Add Pet") { pets.append(Pet("Toby", kind: .dog, trick: "WWDC Presenter")) } List(pets) { pet in HStack { Label(pet.name, systemImage: pet.kind.systemImage) Spacer() Text(pet.trick) } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
5:33 - Layout container
HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") }
-
5:41 - Container views
struct ContentView: View { var body: some View { HStack { Image(whiskers.profileImage) VStack(alignment: .leading) { Label("Whiskers", systemImage: "cat.fill") Text("Tightrope walking") } Spacer() } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
6:23 - View modifiers
struct ContentView: View { var body: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:05 - Custom views: Intro
struct PetRowView: View { var body: some View { // ... } }
-
7:14 - Custom views
struct PetRowView: View { var body: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:20 - Custom views: iteration
struct PetRowView: View { var body: some View { HStack { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle() .stroke(.green, lineWidth: 2) } Text("Whiskers") Spacer() } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:24 - Custom views: view properties
struct PetRowView: View { var body: some View { HStack { profileImage Text("Whiskers") Spacer() } } private var profileImage: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:34 - Custom views: complete row view
struct PetRowView: View { var body: some View { HStack { profileImage VStack(alignment: .leading) { Text("Whiskers") Text("Tightrope walking") .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:41 - Custom views: input properties
struct PetRowView: View { var pet: Pet var body: some View { HStack { profileImage VStack(alignment: .leading) { Text(pet.name) Text(pet.trick) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(pet.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(pet.favoriteColor, lineWidth: 2) } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String var favoriteColor: Color init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage self.favoriteColor = favoriteColor } }
-
7:53 - Custom views: reuse
PetRowView(pet: model.pet(named: "Whiskers")) PetRowView(pet: model.pet(named: "Roofus")) PetRowView(pet: model.pet(named: "Bubbles"))
-
7:59 - List composition
struct ContentView: View { var model: PetStore var body: some View { List(model.allPets) { pet in PetRowView(pet: pet) } } } @Observable class PetStore { var allPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:14 - List composition: ForEach
struct ContentView: View { var model: PetStore var body: some View { List { ForEach(model.allPets) { pet in PetRowView(pet: pet) } } } } @Observable class PetStore { var allPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:27 - List composition: sections
struct ContentView: View { var model: PetStore var body: some View { List { Section("My Pets") { ForEach(model.myPets) { pet in PetRowView(pet: pet) } } Section("Other Pets") { ForEach(model.otherPets) { pet in PetRowView(pet: pet) } } } } } @Observable class PetStore { var myPets: [Pet] = [ Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), ] var otherPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:36 - List composition: section actions
PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Award", systemImage: "trophy") { // Give pet award } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) }
-
9:31 - View updates
struct ContentView: View { var model: PetStore var body: some View { List { Section("My Pets") { ForEach(model.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(model.otherPets) { pet in row(pet: pet) } } } } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Award", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } } struct PetRowView: View { var pet: Pet var body: some View { HStack { profileImage VStack(alignment: .leading) { HStack(alignment: .firstTextBaseline) { Text(pet.name) if pet.hasAward { Image(systemName: "trophy.fill") .foregroundStyle(.orange) } } Text(pet.trick) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(pet.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(pet.favoriteColor, lineWidth: 2) } } } @Observable class PetStore { var myPets: [Pet] = [ Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), ] var otherPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] } @Observable class Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } var name: String var kind: Kind var trick: String var profileImage: String var favoriteColor: Color var hasAward: Bool = false init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage self.favoriteColor = favoriteColor } func giveAward() { hasAward = true } } extension Pet: Transferable { static var transferRepresentation: some TransferRepresentation { ProxyRepresentation { $0.name } } }
-
10:57 - State changes
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { rating -= 1 } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { rating += 1 } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
11:51 - State changes: animation
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:05 - State changes: text content transition
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:22 - State changes: multiple state
struct RatingContainerView: View { @State private var rating: Int = 5 var body: some View { Gauge(value: Double(rating), in: 0...10) { Text("Rating") } RatingView() } } struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:45 - State changes: state and binding
struct RatingContainerView: View { @State private var rating: Int = 5 var body: some View { Gauge(value: Double(rating), in: 0...10) { Text("Rating") } RatingView(rating: $rating) } } struct RatingView: View { @Binding var rating: Int var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
14:16 - Adaptive buttons
Button("Reward", systemImage: "trophy") { // Give pet award } // .buttonStyle(.borderless) // .buttonStyle(.bordered) // .buttonStyle(.borderedProminent)
-
14:53 - Adaptive toggles
Toggle("Nocturnal Mode", systemImage: "moon", isOn: $pet.isNocturnal) // .toggleStyle(.switch) // .toggleStyle(.checkbox) // .toggleStyle(.button)
-
15:19 - Searchable
struct PetListView: View { @Bindable var viewModel: PetStoreViewModel var body: some View { List { Section("My Pets") { ForEach(viewModel.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(viewModel.otherPets) { pet in row(pet: pet) } } } .searchable(text: $viewModel.searchText) } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Reward", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } } @Observable class PetStoreViewModel { var petStore: PetStore var searchText: String = "" init(petStore: PetStore) { self.petStore = petStore } var myPets: [Pet] { // For illustration purposes only. The filtered pets should be cached. petStore.myPets.filter { searchText.isEmpty || $0.name.contains(searchText) } } var otherPets: [Pet] { // For illustration purposes only. The filtered pets should be cached. petStore.otherPets.filter { searchText.isEmpty || $0.name.contains(searchText) } } }
-
15:20 - Searchable: customization
struct PetListView: View { @Bindable var viewModel: PetStoreViewModel var body: some View { List { Section("My Pets") { ForEach(viewModel.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(viewModel.otherPets) { pet in row(pet: pet) } } } .searchable(text: $viewModel.searchText, editableTokens: $viewModel.searchTokens) { $token in Label(token.kind.name, systemImage: token.kind.systemImage) } .searchScopes($viewModel.searchScope) { Text("All Pets").tag(PetStoreViewModel.SearchScope.allPets) Text("My Pets").tag(PetStoreViewModel.SearchScope.myPets) Text("Other Pets").tag(PetStoreViewModel.SearchScope.otherPets) } .searchSuggestions { PetSearchSuggestions(viewModel: viewModel) } } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Reward", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } }
-
16:58 - App definition
@main struct SwiftUIEssentialsApp: App { var body: some Scene { WindowGroup { ContentView() } } }
-
17:15 - App definition: multiple scenes
@main struct SwiftUIEssentialsApp: App { var body: some Scene { WindowGroup { ContentView() } WindowGroup("Training History", id: "history", for: TrainingHistory.ID.self) { $id in TrainingHistoryView(historyID: id) } WindowGroup("Pet Detail", id: "detail", for: Pet.ID.self) { $id in PetDetailView(petID: id) } } }
-
17:23 - Widgets
struct ScoreboardWidget: Widget { var body: some WidgetConfiguration { // ... } } struct ScoreboardWidgetView: View { var petTrick: PetTrick var body: some View { ScoreCard(rating: petTrick.rating) .overlay(alignment: .bottom) { Text(petTrick.pet.name) .padding() } .widgetURL(petTrick.pet.url) } }
-
19:37 - Digital Crown rotation
ScoreCardStack(rating: $rating) .focusable() #if os(watchOS) .digitalCrownRotation($rating, from: 0, through: 10) #endif
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.