SwiftUI: Generic parameter 'C0' could not be inferred - ios

I've been working with this code and I keep getting this error: ''Generic parameter 'C0' could not be inferred'' Additionally it says 'In call to function 'buildBlock' (SwiftUI.ViewBuilder)'on my HStack when I include this line of code:
self.userData.tempBatchUnit = productName
I am not sure why. The code works fine without that line of code. Many thanks
struct enterProductUnitView: View {
#EnvironmentObject var userData: UserData
#State var productName: String = ""
var body: some View {
VStack {
HStack { // error Generic parameter 'C0' could not be inferred
Text("Product Unit:")
.font(.headline)
Spacer()
NavigationLink(destination: InstructionsView(desireInstructions: "Product Unit")) {
Text("?")
}
}
Text("ex: bags of popcorn, jars of jam etc.")
.font(.subheadline)
TextField("Enter here", text: $productName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)
self.userData.tempBatchUnit = productName
}
}
}

Remove the following line - it is not allowed in bodyview builder
self.userData.tempBatchUnit = productName
I assume it should be in .onCommit
TextField("Enter here", text: $productName, onCommit: {
self.userData.tempBatchUnit = self.productName
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)

Related

SwiftUI OnDelete crashes using indices and bindings

I'm building an app where you have cards for each study item that is stored in an array and what I wanted to do was to allow the user to swipe left on a study item and allow them to delete the card.
Each card has two bindings to allow user to edit two textFields, so I have to use indices on the ForEach to specify the textField that is being edited, because when I don't use indices and have the foreach parameter as a binding and iterate through each item, when the user edits a specify textField, he can only type in one letter and it skips to the next textField.
In this case, present below, I am able to swipe left on each card("Section") and delete it when it's empty but when I start typing on individual textFields with different texts and delete it, it crashes.
Any help will be appreciated!
Here is the foreach loop that is in a subview of another view
#ObservedObject var currentStudySet: HomeViewModel
ForEach(currentStudySet.studySet.studyItem.indices, id: \.self) { index in
Section {
VStack {
TextField("Title", text: $currentStudySet.studySet.studyItem[index].itemTitle)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(backgroundColor)
.cornerRadius(10)
TextField("Description", text: $currentStudySet.studySet.studyItem[index].itemDescription)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(backgroundColor)
.cornerRadius(10)
}
.frame(maxWidth: .infinity)
}
}
.onDelete(perform: { (item) in
currentStudySet.studySet.studyItem.remove(atOffsets: item)
})
The view model has studySet but I won't put it for legibility purposes but here is the StudyModel that is "studySet"
struct StudyModel: Hashable{
var title: String = ""
var days = ["One day", "Two days", "Three days", "Four days", "Five days", "Six days", "Seven days"]
var studyGoals = "One day"
var studyItem: [StudyItemModel] = []
}
Here is the studyItemModel which is the studyItem array in StudyModel
struct StudyItemModel: Hashable{
var itemTitle: String = ""
var itemDescription: String = ""
}
You can avoid using the index by doing the model conforms to Identifiable, this is the way the ForEach is meant to be use in SwiftUI atleast
import Foundation
import SwiftUI
struct fortest: View {
#ObservedObject var currentStudySet: HomeViewModel = HomeViewModel()
var body: some View {
List {
ForEach($currentStudySet.studySet.studyItem) { $studyItem in
Section {
VStack {
TextField("Title", text: $studyItem.itemTitle)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray)
.cornerRadius(10)
TextField("Description", text: $studyItem.itemDescription)
.padding(5)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.gray)
.cornerRadius(10)
}
.frame(maxWidth: .infinity)
}
}
.onDelete(perform: { (item) in
currentStudySet.studySet.studyItem.remove(atOffsets: item)
})
}
}
}
class HomeViewModel: ObservableObject {
#Published var studySet: StudyModel = StudyModel()
}
struct StudyModel: Hashable{
var title: String = ""
var days = ["One day", "Two days", "Three days", "Four days", "Five days", "Six days", "Seven days"]
var studyGoals = "One day"
var studyItem: [StudyItemModel] = [StudyItemModel(itemTitle: "test 1", itemDescription: "hello"), StudyItemModel(itemTitle: "test 2", itemDescription: "hello"), StudyItemModel(itemTitle: "test 3", itemDescription: "hello")]
}
struct StudyItemModel: Hashable, Identifiable{
let id = UUID()
var itemTitle: String = ""
var itemDescription: String = ""
}

How do I navigate to another SwiftUI View when an Environment Object update is causing the same view to reload

