|
Does adding a $ to item at the start of the block fix the error?
ForEach(values){ $item in
DesignView(item: $item)
}
The following article describes SwiftUI bindings:
What is the @Binding property wrapper?
|
|
No it doesn't. The problem is that its trying to convert Binding< UpcomingExamsView.ExamData > to Binding< Data >
|
|
try this,
add the binding to your DesignView for the bool value i.e.:
@Binding var completed: Bool
Then in your UpcomingExamsView replace item.completed with
$item.completed
as necessary.
My understanding for the above is that, whenever the @Binding var completed will change the $item.completed will update too.
|
|
I couldn't get it to work with seperate views so I combined it in one view
Here's my updated code.
import SwiftUI
struct UpcomingExamsView: View {
struct Data: Identifiable {
let id = UUID()
var name: String
var completed: Bool
}
@State private var values: [Data] = [
Data(name: "John", completed: false),
Data(name: "Katherine", completed: false),
Data(name: "Jonas", completed: false),
]
func addNewData() {
values.append(Data(name: examName, completed: false))
}
@State var examName: String = ""
@FocusState var keyboardFocused: Bool
@State var DisclosureGroupOpen = false
@State var addNewExam = false
@Binding var Wallpaper: Image
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
ScrollView {
VStack {
ForEach($values){ $item in
if item.completed == false {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
item.completed.toggle()
}
Text(item.name+" \(item.completed.description)")
}
}
.padding(.horizontal)
}
}
DisclosureGroup(isExpanded: $DisclosureGroupOpen) {
ScrollView {
VStack {
ForEach($values){ $item in
if item.completed {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
item.completed.toggle()
}
Text(item.name+" \(item.completed.description)")
}
}
.padding(.horizontal)
}
}
}
}
}
label: {
RoundedRectangle(cornerRadius: 8)
.frame(width: 120, height: 20)
.foregroundColor(colorScheme == .dark ? Color(.systemGray6) : Color.gray.opacity(0.6))
.padding(.horizontal)
.overlay(
HStack {
if DisclosureGroupOpen == false {
Image(systemName: "chevron.right")
.foregroundColor(.white)
} else {
Image(systemName: "chevron.down")
.foregroundColor(.white)
}
Text("Completed")
.foregroundColor(.white)
})
}.accentColor(.clear)
}
}
.navigationTitle("Upcoming Exams")
VStack {
Spacer()
if addNewExam {
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 100)
.foregroundColor(Color(.systemBackground))
.overlay(VStack{
TextField ("Add an Exam", text: $examName)
.focused($keyboardFocused)
.padding()
.onAppear{keyboardFocused = true}
.onSubmit {
addNewData()
addNewExam = false
examName = ""
}
Spacer()
})
}
if keyboardFocused == false {
Rectangle()
.onAppear {
addNewExam = false
}
.onTapGesture {
addNewExam = true
}
.frame(maxWidth: .infinity, maxHeight: 50)
.foregroundColor(Color(.systemBackground))
.overlay(
HStack {
Image(systemName: "plus")
.resizable()
.frame(width: 20, height: 20)
Text("Add an Exam")
})
}
}
}
}
}
But how can I save the Data to be available after the App's restart?
|
|
Saving in UserDeFaults , use it for learning purposes and small data:
Saving
@State private var values: [Data] = [
Data(name: "John", completed: false),
Data(name: "Katherine", completed: false),
Data(name: "Jonas", completed: false),
] {
//Saving into UserDefaults
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(values) {
UserDefaults.standard.set(encoded, forKey: "values")
}
}
}
Loading
init() {
if let savedItems = UserDefaults.standard.data(forKey: "values") {
if let decodedItems = try? JSONDecoder().decode([Data].self, from: savedItems) {
values = decodedItems
return
}
}
values = [] //This is in case the loading fails, you can even add your previous Array
}
and...add Codable to Data struct
struct Data: Identifiable, Codable {
let id = UUID()
var name: String
var completed: Bool
}
|
|
It doesn't work.
import SwiftUI
struct UpcomingExamsView: View {
struct Data: Identifiable, Codable {
let id = UUID()
var name: String
var completed: Bool
}
@State private var values: [Data] = [
Data(name: "John", completed: false),
Data(name: "Katherine", completed: false),
Data(name: "Jonas", completed: false),
] {
//Saving into UserDefaults
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(values) {
UserDefaults.standard.set(encoded, forKey: "values")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "values") {
if let decodedItems = try? JSONDecoder().decode([Data].self, from: savedItems) {
values = decodedItems
return
}
}
values = [] //This is in case the loading fails, you can even add your previous Array
}
func addNewData() {
values.append(Data(name: examName, completed: false))
}
@State var examName: String = ""
@FocusState var keyboardFocused: Bool
@State var DisclosureGroupOpen = false
@State var addNewExam = false
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
ScrollView {
VStack {
ForEach($values){ $item in
if item.completed == false {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
item.completed.toggle()
}
Text(item.name+" \(item.completed.description)")
}
}
.padding(.horizontal)
}
}
DisclosureGroup(isExpanded: $DisclosureGroupOpen) {
ScrollView {
VStack {
ForEach($values){ $item in
if item.completed {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
item.completed.toggle()
}
Text(item.name+" \(item.completed.description)")
}
}
.padding(.horizontal)
}
}
}
}
}
label: {
RoundedRectangle(cornerRadius: 8)
.frame(width: 120, height: 20)
.foregroundColor(colorScheme == .dark ? Color(.systemGray6) : Color.gray.opacity(0.6))
.padding(.horizontal)
.overlay(
HStack {
if DisclosureGroupOpen == false {
Image(systemName: "chevron.right")
.foregroundColor(.white)
} else {
Image(systemName: "chevron.down")
.foregroundColor(.white)
}
Text("Completed")
.foregroundColor(.white)
})
}.accentColor(.clear)
}
}
.navigationTitle("Upcoming Exams")
VStack {
Spacer()
if addNewExam {
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 100)
.foregroundColor(Color(.systemBackground))
.overlay(VStack{
TextField ("Add an Exam", text: $examName)
.focused($keyboardFocused)
.padding()
.onAppear{keyboardFocused = true}
.onSubmit {
addNewData()
addNewExam = false
examName = ""
}
Spacer()
})
}
if keyboardFocused == false {
Rectangle()
.onAppear {
addNewExam = false
}
.onTapGesture {
addNewExam = true
}
.frame(maxWidth: .infinity, maxHeight: 50)
.foregroundColor(Color(.systemBackground))
.overlay(
HStack {
Image(systemName: "plus")
.resizable()
.frame(width: 20, height: 20)
Text("Add an Exam")
})
}
}
}
}
}
|
|
compiles but does not save?
|
|
|
|
its trying to convert Binding< UpcomingExamsView.ExamData > to Binding< Data >
Did you change the type of the values array from Data to ExamData ? If so, you have to change the type of the item binding in the detail view from my original reply from Data to ExamData as well.
You have another problem. You nested the Data or ExamData struct inside UpcomingExamsView so the detail view can't find the struct. Move the code for the struct outside the view.
struct Data: Identifiable {
let id = UUID()
var name: String
var completed: Bool
}
struct UpcomingExamsView: View {
...
}
|
|
@Bnerd in the console it says "set defaults did not run because the user hasn't changed anything"
|
|
Ok you got me a challenge.. :P
Previously I though we could avoid the MVVM, I was wrong..anyhow..again using UserDefaults as storage follow the steps below:
Make a new file and make the following Struct (Your Data)
import Foundation
struct Data: Identifiable, Codable {
var id = UUID()
var name: String
var completed: Bool
}
Make one more file and make the following Class (your model to handle the Data, save/load)
import Foundation
class Values: ObservableObject {
@Published var values = [Data]() {
//@Published so thew view can refresh on updates.
//Saving into UserDefaults
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(values) {
UserDefaults.standard.set(encoded, forKey: "values")
}
}
}
//Loading from UserDefaults
init() {
if let savedItems = UserDefaults.standard.data(forKey: "values") {
if let decodedItems = try? JSONDecoder().decode([Data].self, from: savedItems) {
values = decodedItems
return
}
}
}
}
Finally change your ContenView (or how you call it..) should be like this:
import SwiftUI
struct ContentView: View {
@StateObject var values = Values()
func addNewData() {
values.values.append(Data(name: examName, completed: false))
}
@State var examName: String = ""
@FocusState var keyboardFocused: Bool
@State var DisclosureGroupOpen = false
@State var addNewExam = false
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
ScrollView {
VStack {
ForEach(values.values, id:\.id) { item in
if item.completed == false {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
if let i = values.values.index(where: { $0.id == item.id }) {
values.values[i].completed.toggle()
}
}
Text(item.name+" \(item.completed.description)")
}
}
}
}
DisclosureGroup(isExpanded: $DisclosureGroupOpen) {
ScrollView {
VStack {
ForEach(values.values, id:\.id) { item in
if item.completed {
ZStack (alignment: .leading) {
RoundedRectangle(cornerRadius: 10.0)
.fill(Color(.systemBackground))
.frame(height: 50)
HStack {
Image(systemName: "circle")
.resizable()
.frame(width: 22, height: 22)
.padding(.horizontal)
.onTapGesture {
if let i = values.values.index(where: { $0.id == item.id }) {
values.values[i].completed.toggle()
}
}
Text(item.name+" \(item.completed.description)")
}
}
.padding(.horizontal)
}
}
}
}
}
label: {
RoundedRectangle(cornerRadius: 8)
.frame(width: 120, height: 20)
.foregroundColor(colorScheme == .dark ? Color(.systemGray6) : Color.gray.opacity(0.6))
.padding(.horizontal)
.overlay(
HStack {
if DisclosureGroupOpen == false {
Image(systemName: "chevron.right")
.foregroundColor(.white)
} else {
Image(systemName: "chevron.down")
.foregroundColor(.white)
}
Text("Completed")
.foregroundColor(.white)
})
}.accentColor(.clear)
}
}
.navigationTitle("Upcoming Exams")
VStack {
Spacer()
if addNewExam {
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 100)
.foregroundColor(Color(.systemBackground))
.overlay(VStack{
TextField ("Add an Exam", text: $examName)
.focused($keyboardFocused)
.padding()
.onAppear{keyboardFocused = true}
.onSubmit {
addNewData()
addNewExam = false
examName = ""
}
Spacer()
})
}
if keyboardFocused == false {
Rectangle()
.onAppear {
addNewExam = false
}
.onTapGesture {
addNewExam = true
}
.frame(maxWidth: .infinity, maxHeight: 50)
.foregroundColor(Color(.systemBackground))
.overlay(
HStack {
Image(systemName: "plus")
.resizable()
.frame(width: 20, height: 20)
Text("Add an Exam")
})
}
}
//end
}
}
}
Finally a word of advise, your view is MASSIVE..try to break it down to smaller pieces for better control..it's not mandatory but in the future I am sure you will appreciate it if you have to come back.
|