SwiftUI CoreData - Store Fetch Request Data - swiftui

I am having real difficulty using Fetch Data from Core Data Entity.
All I need to do is to Fetch Data and use within my Code.
My approach technique maybe wrong hence the difficulty.
I hoped to get the Data and store into an array
I have a function which processed the data, but all I am able to do is to display the result in a view.
I would like to store the data in an Array, where I attempted to EmptyArray.append[newElement]
Error: Code is dictionaryWord.append(word.englishName) and error is Type of expression is ambiguous without more context
//Game Play
struct gamePlay: View {
//Set CoreData environment
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Dictionary.entity(), sortDescriptors: []) var dictionary: FetchedResults<Dictionary>
//Variables for Game Play
#State private var dictionaryWord: [String] = []
#State private var correctAnswer = Int.random(in: 0...2)
//Function to retrieve records from CoreData
func processListPicture() -> some View {
NavigationView {
VStack {
List(dictionary,id: \.self) { word in
HStack {
Text(word.englishName)
Image(word.imageName)
.resizable()
.frame(width: 50, height: 50)
.scaledToFit()
Text("- Urhobo: \(word.urhoboName)")
}
}//End of ForEach / List
}
}.navigationBarTitle(Text("English Word Translator"),displayMode: .inline)
}//End of Function

If I understand your question, you are trying to convert FetchedResults<Result> (called 'dictionary') to an array, where Result is a CoreData's entity called Dictionary.
To shuffle it, you can do:
let shuffledDictionaries: [Dictionary] = dictionary.shuffled()
If you wan conserve the original order:
let dictionaries: [Dictionary] = dictionary.map { $0 }
Or if you want any specific order:
let sortedDictionaries: [Dictionary] = dictionary.sorted(by: { ... })

Related

How to loop over #AppStorage values and use them for creating TextFields?

I am trying to loop over #AppStorage variables and use them to create TextFields with predefined values. I've tried several things as adding and removing $ for variable1-5 and variable, but nothing seems to work.
To make it easier I reduced the amount of variables and removed all the unneeded code.
import SwiftUI
struct ContentView: View {
static let store = UserDefaults(suiteName: "group.not-important.etc")
#AppStorage("variable1", store: store) var variable1 = "a"
#AppStorage("variable2", store: store) var variable2 = "b"
#AppStorage("variable3", store: store) var variable3 = "c"
#AppStorage("variable4", store: store) var variable4 = "d"
#AppStorage("variable5", store: store) var variable5 = "e"
var body: some View {
NavigationStack {
Form {
Section(header: Text("Variables")) {
HStack (alignment: .center) {
ForEach([
$variable1, // also tried variable1, etc...
$variable2,
$variable3,
$variable4,
$variable5
], id: \.self) { variable in // also tried $variable
TextField(
"",
text: variable // also tried $variable, variable.wrappedName, variable.text, etc...
)
}
}
}
}
}
}
}
Please help to understand what is the issue here and why I can not get it to work.
More info on errors here:
Provided example throws an error for ForEach line:
Generic struct 'ForEach' requires that 'Binding<String>' conform to 'Hashable'
When trying $variable in instead of variable in I get:
Inferred projection type 'String' is not a property wrapper
TextField("", text: $variable) throws:
Cannot convert value of type 'String' to expected argument type 'Binding<String>'
I think the main issue with your attempts, is that you are trying to create
an array of variables, but an array does not accept that, it accepts values,
String, Int, custom types etc..., but not variables. This is why it does not "work".
Since you may have lots of variables that you want to process in a loop,
I suggest you use an array instead. This makes looping very easy,
as shown in this example code. You can still have other independent variables in your code.
Here is the example code, using the Array extension to easily store the array in AppStorage, from: SwiftUI: What is #AppStorage property wrapper
struct ContentView: View {
static let store = UserDefaults(suiteName: "group.not-important.etc")
#AppStorage("arr", store: store) var arr: [String] = ["a","b","c","d","e"]
var body: some View {
Form {
Section(header: Text("Variables")) {
HStack (alignment: .center) {
ForEach(arr.indices, id: \.self) { index in
TextField("", text: $arr[index])
.border(.red) // <-- for testing
}
}
}
}
}
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}

