Please complete Chinese tutorial and see github.com/WillieWangW...
Using the UI controls
In the
Landmarks
app, users can create a profile to show personality. To allow users to modify personal information, we need to add an edit mode, and a design preferences interface.We will use a variety of common UI controls to process the data, and updated when the user saves the modified
Landmarks
model.
- Estimated Completion Time: 25 minutes
- Project File: Download
1. Display Profile
Landmarks
app to save some of the detailed configuration and preferences locally. Before users to edit their details will not be displayed in a summary view of the modification of the control.
1.1 Landmark
Create a new folder in the folder Profile
, and then create a new file in it ProfileHost.swift
.
ProfileHost
Static summary view and edit mode view is responsible for user information.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@State var profile = Profile.default
var body: some View {
Text("Profile for: \(profile.username)")
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
1.2 Home.swift
in the static Text
into created in the previous step ProfileHost
.
Now the home screen profile
button displays a modal user with information.
Home.swift
import SwiftUI
struct CategoryHome: View {
var categories: [String: [Landmark]] {
.init(
grouping: landmarkData,
by: { $0.category.rawValue }
)
}
var featured: [Landmark] {
landmarkData.filter { $0.isFeatured }
}
var body: some View {
NavigationView {
List {
FeaturedLandmarks(landmarks: featured)
.scaledToFill()
.frame(height: 200)
.clipped()
.listRowInsets(EdgeInsets())
ForEach(categories.keys.sorted().identified(by: \.self)) { key in
CategoryRow(categoryName: key, items: self.categories[key]!)
}
.listRowInsets(EdgeInsets())
NavigationButton(destination: LandmarkList()) {
Text("See All")
}
}
.navigationBarTitle(Text("Featured"))
.navigationBarItems(trailing:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.accessibility(label: Text("User Profile"))
.padding(),
destination: ProfileHost()
)
)
}
}
}
struct FeaturedLandmarks: View {
var landmarks: [Landmark]
var body: some View {
landmarks[0].image(forSize: 250).resizable()
}
}
#if DEBUG
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
}
}
#endif
复制代码
1.3 Creating a new View ProfileSummary
, which holds a Profile
example and show some basic user information.
ProfileSummary
Holds a Profile
value of a profile than the binding
more appropriate, because its parent view ProfileHost
is responsible for managing it state
.
ProfileSummary.swift
import SwiftUI
struct ProfileSummary: View {
var profile: Profile
static let goalFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter
}()
var body: some View {
List {
Text(profile.username)
.bold()
.font(.title)
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
}
}
}
#if DEBUG
struct ProfileSummary_Previews: PreviewProvider {
static var previews: some View {
ProfileSummary(profile: Profile.default)
}
}
#endif
复制代码
1.4 update ProfileHost
to display the summary view.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@State var profile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
ProfileSummary(profile: self.profile)
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
1.5 Creating a new view HikeBadge
, the view of the combination of the drawing path and the shape of the badge and some excursions descriptive text.
Badge is just a graphic, so the HikeBadge
text as well as accessibility(label:)
ways to get the badge meaning more clear to other users.
Two call frame(width:height:)
method to make in order to design a badge size 300 × 300 dots scaled rendering.
HikeBadge.swift
import SwiftUI
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)
.accessibility(label: Text("Badge for \(name)."))
}
}
}
#if DEBUG
struct HikeBadge_Previews : PreviewProvider {
static var previews: some View {
HikeBadge(name: "Preview Testing")
}
}
#endif
复制代码
1.6 update ProfileSummary
, add to it a few badges of different shades and the reasons for obtaining badge.
ProfileSummary.swift
import SwiftUI
struct ProfileSummary: View {
var profile: Profile
static let goalFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter
}()
var body: some View {
List {
Text(profile.username)
.bold()
.font(.title)
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
VStack(alignment: .leading) {
Text("Completed Badges")
.font(.headline)
ScrollView {
HStack {
HikeBadge(name: "First Hike")
HikeBadge(name: "Earth Day")
.hueRotation(Angle(degrees: 90))
HikeBadge(name: "Tenth Hike")
.grayscale(0.5)
.hueRotation(Angle(degrees: 45))
}
}
.frame(height: 140)
}
}
}
}
#if DEBUG
struct ProfileSummary_Previews: PreviewProvider {
static var previews: some View {
ProfileSummary(profile: Profile.default)
}
}
#endif
复制代码
1.7 introduction animation view and transitions in HikeView
to complete ProfileSummary
.
ProfileSummary.swift
import SwiftUI
struct ProfileSummary: View {
var profile: Profile
static var goalFormat: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter
}
var body: some View {
List {
Text(profile.username)
.bold()
.font(.title)
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
VStack(alignment: .leading) {
Text("Completed Badges")
.font(.headline)
ScrollView {
HStack {
HikeBadge(name: "First Hike")
HikeBadge(name: "Earth Day")
.hueRotation(Angle(degrees: 90))
HikeBadge(name: "Tenth Hike")
.grayscale(0.5)
.hueRotation(Angle(degrees: 45))
}
}
.frame(height: 140)
}
VStack(alignment: .leading) {
Text("Recent Hikes")
.font(.headline)
HikeView(hike: hikeData[0])
}
}
}
}
#if DEBUG
struct ProfileSummary_Previews: PreviewProvider {
static var previews: some View {
ProfileSummary(profile: Profile.default)
}
}
#endif
复制代码
2. Add the edit mode
Users need to switch modes to view and edit the personal details. We will, through existing ProfileHost
add a EditButton
to achieve editing mode, and create a view with a single data editing control.
2.1 Creating a Environment
view property and enter \.editMode
.
We can use this property to read and write the current edit range.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
ProfileSummary(profile: profile)
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
2.2 Creating a context switch can switch the edit mode Edit
button.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Spacer()
EditButton()
}
ProfileSummary(profile: profile)
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
2.3 Adding a draft copy of the user information to be passed to the edit control.
In order to avoid updating the app before any editing confirm global state, for example, when a user enters his name, edit view will only operate on its own copy.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
@State var draftProfile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Spacer()
EditButton()
}
ProfileSummary(profile: self.profile)
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
2.4 add a condition view, static information display view or edit mode.
Currently, only a static text edit mode.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
@State var draftProfile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Spacer()
EditButton()
}
if self.mode?.value == .inactive {
ProfileSummary(profile: profile)
} else {
Text("Profile Editor")
}
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
3. Define the message editor
User Information Editor consists mainly of different controls when changing details. Information badge like some of the items can not be edited by the user, so they do not appear in the editor.
In keeping with the message digest, we will add information details in the same order in the editor.
3.1 Creating a new View ProfileEditor
, and then introduced to a draft copy of the user information binding
.
the first view is a control TextField
, which controls and updates a string binding
where the case is a user-selected name.
ProfileEditor.swift
import SwiftUI
struct ProfileEditor: View {
@Binding var profile: Profile
var body: some View {
List {
HStack {
Text("Username").bold()
Divider()
TextField($profile.username)
}
}
}
}
#if DEBUG
struct ProfileEditor_Previews: PreviewProvider {
static var previews: some View {
ProfileEditor(profile: .constant(.default))
}
}
#endif
复制代码
3.2 update ProfileHost
condition content, is introduced ProfileEditor
and pass it a message binding
.
Now when you click Edit
, the message editor view is displayed.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
@State var draftProfile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Spacer()
EditButton()
}
if self.mode?.value == .inactive {
ProfileSummary(profile: profile)
} else {
ProfileEditor(profile: $draftProfile)
}
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
Add 3.3 landmark receiving related event notification toggle
, which correspond to user preference.
Toggle
Is the only on
or off
control, so it fits like yes
or no
like Boolean
value.
ProfileEditor.swift
import SwiftUI
struct ProfileEditor: View {
@Binding var profile: Profile
var body: some View {
List {
HStack {
Text("Username").bold()
Divider()
TextField($profile.username)
}
Toggle(isOn: $profile.prefersNotifications) {
Text("Enable Notifications")
}
}
}
}
#if DEBUG
struct ProfileEditor_Previews: PreviewProvider {
static var previews: some View {
ProfileEditor(profile: .constant(.default))
}
}
#endif
复制代码
3.4 a SegmentedControl
and its label in one VStack
of the landmarks in the photo with selectable season.
ProfileEditor.swift
import SwiftUI
struct ProfileEditor: View {
@Binding var profile: Profile
var body: some View {
List {
HStack {
Text("Username").bold()
Divider()
TextField($profile.username)
}
Toggle(isOn: $profile.prefersNotifications) {
Text("Enable Notifications")
}
VStack(alignment: .leading, spacing: 20) {
Text("Seasonal Photo").bold()
SegmentedControl(selection: $profile.seasonalPhoto) {
ForEach(Profile.Season.allCases.identified(by: \.self)) { season in
Text(season.rawValue).tag(season)
}
}
}
.padding(.top)
}
}
}
#if DEBUG
struct ProfileEditor_Previews: PreviewProvider {
static var previews: some View {
ProfileEditor(profile: .constant(.default))
}
}
#endif
复制代码
3.5 Finally, add a following season selector DatePicker
for modifying the landmark arrival date.
ProfileEditor.swift
import SwiftUI
struct ProfileEditor: View {
@Binding var profile: Profile
var body: some View {
List {
HStack {
Text("Username").bold()
Divider()
TextField($profile.username)
}
Toggle(isOn: $profile.prefersNotifications) {
Text("Enable Notifications")
}
VStack(alignment: .leading, spacing: 20) {
Text("Seasonal Photo").bold()
SegmentedControl(selection: $profile.seasonalPhoto) {
ForEach(Profile.Season.allCases.identified(by: \.self)) { season in
Text(season.rawValue).tag(season)
}
}
}
.padding(.top)
VStack(alignment: .leading, spacing: 20) {
Text("Goal Date").bold()
DatePicker(
$profile.goalDate,
minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate),
maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate),
displayedComponents: .date
)
}
.padding(.top)
}
}
}
复制代码
4. The transmission delay Editors
To edit not take effect until the user exits the edit mode, we need to use a draft copy of the information during the editing, and then only when the user confirms the editor will assign a draft copy to be a true copy.
4.1 to ProfileHost
add a button to confirm.
And EditButton
providing a Cancel
button different Done
button closure editing operation which will be applied in the actual data.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
@State var draftProfile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
if self.mode?.value == .active {
Button(action: {
self.profile = self.draftProfile
self.mode?.animation().value = .inactive
}) {
Text("Done")
}
}
Spacer()
EditButton()
}
if self.mode?.value == .inactive {
ProfileSummary(profile: profile)
} else {
ProfileEditor(profile: $draftProfile)
}
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
4.2 onDisappear(perform:)
The method to clear the user clicks Cancel
to select the button to discard the value.
Otherwise, the old value is displayed the next time the edit mode is activated.
ProfileHost.swift
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var profile = Profile.default
@State var draftProfile = Profile.default
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
if self.mode?.value == .active {
Button(action: {
self.profile = self.draftProfile
self.mode?.animation().value = .inactive
}) {
Text("Done")
}
}
Spacer()
EditButton()
}
if self.mode?.value == .inactive {
ProfileSummary(profile: profile)
} else {
ProfileEditor(profile: $draftProfile)
.onDisappear {
self.draftProfile = self.profile
}
}
}
.padding()
}
}
#if DEBUG
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}
#endif
复制代码
Reproduced in: https: //juejin.im/post/5cfcd906f265da1b6028f669