見出し画像

SwiftUI Tutorials -Working with UI Controls

設定画面を作っていきます。

Display a User Profile

Profile.swiftという  ファイルを作り、構造体Profileを作りプロパティを決めていきます。

struct Profile {
   var username: String
   var prefersNotifications = true
   var seasonalPhoto = Season.winter
   var goalDate = Date()
   
   static let `default` = Profile(username: "g_kumar")
   
   enum Season: String, CaseIterable, Identifiable {
       case spring = "🌷"
       case summer = "🌞"
       case autumn = "🍂"
       case winter = "☃️"
       
       var id: String { self.rawValue }
   }
}

新しいSwiftUIファイルで、ProfileSummary.swiftを作り、構造体ProfileSummaryを作ります。

struct ProfileSummary: View {
   var profile: Profile
   var body: some View {
       ScrollView {
           VStack(alignment: .leading, spacing: 10) {
               Text(profile.username)
                   .bold()
                   .font(.title)
               Text("Notifications: \(profile.prefersNotifications ? "On": "Off" )")
               Text("Seasonal Photos: \(profile.seasonalPhoto.rawValue)")
               Text("Goal Date: ") + Text(profile.goalDate, style: .date)
           }
       }
   }
}

同じく、ProfileHost.swiftというSwiftUIファイルを作り構造体ProfileHostを作ります。ProfileSummaryを入れ込みます。

struct ProfileHost: View {

   @State private var draftProfile = Profile.default
   
   var body: some View {
           VStack(alignment: .leading, spacing: 20) {
           ProfileSummary(profile: draftProfile)
       }
       .padding()
   }
}

もう一つViewを作ります。

HikeBadge.swiftという名前のSwiftUIファイルで構造体HikeBadgeを作ります。

struct HikeBadge: View {
   var name: String
   var body: some View {
       VStack(alignment: .center) {
           Badge()
               .frame(width: 300, height: 300)
               .scaleEffect(1.0 / 3.0)
               .frame(width: 100, height: 100)
           Text(name)
               .font(.caption)
               .accessibilityLabel("Badge for \(name).")
       }
   }
}

ここでProfileSummary.swiftを編集してコードを追加します。

  @EnvironmentObject var modelData: ModelData

var bodyの編集後です。

   
    var body: some View {
       ScrollView {
           VStack(alignment: .leading, spacing: 10) {
               Text(profile.username)
                   .bold()
                   .font(.title)

               Text("Notifications: \(profile.prefersNotifications ? "On": "Off" )")
               Text("Seasonal Photos: \(profile.seasonalPhoto.rawValue)")
               Text("Goal Date: ") + Text(profile.goalDate, style: .date)

               Divider()

               VStack(alignment: .leading) {
                   Text("Completed Badges")
                       .font(.headline)

                   ScrollView(.horizontal) {
                       HStack {
                           HikeBadge(name: "First Hike")
                           HikeBadge(name: "Earth Day")
                               .hueRotation(Angle(degrees: 90))
                           HikeBadge(name: "Tenth Hike")
                               .grayscale(0.5)
                               .hueRotation(Angle(degrees: 45))
                       }
                       .padding(.bottom)
                   }
               }

               Divider()

               VStack(alignment: .leading) {
                   Text("Recent Hikes")
                       .font(.headline)

                   HikeView(hike: modelData.hikes[0])
               }
           }
       }
   }

次にCategoryHome.swiftの編集します。まず、変数showingProfileを追加します。

 @State private var showingProfile = false

var body: some View()の部分は

 var body: some View {
       NavigationView {
           List {
               modelData.features[0].image
                   .resizable()
                   .scaledToFill()
                   .frame(height: 200)
                   .clipped()
                   .listRowInsets(EdgeInsets())

               ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
                   CategoryRow(categoryName: key, items: modelData.categories[key]!)
               }
               .listRowInsets(EdgeInsets())
           }
           .listStyle(InsetListStyle())
           .navigationTitle("Featured")
           .toolbar {
               Button(action: { showingProfile.toggle() }) {
                   Image(systemName: "person.crop.circle")
                       .accessibilityLabel("User Profile")
               }
           }
           .sheet(isPresented: $showingProfile) {
               ProfileHost()
                   .environmentObject(modelData)
           }
       }
   }

変更点するコードは以下