SwiftUI: Using List and ForEach to display model data of different types with an array of Strings

I want to display a dynamic list of items that come from model data. The problem I have is how to properly use ForEach to display a var that is an array of Strings. There are no compiling issues with the code, but I cannot get the List to display.
Here is the struct for the model data:
struct OrderInfo: Codable, Identifiable, Hashable {
var id: Int
var orderNumber: String
var activeOrderStatus: Bool
var pickupSection: Int
var pickupStand: String
var pickupItems: [String]
}
Here is the code for my View:
struct CompletedOrderPickUp: View {
var completedOrder: OrderInfo
var body: some View {
ScrollView {
VStack {
HStack {
Text("Section")
Spacer()
Text("\(completedOrder.pickupSection)")
}
.pickUpDetailsTitleModifier()
HStack {
Text("Stand Name")
Spacer()
Text(completedOrder.pickupStand)
}
.pickUpDetailsTitleModifier()
HStack {
Text("Order Items")
Spacer()
}
.pickUpDetailsTitleModifier()
List {
ForEach(completedOrder.pickupItems, id: \.self) { items in
Text("\(items)")
Text(items)
}
}
}
}
}
}
And here is the screenshot of what it produces:
Screenshot with no items under "Order Items"
There's no issue with accessing the two other variables (pickupSection and pickupStand), which leads me to believe the problem lies with how to properly access the array of Strings within the data for pickupItems.
My friend Google has lots to say about ForEach and accessing data but not a lot about the combination of the two. Please let me know if I may clarify my question. Any help is much appreciated!

SwiftUI - How to pass data then initialise and edit data

I'm downloading data from Firebase and trying to edit it. It works, but with an issue. I am currently passing data to my EditViewModel with the .onAppear() method of my view. And reading the data from EditViewModel within my view.
class EditViewModel: ObservableObject {
#Published var title: String = ""
}
struct EditView: View {
#State var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}
I have given you the extremely short-hand version as it's much easier to follow.
However, I push to another view to select options from a list and pop back. As a result, everything is reset due to using the onAppear method. I have spent hours trying to use init() but I am struggling to get my application to even compile, getting errors in the process. I understand it's due to using the .onAppear method, but how can I use init() for this particular view/view-model?
I've search online but I've found the answers to not be useful, or different from what I wish to achieve.
Thank you.
You don't need to use State for input property - it is only for internal view usage. So as far as I understood your scenario, here is a possible solution:
struct EditView: View {
private var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
init(selectedItem: ItemModel) {
selected_item = selectedItem
editViewModel.title = selectedItem.title
}
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}

SwiftUI - Trying to re run a class from a different view to force data to reload

I'm new to SwiftUI and struggle sometimes with the whole 'declarative' concept but normally get there in the end...Anyway I've made a simple app that loads data from a remote JSON file on line. That part all works fine.
What I then want to do is, on a different view, I call the class FetchData - trying to get it to revisit the data source and update the variables. It appears It runs the class ok, as the print statements reappear when called - but the variables do not update with new values.
Heres code for the class to load the data.
import SwiftUI
struct GetData: Hashable, Codable, Identifiable {
var id: Int
var CODE: String
var RATE: Double
var TIME: String
var FLAG: String
var COUNTRY: String
var NAME: String
}
public class FetchData: ObservableObject {
// 1.
#Published var rates = [Rates]()
#State var showingAlert = true
init() {
let url = URL(string: "https://www.exampledata.co.uk/example.php")!
// 2.
URLSession.shared.dataTask(with: url) { [self](data, response, error) in
do {
if let processData = data {
// 3.
let decodedData = try JSONDecoder().decode([Rates].self, from: processData)
DispatchQueue.main.async {
self.rates = decodedData
print("Data Loaded from Internet")
self._showingAlert = State(initialValue: false)
}
} else {
print("No data")
self.rates=exchange
// use diferent method to load data from hardcopy if no internet avail
print("Data Loaded from Hardcopy")
self._showingAlert = State(initialValue: true)
}
} catch {
print("Error")
self.rates=exchange
print("Data Loaded from Hardcopy")
// use diferent method to load data from hardcopy if server down or general error
self._showingAlert = State(initialValue: false)
}
}.resume()
}
}
Then in the other view where I want to call it from I have ...
Text("Data last updated:" + RowData.TIME)
.font(.footnote)
.foregroundColor(Color.gray)
Button(action: {
FetchData.init()
}) {
HStack{
Image(systemName: "arrow.clockwise").resizable()
.frame(width: 15.0, height: 15.0)
Text("Reload rates")
.font(.footnote)
}}
}
2 things
First, #State should only be used in a View not an ObservableObject per the documentation. Switch #State to #Published.
Second, and what is probably your issue is in your view. How are you initializing the ObservableObject(Code not available)? it should be an #ObservedObject, #EnvironmentObject, or #StateObject. Also, make sure you aren't creating two different instances a singleton pattern or an #EnvironemntObject can help with that.

