Skip to content

Commit 3d908db

Browse files
committed
macos tutorial
1 parent 932b984 commit 3d908db

11 files changed

+449
-6
lines changed

TaskManager.xcodeproj/project.pbxproj

+44
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
175799DE2A934C4E00948A2E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799DD2A934C4E00948A2E /* ContentView.swift */; };
1212
175799E02A934C5200948A2E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 175799DF2A934C5200948A2E /* Assets.xcassets */; };
1313
175799E32A934C5200948A2E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 175799E22A934C5200948A2E /* Preview Assets.xcassets */; };
14+
175799EC2A934CCC00948A2E /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799EB2A934CCC00948A2E /* Task.swift */; };
15+
175799EE2A934DE100948A2E /* TaskGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799ED2A934DE100948A2E /* TaskGroup.swift */; };
16+
175799F02A934ECE00948A2E /* TaskSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799EF2A934ECE00948A2E /* TaskSection.swift */; };
17+
175799F22A934FF800948A2E /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799F12A934FF800948A2E /* SidebarView.swift */; };
18+
175799F42A93500A00948A2E /* StaticTaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799F32A93500A00948A2E /* StaticTaskListView.swift */; };
19+
175799F62A93560D00948A2E /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799F52A93560D00948A2E /* TaskListView.swift */; };
20+
175799F82A93572900948A2E /* TaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 175799F72A93572900948A2E /* TaskView.swift */; };
1421
/* End PBXBuildFile section */
1522

1623
/* Begin PBXFileReference section */
@@ -20,6 +27,13 @@
2027
175799DF2A934C5200948A2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2128
175799E22A934C5200948A2E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2229
175799E42A934C5200948A2E /* TaskManager.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TaskManager.entitlements; sourceTree = "<group>"; };
30+
175799EB2A934CCC00948A2E /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
31+
175799ED2A934DE100948A2E /* TaskGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskGroup.swift; sourceTree = "<group>"; };
32+
175799EF2A934ECE00948A2E /* TaskSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSection.swift; sourceTree = "<group>"; };
33+
175799F12A934FF800948A2E /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
34+
175799F32A93500A00948A2E /* StaticTaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTaskListView.swift; sourceTree = "<group>"; };
35+
175799F52A93560D00948A2E /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
36+
175799F72A93572900948A2E /* TaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskView.swift; sourceTree = "<group>"; };
2337
/* End PBXFileReference section */
2438

2539
/* Begin PBXFrameworksBuildPhase section */
@@ -54,6 +68,11 @@
5468
children = (
5569
175799DB2A934C4E00948A2E /* TaskManagerApp.swift */,
5670
175799DD2A934C4E00948A2E /* ContentView.swift */,
71+
175799F12A934FF800948A2E /* SidebarView.swift */,
72+
175799F52A93560D00948A2E /* TaskListView.swift */,
73+
175799F72A93572900948A2E /* TaskView.swift */,
74+
175799F32A93500A00948A2E /* StaticTaskListView.swift */,
75+
175799EA2A934CBF00948A2E /* Model */,
5776
175799DF2A934C5200948A2E /* Assets.xcassets */,
5877
175799E42A934C5200948A2E /* TaskManager.entitlements */,
5978
175799E12A934C5200948A2E /* Preview Content */,
@@ -69,6 +88,16 @@
6988
path = "Preview Content";
7089
sourceTree = "<group>";
7190
};
91+
175799EA2A934CBF00948A2E /* Model */ = {
92+
isa = PBXGroup;
93+
children = (
94+
175799EB2A934CCC00948A2E /* Task.swift */,
95+
175799ED2A934DE100948A2E /* TaskGroup.swift */,
96+
175799EF2A934ECE00948A2E /* TaskSection.swift */,
97+
);
98+
path = Model;
99+
sourceTree = "<group>";
100+
};
72101
/* End PBXGroup section */
73102

