View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Todos os vídeos
  • Sobre

Mais vídeos

  • Sobre
  • Código
  • Build a productivity app for Apple Watch

    Your wrist has never been more productive. Discover how you can use SwiftUI and system features to build a great productivity app for Apple Watch. We'll show you how you can design great work experiences for the wrist, and explore how you can get text input, display a basic chart, and share content with friends.

    Recursos

    • Building a productivity app for Apple Watch
    • watchOS apps
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC22

    • Hello Swift Charts
    • Meet Transferable
    • Swift Charts: Raise the bar
    • The SwiftUI cookbook for navigation
    • What's new in SwiftUI
  • Buscar neste vídeo...
    • 6:12 - Initial ListItem struct

      struct ListItem: Identifiable, Hashable {
          
          let id = UUID()
          var description: String
          
          init(_ description: String) {
              self.description = description
          }
      }
    • 6:24 - ItemListModel

      class ItemListModel: NSObject, ObservableObject {
          @Published var items = [ListItem]()
      }
    • 6:30 - Add the ItemListModel as an EnvironmentObject

      @main
      struct WatchTaskListSampleApp: App {
          
          @StateObject var itemListModel = ItemListModel()
          
          @SceneBuilder var body: some Scene {
              WindowGroup {
                  ContentView()
                      .environmentObject(itemListModel)
              }
          }
      }
    • 6:37 - Create a simple SwiftUI List

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              List {
                  ForEach($model.items) { $item in
                      ItemRow(item: $item)
                  }
                  
                  if model.items.isEmpty {
                      Text("No items to do!")
                          .foregroundStyle(.gray)
                  }
              }
              .navigationTitle("Tasks")
          }
      }
    • 7:11 - TextFieldLink with a simple String

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              VStack {
                  TextFieldLink("Add") {
                      model.items.append(ListItem($0))
                  }
              }
              .navigationTitle("Tasks")
          }
      }
    • 7:16 - TextFieldLink with a Label

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              VStack {
                  TextFieldLink {
                      Label(
                          "Add", 
                          systemImage: "plus.circle.fill")
                  } onSubmit: {
                      model.items.append(ListItem($0))
                  }
              }
              .navigationTitle("Tasks")
          }
      }
    • 7:20 - TextFieldLink with foregroundStyle modifier

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              VStack {
                  TextFieldLink {
                      Label(
                          "Add", 
                          systemImage: "plus.circle.fill")
                  } onSubmit: {
                      model.items.append(ListItem($0))
                  }
                  .foregroundStyle(.tint)
              }
              .navigationTitle("Tasks")
          }
      }
    • 7:27 - TextFieldLink with buttonStyle modifier

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              VStack {
                  TextFieldLink {
                      Label(
                          "Add", 
                          systemImage: "plus.circle.fill")
                  } onSubmit: {
                      model.items.append(ListItem($0))
                  }
                  .buttonStyle(.borderedProminent)
              }
              .navigationTitle("Tasks")
          }
      }
    • 7:30 - Create the AddItemLink View to encapsulate the style and behavior of the TextFieldLink to add list items

      struct AddItemLink: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              TextFieldLink(prompt: Text("New Item")) {
                  Label("Add",
                        systemImage: "plus.circle.fill")
              } onSubmit: {
                  model.items.append(ListItem($0))
              } 
          }
      }
    • 8:38 - Add a toolbar item to allow people to add new list items

      struct ContentView: View {
          @EnvironmentObject private var model: ItemListModel
          
          var body: some View {
              List {
                  ForEach($model.items) { $item in
                      ItemRow(item: $item)
                  }
                  
                  if model.items.isEmpty {
                      Text("No items to do!")
                          .foregroundStyle(.gray)
                  }
              }
              .toolbar {
                  AddItemLink()
              }
              .navigationTitle("Tasks")
          }
      }
    • 11:40 - Display a modal sheet

      struct ItemRow: View {
          @EnvironmentObject private var model: ItemListModel
          
          @Binding var item: ListItem
          @State private var showDetail = false
          
          var body: some View {
              Button {
                  showDetail = true
              } label: {
                  HStack {
                      Text(item.description)
                          .strikethrough(item.isComplete)
                      Spacer()
                      Image(systemName: "checkmark").opacity(item.isComplete ? 100 : 0)
                  }
              }
              .sheet(isPresented: $showDetail) {
                  ItemDetail(item: $item)
              }
          }
      }
    • 11:58 - Display a modal sheet with custom toolbar items

      struct ItemRow: View {
          @EnvironmentObject private var model: ItemListModel
          
          @Binding var item: ListItem
          @State private var showDetail = false
          
          var body: some View {
              Button {
                  showDetail = true
              } label: {
                  HStack {
                      Text(item.description)
                          .strikethrough(item.isComplete)
                      Spacer()
                      Image(systemName: "checkmark").opacity(item.isComplete ? 100 : 0)
                  }
              }
              .sheet(isPresented: $showDetail) {
                  ItemDetail(item: $item)
                      .toolbar {
                          ToolbarItem(placement: .confirmationAction) {
                              Button("Done") {
                                  showDetail = false
                              }
                          }
                      }
              }
          }
      }
    • 12:36 - Add more properties to the ListItem

      struct ListItem: Identifiable, Hashable {
          
          let id = UUID()
          var description: String
          var estimatedWork: Double = 1.0
          var creationDate = Date()
          var completionDate: Date?
          
          init(_ description: String) {
              self.description = description
          }
      
          var isComplete: Bool {
              get {
                  completionDate != nil
              }
              set {
                  if newValue {
                      guard completionDate == nil else { return }
                      completionDate = Date()
                  } else {
                      completionDate = nil
                  }
              }
          }
      }
    • 12:48 - Create the ItemDetail View with the Stepper

      struct ItemDetail: View {
          @Binding var item: ListItem
          
          var body: some View {
              Form {
                  Section("List Item") {
                      TextField("Item", text: $item.description, prompt: Text("List Item"))
                  }
                  Section("Estimated Work") {
                      Stepper(value: $item.estimatedWork,
                              in: (0.0...14.0),
                              step: 0.5,
                              format: .number) {
                          Text("\(item.estimatedWork, specifier: "%.1f") days")
                      }
                  }
                  
                  Toggle(isOn: $item.isComplete) {
                      Text("Completed")
                  }
              }
          }
      }
    • 13:29 - A Stepper with Emoji

      // Use a Stepper to edit the stress level of an item
      struct StressStepper: View {
          private let stressLevels = [
              "😱", "😡", "😳", "🙁", "🫤", "🙂", "🥳"
          ]
          @State private var stressLevelIndex = 5
          
          var body: some View {
              VStack {
                  Text("Stress Level")
                      .font(.system(.footnote, weight: .bold))
                      .foregroundStyle(.tint)
                  
                  Stepper(value: $stressLevelIndex,
                          in: (0...stressLevels.count-1)) {
                      Text(stressLevels[stressLevelIndex])
                  }
              }
          }
      }
    • 14:43 - Add a ShareLink to the ItemDetail View

      struct ItemDetail: View {
          @Binding var item: ListItem
          
          var body: some View {
              Form {
                  Section("List Item") {
                      TextField("Item", text: $item.description, prompt: Text("List Item"))
                  }
                  Section("Estimated Work") {
                      Stepper(value: $item.estimatedWork,
                              in: (0.0...14.0),
                              step: 0.5,
                              format: .number) {
                          Text("\(item.estimatedWork, specifier: "%.1f") days")
                      }
                  }
                  
                  Toggle(isOn: $item.isComplete) {
                      Text("Completed")
                  }
                  
                  ShareLink(item: item.description,
                            subject: Text("Please help!"),
                            message: Text("(I need some help finishing this.)"),
                            preview: SharePreview("\(item.description)"))
                  .buttonStyle(.borderedProminent)
                  .buttonBorderShape(.roundedRectangle)
                  .listRowInsets(
                      EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
                  )
              }
          }
      }
    • 16:39 - Page-style TabView with navigation titles for each page

      struct ContentView: View {
          var body: some View {
              TabView {
                  NavigationStack {
                      ItemList()
                  }
                  NavigationStack {
                      ProductivityChart()
                  }
              }.tabViewStyle(.page)
          }
      }
    • 17:20 - ChartData struct for aggregate data

      /// Aggregate data for charting productivity.
      struct ChartData {
          struct DataElement: Identifiable {
              var id: Date { return date }
              let date: Date
              let itemsComplete: Double
          }
          
          /// Create aggregate chart data from list items.
          /// - Parameter items: An array of list items to aggregate for charting.
          /// - Returns: The chart data source.
          static func createData(_ items: [ListItem]) -> [DataElement] {
              return Dictionary(grouping: items, by: \.completionDate)
                  .compactMap {
                      guard let date = $0 else { return nil }
                      return DataElement(date: date, itemsComplete: Double($1.count))
                  }
                  .sorted {
                      $0.date < $1.date
                  }
          }
      }
    • 17:36 - Static sample data for chart and basic bar chart

      extension ChartData {
          
          /// Some static sample data for displaying a `Chart`.
          static var chartSampleData: [DataElement] {
              let calendar = Calendar.autoupdatingCurrent
              var startDateComponents = calendar.dateComponents(
                  [.year, .month, .day], from: Date())
              startDateComponents.setValue(22, for: .day)
              startDateComponents.setValue(5, for: .month)
              startDateComponents.setValue(2022, for: .year)
              startDateComponents.setValue(0, for: .hour)
              startDateComponents.setValue(0, for: .minute)
              startDateComponents.setValue(0, for: .second)
              let startDate = calendar.date(from: startDateComponents)!
              
              let itemsToAdd = [
                  6, 3, 1, 4, 1, 2, 7,
                  5, 2, 0, 5, 2, 3, 9
              ]
              var items = [DataElement]()
              for dayOffset in (0..<itemsToAdd.count) {
                  items.append(DataElement(
                      date: calendar.date(byAdding: .day, value: dayOffset, to: startDate)!,
                      itemsComplete: Double(itemsToAdd[dayOffset])))
              }
              
              return items
          }
      }
      
      struct ProductivityChart: View {
             
          let data = ChartData.createData(
              ListItem.chartSampleData)
                   
          var body: some View {
              Chart(data) { dataPoint in
                  BarMark(
                      x: .value("Date", dataPoint.date),
                      y: .value(
                          “Completed", 
                          dataPoint.itemsComplete)
                  )
                  .foregroundStyle(Color.accentColor)
              }
              .navigationTitle("Productivity")
              .navigationBarTitleDisplayMode(.inline)
          }
      }
    • 17:50 - Chart with chartXAxis modifier

      struct ProductivityChart: View {
             
          let data = ChartData.createData(
              ListItem.chartSampleData)
        
          private var shortDateFormatStyle = DateFormatStyle(dateFormatTemplate: "Md")
                   
          var body: some View {
              Chart(data) { dataPoint in
                  BarMark(
                      x: .value("Date", dataPoint.date),
                      y: .value(
                          “Completed", 
                          dataPoint.itemsComplete)
                  )
                  .foregroundStyle(Color.accentColor)
              }
            	.chartXAxis {
                  AxisMarks(format: shortDateFormatStyle)
              }
              .navigationTitle("Productivity")
              .navigationBarTitleDisplayMode(.inline)
          }
      }
      
      /// `ProductivityChart` uses this type to format the dates on the x-axis.
      struct DateFormatStyle: FormatStyle {
          enum CodingKeys: CodingKey {
              case dateFormatTemplate
          }
          
          private var dateFormatTemplate: String
          private var formatter: DateFormatter
          
          init(dateFormatTemplate: String) {
              self.dateFormatTemplate = dateFormatTemplate
              formatter = DateFormatter()
              formatter.locale = Locale.autoupdatingCurrent
              formatter.setLocalizedDateFormatFromTemplate(dateFormatTemplate)
          }
          
          init(from decoder: Decoder) throws {
              let container = try decoder.container(keyedBy: CodingKeys.self)
              dateFormatTemplate = try container.decode(String.self, forKey: .dateFormatTemplate)
              formatter = DateFormatter()
              formatter.setLocalizedDateFormatFromTemplate(dateFormatTemplate)
          }
          
          func encode(to encoder: Encoder) throws {
              var container = encoder.container(keyedBy: CodingKeys.self)
              try container.encode(dateFormatTemplate, forKey: .dateFormatTemplate)
          }
          
          func format(_ value: Date) -> String {
              formatter.string(from: value)
          }
      }
    • 19:05 - Add the digitalCrownRotation modifier

      struct ProductivityChart: View {
             
          let data = ChartData.createData(
              ListItem.chartSampleData)
      
          /// The index of the highlighted chart value. This is for crown scrolling.
          @State private var highlightedDateIndex: Int = 0
      
          /// The current offset of the crown while it's rotating. This sample sets the offset with
          /// the value in the DigitalCrownEvent and uses it to show an intermediate
          /// (between detents) chart value in the view.
          @State private var crownOffset: Double = 0.0
      
          @State private var isCrownIdle = true
        
          private var chart: some View {
              Chart(data) { dataPoint in
                  BarMark(
                      x: .value("Date", dataPoint.date),
                      y: .value(
                          “Completed", 
                          dataPoint.itemsComplete)
                  )
                  .foregroundStyle(Color.accentColor)
              }
            	.chartXAxis {
                  AxisMarks(format: shortDateFormatStyle)
              }
          }
              
          var body: some View {
              chart
                  .focusable()
                  .digitalCrownRotation(
                      detent: $highlightedDateIndex,
                      from: 0,
                      through: data.count - 1,
                      by: 1,
                      sensitivity: .medium
                  ) { crownEvent in
                      isCrownIdle = false
                      crownOffset = crownEvent.offset
                  } onIdle: {
                      isCrownIdle = true
                  }
                  .navigationTitle("Productivity")
                  .navigationBarTitleDisplayMode(.inline)
          }
      }
    • 21:07 - Add a RuleMark to the Chart to show the current Digital Crown position

      /// The date value that corresponds to the crown offset.
      private var crownOffsetDate: Date {
          let dateDistance = data[0].date.distance(
              to: data[data.count - 1].date) * (crownOffset / Double(data.count - 1))
          return data[0].date.addingTimeInterval(dateDistance)
      }
      
      private var chart: some View {
          Chart(data) { dataPoint in
              BarMark(
                  x: .value("Date", dataPoint.date),
                  y: .value(
                      "Completed", 
                      dataPoint.itemsComplete)
              )
              .foregroundStyle(Color.accentColor)
                   
              RuleMark(x: .value("Date", crownOffsetDate))
                  .foregroundStyle(Color.appYellow)
          }
          .chartXAxis {
              AxisMarks(format: shortDateFormatStyle)
          }
      }
    • 21:37 - Add animation to dim the crown position line when the scrolling idle state changes

      struct ProductivityChart: View {
             
          let data = ChartData.createData(
              ListItem.chartSampleData)
      
          /// The index of the highlighted chart value. This is for crown scrolling.
          @State private var highlightedDateIndex: Int = 0
      
          /// The current offset of the crown while it's rotating. This sample sets the offset with
          /// the value in the DigitalCrownEvent and uses it to show an intermediate
          /// (between detents) chart value in the view.
          @State private var crownOffset: Double = 0.0
      
          @State private var isCrownIdle = true
      
          @State var crownPositionOpacity: CGFloat = 0.2
        
          private var chart: some View {
              Chart(data) { dataPoint in
                  BarMark(
                      x: .value("Date", dataPoint.date),
                      y: .value(
                          “Completed", 
                          dataPoint.itemsComplete)
                  )
                  .foregroundStyle(Color.accentColor)
                           
                  RuleMark(x: .value("Date", crownOffsetDate))
                      .foregroundStyle(Color.appYellow.opacity(crownPositionOpacity))
              }
            	.chartXAxis {
                  AxisMarks(format: shortDateFormatStyle)
              }
          }
                   
          var body: some View {
              chart
                  .focusable()
                  .digitalCrownRotation(
                      detent: $highlightedDateIndex,
                      from: 0,
                      through: data.count - 1,
                      by: 1,
                      sensitivity: .medium
                  ) { crownEvent in
                      isCrownIdle = false
                      crownOffset = crownEvent.offset
                  } onIdle: {
                      isCrownIdle = true
                  }
                  .onChange(of: isCrownIdle) { newValue in
                      withAnimation(newValue ? .easeOut : .easeIn) {
                          crownPositionOpacity = newValue ? 0.2 : 1.0
                      }
                  }
                  .navigationTitle("Productivity")
                  .navigationBarTitleDisplayMode(.inline)
          }
      }
    • 22:14 - Add an annotation to the bar chart to display the current value

      private func isLastDataPoint(_ dataPoint: ChartData.DataElement) -> Bool {
          data[chartDataRange.upperBound].id == dataPoint.id
      }
      
      private var chart: some View {
          Chart(chartData) { dataPoint in
              BarMark(x: .value("Date", dataPoint.date, unit: .day),
              y: .value("Completed", dataPoint.itemsComplete))
              .foregroundStyle(Color.accentColor)
              .annotation(
                  position: isLastDataPoint(dataPoint) ? .topLeading : .topTrailing,
                  spacing: 0
              ) {
                  Text("\(dataPoint.itemsComplete, format: .number)")
                      .foregroundStyle(dataPoint.date == crownOffsetDate ? Color.appYellow : Color.clear)
              }
      
              RuleMark(x: .value("Date", crownOffsetDate, unit: .day))
                  .foregroundStyle(Color.appYellow.opacity(crownPositionOpacity))
          }
          .chartXAxis {
              AxisMarks(format: shortDateFormatStyle)
          }
      }
    • 22:44 - Make the chart data range scrollable

      @State var chartDataRange = (0...6)
      
      private func updateChartDataRange() {
          if (highlightedDateIndex - chartDataRange.lowerBound) < 2, chartDataRange.lowerBound > 0 {
              let newLowerBound = max(0, chartDataRange.lowerBound - 1)
              let newUpperBound = min(newLowerBound + 6, data.count - 1)
              chartDataRange = (newLowerBound...newUpperBound)
              return
          }
          if (chartDataRange.upperBound - highlightedDateIndex) < 2, chartDataRange.upperBound < data.count - 1 {
              let newUpperBound = min(chartDataRange.upperBound + 1, data.count - 1)
              let newLowerBound = max(0, newUpperBound - 6)
              chartDataRange = (newLowerBound...newUpperBound)
              return
          }
      }
      
      private var chartData: [ChartData.DataElement] {
          Array(data[chartDataRange.clamped(to: (0...data.count - 1))])
      }
      
      private var chart: some View {
          Chart(chartData) { dataPoint in
              BarMark(x: .value("Date", dataPoint.date, unit: .day),
                      y: .value("Completed", dataPoint.itemsComplete)
              )
              .foregroundStyle(Color.accentColor)
              .annotation(
                  position: isLastDataPoint(dataPoint) ? .topLeading : .topTrailing,
                  spacing: 0
              ) {
                  Text("\(dataPoint.itemsComplete, format: .number)")
                      .foregroundStyle(dataPoint.date == crownOffsetDate ? Color.appYellow : Color.clear)
              }
      
              RuleMark(x: .value("Date", crownOffsetDate, unit: .day))
                  .foregroundStyle(Color.appYellow.opacity(crownPositionOpacity))
          }
          .chartXAxis {
              AxisMarks(format: shortDateFormatStyle)
          }
      }
      
      var body: some View {
          chart
              .focusable()
              .digitalCrownRotation(
                  detent: $highlightedDateIndex,
                  from: 0,
                  through: data.count - 1,
                  by: 1,
                  sensitivity: .medium
              ) { crownEvent in
                  isCrownIdle = false
                  crownOffset = crownEvent.offset
              } onIdle: {
                  isCrownIdle = true
              }
              .onChange(of: isCrownIdle) { newValue in
                  withAnimation(newValue ? .easeOut : .easeIn) {
                      crownPositionOpacity = newValue ? 0.2 : 1.0
                  }
              }
              .onChange(of: highlightedDateIndex) { newValue in
                  withAnimation {
                      updateChartDataRange()
                  }
              }
              .navigationTitle("Productivity")
              .navigationBarTitleDisplayMode(.inline)
      }

Developer Footer

  • Vídeos
  • WWDC22
  • Build a productivity app for Apple Watch
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • 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
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines