Part 8: A Quick and Dirty App UI

xcode15-and-vision
Alan Francis 2 years ago
parent 8339073039
commit 7c9938e29f
  1. 21
      AppShared/ContentView.swift
  2. 3
      AppShared/JustOneThingApp.swift
  3. 25
      AppShared/SimpleListOfThingsView.swift
  4. 10
      JustOneThing.xcodeproj/project.pbxproj
  5. 85
      JustOneThing/ListOfThingsView.swift
  6. 24
      Model/PersistenceController.swift

@ -9,22 +9,11 @@ import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
#if os(iOS)
Text("Hello \(UIDevice.isIPad ? "iPadOS" : "iOS")")
#elseif os(macOS)
Text("Hello macOS")
#elseif os(tvOS)
Text("Hello tvOS")
#elseif os(watchOS)
Text("Hello watchOS")
#endif
}
.padding()
#if os(iOS) // iOS can add and remove
ListOfThingsView()
#elseif os(tvOS) || os(watchOS) || os(macOS) // macOS and tvOS and watchOS just show a readonly list
SimpleListOfThingsView()
#endif
}
}

@ -9,10 +9,11 @@ import SwiftUI
@main
struct JustOneThingApp: App {
let persistenceController = PersistenceController.shared
let persistenceController = PersistenceController.preview // or PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}

@ -0,0 +1,25 @@
//
// SimpleListOfThingsView.swift
// JustOneThingWatch
//
// Created by Alan Francis on 10/07/2023.
//
import SwiftUI
import CoreData
struct SimpleListOfThingsView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \PersistentThing.createdAt, ascending: true)],
animation: .default)
private var items: FetchedResults<PersistentThing>
var body: some View {
List {
ForEach(items) { item in
Text(item.text ?? "no text")
}
}
}
}

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
5405B2992A5C939C00B36DB6 /* ListOfThingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405B2982A5C939C00B36DB6 /* ListOfThingsView.swift */; };
5405B29B2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405B29A2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift */; };
5405B2BC2A5CA1A700B36DB6 /* SimpleListOfThingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405B29A2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift */; };
5407E5022A2A8AE200EF00B8 /* JustOneThingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5483903A2A2A773900BF5624 /* JustOneThingApp.swift */; };
5407E5032A2A8AE700EF00B8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5483903C2A2A773900BF5624 /* ContentView.swift */; };
5407E5042A2A8AEA00EF00B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5483903E2A2A773A00BF5624 /* Assets.xcassets */; };
@ -131,6 +134,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5405B2982A5C939C00B36DB6 /* ListOfThingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOfThingsView.swift; sourceTree = "<group>"; };
5405B29A2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleListOfThingsView.swift; sourceTree = "<group>"; };
5407E5062A35B96E00EF00B8 /* JustOneThingWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JustOneThingWatch.entitlements; sourceTree = "<group>"; };
5416C7372A56A867002196EE /* InterfaceExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceExtensions.swift; sourceTree = "<group>"; };
5431ED6D2A2A77C900515680 /* JustOneThingWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JustOneThingWatch.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -212,6 +217,7 @@
children = (
5483903A2A2A773900BF5624 /* JustOneThingApp.swift */,
5483903C2A2A773900BF5624 /* ContentView.swift */,
5405B29A2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift */,
5483903E2A2A773A00BF5624 /* Assets.xcassets */,
);
path = AppShared;
@ -271,6 +277,7 @@
children = (
5432A6032A582D5D00FE2351 /* Info.plist */,
548390402A2A773A00BF5624 /* JustOneThing.entitlements */,
5405B2982A5C939C00B36DB6 /* ListOfThingsView.swift */,
);
path = JustOneThing;
sourceTree = "<group>";
@ -532,6 +539,7 @@
files = (
5407E5032A2A8AE700EF00B8 /* ContentView.swift in Sources */,
54AE28422A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */,
5405B2BC2A5CA1A700B36DB6 /* SimpleListOfThingsView.swift in Sources */,
5407E5022A2A8AE200EF00B8 /* JustOneThingApp.swift in Sources */,
547201932A365868005B2FCC /* Widget.intentdefinition in Sources */,
54AE28482A58245700719A56 /* PersistenceController.swift in Sources */,
@ -544,7 +552,9 @@
files = (
5483903D2A2A773900BF5624 /* ContentView.swift in Sources */,
54AE28412A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */,
5405B2992A5C939C00B36DB6 /* ListOfThingsView.swift in Sources */,
5483903B2A2A773900BF5624 /* JustOneThingApp.swift in Sources */,
5405B29B2A5C94BA00B36DB6 /* SimpleListOfThingsView.swift in Sources */,
54928C222A35F89A00095445 /* Widget.intentdefinition in Sources */,
54AE28472A58245700719A56 /* PersistenceController.swift in Sources */,
);

@ -0,0 +1,85 @@
//
// ListOfThingsView.swift
// JustOneThing
//
// Created by Alan Francis on 10/07/2023.
//
import SwiftUI
import CoreData
struct ListOfThingsView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \PersistentThing.createdAt, ascending: true)],
animation: .default)
private var things: FetchedResults<PersistentThing>
private let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .medium
return formatter
}()
var body: some View {
NavigationView {
List {
ForEach(things) { thing in
NavigationLink {
Text(thing.text ?? "no text")
} label: {
Text(thing.text ?? "no text")
}
}
.onDelete(perform: deleteItems)
}
#if os(iOS)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
#endif
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newThing = PersistentThing(context: viewContext)
newThing.createdAt = Date()
newThing.uuid = UUID()
newThing.text = "thing created \(timeFormatter.string(from: Date()))"
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { things[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}

@ -84,4 +84,26 @@ extension PersistenceController {
}
extension PersistenceController {
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
var i = 1
for _ in 0..<10 {
let newItem = PersistentThing(context: viewContext)
newItem.text = "action \(i)"
newItem.createdAt = Date()
newItem.uuid = UUID()
i = i+1
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
}

Loading…
Cancel
Save