What I'm trying to achieve: I'm a new SwiftUI developer. I'm trying to build a simple Address Book app. I have three views:
ContentView - The main view which contains all contacts in a List View with an Add Contact ('+') and Edit button at the top of the Navigation View
AddContact View - which has a "Name" and "Email" text field and a "Submit" button
DisplayContactDetails View - not relevant to this question.
I've created an Environment Object "myContacts" which is an array of "Contact" objects and passed it in the ContentView to keep track of all contacts in the Address Book
When the user navigates to AddContact View, adds a name and email and submits, I'd like the Environment Object "myContacts" to be updated and for the user to be navigated back to ContentView so they can see the Address Book with the new contact included.
Problem:
When the user presses "Submit" on AddContact View, it correctly invokes a navigation link I've created to send the user back to ContentView. But because the Environment Object "myContacts" has also been updated by submit, it immediately navigates back from ContentView to AddContact View again. So it appears to be executing the Navigation Link first but then reloading AddContact View due to the refresh of myContacts.
Code - Content view:
struct ContentView: View {
#EnvironmentObject var myContacts: Contacts
#State var isAddButtonPressed: Bool = false
var body: some View {
NavigationView{
List {
ForEach(myContacts.contacts) { item in
NavigationLink(
//Display items and send user to DisplayContactDetails
})
}
}
.navigationBarTitle("Address Book")
.toolbar {
ToolbarItem(placement: .navigationBarLeading){
Button(action: {
isAddButtonPressed.toggle()
}, label: {
NavigationLink(
destination: AddContactView(),
isActive: $isAddButtonPressed,
label: {
Image(systemName: "plus")
})
})
}
ToolbarItem(placement: .navigationBarTrailing){
EditButton()
}
}
}
}
}
Code - AddContactView
struct AddContactView: View {
#State var name: String = ""
#State var email: String = ""
#State var isButtonPressed: Bool = false
#EnvironmentObject var myContacts: Contacts
var body: some View {
VStack{
HStack{
Text("Name:")
TextField("Enter name", text: $name)
}
.padding(.bottom, 50)
HStack{
Text("Email:")
TextField("Enter email", text: $email)
}
.padding(.bottom, 50)
Button("Submit") {
let contactToAdd = Contact(name: name, email: email)
//Add is a simple function - all it does is append an item to the myContacts array using the .append method
myContacts.add(contact: contactToAdd)
isButtonPressed = true
}
.frame(width: 80, height: 30, alignment:.center)
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
NavigationLink(destination: ContentView().navigationBarHidden(true),
isActive: $isButtonPressed,
label: {
EmptyView()
}).hidden()
}.padding()
}
}
What I've tried
If I comment out the the .add method and don't update the environment object, then the navigation back to ContentView works as expected. So I know that specifically is the cause of the problem.
I've tried adding a .onTapGesture modifier to the Button and invoking .add there.
I've tried adding a .onDisappear modifier to the entire view and invoking .add there.
--
Any help or clarity on resolving this would be much appreciated
Edit: Screen Recording - trying solution based on first comment:
What happens when I try the solution
Odd behaviour: The first attempt at adding a contact auto-routes back to AddContactView, producing the same error. But if I try it a second time then it routes correctly to ContactView.
Edit update. This is the code I used to test my answer:
import SwiftUI
#main
struct TestApp: App {
#StateObject var myContacts = Contacts()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(myContacts)
}
}
}
struct Contact: Identifiable {
var id = UUID()
var name: String = ""
var email: String = ""
}
class Contacts: ObservableObject {
#Published var contacts: [Contact] = [Contact(name: "name1", email: "email1"), Contact(name: "name2", email: "email2")]
func add(contact: Contact) {
contacts.append(contact)
}
}
struct AddContactView: View {
#Environment(\.presentationMode) private var presentationMode
#EnvironmentObject var myContacts: Contacts
#State var name: String = ""
#State var email: String = ""
var body: some View {
VStack{
HStack{
Text("Name:")
TextField("Enter name", text: $name)
}
.padding(.bottom, 50)
HStack{
Text("Email:")
TextField("Enter email", text: $email)
}
.padding(.bottom, 50)
Button("Submit") {
let contactToAdd = Contact(name: name, email: email)
myContacts.add(contact: contactToAdd)
presentationMode.wrappedValue.dismiss()
}
.frame(width: 80, height: 30, alignment:.center)
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}.padding()
}
}
struct ContentView: View {
#EnvironmentObject var myContacts: Contacts
#State var isAddButtonPressed: Bool = false
var body: some View {
NavigationView {
List {
ForEach(myContacts.contacts) { item in
NavigationLink(destination: AddContactView()) {
Text(item.name)
}
}
}
.navigationBarTitle("Address Book")
.toolbar {
ToolbarItem(placement: .navigationBarLeading){
Button(action: {
isAddButtonPressed.toggle()
}, label: {
NavigationLink(
destination: AddContactView(),
isActive: $isAddButtonPressed,
label: {
Image(systemName: "plus")
})
})
}
ToolbarItem(placement: .navigationBarTrailing){
EditButton()
}
}
}
}
}

