Is it possible to display saved DatePicker inputs to another view - swift

I am trying to return DatePicker inputs which is saved to core data, from details view to another view. But nothing is being returned.
Where am I going wrong?
truct ProductCard: View {
#Environment (\.managedObjectContext) var managedObjContext
#FetchRequest(entity: Task.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)])
var body: some View {
ScrollView(.horizontal){
HStack{
ForEach( Task, id: \.self){ Task in
NavigationLink {
ProductCardDetails(taskData: Task)
} label: {
HStack{
Text(task.self.taskTitle ?? "Error")
.font(.title3)
.fontWeight(.bold)
.foregroundColor(.red)
Spacer()
// this should be Date right?
Text(task.self.taskDate ?? "Error")
.font(.caption)
.foregroundColor(.red)
}.padding(.horizontal, 10)
}
}
}
}
}
}
// where the inputs will be put in
struct ProductCardDetails: View {
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-dd-MM HH:mm:ss"
return formatter
}()
#Environment(\.managedObjectContext) var managedObjContext
#Environment(\.dismiss) var dismiss
var taskData: Task
#State private var taskTitle: String = ""
var body: some View {
let binding = Binding<Date> {
formatter.date(from: taskData.taskDate ?? "") ?? Date.now
} set: { date, transaction in
taskData.taskDate = formatter.string(from: date)
}
VStack{
Form{
Section(header: Text("Name the task"),
footer: Text("Footer of a section can contain extra information about this section")) {
TextField("Name your task", text: $taskTitle)
.disableAutocorrection(true)
}
Section(header: Text("When do you need to do this"),
footer: Text("")) {
DatePicker(selection: binding, in: ...Date()){
Text("Pick time")
}
}
Button("Save and close"){
Task{
do{
try await managedObjContext.perform {
try managedObjContext.save()
}
dismiss.callAsFunction()
} catch{
print(error)
}
}
}
}.navigationBarBackButtonHidden()
}
}
}
\
Added an image of my new core data and hopefully the naming conventions are clearer
\
core data
core data
and
current errors
[Entering in product details error][3]
Product card errors

Related

How to add a selected value from cloudKit and display it in a view (SwiftUI)