74103
/* Begin PBXNativeTarget section */
@@ -139,8 +168,15 @@
139168
isa = PBXSourcesBuildPhase;
140169
buildActionMask = 2147483647;
141170
files = (
171+
175799F82A93572900948A2E /* TaskView.swift in Sources */,
172+
175799F42A93500A00948A2E /* StaticTaskListView.swift in Sources */,
142173
175799DE2A934C4E00948A2E /* ContentView.swift in Sources */,
174+
175799EC2A934CCC00948A2E /* Task.swift in Sources */,
175+
175799F02A934ECE00948A2E /* TaskSection.swift in Sources */,
176+
175799EE2A934DE100948A2E /* TaskGroup.swift in Sources */,
143177
175799DC2A934C4E00948A2E /* TaskManagerApp.swift in Sources */,
178+
175799F22A934FF800948A2E /* SidebarView.swift in Sources */,
179+
175799F62A93560D00948A2E /* TaskListView.swift in Sources */,
144180
);
145181
runOnlyForDeploymentPostprocessing = 0;
146182
};
@@ -280,15 +316,19 @@
280316
ENABLE_PREVIEWS = YES;
281317
GENERATE_INFOPLIST_FILE = YES;
282318
INFOPLIST_KEY_NSHumanReadableCopyright = "";
319+
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
283320
LD_RUNPATH_SEARCH_PATHS = (
284321
"$(inherited)",
285322
"@executable_path/../Frameworks",
286323
);
287324
MARKETING_VERSION = 1.0;
288325
PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.TaskManager;
289326
PRODUCT_NAME = "$(TARGET_NAME)";
327+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
328+
SUPPORTS_MACCATALYST = NO;
290329
SWIFT_EMIT_LOC_STRINGS = YES;
291330
SWIFT_VERSION = 5.0;
331+
TARGETED_DEVICE_FAMILY = 1;
292332
};
293333
name = Debug;
294334
};
@@ -307,15 +347,19 @@
307347
ENABLE_PREVIEWS = YES;
308348
GENERATE_INFOPLIST_FILE = YES;
309349
INFOPLIST_KEY_NSHumanReadableCopyright = "";
350+
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
310351
LD_RUNPATH_SEARCH_PATHS = (
311352
"$(inherited)",
312353
"@executable_path/../Frameworks",
313354
);
314355
MARKETING_VERSION = 1.0;
315356
PRODUCT_BUNDLE_IDENTIFIER = com.karinprater.TaskManager;
316357
PRODUCT_NAME = "$(TARGET_NAME)";
358+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
359+
SUPPORTS_MACCATALYST = NO;
317360
SWIFT_EMIT_LOC_STRINGS = YES;
318361
SWIFT_VERSION = 5.0;
362+
TARGETED_DEVICE_FAMILY = 1;
319363
};
320364
name = Release;
321365
};

TaskManager/Assets.xcassets/AccentColor.colorset/Contents.json

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
{
22
"colors" : [
33
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "1.000",
9+
"green" : "0.082",
10+
"red" : "0.307"
11+
}
12+
},
413
"idiom" : "universal"
514
}
615
],

TaskManager/ContentView.swift

+30-6
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,38 @@
88
import SwiftUI
99