Pass a SwiftUI view that has its own arguments as a variable to another view struct

I'll try to outline my case here, I have a NavigationLink I'm wanting to turn into it's own structure so I can re-use it. The Label inside the NavigationLink is the same for all the cases I'm using, just different text and images. I'm trying to make that new struct that contains the NavigationLink have an argument I use for the destination View of the NavigationLink.
I found this link that got me most of the way, but I just don't seem to be able to get it the last mile.
How to pass one SwiftUI View as a variable to another View struct
Here is the re-usable NavigationLink struct I made:
struct MainMenuButtonView<Content: View>: View {
var imageName: String
var title: String
var description: String
var content: Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content()
}
var body: some View {
VStack {
NavigationLink(destination: content) {
Image(imageName)
.resizable()
.frame(width: 100, height: 100)
Text(title)
.font(.title)
Text(description)
.foregroundColor(Color(UIColor.systemGray2))
.multilineTextAlignment(.center)
.font(.footnote)
.frame(width: 175)
}
.buttonStyle(PlainButtonStyle())
}
}
}
I currently don't get any errors on that part, but that doesn't mean there isn't something wrong with it.
And here is where I'm using it at, currently, I just shown one, but I'll have more once I get it working.
struct MainMenuView: View {
var body: some View {
NavigationView {
MainMenuButtonView(imageName: "List Icon",
title: "Lists",
description: "Auto generated shopping lists by store",
content: TestMainView(testText: "Lists"))
}
.buttonStyle(PlainButtonStyle())
.navigationBarTitle(Text("Main Menu"))
}
}
}
When I leave it as above, it tells me that there is an extra argument and that 'Contect' can't be inferred. Xcode does offer a fix, and it ends up looking like this after I do the fix
MainMenuButtonView<Content: View>(imageName: "List Icon",
But then I get an error that it cannot find 'Content' in scope. I know the main difference between my question and the example I linked above is my View I'm passing in also has arguments. I'm not sure if I'm also supposed to put all the arguments in the callout within the <>.
Thank you for any help you can give.
You need to correct the init in the MainMenuButtonView:
struct MainMenuButtonView<Content: View>: View {
var imageName: String
var title: String
var description: String
var content: () -> Content // change to closure
// add all parameters in the init
init(imageName: String, title: String, description: String, #ViewBuilder content: #escaping () -> Content) {
self.imageName = imageName // assign all the parameters, not only `content`
self.title = title
self.description = description
self.content = content
}
var body: some View {
VStack {
NavigationLink(destination: content()) { // use `content()`
Image(imageName)
.resizable()
.frame(width: 100, height: 100)
Text(title)
.font(.title)
Text(description)
.foregroundColor(Color(UIColor.systemGray2))
.multilineTextAlignment(.center)
.font(.footnote)
.frame(width: 175)
}
.buttonStyle(PlainButtonStyle())
}
}
}
Also, you need to pass a closure to the content parameter (as you indicated in the init):
struct MainMenuView: View {
var body: some View {
NavigationView {
MainMenuButtonView(
imageName: "List Icon",
title: "Lists",
description: "Auto generated shopping lists by store",
content: { TestMainView(testText: "Lists") } // pass as a closure
)
.navigationBarTitle(Text("Main Menu"))
}
}
}

The compiler is unable to type-check this expression in a reasonable time in SwiftUI?

I have a line of code that sets the background of Text to an Image that is fetched by finding the first three letters of the string. For some reason this won't run and keeps giving me the error above. Any ideas on how I can fix this?
There are a lot of images that need to be set as the backgrounds for multiple different pieces of text. I believe I have the right idea by using the prefix of the string, but it seems like Xcode is having difficulty/won't run this.
Pretty sure this specific line is giving me issues, but would love some feedback.
.background(Image(colorOption.prefix(3)).resizable())
import SwiftUI
struct ColorView: View {
// #ObservedObject var survey = Survey()
#ObservedObject var api = ColorAPIRequest(survey: DataStore.instance.currentSurvey!)
#State var showingConfirmation = true
#State var showingColorView = false
#State var tempSelection = ""
#EnvironmentObject var survey: Survey
//#EnvironmentObject var api: APIRequest
var colorOptionsGrid: [[String]] {
var result: [[String]] = [[]]
let optionsPerRow = 4
api.colorOptions.dropFirst().forEach { colorOption in
if result.last!.count == optionsPerRow { result.append([]) }
result[result.count - 1].append(colorOption)
}
return result
}
var body: some View {
VStack {
Text("Select Tape Color")
.font(.system(size:70))
.bold()
.padding(.top, 20)
NavigationLink("", destination: LengthView(), isActive: $showingColorView)
HStack {
List {
ForEach(colorOptionsGrid, id: \.self) { colorOptionRow in
HStack {
ForEach(colorOptionRow, id: \.self) { colorOption in
Button(action: {
// self.survey.length = lengthOption
self.tempSelection = colorOption
self.showingConfirmation = false
}
) {
ZStack {
Color.clear
Text(colorOption.prefix(3))
.font(.title)
.foregroundColor(self.tempSelection == colorOption ? Color.white : Color.black)
.frame(width: 200, height: 100)
.background(Image(colorOption.prefix(3)).resizable())
//Image(colorOption.prefix(3)).resizable()
}
}.listRowBackground(self.tempSelection == colorOption ? Color.pink : Color.white)
.multilineTextAlignment(.center)
}
}
}
}.buttonStyle(PlainButtonStyle())
}
Button(action: {
self.survey.color = self.tempSelection
self.showingColorView = true
self.showingConfirmation = true
}) {
Text("Press to confirm \(tempSelection)")
.bold()
.padding(50)
.background(Color.pink)
.foregroundColor(.white)
.font(.system(size:40))
.cornerRadius(90)
}.isHidden(showingConfirmation)
.padding(.bottom, 50)
}
}
}
The compiler actually gives a fairly decent suggestion when it tells you to break the expression up. The simplest you can do is extract the background image into a separate function like this:
func backgroundImage(for colorOption: String) -> some View {
Image(String(colorOption.prefix(3))).resizable()
}
and then replace the call to
.background(Image(colorOption.prefix(3)).resizable())
with
.background(self.backgroundImage(for: colorOption))
Also note that I wrapped colorOption.prefix(3) in a String constructor, simply because .prefix(_:) returns a Substring, but the Image(_:) constructor requires a String.

SwiftUI Textfield Controls, Errors on Exit

SwiftUI Textfield Controls, Errors on Exit
I'm receiving multiple console chatter items when simply moving from textfield to
textfield in SwiftUI. When adding a new record or editing an existing one, if I
hit the keyboard return key (running on a device) I receive no warnings or errors. If
I simply tap another textfield (without using the return key) I get all this chatter.
Start:
19:45:46.969678-0700 ToDo[688:477250] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"",
"",
"",
""
)
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.
2019-08-29 19:45:46.995748-0700 ToDo[688:477250] [Snapshotting] Snapshotting a view (0x14fe87ff0, _UIReplicantView) that has not been rendered at least once requires afterScreenUpdates:YES.
End:
In both cases the data is saved correctly. I am storing the data in Core Data.iOS 13.1, Xcode 11M392r - website says Beta 7, my Xcode says this build is Beta 6, Catalina19A546d.
Here's the view:
struct AddNewToDo: View {
#Environment(\.managedObjectContext) var managedObjectContext
#Environment(\.presentationMode) var presentationMode
#State private var updatedTitle: String = "No Title"
#State private var updatedFirstName: String = "No Title"
#State private var updatedLastName: String = "No Title"
#State private var updatedDate: Date = Date()
#State private var updatedDateString: String = "July 2019"
var body: some View {
VStack {
VStack(alignment: .leading) {
Text("ToDo Title:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter a Title", text: $updatedTitle)
.onAppear {
self.updatedTitle = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("First Name:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter First Name", text: $updatedFirstName)
.onAppear {
self.updatedFirstName = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("Last Name:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter Last Name", text: $updatedLastName)
.onAppear {
self.updatedLastName = ""
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack(alignment: .leading) {
Text("Created Date:")
.padding(.leading, 5)
.font(.headline)
TextField("Enter a date", text: $updatedDateString)
.onAppear {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.dateFormat = "M-d-yyyy HH:mm:ss"
let myString = formatter.string(from: Date())
//let myCoreDataString = formatter.string(from: self.toDoItem.createdAt!)
//print(myString)
//print("myCoredataString is \(myCoreDataString)")
self.updatedDateString = myString
}
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(10)
VStack {
Button(action: ({
let nmo = ToDoItem(context: self.managedObjectContext)
nmo.id = UUID()
nmo.title = self.updatedTitle
nmo.firstName = self.updatedFirstName
nmo.lastName = self.updatedLastName
nmo.createdAt = Date()
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
self.updatedTitle = ""
self.updatedFirstName = ""
self.updatedLastName = ""
self.updatedDateString = ""
self.presentationMode.wrappedValue.dismiss()
})) {
Text("Save")
}
.padding()
}
.padding(10)
Spacer()
}
}
}
Any guidance would be appreciated.

Resources