스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
컴플리케이션 및 위젯: 리로리드
watchOS 및 iOS 잠금 화면으로의 모험과 함께 위젯 코드 실습을 해보겠습니다. watchOS에서의 복잡한 컴플리케이션에 강력한 기능을 더하고 iPhone의 잠금 화면 위젯을 만들 수 있도록 해 주는 WidgetKit의 향상된 최신 기능에 대해 알아보세요. 최신 SwiftUI 보기를 통합하여 한 눈에 볼 수 있는 우수한 데이터를 제공하는 방법을 안내하고, 각 플랫폼이 콘텐츠를 렌더링하는 방식을 알아보고, 위젯이나 컴플리케이션 내 콘텐츠의 디자인과 느낌을 맞춤화하는 방법을 배워보겠습니다.
리소스
- Creating accessory widgets and watch complications
- Emoji Rangers: Supporting Live Activities, interactivity, and animations
- WidgetKit
관련 비디오
Tech Talks
WWDC23
WWDC22
WWDC21
WWDC20
-
다운로드
♪ 부드러운 힙합 반주 ♪ ♪ 안녕하세요, 저는 Devon입니다 watchOS팀의 엔지니어고 iOS에 관해 이야기해보려고 합니다 저는 iOS 엔지니어인 Graham이고요 watchOS에 대해서 이야기 나누려고 합니다 watchOS 잠금 화면과 컴플리케이션용으로 액세서리 위젯을 쓸 수 있는 WidgetKit의 API 추가사항에 대해 이야기할 겁니다 여러분의 여정을 도와줄 SwiftUI 추가사항과 함께 어떻게 둘 다 개발할지 보여드릴게요 위젯, 타임라인, 리로딩에 친숙하지 않다면 이전 WidgetKit 세션들을 찾아보시기를 권합니다 먼저 컴플리케이션의 역사와 진화된 형태에 대해 얘기할 겁니다 그러고 나서 새 환경에서 여러분의 위젯과 컴플리케이션에 색을 입힐 새로운 API에 관해 얘기해 보죠 그다음엔 Graham이 직접 위젯을 만드는 방법과 기존 위젯을 watchOS에 확장하여 옮기는 걸 시연할 겁니다 다음으로 Graham이 더 작은 view의 대부분을 만드는 방법을 알려주고 마지막으로 여러분의 위젯이 보일 여러 프라이버시 환경에 대해 얘기할 거예요 컴플리케이션은 watchOS 플랫폼의 주요 요소로 시계 페이스에 빠르고 쉽게 알아볼 수 있는 정보를 줍니다 즉시 접속할 수 있게 가치가 높은 정보를 전달하고 탭 하여 앱의 관련 위치로 이동시킵니다 watchOS 2에서는 ClockKit가 고유의 컴플리케이션을 만들게 해줍니다 컴플리케이션은 그때부터 먼 길을 왔습니다
풍부한 컴플리케이션은 그래픽 콘텐츠와 새로운 가족 세트와 함께 watchOS 5에서 소개됐습니다 SwiftUI 컴플리케이션과 많은 컴플리케이션은 watchOS 7에서 소개되어 컴플리케이션을 다음 단계로 발전시킬 수 있게 해주었고 이전보다 훨씬 더 많은 옵션을 제공했습니다 오늘 컴플리케이션은 WidgetKit과 재창조되고 다시 만들어져 iOS에 위젯의 형태로 파악하기 쉬운 컴플리케이션 경험을 가져오고 SwiftUI를 받아들이고 있습니다 iOS 16과 watchOS 9의 WidgetKit으로 두 플랫폼 모두에서 쉽게 파악할 수 있는 훌륭한 컴플리케이션과 위젯을 구축해 코드를 한 번 쓰면 기존 홈 화면 위젯과 인프라를 구축할 수 있게 됩니다 이를 위해서 'accessory'란 단어로 미리 지정된 기존 WidgetFamily 유형에 새로운 위젯군을 추가했습니다 새 accessoryRectangular군은 기존의 ClockKit graphicRectangular군과 유사하게 텍스트의 여러 라인이나 작은 그래프와 차트를 보이는 데 쓸 수 있습니다 accessoryCircular군은 간단한 정보, 게이지 progress view에 좋습니다 이 그룹군은 또한 graphicCircular ClockKit군을 대체합니다 모든 새로운 accessoryInline 텍스트만 되는 슬롯이라 watchOS의 많은 페이스와 iOS에 시간을 줍니다 인라인 슬롯은 다양한 사이즈로 나오는데 어떻게 최선으로 이용할지 이후에 얘기할 겁니다
watchOS를 새로운 accessoryCorner군으로 특정하려 위젯 콘텐츠의 작은 원을 게이지 및 텍스트와 섞고 있습니다 iOS와 watchOS 사이의 공통 그룹군에 초점을 맞춘 얘기입니다 새 watchOS군의 자세한 사항이나 컴플리케이션 고유의 특성을 더 알고 싶다면 'WidgetKit로 멋진 컴플리케이션 만들기’ 세션을 보세요 색상과 렌더링 모드에 대해 이야기해봅시다 저 accessory 위젯이 몇 가지의 다양한 외관을 보여준다는 걸 눈치채셨을지도 모르겠네요 시스템은 accessory군 위젯의 모습을 제어하고 우리는 렌더링 스타일에 적응하는 걸 도와줄 도구를 주었습니다 여러분의 위젯이 보여주었을 다른 렌더링 모드 셋이 있습니다 여러분의 위젯은 풀 컬러나 엑센트, 혹은 바이브런트가 됩니다 WidgetRenderingMode 타입을 도입해 이 세 가지 표상을 나타냅니다 WidgetRenderingMode 주요 경로를 사용해 환경에서 이 값에 접근할 수 있습니다 그 이후에 어디서 보이든 콘텐츠가 적합해 보이도록 조건적으로 바꿀 수 있습니다 watchOS의 풀 컬러 모드에서 여러분의 콘텐츠는 지정한 대로 정확히 게시됩니다 기존의 많은 컴플리케이션은 날씨 게이지의 변화도나 Activity ring의 색깔처럼 풀 컬러로 다채로운 모습을 띱니다 엑센트 컬러 렌더링 모드에서는 여러분의 view가 독립된 컬러의 두 그룹으로 나뉩니다 두 컬러링 그룹은 차분한 색으로 원래의 불투명도만 보존합니다 시스템이 어떻게 .widgetAccentable() view 수식어로 view를 그룹화하는지 혹은 위젯 렌더링 모드 환경 값에 근거해 단일화됐을 때 완벽하게 보이도록 콘텐츠를 전환하는지 알 수 있습니다 시스템이 콘텐츠를 여러 방법으로 일부는 뒤집어서 색을 넣을 수 있다는 걸 기억하세요 일부는 검정 배경인 반면에 다른 건 watchOS 9에서 풀 컬러 배경입니다 iOS 바이브런트 렌더링 모드에서 콘텐츠는 포화도가 낮아진 다음 잠금 화면 배경에 맞게 적절한 색으로 바뀝니다 시스템은 여러분의 회색톤 콘텐츠를 주목할 만하게 그려냅니다 이것은 그 뒤의 콘텐츠에 적응되어 그 환경에 적합하게 나타납니다 추가로, 잠금 화면은 바이브런트 렌더링 모드를 색상으로 물들이도록 설정될 수 있습니다 밝은 소스 컬러는 결국 주로 불투명해지고 더 밝아집니다 한편, 어두운 소스 컬러는 그 뒤의 배경에서 약간의 광택만으로 덜 눈에 띄게 흐릿하게 나타납니다 가독성을 보장하기 위해 이 모드에서 투명 컬러 사용은 피하고 대신 더 어두운색이나 검정을 써서 가독성은 유지하면서 콘텐츠는 눈에 덜 띄게 하세요 그 미묘한 차이의 구현을 돕기 위해 이 원형 달력처럼 일관된 배경을 위젯에 주려고 AccessoryWidgetBackground view를 도입했어요 대부분의 accessory 위젯은 배경이 없는 반면 일부 스타일은 개선될 수 있습니다 배경 view는 다양한 위젯 렌더링 모드에서 여러 모습을 취하고 페이스 스타일이나 잠금 화면에서 정확히 보이려고 시스템에 의해 조정됐습니다 이건 풀 컬러와 엑센트 바이브런트 환경의 검정에서 부드럽게 투명한 view입니다 밝기가 어둡고 완전히 흐릿해 보입니다 Graham은 watchOS의 잠금 화면과 컴플리케이션을 위해 새 위젯을 만들게 되어 굉장히 흥분했어요 Graham에게 넘기겠습니다 또 만나 뵙네요! 저는 기존 앱인 Emoji Rangers에 새로운 위젯 그룹군을 추가하려고 합니다 여러분 중에는 WWDC 2020의 'Widgets Code-along'이 익숙하신 분도 계실 겁니다 시작하기 전에 기존 위젯 프리 프로젝트에 관한 공지사항이 있습니다 이미 iOS에 있었고 watchOS에 도입된 위젯 확장 타깃을 프로젝트에 추가하여 시작할 수 있습니다 하지만 여러분 상당수가 이미 위젯이 있는 앱이 있는 걸 알기에 오늘은 새 위젯과 컴플리케이션을 추가하는 것에 관한 이야기를 나눠봅시다
Emoji Rangers 프로젝트를 계속 다뤄보죠 이 앱은 가장 좋아하는 Emoji Rangers를 추적하고 홈 화면 위젯 사용으로 Ranger의 건강과 재충전 시간의 최신 정보를 알려줍니다 우리는 이미 watchOS에 Emoji Rangers를 도입했고 인기 많은 이 앱을 우리 손목까지 데려왔습니다 오늘은 우리의 새로운 위젯 가족에 Emoji Rangers와 지원을 추가하고 Watch에 위젯을 확장할 겁니다 그럼 Watch에 위젯 확장을 시작해 봅시다 기존 iOS 타깃과 코드를 공유하는 새로운 watchOS 타깃을 추가할 겁니다 iOS 위젯 확장 타깃을 복사하고
더 나은 이름을 주고
Watch 앱과 타깃 watchOS와 프리픽스 되기 위해 번들 식별자를 바꾸고
Watch 앱에 새로운 확장을 내장했습니다 이제 watchOS 구축하는 코드를 얻어야 합니다 그렇게 해봅시다
EmojiRangerWidget 코드를 살짝 훑어보고 있는데 타임라인 제공자를 볼 수 있는데 시스템이 콘텐츠를 다시 로드할 때 쓰는 겁니다
view는 여러 그룹군을 위한 콘텐츠 생성 위젯 환경 설정
Xcode preview 제공자를 위해 SwiftUI를 사용합니다 Emoji Rangers 앱은 이미 iOS 홈 화면 위젯을 지원합니다 시스템에 작은, 중간 그룹군을 제공하며 위젯 환경 설정에서 새 그룹군을 추가할 겁니다
왜냐하면 시스템 그룹군은 Watch에서 사용 불가능해서 supportedFamilies를 지정하기 위해 플랫폼 매크로를 사용해야 할 거예요
preview provider에서 새 그룹군을 위한 preview를 추가할 겁니다
다음으로 watchOS에 성공적으로 빌드하기 전에 새로운 IntentRecommendation API를 실행해야 합니다 Intent가 iOS의 위젯 편집 UI에서 완전히 설정 변경 가능할 때 watchOS에선 사전 환경 설정 목록을 제공해야 합니다 IntentTimelineProvider에서 새로운 추천 방법을 오버라이딩해 목록을 제공할 수 있습니다
이제 성공적으로 빌드하고 있습니다 Preview를 재개하고 원형 위젯이 어떻게 보이는지 살펴봅시다
작은 위젯을 겨냥한 콘텐츠도 새 폼 팩터 안에서 잘 맞지 않습니다 새로운 위젯 가족은 홈 화면에서 찾은 iOS 위젯보다 작으니 여러분의 컴플리케이션 콘텐츠를 고려해 봐야 합니다 이제 우리의 컴플리케이션을 눈에 띄게 할 새로운 view에 관해 얘기해 보죠 view로 가봅시다 systemSmall과 다른 위젯을 위한 코드를 볼 수 있습니다 accessoryCircular 케이스에 코드를 추가해 봅시다 아바타와 함께라면 괜찮아 보일 겁니다
이건 우리 앱에 짧은 지름길을 제공하지만 사용자에게 정보를 제공하지는 않습니다 엣지 주변에 progress view를 추가해서 언제 Ranger가 다시 전투 준비가 되는지 사용자에게 알려주도록 하죠
문제는 이 progress view가 통용되도록 활성화하기 위해 연달아 타임라인 엔트리를 많이 요구할 거라는 점입니다 대신 SwiftUI 새 자동 업데이트 ProgressView를 사용할 수 있죠 그건 레인저가 완전히 회복될 날짜 간격을 맡습니다 시스템에서 progress view를 업데이트한다는 건 여기선 타임라인 엔트리 하나만 필요하단 뜻입니다
훨씬 낫죠 이제 직사각형 그룹군을 추가해 봅시다
직사각형 preview를 선택할 거예요 공간을 더 주니까 컴플리케이션 스타일에서 세 줄 view를 만들 겁니다 처음에는 캐릭터 이름 다음에는 레벨 그리고 자동 업데이트 날짜 필드에 쓸 완전히 치유될 때까지의 시간입니다 캐릭터 이름을 눈에 띄게 하고 싶어서 헤드라인의 서체를 이용해 텍스트의 사이즈를 바꾸고 색상을 조정하는 widgetAccentable 수식어를 추가할 겁니다
바이브런트에선 우리 view가 근사하게 보이는데 이제 Watch의 렌더링 모드에선 어떻게 보이는지 살펴보도록 하죠
캐릭터 이름이 엑센트 컬러를 어떻게 취하는지 볼 수 있습니다 위젯과 컴플리케이션을 환경에서 편안하게 느끼게 만들고 기본 서체 파라미터를 사용하고 서체를 사용하는 데 중요합니다 iOS와 watchOS 사이에선 서체와 사이즈가 다릅니다 iOS는 일반 텍스트 디자인을 사용하는 반면 watchOS는 더 무거운 둥근 디자인을 사용합니다 위젯과 컴플리케이션은 다른 것들과 가까이 화면에 자리 잡을 것입니다 그래서 일관되게 보이는데 서체 타이틀, 헤드라인 보디와 캡션 사용을 권합니다
Xcode의 preview 아바타에도 추가할 공간이 아직 있다는 걸 보여줍니다
iPhone에서는 어떤지 볼게요
훌륭하군요! 마지막으로 텍스트 라인과 선택적으로 이미지를 게시하는 세 번째 스타일인 accessoryInline을 추가해 봅시다 인라인 액세서리가 시스템 규정 색상과 폰트에 따라 그려졌다는 데 주목하세요 Preview를 선택해 봅시다
히어로 이름과 재충전 카운트다운을 보여줍시다
이 텍스트는 우리 Watch 슬롯에 너무 길어요 이제 여러분께 ViewThatFits를 보여드릴 때입니다 길이를 축약해서 여러 view를 제공할 수 있고 ViewThatFits는 끊기나 잘라내기 없이 가능한 공간에 맞도록 처음 콘텐츠 view를 선택할 것입니다 텍스트를 줄여봅시다
가장 짧은 Watch 슬롯엔 너무 길지 모르지만 아바타 이름을 바꿔서 세 번째 대안을 제시해봅시다
어떻게 보이는지 살펴보죠
더 알고 싶다면 'SwiftUI로 맞춤형 레이아웃 작성’ 세션을 참고해주세요 끝내주네요! Emoji Rangers도 프라이버시를 즐기는 걸 좋아하는데 그에 대해 얘기해드릴 Devon에게 자리를 넘기겠습니다 또 뵙네요 프라이버시에 관해 얘기해 보죠 지금까지 여러분의 위젯과 컴플리케이션의 활성화 상태를 논의했습니다 하지만 플랫폼을 넘어서 어떤 기기가 콘텐츠를 편집 중이거나 밝기가 어두운지 고려해야 합니다 iOS 잠금 화면에서 기본 행동은 기기가 잠겨 있는 동안에도 그리드의 셀 제일 왼쪽에 있는 콘텐츠를 보여주는 겁니다 하지만 이건 Setting에서 환경 설정이 가능하고 사용자들은 알림처럼 잠긴 상태에서도 위젯을 삭제하도록 선택할 수 있습니다 watchOS에서는 Watch를 차고 있는 한은 기기가 잠금 해제된 상태로 있습니다 비활성화일 때 Watch는 어두운 밝기 표현의 콘텐츠와 낮은 업데이트 케이던스로 always-on으로 변합니다 기본적으로 콘텐츠는 제일 하단 왼쪽의 상태인 어두운 밝기로 수정되지 않습니다 잠금 화면에서처럼 여러분의 사용자들이 always-on 상태에서 컴플리케이션 콘텐츠가 삭제되도록 설정할 수 있습니다 이 상태에서 콘텐츠가 편집과 어두운 밝기에서도 준비되도록 확인해야 합니다 플랫폼과 함께 여기서 네 상태를 각각 다룹니다 이 모든 상태를 고려해 여러분의 컴플리케이션과 위젯이 모든 경우에 잘 동작하게 하세요 방법을 얘기해 봅시다 Watch에서 여러분의 위젯은 always-on 디스플레이 경험을 지원해야 합니다 \.isLuminanceReduced 환경 값으로 콘텐츠를 늘 always-on 상태에 적응하게 할 수 있습니다 ClockKit에서 나온다면 하나 아닌 모든 타임라인 엔트리에 always-on 콘텐츠를 준비할 수 있단 걸 알아두세요 always-on이면 시간 관련 텍스트와 progress view가 always-on의 낮은 업데이트 케이던스를 지원하려고 감소한 정확도 모드로 바뀔 겁니다 이 모드를 지원하기 위해 환경 값이 시간에 민감한 콘텐츠를 삭제하고 업데이트 빈도를 낮추기 위해 콘텐츠를 최적화하도록 사용하세요 이제 편집에 관해 이야기할게요 기본으로 프라이버시 모드는 여러분의 TimelineProvider가 생성하는 placeholde view의 편집 버전을 보여줄 겁니다 일부 엘리먼트가 민감하고 다른 엘리먼트들은 편집할 필요가 없다면 편집될 view에만 표시하기 위해 .privacySensitive 편집자를 사용할 수 있습니다 이 예시에서 위젯의 심장 박동은 편집했지만 이미지는 편집되지 않은 채로 남습니다 이제 여러분은 잠금 화면과 WidgetKit 컴플리케이션을 근사하게 만들 준비가 됐습니다 SwiftUI의 새로운 기능을 더 알고 싶다면 'SwiftUI로 맞춤형 레이아웃 작성'를 봐주세요 시청해 주셔서 감사합니다 ♪
-
-
4:07 - widgetAccentable
VStack(alignment: .leading) { Text("Headline") .font(.headline) .widgetAccentable() Text("Body 1") Text("Body 2") }.frame(maxWidth: .infinity, alignment: .leading)
-
5:24 - AccessoryWidgetBackground
ZStack { AccessoryWidgetBackground() VStack { Text("MON") Text("6") .font(.title) } }
-
9:02 - Xcode Previews
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty)) .previewContext(WidgetPreviewContext(family: .accessoryCircular)) .previewDisplayName("Circular") EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty)) .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) .previewDisplayName("Rectangular") EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .spouty)) .previewContext(WidgetPreviewContext(family: .accessoryInline)) .previewDisplayName("Inline") #if os(iOS)
-
9:38 - recommendations method
return recommendedIntents() .map { intent in return IntentRecommendation(intent: intent, description: intent.hero!.displayString) }
-
11:05 - ProgressView
ProgressView(interval: entry.character.injuryDate...entry.character.fullHealthDate, countdown: false, label: { Text(entry.character.name) }, currentValueLabel: { Avatar(character: entry.character, includeBackground: false) }) .progressViewStyle(.circular)
-
11:26 - Rectangular
case .accessoryRectangular: HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading) { Text(entry.character.name) Text("Level \(entry.character.level)") Text(entry.character.fullHealthDate, style: .timer) }.frame(maxWidth: .infinity, alignment: .leading) Avatar(character: entry.character, includeBackground: false) }
-
14:03 - ViewThatFits
ViewThatFits { Text("\(entry.character.name) is resting, combat-ready in \(entry.character.fullHealthDate, style: .relative)") Text("\(entry.character.name) ready in \(entry.character.fullHealthDate, style: .timer)") Text("\(entry.character.avatar) \(entry.character.fullHealthDate, style: .timer)") }
-
16:18 - isLuminanceReduced
@Environment(\.isLuminanceReduced) var isLuminanceReduced var body: some View { if isLuminanceReduced { Text("🙈").font(.title) } else { Text("🐵").font(.title) } }
-
16:52 - privacySensitive
VStack(spacing: -2) { Image(systemName: "heart") .font(.caption.bold()) .widgetAccentable() Text("\(currentHeartRate)") .font(.title) .privacySensitive() }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.