I'm currently working on an app that has a function that adds a selected country from a search list retrieved from CloudKit. I got the search to work and the list to show and everything but I'm stuck at the part where the selected country will be added to the list.
To clarify my goal, I want this function to work similarly to the weather app on iPhone.
In order to add a city to your saved counties, you need to search the country and then select it to add it to the list of saved countries. I will attach some images at the end of the page for extra clarification.
I'm new to this whole Swift thing and I'm trying to sharpen my skills in it. I tried to look for documentation regarding this very thing without success. I would appreciate some help.
This is my code
import SwiftUI
import CloudKit
struct addCity: View {
#State private var searchText = ""
#State private var showingNatio = false
#State private var showingNotifi = false
#State private var country = ""
#State var em :[Emergency] = []
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var selectCountry: FetchedResults<CountriesList>
#State private var shoeingAddscreen = false
var body: some View {
VStack() {
NavigationStack {
VStack{
List{
ForEach(selectCountry, id: \.self){ cont in
Text("Name \(selectCountry.count)")
city(cityPic: "france" , cityName: cont.country)}
}.scrollContentBackground(.hidden)
}
Spacer()
.navigationTitle("Countries")
.font(.system(size: 30))
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing){
Menu {
Section {
Button(action: {}) {
Label("Edit List", systemImage: "pencil")
}
Button(action: {
showingNatio.toggle()
}) {
Label("Edit Nationality", systemImage: "globe.asia.australia.fill")
}
Button(action: {
showingNotifi.toggle()
}) {
Label("Notification", systemImage: "bell.badge")
}
}
}label: {
Image(systemName: "ellipsis.circle")
.resizable()
.scaledToFit()
.frame(width: 22)
.frame(maxWidth: 330, alignment: .trailing)
.padding(.top)
}
}//ToolbarItemGroup
}//toolbar
//.searchable(text: $searchText)
}.searchable(text: $searchText) {
ForEach(array) { emergency in
//Text(emergency.Country).searchCompletion(emergency)
HStack{
Text(emergency.Country).searchCompletion(emergency)
Spacer()
Button {
var slected = emergency.Country
let cont = CountriesList(context: moc)
cont.country = emergency.Country
try? moc.save()
} label: {
Text("Add")
.foregroundColor(Color.blue)
}
}
.padding(.horizontal).frame(maxWidth: 390)
}
// NavigationStack
}.onAppear{
fetchEvent()
}
}.sheet(isPresented: $showingNatio) {
Nationality()
}.sheet(isPresented: $showingNotifi) {
Notification()
}
}//vstack
var array : [Emergency]{
searchText.isEmpty ? em : em.filter{$0.Country.contains(searchText)
}
}
func fetchEvent(){
em.removeAll()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType:"Emergency", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordMatchedBlock = {recordID, result in
switch result{
case .success(let record):
let emer = Emergency(record: record)
em.append(emer)
case .failure(let error):
print("Error:\(error.localizedDescription)")
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
func fetchSpecific(){
em.removeAll()
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType:"Emergency", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordMatchedBlock = {recordID, result in
switch result{
case .success(let record):
let emer = Emergency(record: record)
em.append(emer)
case .failure(let error):
print("Error:\(error.localizedDescription)")
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
}
struct addCity_Previews: PreviewProvider {
static var previews: some View {
addCity()
}
}
struct city: View {
#State var cityPic = "france"
#State var cityName = ""
#State private var country = ""
#State var em :[Emergency] = []
//#FetchRequest(sortDescriptors: []) var countries: FetchedResults <CountryList>
#Environment(\.managedObjectContext) var moc
var body: some View {
ZStack (alignment: .leading){
Image(cityPic)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 360, height: 100)
.cornerRadius(7)
.overlay( Rectangle()
.foregroundColor(.black)
.cornerRadius(7)
.opacity(0.4))
Text(cityName)
.foregroundColor(.white)
.bold()
.font(.system(.largeTitle, design: .rounded))
.fontWeight(.black)
.foregroundColor(.white)
.padding()
}.padding(.horizontal,40)
}
}
struct Emergency: Identifiable{
let record: CKRecord
let Country: String
let id: CKRecord.ID
init(record: CKRecord){
self.record = record
self.id = record.recordID
self.Country = record["Country"] as? String ?? ""
}
}
Here the user seraches throught the cloud then selcts the country to add it.
Here the selected country will be added to the list

SWIFTUI/ How to delay .task and let it work only if the data from textFields are passed

I just tried to make an API app in SwiftUI with loveCalculator from rapidapi.com
The problem is that API first needs names from me before it gives me the results.
My program works but fetching data form API earlier that I want (when I click to show my data in next view, first show default data, then show the data that should be displayed when I click).
Also Is it possible to initialize #Published var loveData (in LoveViewModel) without passing any default data or empty String?
Something like make the data from LoveData optional ?
Can You tell me when I make mistake?
MY CODE IS :
LoveData (for api)
struct LoveData: Codable {
let percentage: String
let result: String
}
LoveViewModel
class LoveViewModel: ObservableObject {
#Published var loveData = LoveData(percentage: "50", result: "aaa")
let baseURL = "https://love-calculator.p.rapidapi.com/getPercentage?"
let myApi = "c6c134a7f0msh980729b528fe273p1f337fjsnd17137cb2f24"
func loveCal (first: String, second: String) async {
let completedurl = "\(baseURL)&rapidapi-key=\(myApi)&sname=\(first)&fname=\(second)"
guard let url = URL(string: completedurl) else {return }
do {
let decoder = JSONDecoder()
let (data, _) = try await URLSession.shared.data(from: url)
if let safeData = try? decoder.decode(LoveData.self, from: data) {
print("succesfully saved data")
self.loveData = safeData
}
} catch {
print(error)
}
}
}
LoveCalculator View
struct LoveCalculator: View {
#State var firstName = ""
#State var secondName = ""
#ObservedObject var loveViewModel = LoveViewModel()
var body: some View {
NavigationView {
ZStack{
Color(.systemTeal).ignoresSafeArea()
VStack(alignment: .center) {
Text("Love Calculator")
.padding(.top, 100)
Text("Enter first name:")
.padding()
TextField("first name", text: $firstName)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
Text("Enter second name:")
.padding()
TextField("second name", text: $secondName, onEditingChanged: { isBegin in
if isBegin == false {
print("finish get names")
}
})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
NavigationLink {
LoveResults(percentage: loveViewModel.loveData.percentage, description: loveViewModel.loveData.result)
} label: {
Text("CHECK IT!")
}
Spacer()
}
}
.task {
await loveViewModel.loveCal(first: firstName, second: secondName)
}
}
}
}
LoveResults View
struct LoveResults: View {
var percentage: String
var description: String
var body: some View {
ZStack{
Color(.green).ignoresSafeArea()
VStack{
Text("RESULTS :")
.padding()
Text(percentage)
.font(.system(size: 80, weight: .heavy))
.foregroundColor(.red)
.padding()
Text(description)
}
}
}
}
Thanks for help!
Regards,
Michal
.task is the same modifier as .onAppear in terms of view lifecycle, meaning it will fire immediately when the view appears. You have to move your API call to a more controlled place, where you can call it once you have all the required data.
If you want to fire the API request only after both names are entered, you can create a computed variable, that checks for desired state of TextFields and when that variable turns to true, then you call the API.
Like in this example:
struct SomeView: View {
#State var firstName: String = ""
#State var secondName: String = ""
var namesAreValid: Bool {
!firstName.isEmpty && !secondName.isEmpty // << validation logic here
}
var body: some View {
Form {
TextField("Enter first name", text: $firstName)
TextField("Enter second name", text: $secondName)
}
.onChange(of: namesAreValid) { isValid in
if isValid {
// Call API
}
}
}
}
You can also set your loveData to optional using #Published var loveData: LoveData? and disable/hide the navigation link, until your data is not nil. In that case, you might need to provide default values with ?? to handle optional string errors in LoveResults view initializer
It was very helpful but still not enough for me, .onChange work even if I was type 1 letter in my Form.
I find out how to use Task { } and .onCommit on my TextField, now everything working well !
My code now looks like that :
LoveCalculator View :
struct LoveCalculator: View {
#State var firstName = ""
#State var secondName = ""
var namesAreValid: Bool {
!firstName.isEmpty && !secondName.isEmpty
}
func makeApiCall() {
if namesAreValid {
DispatchQueue.main.async {
Task {
await self.loveViewModel.loveCal(first: firstName, second: secondName)
}
}
}
}
#ObservedObject var loveViewModel = LoveViewModel()
var body: some View {
NavigationView {
ZStack{
Color(.systemTeal).ignoresSafeArea()
VStack(alignment: .center) {
Text("Love Calculator")
.padding(.top, 100)
Text("Enter first name:")
.padding()
TextField("first name", text: $firstName, onCommit: {makeApiCall()})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
Text("Enter second name:")
.padding()
TextField("second name", text: $secondName, onCommit: {
makeApiCall()
})
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
NavigationLink {
LoveResults(percentage: loveViewModel.loveData.percentage ?? "", description: loveViewModel.loveData.result ?? "")
} label: {
Text("CHECK IT!")
}
Spacer()
}
}
}
}
}
Thank You one more time for very helpful tip!
Peace!
Michał ;)
Editing :
Just look at Apple documentation and I see that they say that .onCommit is deprecated whatever it means.
So instead of this I use .onSubmit and works the same !
TextField("second name", text: $secondName)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
.padding(.bottom)
.onSubmit {
makeApiCall()
}
Peace! :)

SourceKitService taking up to 200% of CPU

I just updated to Xcode 12.5.1, and now my SourceKitService is taking insanely high amounts of my CPU whenever I edit a specific file. After editing this file to any extent my CPU usage jumps through the roof, and basic services such as code completion stop working. I've already tried most of the solutions online about this issue, and nothing is helping. Does anyone have any ideas for this? Thanks.
I'll just put all of the file's code in here, because I'm not sure where the issue might originate.
//
// ScheduleView.swift
// ClassWidget
//
// Created by Ben K on 6/17/21.
//
import SwiftUI
import CoreData
struct ScheduleView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var schedule: Schedule
#State private var showingAddPeriod = false
#State private var showingEditPeriod = false
#State private var editPeriod: Period?
#State private var isEditMode: EditMode = .inactive
#State private var showingSettings = false
#State private var showingPreview = false
#State private var showingWarning = false
#State private var warningPeriod: Period?
var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter
}
var body: some View {
ZStack {
Text("\(editPeriod?.uName ?? "")")
.hidden()
List {
Section(header: Text("Classes")) {
if !schedule.periods.isEmpty {
ForEach(schedule.periods) { period in
Button(action: { editPeriod = period; isEditMode = .inactive; showingEditPeriod = true }) {
HStack {
VStack {
Text(timeFormatter.string(from: period.uStart))
Text("to")
Text(timeFormatter.string(from: period.uEnd))
}
.font(.caption)
.padding(.trailing, 10)
.padding(6)
Divider()
.frame(height: 35)
.padding(.trailing)
VStack(alignment: .leading) {
Text(period.uName)
if period.uTeacher != "" && period.uRoom != "" {
Text("\(period.uTeacher) • \(period.uRoom)")
.font(.caption)
.foregroundColor(.secondary)
} else if period.uTeacher != "" {
Text("\(period.uTeacher)")
.font(.caption)
.foregroundColor(.secondary)
} else if period.uRoom != "" {
Text("\(period.uRoom)")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
Image(systemName: "chevron.right")
.renderingMode(.template)
.padding(.trailing, 10)
.opacity(0.5)
}
.foregroundColor(.primary)
}
}
.onDelete(perform: delete)
} else {
VStack(alignment: .leading) {
Text("No classes yet")
.font(.headline)
Text("Start adding classes to create this schedule!")
.font(.caption)
.italic()
}
.padding(8)
}
}
Section {
Button(action: {
showingSettings = true
}) {
HStack {
Text("Settings")
Spacer()
Image(systemName: "chevron.right")
.padding(.trailing, 10)
.opacity(0.5)
}
.foregroundColor(.primary)
}
}
Button("Preview Widget") {
showingPreview = true
}
}
.listStyle(InsetGroupedListStyle())
}
.navigationTitle(schedule.uName)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing: Button(action: {
showingAddPeriod = true
}) {
Image(systemName: "plus").padding([.vertical, .leading])
})
.sheet(isPresented: $showingAddPeriod) {
AddPeriod(schedule: schedule)
.environment(\.managedObjectContext, self.moc)
}
/*.sheet(isPresented: $showingEditPeriod) {
if let period = editPeriod {
AddPeriod(period: period)
.environment(\.managedObjectContext, self.moc)
}
}*/
.fullScreenCover(isPresented: $showingEditPeriod, onDismiss: dismissedSheet) {
if let period = editPeriod {
AddPeriod(period: period)
.environment(\.managedObjectContext, self.moc)
}
}
.fullScreenCover(isPresented: $showingSettings) {
ScheduleSettingsView(schedule: schedule)
.environment(\.managedObjectContext, self.moc)
}
.sheet(isPresented: $showingPreview) {
PreviewWidget(schedule: schedule)
}
.alert(isPresented: $showingWarning) {
Alert(title: Text("Delete \(warningPeriod?.uName ?? "")"), message: Text("Are you sure?"), primaryButton: .destructive(Text("Delete")) {
try? moc.save()
}, secondaryButton: .cancel() {
if let period = warningPeriod {
readdPeriod(period: period)
}
})
}
.environment(\.editMode, self.$isEditMode)
}
func delete(at offsets: IndexSet) {
for offset in offsets {
warningPeriod = schedule.periods[offset]
moc.delete(schedule.periods[offset])
showingWarning = true
}
}
func readdPeriod(period: Period) {
let newPeriod = Period(period: period, context: moc)
newPeriod.schedule = schedule
try? moc.save()
}
func dismissedSheet() {
schedule.objectWillChange.send()
}
}
struct ScheduleView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let schedule = Schedule(name: "Example Schedule", number: 0, context: moc)
NavigationView {
ScheduleView(schedule: schedule)//.preferredColorScheme(.dark)
}
}
}
Looking at your code there seems to be a lot going on but along with the other solutions you will find in SO,
It is helpful to start commenting out portions of your code to try and narrow down a syntax issue that might be causing it.
Per out conversation in the comments the issue was caused by the multiple view modifiers that were attached to your ZStack moving them to the views that activated them has so far resolved it.
This is a compiler type-checking engine failure, aka
compiler is unable to type-check this expression in reasonable time;
try breaking up the expression into distinct sub-expressions
For some reason it fails silently and causes SourceKitService and swift-frontend to leak.
You need to simplify you view by moving logically discrete parts of the view into separate helper vars/funcs or structs.

