SwiftUI Textfield Controls, Errors on Exit - ios

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.

Related

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()
}
}
}
}
}

Swift 2.0 .contextMenu Multiple Delete From Core Data

First time post in here and new to coding... so I hope I am following proper protocol. I am putting together a view in Xcode 12.2 (SwiftUI 2) that outputs a list of data from Core Data and have a context menu to provide the user the option to edit, delete, and delete multiple. The context menu is working properly for edit and delete, however, I am facing a road block in how to implement the functionality to delete multiple list items. I am imagining the user would hard press one of the list items, the context menus pops open and if they press the "Delete Multiple" option, the view activates something similar to an edit mode that populates little circle on the left of each item which the user can select and delete more than one item at a time. I can see other article on how to do this, however, I cannot find guidance on how to implement this through Core Data. I have pasted my code below.
Please let me know if I am missing any other information that would make my question more clear.
I really appreciate the forums expertise and guidance.
Struct List : View {
#StateObject var appData = AppViewModel()
#Environment(\.managedObjectContext) var context
//Fetch Data...
#FetchRequest(entity: EntryData.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], animation: .spring()) var results : FetchedResults<EntryData>
var body : some View {
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom), content: {
VStack{
ScrollView(.vertical, showsIndicators: false, content: {
LazyVStack(alignment: .leading, spacing: 20){
ForEach(results){task in
VStack(alignment: .leading, spacing: 5, content: {
Text(task.category ?? "")
.font(.title)
.fontWeight(.bold)
Text(task.date ?? Date(), style:. date)
.fontWeight(.bold)
Text("\(task.number.formattedCurrencyText)")
})
.padding(.horizontal, 14)
.padding(.top, 10)
.foregroundColor(Color("ColorTextList"))
.contextMenu{
Button(action: {appData.editItem(item: task)}, label: {
Text("Edit")
})
Button(action: {
context.delete(task)
try! context.save()
}, label: {
Text("Delete")
})
Button(action: {}, label: {
Text("Delete Mutiple")
})
}
}
}
})
}
VStack(){
VisualEffectView(effect: UIBlurEffect(style: .regular))
.frame(width: UIScreen.main.bounds.width, height: 50, alignment: .top)
.edgesIgnoringSafeArea(.all)
.background(Color.clear)
Spacer()
}
})
.background(Color.clear)
.sheet(isPresented: $appData.isNewData, content: {
AddDataView(appData: appData)
.environment(\.managedObjectContext, self.context)
})
}
}
Adding the viewModel of the app. How do I tap into into this and delete each of the attributes in a multi-list selection?
import SwiftUI
import CoreData
class AppViewModel : ObservableObject, Identifiable{
#Published var cateogry = ""
#Published var date = Date()
#Published var number : Double? = nil
#Published var notes = ""
#Published var id = UUID()
}
And adding the actual Core Data model screenshot.
You can implement a Selection Set for your List. This will contain all elements that are selected. Then you can dynamically show the contextMenu for delete or deleteAll based on the count of the set. Here is a full example with the implementation of deleteAll
struct SelectionDemo : View {
#State var demoData = ["Dave", "Tom", "Phillip", "Steve"]
#State var selected = Set<String>()
var body: some View {
HStack {
List(demoData, id: \.self, selection: $selected){ name in
Text(name)
.contextMenu {
Button(action: {
//Delete only one item
}){
Text("Delete")
}
if (selected.count > 1) {
Button(action: {
//Delete all
deleteAll()
})
{
Text("Delete all")
}
}
}
}.frame(width: 500, height: 460)
}
}
func deleteAll() {
for element in selected {
self.demoData.removeAll(where: {
$0 == element
})
}
}
}

SwiftUI - How to implement "hide when scrolling" action to Search Bar (Xcode 12.1 - iOS 14)

I tried looking for an answer to this problem, but all of the solutions I found either were with UIKit, or needed me to add something to the AppDelegate/SceneDelegate. Since I'm using SwiftUI and Xcode 12.1 (that doesn't have AppDelegate and SceneDelegate anymore), I'm here asking this question:
How can I make this SearchBar disappear when user scrolls down (and leave it hidden until he/she scrolls down)?
Right now it works fine (even though I don't really like the animation of the cancel Button), and it look like this.
I have Search.swift like this:
import SwiftUI
struct Search: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search ...", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
Then in my Content View I have this:
import SwiftUI
struct ContentView: View {
#ObservedObject var dm : DataManager
#State var searchText = ""
#State private var isEditing = false
var body: some View {
NavigationView {
VStack {
Search(text: $searchText)
.padding(.horizontal, 10)
Form {
ForEach(dm.storageValues.filter({ searchText.isEmpty ? true : $0.valueName.contains(searchText) })) { data in
Text(data.valueName)
}
}
.navigationBarTitle("Example")
}
}
}
}
I don't know if they can be useful, but the DataManager is like this:
import SwiftUI
import Combine
class DataManager : Equatable, Identifiable, ObservableObject {
static let shared = DataManager()
#Published var storageValues : [ValueModel] = []
typealias StorageValues = [ValueModel]
//The rest of the code
}
With the ValueModel that is like this:
import SwiftUI
import Combine
class ValueModel : Codable, Identifiable, Equatable, ObservableObject, Comparable {
var id = UUID()
var valueName : String
var notes : String?
var expires : Date?
init(valueName: String, notes: String?, expires: Date?) {
self.valueName = valueName
self.notes = notes
self.expires = expires
}
}
For the SearchBar I followed this guide
Thanks to everyone who will help me!

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: Generic parameter 'C0' could not be inferred

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)

Resources