View is not rerendered in Nested ForEach loop

I have the following component that renders a grid of semi transparent characters:
var body: some View {
VStack{
Text("\(self.settings.numRows) x \(self.settings.numColumns)")
ForEach(0..<self.settings.numRows){ i in
Spacer()
HStack{
ForEach(0..<self.settings.numColumns){ j in
Spacer()
// why do I get an error when I try to multiply i * j
self.getSymbol(index:j)
Spacer()
}
}
Spacer()
}
}
}
settings is an EnvironmentObject
Whenever settings is updated the Text in the outermost VStack is correctly updated. However, the rest of the view is not updated (Grid has same dimenstions as before). Why is this?
Second question:
Why is it not possible to access the i in the inner ForEach-loop and pass it as a argument to the function?
I get an error at the outer ForEach-loop:
Generic parameter 'Data' could not be inferred
TL;DR
Your ForEach needs id: \.self added after your range.
Explanation
ForEach has several initializers. You are using
init(_ data: Range<Int>, #ViewBuilder content: #escaping (Int) -> Content)
where data must be a constant.
If your range may change (e.g. you are adding or removing items from an array, which will change the upper bound), then you need to use
init(_ data: Data, id: KeyPath<Data.Element, ID>, content: #escaping (Data.Element) -> Content)
You supply a keypath to the id parameter, which uniquely identifies each element that ForEach loops over. In the case of a Range<Int>, the element you are looping over is an Int specifying the array index, which is unique. Therefore you can simply use the \.self keypath to have the ForEach identify each index element by its own value.
Here is what it looks like in practice:
struct ContentView: View {
#State var array = [1, 2, 3]
var body: some View {
VStack {
Button("Add") {
self.array.append(self.array.last! + 1)
}
// this is the key part v--------v
ForEach(0..<array.count, id: \.self) { index in
Text("\(index): \(self.array[index])")
//Note: If you want more than one views here, you need a VStack or some container, or will throw errors
}
}
}
}
If you run that, you'll see that as you press the button to add items to the array, they will appear in the VStack automatically. If you remove "id: \.self", you'll see your original error:
`ForEach(_:content:)` should only be used for *constant* data.
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)`
and provide an explicit `id`!"
ForEach should only be used for constant data. So it is only evaluated once by definition. Try wrapping it in a List and you will see errors being logged like:
ForEach, Int, TupleView<(Spacer, HStack, Int, TupleView<(Spacer, Text, Spacer)>>>, Spacer)>> count (7) != its initial count (0). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!
I was surprised by this as well, and unable to find any official documentation about this limitation.
As for why it is not possible for you to access the i in the inner ForEach-loop, I think you probably have a misleading compiler error on your hands, related to something else in the code that is missing in your snippets. It did compile for me after completing the missing parts with a best guess (Xcode 11.1, Mac OS 10.14.4).
Here is what I came up with to make your ForEach go over something Identifiable:
struct SettingsElement: Identifiable {
var id: Int { value }
let value: Int
init(_ i: Int) { value = i }
}
class Settings: ObservableObject {
#Published var rows = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
#Published var columns = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
}
struct ContentView: View {
#EnvironmentObject var settings: Settings
func getSymbol(index: Int) -> Text { Text("\(index)") }
var body: some View {
VStack{
Text("\(self.settings.rows.count) x \(self.settings.columns.count)")
ForEach(self.settings.rows) { i in
VStack {
HStack {
ForEach(self.settings.columns) { j in
Text("\(i.value) \(j.value)")
}
}
}
}
}
}
}