SwiftUI list over Set from core data - swiftui

I'm trying to iterate over a Set of data coming from core data.
Action is another ManagedEntity
#NSManaged public var action: Set<Action>?
Action
public class Action: NSManagedObject, Identifiable {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Action> {
return NSFetchRequest<Action>(entityName: "Action")
}
#NSManaged public var identifer: UUID?
#NSManaged public var name: String?
#NSManaged public var place: NSSet?
}
I tried different option with List, but no luck.
Any idea ?
Thanks,
Nicolas

Try like this:
import SwiftUI
import CoreData
struct ContentView: View {
#FetchRequest(sortDescriptors: [])
var actions : FetchedResults<Action>
var body: some View {
List() {
ForEach (actions){ action in
Text(action.name ?? "no Name")
}
}
}
}

Related

Can SwiftUI Text Fields work with optional Bindings? : Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'

Can SwiftUI Text Fields work with optional Bindings?
import SwiftUI
struct CategoryListDetail: View {
#Binding var firstCategory : Category?
var body: some View {
Form{
Section(header: Text("Category")){
TextField("Category Name", text: $firstCategory.name)
}
Toggle("Availability",isOn: $firstCategory.availability)
}
}
}
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding'
Here is the Category Entity
import Foundation
import CoreData
extension Category {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Category> {
return NSFetchRequest<Category>(entityName: "Category")
}
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var availability: Bool
#NSManaged public var sortOrder: Int64
}
extension Category : Identifiable {
}

SwiftUI Model by ViewModel edit other ViewModel

let's say you have a list of some objects - let's say cats. There is a common viewmodel that stores the data of the cats received from the server. Each element on the view is a small sector of data for one cat. Within this sector, the data can be changed by the user. How to correctly link the viewmodel of the list of cats and the viewmodel of one cat?
struct Cat : Identifiable {
let id = UUID()
var name: String
var breed: String
}
struct CatListView : View {
#ObservedObject private(set) var viewModel: ViewModel
var body: some View {
List(self.viewModel.items) { (item) in
CatView(viewModel: self.viewModel.createViewModel(item: item))
}
}
}
extension CatListView {
class ViewModel : ObservableObject {
#Published var items: [Cat]
init(items: [Cat]) {
self._items = .init(initialValue: items)
}
public func createViewModel(item: Cat) -> CatView.ViewModel {
.init(data: item)
}
}
}
struct CatView : View {
#ObservedObject private(set) var viewModel: ViewModel
var body: some View {
VStack {
TextField("Name", text: self.$viewModel.data.name)
TextField("Breed", text: self.$viewModel.data.breed)
}
}
}
extension CatView {
class ViewModel : ObservableObject {
#Published var data: Cat
init(data: Cat) {
self._data = .init(initialValue: data)
}
}
}
How to properly transfer changes from CatView.ViewModel to CatListView.ViewModel?
Is Binding possible? But I think this is a variant of the connection between View and View.
Perhaps this option will be correct?
extension CatListView {
class ViewModel : ObservableObject {
#Published private(set) var items: [Cat]
private var cancelItems: [Int: AnyCancellable] = [:]
init(items: [Cat]) {
self._items = .init(initialValue: items)
}
public func createViewModel(item: Cat) -> CatView.ViewModel {
let model: CatView.ViewModel = .init(data: item)
if let index = self.items.firstIndex(where: { $0.id == item.id }) {
self.cancelItems[index] = model.$data
.dropFirst()
.removeDuplicates()
.sink {
self.items[index] = $0
}
}
return model
}
}
}
You have to manage these items:
Model: single Cat Model
ViewModel: Cat Collection Model that stores all cats and fetch them
View 1 with cats List
View 2 (optional) A View with single cat, can be inside View 1 or separate, but for illustrate this we will separate the View 2.
struct Cat: Identifiable, Hashable {
let id = UUID()
var name: String
var colorDescription: String
}
class CatsModel: ObservableObject {
#Published var catsBag: [Cat]
init() {
// Fetch here the cats from your server
}
// CRUD cicle
func create(_ cat: Cat) { ... }
func update(_ cat: Cat) { ... }
func delete(_ cat: Cat) { ... }
}
struct CatsList: View {
#StateObject var catsModel = CatsModel()
var body: some View {
List {
ForEach(catsModel.catsBag.indices, id: \.self) { catIndice in
CatCell(cat: $catsModel.catsBag[catIndice])
}
}
}
}
struct CatCell: View {
#Binding var cat: Cat
var body: some View {
TextField("The cat name is: ", text: $cat.name)
.padding()
}
}
You will appreciate at this point that if you delete the last cat of that list the app crashes. This could be a SwiftUI error, you can find the solution here: https://www.swiftbysundell.com/articles/bindable-swiftui-list-elements/