SwiftUI: How to Save value from a TextField in core data

I am building a very simple SwiftUI App, following a tutorial.
I created a View that contains a list of items which is #fetched from CoreData, and the list is shown with no problem.
Then I added a modal window with a TextField and a Button to save the data.
This is the code of the modal:
import SwiftUI
struct AddCategoryView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) var context
#State public var name: String = ""
#State public var id = UUID()
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Close")
}
Text("Add a new Category")
.font(.largeTitle)
TextField("Enter category name", text: $name)
.padding(.all)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
let category = Category(context: self.context)
category.name = self.name
category.id = self.id
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("SAVE \(self.name)")
}
}
}
}
struct AddCategoryView_Previews: PreviewProvider {
static var previews: some View {
AddCategoryView()
}
}
In this line,
Text("SAVE \(self.name)")
I am printing (for debugging) the value of the variable name, and I can see that the variables changes to the value that is in the catch statement
self.name = "There is an error!"
So I know that the saving is failing. But I have no idea why.
In the List View I have a button that will open this modal; I changed the value of this Button from
self.showAddModal.toggle()
TO
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
And it works! I can see that the List View is updated with the value.
This is the code for the List View
struct CategoriesView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: Category.entity(), sortDescriptors: []) var categories: FetchedResults<Category>
#State private var showAddModal = false;
#State public var name = ""
var body: some View {
VStack {
Text("\(name)")
List {
ForEach(categories, id: \.id) { category in
VStack {
Text(category.name ?? "Unknown")
}
}
}
Button(action: {
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("+")
.font(.system(.largeTitle))
.fontWeight(.bold)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.frame(width: 48, height: 48)
.background(Color.purple)
.cornerRadius(24)
.padding(.all)
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
}.sheet(isPresented: $showAddModal) {
AddCategoryView()
}
}
}
}
struct CategoriesView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return CategoriesView().environment(\.managedObjectContext, context)
}
}
I have spent the last 2 hours looking and googling for a solution, but I cannot find out what is wrong in my code. I also tried on the simulator but got the same error; I cannot save the core data from the TextField.
Thanks in advance!
Ok I finally found the solution, which maybe is not the right one but at least it works.
.sheet(isPresented: $showAddModal) {
AddCategoryView().environment(\.managedObjectContext, self.managedObjectContext)
}
Yep, just passing the Environment with the context to the Modal View fixed the issue. I am not sure this is the correct way to do it.