.listStyle(InsetListStyle())
.toolbar {
Button(action: { showingProfile.toggle() }) {
Image(systemName: "person.crop.circle")
.accessibilityLabel("User Profile")
}
}
.sheet(isPresented: $showingProfile) {
ProfileHost()
.environmentObject(modelData)
}


Add an Edit Mode

プロファイルの表示と編集画面に遷移する仕組みを作ります。

まず、ProfileHost.swiftを編集します。

  @Environment(\.editMode) var editMode

と var body: some View {}に

 HStack {
               Spacer()
               EditButton()
           }

を追加して"Edit"ボタンを表示させます。

次に、ModelData.swift

 @Published var profile = Profile.default

を追加します。これで一度閉じても表示されていた画面を保持できるようになります。

ProfileHost.swiftに戻り、

struct ProfileHost: View {
   @Environment(\.editMode) var editMode
   @EnvironmentObject var modelData: ModelData
   @State private var draftProfile = Profile.default
   var body: some View {
       VStack(alignment: .leading, spacing: 20) {
           HStack {
               Spacer()
               EditButton()
           }
           if editMode?.wrappedValue == .inactive {
               ProfileSummary(profile: modelData.profile)
           } else {
               Text("Profile Editor")
           }
       }
       .padding()
   }
}
@EnvironmentObject var modelData: ModelData
if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile)
} else {
Text("Profile Editor")
}

を追加します。

Define the Profile Editor

設定を変更する画面Profile Editorを作っていきます。

新規ファイルでProfileEditor.swiftを作成して、構造体ProfileEditorで新しいViewを作ります。

@Binding var profile: Profile

@Bindingした変数を作りViewを以下のように作ります。

var body: some View {
       List {
           HStack {
               Text("Username").bold()
               Divider()
               TextField("Username", text: $profile.username)
           }
       }
   }

そしてProfileHost.swiftのeditModeを選択するところ、

if editMode?.wrappedValue == .inactive {
               ProfileSummary(profile: modelData.profile)
           } else {
               ProfileEditor(profile: $draftProfile)
           }

に追加します。

ProfileEditor.swiftに戻って、
トグルボタンとピッカーコントロールを設置、訪問日を変更できるようにしています。まず、トグルボタン。

Toggle(isOn: $profile.prefersNotifications) {
               Text("Enable Notifications").bold()
           }

変数dateRangeを宣言します。

var dateRange: ClosedRange<Date> {
       let min = Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate)!
       let max = Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate)!
       return min...max
   }

次に写真が選択できるようにPicker()を設置します。その下には日付が変更できるようにDatePicker()を付けます。

VStack(alignment: .leading, spacing: 20) {
               Text("Seasonal Photo").bold()
               
               Picker("Seasonal Photo", selection: $profile.seasonalPhoto) {
                   ForEach(Profile.Season.allCases) { season in
                       Text(season.rawValue).tag(season)
                   }
               }
               .pickerStyle(SegmentedPickerStyle())
           }
DatePicker(selection: $profile.goalDate, in: dateRange, displayedComponents: .date) {
               Text("Goal Date").bold()
           }


Delay Edit Propagation

設定完了までは変更した設定が有効にならないようにします。

ProfileHost.swiftを編集していきます。

    var body: some View {
       VStack(alignment: .leading, spacing: 20) {
           HStack {
               if editMode?.wrappedValue == .active {
                   Button("Cancel") {
                       draftProfile = modelData.profile
                       editMode?.animation().wrappedValue = .inactive
                   }
               }
               Spacer()
               EditButton()
           }
           if editMode?.wrappedValue == .inactive {
               ProfileSummary(profile: modelData.profile)
           } else {
               ProfileEditor(profile: $draftProfile)
                   .onAppear {
                       draftProfile = modelData.profile
                   }
                   .onDisappear {
                       modelData.profile = draftProfile
                   }
           }
       }
       .padding()
   }

変更カ所です。

if editMode?.wrappedValue == .active {
Button("Cancel") {
draftProfile = modelData.profile
editMode?.animation().wrappedValue = .inactive
}
}

if editMode?.wrappedValue == .inactive {
ProfileSummary(profile: modelData.profile)
} else {
ProfileEditor(profile: $draftProfile)
.onAppear {
draftProfile = modelData.profile
}
.onDisappear {
modelData.profile = draftProfile
}
}

この記事が気に入ったらサポートをしてみませんか?