1010
struct ContentView: View {
11+
12+
@State private var selection: TaskSection? = TaskSection.all
13+
14+
@State private var allTasks = Task.examples()
15+
@State private var userCreatedGroups: [TaskGroup] = TaskGroup.examples()
16+
@State private var searchTerm: String = ""
17+
1118
var body: some View {
12-
VStack {
13-
Image(systemName: "globe")
14-
.imageScale(.large)
15-
.foregroundStyle(.tint)
16-
Text("Hello, world!")
19+
NavigationSplitView {
20+
SidebarView(userCreatedGroups: $userCreatedGroups,
21+
selection: $selection)
22+
23+
} detail: {
24+
25+
if searchTerm.isEmpty {
26+
switch selection {
27+
case .all:
28+
TaskListView(title: "All", tasks: $allTasks)
29+
case .done:
30+
StaticTaskListView(title: "All", tasks: allTasks.filter({ $0.isCompleted }))
31+
case .upcoming:
32+
StaticTaskListView(title: "All", tasks: allTasks.filter({ !$0.isCompleted }))
33+
case .list(let taskGroup):
34+
StaticTaskListView(title: taskGroup.title, tasks: taskGroup.tasks)
35+
case .none:
36+
Text("none")
37+
}
38+
} else {
39+
StaticTaskListView(title: "All", tasks: allTasks.filter({ $0.title.contains(searchTerm) }))
40+
}
1741
}
18-
.padding()
42+
.searchable(text: $searchTerm)
1943
}
2044
}
2145

TaskManager/Model/Task.swift

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Task.swift
3+
// TaskManager
4+
//
5+
// Created by Karin Prater on 21.08.2023.
6+
//
7+
8+
import Foundation
9+
10+
struct Task: Identifiable, Hashable {
11+
12+
let id = UUID()
13+
var title: String
14+
var isCompleted: Bool
15+
var dueDate: Date
16+
var details: String?
17+
18+
init(title: String, isCompleted: Bool = false, dueDate: Date = Date(), details: String? = nil) {
19+
self.title = title
20+
self.isCompleted = isCompleted
21+
self.dueDate = dueDate
22+
self.details = details
23+
}
24+
25+
static func example() -> Task {
26+
Task(title: "Buy milk", dueDate: Calendar.current.date(byAdding: .day, value: 2, to: Date())!)
27+
}
28+
29+
static func examples() -> [Task] {
30+
[
31+
Task(title: "Here’s to the crazy ones"),
32+
Task(title: "the misfits, the rebels, the troublemakers", isCompleted: true),
33+
Task(title: "the round pegs in the square holes…", dueDate: Calendar.current.date(byAdding: .day, value: 1, to: Date())!),
34+
Task(title: "the ones who see things differently — they’re not fond of rules…"),
35+
Task(title: "You can quote them, disagree with them, glorify or vilify them"),
36+
Task(title: "but the only thing you can’t do is ignore them because they change things…"),
37+
Task(title: "they push the human race forward", isCompleted: true, dueDate: Calendar.current.date(byAdding: .day, value: 4, to: Date())!),
38+
Task(title: "and while some may see them as the crazy ones"),
39+
Task(title: "we see genius", isCompleted: true, dueDate: Calendar.current.date(byAdding: .day, value: 2, to: Date())!),
40+
Task(title: "because the ones who are crazy enough to think that they can change the world"),
41+
Task(title: "are the ones who do.")
42+
]
43+
44+
}
45+
46+
}

TaskManager/Model/TaskGroup.swift

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// TaskGroup.swift
3+
// TaskManager
4+
//
5+
// Created by Karin Prater on 21.08.2023.
6+
//
7+
8+
import Foundation
9+
10+
struct TaskGroup: Identifiable, Hashable {
11+
let id = UUID()
12+
var title: String
13+
let creationDate: Date
14+
var tasks: [Task]
15+
16+
init(title: String, tasks: [Task] = []) {
17+
self.title = title
18+
self.creationDate = Date()
19+
self.tasks = tasks
20+
}
21+
22+
23+
static func example() -> TaskGroup {
24+
let task1 = Task(title: "Buy groceries")
25+
let task2 = Task(title: "Finish project")
26+
let task3 = Task(title: "Call dentist")
27+
28+
var group = TaskGroup(title: "Personal")
29+
group.tasks = [task1, task2, task3]
30+
return group
31+
}
32+
33+
static func examples() -> [TaskGroup] {
34+
let group1 = TaskGroup.example()
35+
let group2 = TaskGroup(title: "New list")
36+
return [group1, group2]
37+
}
38+
}

TaskManager/Model/TaskSection.swift

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// TaskSection.swift
3+
// TaskManager
4+
//
5+
// Created by Karin Prater on 21.08.2023.
6+
//
7+
8+
import Foundation
9+
10+
enum TaskSection: Identifiable, CaseIterable, Hashable {
11+
12+
case all
13+
case done
14+
case upcoming
15+
case list(TaskGroup)
16+
17+
var id: String {
18+
switch self {
19+
case .all:
20+
"all"
21+
case .done:
22+
"done"
23+
case .upcoming:
24+
"upcoming"
25+
case .list(let taskGroup):
26+
taskGroup.id.uuidString
27+
}
28+
}
29+
30+
var displayName: String {
31+
switch self {
32+
case .all:
33+
"All"
34+
case .done:
35+
"Done"
36+
case .upcoming:
37+
"Upcoming"
38+
case .list(let taskGroup):
39+
taskGroup.title
40+
}
41+
}
42+
43+
var iconName: String {
44+
switch self {
45+
case .all:
46+
"star"
47+
case .done:
48+
"checkmark.circle"
49+
case .upcoming:
50+
"calendar"
51+
case .list(_):
52+
"folder"
53+
}
54+
}
55+
56+
static var allCases: [TaskSection] {
57+
[.all, .done, .upcoming]
58+
}
59+
60+
static func == (lhs: TaskSection, rhs: TaskSection) -> Bool {
61+
lhs.id == rhs.id
62+
}
63+
}

TaskManager/SidebarView.swift

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// SidebarView.swift
3+
// TaskManager
4+
//
5+
// Created by Karin Prater on 21.08.2023.
6+
//
7+
8+
import SwiftUI
9+
10+
struct SidebarView: View {
11+
12+
@Binding var userCreatedGroups: [TaskGroup]
13+
@Binding var selection: TaskSection?
14+
15+
var body: some View {
16+
List(selection: $selection) {
17+
Section("Favorites") {
18+
ForEach(TaskSection.allCases) { selection in
19+
Label(selection.displayName,
20+
systemImage: selection.iconName)
21+
.tag(selection)
22+
}
23+
}
24+
25+
Section("Your Groups") {
26+
ForEach($userCreatedGroups) { $group in
27+
HStack {
28+
Image(systemName: "folder")
29+
TextField("New Group", text: $group.title)
30+
}
31+
.tag(TaskSection.list(group))
32+
.contextMenu {
33+
Button("Delete", role: .destructive) {
34+
if let index = userCreatedGroups.firstIndex(where: { $0.id == group.id}) {
35+
userCreatedGroups.remove(at: index)
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+
.safeAreaInset(edge: .bottom) {
43+
Button(action: {
44+
let newGroup = TaskGroup(title: "New Group")
45+
userCreatedGroups.append(newGroup)
46+
}, label: {
47+
Label("Add Group", systemImage: "plus.circle")
48+
})
49+
.buttonStyle(.borderless)
50+
.foregroundColor(.accentColor)
51+
.padding()
52+
.frame(maxWidth: .infinity, alignment: .leading)
53+
.keyboardShortcut(/*@START_MENU_TOKEN@*/KeyEquivalent("a")/*@END_MENU_TOKEN@*/, modifiers: /*@START_MENU_TOKEN@*/.command/*@END_MENU_TOKEN@*/)
54+
}
55+
56+
}
57+
}
58+
59+
#Preview {
60+
SidebarView(userCreatedGroups: .constant(TaskGroup.examples()),
61+
selection: .constant(.all))
62+
.listStyle(.sidebar)
63+
}

0 commit comments

Comments
 (0)