SwiftUI: Is there any solution to clear value in DatePicker?

I am finding a solution to clear value of a DatePicker in SwiftUI. I tried and it's not success. Please check:
struct EditProfileView: View {
#State var birthDate = Date()
var body: some View {
Form {
Section (header: Text("Birth day")) {
DatePicker(selection: $birthDate, in: ...Date(), displayedComponents: .date) {
Text("Select a date")
}
HStack {
Text("You were born in \(birthDate, formatter: dateFormatter)")
.font(.subheadline)
.foregroundColor(Color.gray)
Spacer()
Button(action: {
self.clearDate()
}) {
Text("Clear")
}
}
}
}
}
func clearDate () {
self.$birthDate = nil
}
}
This line of code is not work:
self.$birthDate = nil
I think because of Date type cant not leave nil but I cant find a solution to handle with it
Set the birthDate (not $birthDate) to the following, which will default back to the current date:
self.birthDate = Date()
You can create a Binding from Date?
struct EditProfileView: View {
#State var birthDate : Date?
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
var body: some View {
Form {
Section (header: Text("Birth day")) {
DatePicker(selection: Binding<Date>(get: { () -> Date in
self.birthDate ?? Date()
}, set: { (Date) in
self.birthDate = Date
}), in: ...Date(), displayedComponents: .date) {
Text("Select a date")
}
HStack {
Text( "You were born in :") + (birthDate == nil ? Text("") : Text("\(birthDate!, formatter: dateFormatter)"))
.font(.subheadline)
.foregroundColor(Color.gray)
Spacer()
Button(action: {
self.clearDate()
}) {
Text("Clear")
}
}
}
}
}
func clearDate () {
self.birthDate = nil
}
}