Skip to content

Commit 8790e77

Browse files
committed
Add "App Design and Layout" > "Working with UI Controls"
1 parent bc18c83 commit 8790e77

File tree

9 files changed

+388
-1
lines changed

9 files changed

+388
-1
lines changed

App-Design-and-Layout/HikeBadge.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// HikeBadge.swift
3+
// App-Design-and-Layout
4+
//
5+
// Created by 王炜 on 2019/6/9.
6+
//
7+
8+
import SwiftUI
9+
10+
struct HikeBadge: View {
11+
var name: String
12+
var body: some View {
13+
VStack(alignment: .center) {
14+
Badge()
15+
.frame(width: 300, height: 300)
16+
.scaleEffect(1.0 / 3.0)
17+
.frame(width: 100, height: 100)
18+
Text(name)
19+
.font(.caption)
20+
.accessibility(label: Text("Badge for \(name)."))
21+
}
22+
}
23+
}
24+
25+
#if DEBUG
26+
struct HikeBadge_Previews : PreviewProvider {
27+
static var previews: some View {
28+
HikeBadge(name: "Preview Testing")
29+
}
30+
}
31+
#endif
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
See LICENSE folder for this sample’s licensing information.
3+
4+
Abstract:
5+
A view showing the details for a hike.
6+
*/
7+
8+
import SwiftUI
9+
10+
struct HikeDetail: View {
11+
let hike: Hike
12+
@State var dataToShow = \Hike.Observation.elevation
13+
14+
var buttons = [
15+
("Elevation", \Hike.Observation.elevation),
16+
("Heart Rate", \Hike.Observation.heartRate),
17+
("Pace", \Hike.Observation.pace),
18+
]
19+
20+
var body: some View {
21+
return VStack {
22+
HikeGraph(hike: hike, path: dataToShow)
23+
.frame(height: 200, alignment: .center)
24+
25+
HStack(spacing: 25) {
26+
ForEach(buttons.identified(by: \.0)) { value in
27+
Button(action: {
28+
self.dataToShow = value.1
29+
}) {
30+
Text(verbatim: value.0)
31+
.font(.system(size: 15))
32+
.color(value.1 == self.dataToShow
33+
? Color.gray
34+
: Color.accentColor)
35+
.animation(nil)
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+
43+
#if DEBUG
44+
struct HikeDetail_Previews: PreviewProvider {
45+
static var previews: some View {
46+
HikeDetail(hike: hikeData[0])
47+
}
48+
}
49+
#endif

App-Design-and-Layout/HikeView.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
See LICENSE folder for this sample’s licensing information.
3+
4+
Abstract:
5+
A view displaying inforamtion about a hike, including an elevation graph.
6+
*/
7+
8+
import SwiftUI
9+
10+
struct HikeView: View {
11+
var hike: Hike
12+
@State private var showDetail = false
13+
14+
var transition: AnyTransition {
15+
let insertion = AnyTransition.move(edge: .trailing)
16+
.combined(with: .opacity)
17+
let removal = AnyTransition.scale()
18+
.combined(with: .opacity)
19+
return .asymmetric(insertion: insertion, removal: removal)
20+
}
21+
22+
var body: some View {
23+
VStack {
24+
HStack {
25+
HikeGraph(hike: hike, path: \.elevation)
26+
.frame(width: 50, height: 30)
27+
.animation(nil)
28+
29+
VStack(alignment: .leading) {
30+
Text(verbatim: hike.name)
31+
.font(.headline)
32+
Text(verbatim: hike.distanceText)
33+
}
34+
35+
Spacer()
36+
37+
Button(action: {
38+
withAnimation {
39+
self.showDetail.toggle()
40+
}
41+
}) {
42+
Image(systemName: "chevron.right.circle")
43+
.imageScale(.large)
44+
.rotationEffect(.degrees(showDetail ? 90 : 0))
45+
.scaleEffect(showDetail ? 1.5 : 1)
46+
.padding()
47+
}
48+
}
49+
50+
if showDetail {
51+
HikeDetail(hike: hike)
52+
.transition(transition)
53+
}
54+
}
55+
}
56+
}
57+
58+
#if DEBUG
59+
struct HikeView_Previews: PreviewProvider {
60+
static var previews: some View {
61+
VStack {
62+
HikeView(hike: hikeData[0])
63+
.padding()
64+
Spacer()
65+
}
66+
}
67+
}
68+
#endif

App-Design-and-Layout/Home.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct CategoryHome: View {
4444
.imageScale(.large)
4545
.accessibility(label: Text("User Profile"))
4646
.padding(),
47-
destination: Text("User Profile")
47+
destination: ProfileHost()
4848
)
4949
)
5050
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
See LICENSE folder for this sample’s licensing information.
3+
4+
Abstract:
5+
A model object that stores user profile data.
6+
*/
7+
8+
import Foundation
9+
10+
struct Profile {
11+
var username: String
12+
var prefersNotifications: Bool
13+
var seasonalPhoto: Season
14+
var goalDate: Date
15+
16+
static let `default` = Self(username: "g_kumar", prefersNotifications: true, seasonalPhoto: .winter)
17+
18+
init(username: String, prefersNotifications: Bool = true, seasonalPhoto: Season = .winter) {
19+
self.username = username
20+
self.prefersNotifications = prefersNotifications
21+
self.seasonalPhoto = seasonalPhoto
22+
self.goalDate = Date()
23+
}
24+
25+
enum Season: String, CaseIterable {
26+
case spring = "🌷"
27+
case summer = "🌞"
28+
case autumn = "🍂"
29+
case winter = "☃️"
30+
}
31+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// ProfileEditor.swift
3+
// App-Design-and-Layout
4+
//
5+
// Created by 王炜 on 2019/6/9.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ProfileEditor: View {
11+
@Binding var profile: Profile
12+
13+
var body: some View {
14+
List {
15+
HStack {
16+
Text("Username").bold()
17+
Divider()
18+
TextField($profile.username)
19+
}
20+
21+
Toggle(isOn: $profile.prefersNotifications) {
22+
Text("Enable Notifications")
23+
}
24+
25+
VStack(alignment: .leading, spacing: 20) {
26+
Text("Seasonal Photo").bold()
27+
28+
SegmentedControl(selection: $profile.seasonalPhoto) {
29+
ForEach(Profile.Season.allCases.identified(by: \.self)) { season in
30+
Text(season.rawValue).tag(season)
31+
}
32+
}
33+
}
34+
.padding(.top)
35+
36+
VStack(alignment: .leading, spacing: 20) {
37+
Text("Goal Date").bold()
38+
DatePicker(
39+
$profile.goalDate,
40+
minimumDate: Calendar.current.date(byAdding: .year, value: -1, to: profile.goalDate),
41+
maximumDate: Calendar.current.date(byAdding: .year, value: 1, to: profile.goalDate),
42+
displayedComponents: .date
43+
)
44+
}
45+
.padding(.top)
46+
}
47+
}
48+
}
49+
50+
#if DEBUG
51+
struct ProfileEditor_Previews: PreviewProvider {
52+
static var previews: some View {
53+
ProfileEditor(profile: .constant(.default))
54+
}
55+
}
56+
#endif
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// ProfileHost.swift
3+
// App-Design-and-Layout
4+
//
5+
// Created by 王炜 on 2019/6/9.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ProfileHost: View {
11+
@Environment(\.editMode) var mode
12+
@State var profile = Profile.default
13+
@State var draftProfile = Profile.default
14+
15+
var body: some View {
16+
VStack(alignment: .leading, spacing: 20) {
17+
HStack {
18+
if self.mode?.value == .active {
19+
Button(action: {
20+
self.profile = self.draftProfile
21+
self.mode?.animation().value = .inactive
22+
}) {
23+
Text("Done")
24+
}
25+
}
26+
27+
Spacer()
28+
29+
EditButton()
30+
}
31+
if self.mode?.value == .inactive {
32+
ProfileSummary(profile: profile)
33+
} else {
34+
ProfileEditor(profile: $draftProfile)
35+
.onDisappear {
36+
self.draftProfile = self.profile
37+
}
38+
}
39+
}
40+
.padding()
41+
}
42+
}
43+
44+
#if DEBUG
45+
struct ProfileHost_Previews: PreviewProvider {
46+
static var previews: some View {
47+
ProfileHost()
48+
}
49+
}
50+
#endif
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// ProfileSummary.swift
3+
// App-Design-and-Layout
4+
//
5+
// Created by 王炜 on 2019/6/9.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ProfileSummary: View {
11+
var profile: Profile
12+
13+
static var goalFormat: DateFormatter {
14+
let formatter = DateFormatter()
15+
formatter.dateFormat = "MMMM d, yyyy"
16+
return formatter
17+
}
18+
19+
var body: some View {
20+
List {
21+
Text(profile.username)
22+
.bold()
23+
.font(.title)
24+
25+
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
26+
27+
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
28+
29+
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
30+
31+
VStack(alignment: .leading) {
32+
Text("Completed Badges")
33+
.font(.headline)
34+
ScrollView {
35+
HStack {
36+
HikeBadge(name: "First Hike")
37+
38+
HikeBadge(name: "Earth Day")
39+
.hueRotation(Angle(degrees: 90))
40+
41+
42+
HikeBadge(name: "Tenth Hike")
43+
.grayscale(0.5)
44+
.hueRotation(Angle(degrees: 45))
45+
}
46+
}
47+
.frame(height: 140)
48+
}
49+
50+
VStack(alignment: .leading) {
51+
Text("Recent Hikes")
52+
.font(.headline)
53+
54+
HikeView(hike: hikeData[0])
55+
}
56+
}
57+
}
58+
}
59+
60+
#if DEBUG
61+
struct ProfileSummary_Previews: PreviewProvider {
62+
static var previews: some View {
63+
ProfileSummary(profile: Profile.default)
64+
}
65+
}
66+
#endif

0 commit comments

Comments
 (0)