Setting a default value for an NSManagedObject in SwiftUI Picker?

I'm working with Picker in SwiftUI to choose from a list of Core Data NSManagedObjects, and can't get the picker to display a default value. It also won't set a new value after one is chosen. Is there a way to have a default value for the picker to display?
Here's where the properties for my NSManagedObject are set up.
extension Company {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Company> {
return NSFetchRequest<Company>(entityName: "Company")
}
#NSManaged public var id: UUID?
#NSManaged public var name: String?
#NSManaged public var companyContacts: NSSet?
#NSManaged public var companyRoles: NSSet?
//...
}
And here's where I'm trying to use it.
struct AddRoleSheet: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(
entity: Company.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Company.name, ascending: true)
]
) var companies: FetchedResults<Company>
//...
#State var company: Company? = // Can I put something here? Would this solve my problem?
//...
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $company, label: Text("Company")) {
List {
ForEach(companies, id: \.self) { company in
company.name.map(Text.init)
}
}
}
//...
}
}
//...
}
}
There is no fetched results on View.init phase yet, so try the following
#State var company: Company? = nil // << just initialize
//...
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $company, label: Text("Company")) {
List {
ForEach(companies, id: \.self) { company in
company.name.map(Text.init)
}
}
}
//...
}
}
}.onAppear {
// here companies already fetched from database
self.company = self.companies.first // << assign any needed
}
}

Why can't swiftui distinguish 2 different environment objects?

I have this code and would expected a b as Text.
Result: a a -> see screenshot. What am I doing wrong?
import SwiftUI
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(PublishString(string: "a"))
.environmentObject(PublishString(string: "b"))
}
}
and ...this works:
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
class PublishString2 : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString2
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
As noted by Asperi in the comment, SwiftUI identifies Environment Objects by the type (the class definition you have used). It looks for an object of that type and uses the first one it finds.
One option is to have multiple properties on the one object that you can access (this would mean two separate String properties in your case.
Further information is available on the Apple documentation.
The accepted answer is perfectly fine and correct and answers the question.
The following is a simple workaround, if you must use two EnvironmentObjects of the same type to be passed around within your app, and stumble upon this question:
You can make a second class that inherits everything from the first class. Therefore you avoid redundancy and can use both EnvironmentObjects separately.
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
class PublishString2 : PublishString {}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString2
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
instantiation:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(PublishString(string: "a"))
.environmentObject(PublishString2(string: "b"))
}
}

Use protocol to define property of swiftui view

I have multiple classes that I want to use with a budget picker view. They all have this budgetable protocol defined.
import SwiftUI
struct BudgetPickerView: View {
#EnvironmentObject var userData: UserData
#State var budgetable: Budgetable
...
}
import Foundation
protocol Budgetable
{
var budgetId: String { get set }
}
For example this Allocation class
import Foundation
import Combine
class Allocation: ObservableObject, Identifiable, Budgetable {
let objectWillChange = ObservableObjectPublisher()
let id: String?
var amount: String { willSet { self.objectWillChange.send() } }
var budgetId: String { willSet { self.objectWillChange.send() } }
init(id: String? = nil, amount: String, budgetId: String) {
self.id = id
self.amount = amount.removePrefix("-")
self.budgetId = budgetId
}
}
However, when I try to pass an allocation into my budget picker view I get an error
NavigationLink(destination: BudgetPickerView(budgetable: allocation))...
Cannot convert return expression of type 'NavigationLink>, BudgetPickerView>' to return type 'some View'
Expression type 'BudgetPickerView' is ambiguous without more context
Change as bellow code
struct BudgetPickerView: View {
#EnvironmentObject var userData: UserData
var budgetable: Budgetable
var body: some View {
...
}
}
and
NavigationLink(destination: BudgetPickerView(budgetable: allocation).EnvironmentObject(UserData()))
By SwiftUI concept you are not allowed to work with #State outside of View, but the following works well (having other your parts unchanged)
struct BudgetPickerView: View {
#State private var budgetable: Budgetable
init(budgetable: Budgetable) {
_budgetable = State<Budgetable>(initialValue: budgetable)
}
var body: some View {
Text("Hello, World!")
}
}
struct TestBudgetPickerView: View {
var body: some View {
NavigationView {
NavigationLink(destination:
BudgetPickerView(budgetable: Allocation(amount: "10", budgetId: "1")))
{ Text("Item") }
}
}
}
BTW, just incase, again by design #State is intended to hold temporary-view-state-only data, not a model. For model is more preferable to use ObservableObject. In your case Budgetable looks like a model.