I am trying to create a menu picker for two or three text items. Small miracles, but I have it basically working. Problem is it uses a set, and I want to pass arrays. I need to modify PickerView so the Bound Parameter is an [String] instead of Set. Have been fighting this for a while now... Hoping for insights.
struct PickerView: View {
@Binding var colorChoices: Set<String>
let defaults = UserDefaults.standard
var body: some View {
let possibleColors = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
Menu {
ForEach(possibleColors, id: \.self) { item in
Button(action: {
if colorChoices.contains(item) {
colorChoices.remove(item)
} else {
colorChoices.insert(item)
}
}) {
HStack {
Text(item)
Spacer()
if colorChoices.contains(item) {
Image(systemName: "checkmark")
}
}
}
}
} label: {
Label("Select Items", systemImage: "ellipsis.circle")
}
Text("Selected Colors: \(colorChoices, format: .list(type: .and))")
}
}
#Preview("empty") {
@Previewable @State var colorChoices: Set<String> = []
PickerView(colorChoices: $colorChoices)
}
#Preview("Prefilled") {
@Previewable @State var colorChoices: Set<String> = ["Red","Blue"]
PickerView(colorChoices: $colorChoices)
}
My Content View is suppose to set default values the first time it runs, if no values already exist...
import SwiftUI
struct ContentView: View {
@State private var viewDidLoad: Bool = false
var body: some View {
HomeView()
.onAppear { // The following code should execute once the first time contentview loads. If a user navigates back to it, it should not execute a second time.
if viewDidLoad == false {
viewDidLoad = true
// load user defaults
let defaults = UserDefaults.standard
// set the default list of school colors, unless the user has already updated it prior
let defaultColorChoices: [String] = ["Black","Gold","Blue","Red","Green","White"]
let colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? defaultColorChoices
defaults.set(colorChoices, forKey: "ColorChoices")
}
}
}
}
#Preview {
ContentView()
}
PickLoader allows you to dynamically add or delete choices from the list...
import SwiftUI
struct PickLoader: View {
@State private var newColor: String = ""
var body: some View {
Form {
Section("Active Color Choices") {
// we should have set a default color list in contentview, so empty string should not be possible.
let defaults = UserDefaults.standard
let colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
List {
ForEach(colorChoices, id: \.self) { color in
Text(color)
}
.onDelete(perform: delete)
HStack {
TextField("Add a color", text: $newColor)
Button("Add"){
defaults.set(colorChoices + [newColor], forKey: "ColorChoices")
newColor = ""
}
}
}
}
}
.navigationTitle("Load Picker")
Button("Reset Default Choices") {
let defaults = UserDefaults.standard
//UserDefaults.standard.removeObject(forKey: "ColorChoices")
let colorChoices: [String] = ["Black","Gold","Blue","Red","Green","White"]
defaults.set(colorChoices, forKey: "ColorChoices")
}
Button("Clear all choices") {
let defaults = UserDefaults.standard
defaults.removeObject(forKey: "ColorChoices")
}
}
}
func delete(at offsets: IndexSet) {
let defaults = UserDefaults.standard
var colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
colorChoices.remove(atOffsets: offsets)
defaults.set(colorChoices, forKey: "ColorChoices")
}
#Preview {
PickLoader()
}
And finally HomeView is where I am testing from - to see if binding works properly...
import SwiftUI
struct HomeView: View {
//@State private var selection: Set<String> = []
//@State private var selection: Set<String> = ["Blue"]
@State private var selection: Set<String> = ["Blue", "Red"]
var body: some View {
NavigationStack {
List {
Section("Edit Picker") {
NavigationLink("Load Picker") {
PickLoader()
}
}
Section("Test Picker") {
PickerView(colorChoices: $selection)
}
Section("Current Results") {
Text("Current Selection: \(selection, format: .list(type: .and))")
}
}
.navigationBarTitle("Hello, World!")
}
}
}
#Preview {
HomeView()
}
If anyone uses this code, there are still issues - buttons on Loader don't update the list on the screen for one, and also dealing with deleting choices that are in use - how does picker deal with them? Probably simply add to the list automatically and move on. If anyone has insights on any of this also, great! but first I just need to understand how to accept an array instead of a set in pickerView.
I have tried using a computed value with a get and set, but I can't seem to get it right.
Thanks for any assistance! Cheers!
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Someone smarter than me please tell me if this will work... I want to have an edit screen for a SwiftData class. Auto Save is on, but I want to be able to revert changes. I have read all about sending a copy in, sending an ID and creating a new context without autosave, etc.
What about simply creating a second set of ephemeral values in the actual original model. initialize them with the actual fields. Edit them and if you save changes, migrate that back to the permanent fields before returning.
Don't have to manage a list of @State variables corresponding to every model field, and don't have to worry about a second model context.
Anyone have any idea of the memory / performance implications of doing it this way, and if it is even possible? Does this just make a not quite simple situation even more complicated? Haven't tried yet, just got inspiration from reading some medium content on attributes on my lunch break, and wondering if I am just silly for considering it.
Been messing with this for a while... And cannot figure things out...
Have a basic router implemented...
import Foundation
import SwiftUI
enum Route: Hashable {
case profile(userID: String)
case settings
case someList
case detail(id: String)
}
@Observable
class Router {
var path = NavigationPath()
private var destinations: [Route] = []
var currentDestination: Route? {
destinations.last
}
var navigationHistory: [Route] {
destinations
}
func navigate(to destination: Route) {
destinations.append(destination)
path.append(destination)
}
}
And have gotten this to work with very basic views as below...
import SwiftUI
struct ContentView: View {
@State private var router = Router()
var body: some View {
NavigationStack(path: $router.path) {
VStack {
Button("Go to Profile") {
router.navigate(to: .profile(userID: "user123"))
}
Button("Go to Settings") {
router.navigate(to: .settings)
}
Button("Go to Listings") {
router.navigate(to: .someList)
}
.navigationDestination(for: Route.self) { destination in
destinationView(for: destination)
}
}
}
.environment(router)
}
@ViewBuilder
private func destinationView(for destination: Route) -> some View {
switch destination {
case .profile(let userID):
ProfileView(userID: userID)
case .settings:
SettingsView()
case .someList:
SomeListofItemsView()
case .detail(id: let id):
ItemDetailView(id: id)
}
}
}
#Preview {
ContentView()
}
I then have other views named ProfileView, SettingsView, SomeListofItemsView, and ItemDetailView....
Navigation works AWESOME from ContentView. Expanding this to SomeListofItemsView works as well... Allowing navigation to ItemDetailView, with one problem... I cannot figure out how to inject the Canvas with a router instance from the environment, so it will preview properly... (No idea if I said this correctly, but hopefully you know what I mean)
import SwiftUI
struct SomeListofItemsView: View {
@Environment(Router.self) private var router
var body: some View {
VStack {
Text("Some List of Items View")
Button("Go to Item Details") {
router.navigate(to: .detail(id: "Test Item from List"))
}
}
}
}
//#Preview {
// SomeListofItemsView()
//}
As you can see, the Preview is commented out. I know I need some sort of ".environment" added somewhere, but am hitting a wall on figuring out exactly how to do this.
Everything works great starting from contentview (with the canvas)... previewing every screen you navigate to and such, but you cannot preview the List view directly.
I am using this in a few other programs, but just getting frustrated not having the Canvas available to me to fine tune things... Especially when using navigation on almost all views... Any help would be appreciated.
I am struggling with exactly how to set up SwiftData relationships, beyond the single relationship model...
Let's say I have a school. Each school offers a set of classes. Each class is taught by one teacher and attended by several students. Teachers may teach more than one class, but only at one school. Similarly students may attend more than one class, but only at one school. Classes themselves may be offered at more than one school.
Can someone create a class for School, SchoolClass, Teacher, and Student with id, name, and relationships... I have tried it unsuccessfully about 10 different ways at this point.
My most recent is below... I am struggling getting beyond a school listing in the app, and I'll cross that bridge next. I am just wondering if all the trouble I am having is because I am not smart with the class definitions. And wondering if this is to complex for SwiftData and CoreData is the requirement.
This is not a real app, just my way of really trying to get a handle on Swift Data models and Navigation.
I am very new to Swift, and will take any and all suggestions with enthusiasm! Thanks for taking the time.
import Foundation
import SwiftData
@Model
class School: Identifiable {
var id: UUID = UUID()
var name: String
var mascot: String
var teachers: [Teacher]
var schoolClasses: [SchoolClass]
init (name: String, mascot: String = "", teachers: [Teacher] = [], schoolClasses: [SchoolClass] = []) {
self.name = name
self.mascot = mascot
self.teachers = teachers
}
class SchoolClass: Identifiable {
var id: UUID = UUID()
var name: String
var teacher: Teacher?
var students: [Student] = []
init (name: String, teacher: Teacher? = nil, students: [Student] = []) {
self.name = name
self.teacher = teacher
self.students = students
}
}
class Teacher: Identifiable {
var id: UUID = UUID()
var name: String
var tenured: Bool
var school: School?
var students: [Student] = []
init (name: String, tenured: Bool = false, students: [Student] = []) {
self.name = name
self.tenured = tenured
self.students = students
}
}
class Student: Identifiable {
var id: UUID = UUID()
var name: String
var grade: Int?
var teacher: Teacher?
init (name: String, grade: Int? = nil, teacher: Teacher? = nil) {
self.name = name
self.grade = grade
self.teacher = teacher
}
}
}
Hi all,
Very new to this. Just getting into swift data, and am frustrated with the canvas not working with modelContainers in SwiftData. My understanding is that they work if inMemory = true, but not in the default case where data is persistent after an app is quit.
Can anyone tell me if it is possible to conditionally create the modelContainer type based on a flag... If Bool:Canvas then inMemory = True, Else False... Then using this flag for all data models so my list views populate on the canvas, without having to run the simulator each time... I would assume you could also pre-populate the inMemory option if it is empty also...
Or is there a simple and obvious solution that I am oblivious to.
If it is possible, is it worth the time, hassle, and any possible issues?