I've been struggling mightily to get multiple Pickers in Form layout to work in SwiftUI.
As a test, I have created a simple view that has some Text labels, some TextFields and two pickers. It is a Core Data app. The first picker is populated by first creating an array. The second is populated directly from Core Data. There doesn't seem to be any difference in function or errors.
The functionality seems to be working, but I get these errors that look like a real problem. Once I click on the Picker, I transition to the picker list without issue. Once I click on a list item, the picker list is dismissed and I return to ContentView with the appropriate choice listed in the picker. However, in both picker cases I get these errors:
1.
ForEach, Int, Text> count (10) != its initial count (1). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!
2. [TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information ...and more verbiage.
This is the main view:
struct ContentView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(fetchRequest: Clinic.getAllClinics()) var clinics: FetchedResults<Clinic>
@State var nameArray = Clinic.makeClinicNameArray()
@State private var selectArrayItem = 0
@State private var selectCoreDataItem = 0
@State private var tfOne = "TextField One"
@State private var tfTwo = "TextField Two"
@State private var tfThree = "TextField Three"
@State private var tfFour = "TextField Four"
var body: some View {
NavigationView {
Form {
//I have include groups because in the real app I have many more than
//ten views in the parent view
Group {
TextField("enter a value for tfOne", text: $tfOne).foregroundColor(.blue)
}
Section(header: Text("From Generated Array:"), footer: Text("Section End")) {
Picker(selection: $selectArrayItem, label: Text("Choose Array Clinic")) {
ForEach(0 ..< nameArray.count) {
Text(self.nameArray[$0])
}
}
.foregroundColor(.red)
Picker(selection: $selectCoreDataItem, label: Text("Choose Core Data Clinic")) {
ForEach(0 ..< clinics.count) {
Text("\(self.clinics[$0].name ?? "Nameless")")
}
}.foregroundColor(.orange)
Text("$selectArrayItem is \(selectArrayItem)")
Text("item name is \(self.nameArray[selectArrayItem])")
}//section 1
Group {
TextField("enter a value for tfTwo", text: $tfTwo).foregroundColor(.blue)
TextField("enter a value for tfThree", text: $tfThree).foregroundColor(.blue)
}
Section(header: Text("From Core Data:"), footer: Text("Section End")) {
Text("$selectCoreDataItem is \(selectCoreDataItem)")
Text("item name is \(self.clinics[selectCoreDataItem].name ?? "Nameless")")
}//section two
TextField("enter a value for tfFour", text: $tfFour).foregroundColor(.blue)
}//Form
.navigationBarTitle(Text("Spinners"))
}//nav view
}
}
After reading many SO posts and Apple docs I still have not found anything that relates to my situation.
For Reference, I had posted a similar question in SO 58784465. In that case changing the outside wrapper from a ScrollView to a Form worked - kind of - but ONLY for one picker. Adding a second picker broke the entire view and data handling. The above app was created just to solve these issues.
And the ManagedObject:
public class Clinic : NSManagedObject, Identifiable {
@NSManaged public var myID: UUID
@NSManaged public var name: String?
@NSManaged public var comment: String?
@NSManaged public var isShown: Bool
}
extension Clinic {
static func getAllClinics() -> NSFetchRequest<Clinic> {
let request: NSFetchRequest<Clinic> = Clinic.fetchRequest() as! NSFetchRequest<Clinic>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
request.sortDescriptors = [sortDescriptor]
return request
}
static func makeClinicArray() -> [Clinic] {
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
let request: NSFetchRequest<Clinic> = Clinic.fetchRequest() as! NSFetchRequest<Clinic>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
request.sortDescriptors = [sortDescriptor]
do {
let results = try kAppDelegate.persistentContainer.viewContext.fetch(request)
return results
} catch {
print("error retrieving filtered picker1s")
}
return [Clinic]()
}
static func makeClinicNameArray() -> [String] {
var returnedArray = [String]()
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
let request: NSFetchRequest<Clinic> = Clinic.fetchRequest() as! NSFetchRequest<Clinic>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
request.sortDescriptors = [sortDescriptor]
do {
let results = try kAppDelegate.persistentContainer.viewContext.fetch(request)
for result in results {
returnedArray.append(result.name ?? "Nameless")
}
} catch {
print("error retrieving filtered picker1s")
}
return returnedArray
}
}
Xcode Version 11.2.1 (11B500) Any guidance would be appreciated.
EDIT: Adding Core Data setup in SceneDelegate (AppDelegate code is strictly stock.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let tabby = TabBar().environment(\.managedObjectContext, managedObjectContext)
window.rootViewController = UIHostingController(rootView: tabby)
self.window = window
window.makeKeyAndVisible()
}
}
And very simple TabBar: struct TabBar: View {
@Environment(\.managedObjectContext) var managedObjectContext
@State private var selectionValue = 1
var body: some View {
TabView(selection : $selectionValue) {
ContentView().tabItem ({
Image(systemName: "house")
Text("Home")
})
.tag(1)
Utilities().tabItem ({
Image(systemName: "hammer")
Text("Utilities")
})
.tag(2)
StartView().tabItem ({
Image(systemName: "forward.fill")
Text("Start")
})
.tag(3)
}
}
}
