Refreshable not working or showing any errors - swift

I am trying to use refreshable on my List from this tutorial
struct HomeTab: View {
#StateObject var getMsgs = GetMessages()
#State private var receivedMessages = [Timestamp(event: "nil", imgURL: "nil", read: false, readableDate: "nil", temp: "nil", time: "nil")]
var body: some View {
VStack(spacing: 0) {
greeting.edgesIgnoringSafeArea(.top)
messages
Spacer()
}
.onAppear {
getMsgs.fetchMessages()
}
.refreshable {
do {
let BASE_URL = "url.com"
guard let url = URL(string: BASE_URL) else { return }
let (data, _) = try await URLSession.shared.data(from:url)
receivedMessages = try JSONDecoder().decode([String: Timestamp].self, from: data).values.sorted{ $0.readableDate < $1.readableDate }
} catch {
receivedMessages = []
}
}
}
I am trying to implement it for this list:
private var messages: some View {
List(receivedMessages.filter{$0.read == false}) {msg in
NavigationLink(destination: MsgDetailView(message: msg)) {
HStack {
Spacer()
VStack {
Text(msg.event)
.font(.system(size: 17))
.bold()
.frame(maxWidth: .infinity, alignment: .center)
}
}.padding(.leading)
.padding(.top)
.padding(.trailing)
Divider()
.frame(height: 1)
.background(Color.gray)
.opacity(0.3)
.navigationBarHidden(true)
}
}
My data is being downloaded and decoded fine, it's just that the data doesn't refresh when I pull

Related

Can't solve scroll view in SwiftUI

Could someone please explain to me how the ScrollView works, how can I make that when I receive a message it scrolls to the last message ( item )?
I tried to make it with a, ScrollViewReader { proxy in } but I still can't make this code work.
I tried also to make it ' .onchange(of: ) { } ' (with ScrollViewReader)
struct ContentView: View{
#ObservedObject var viewModel = ViewModel()
#State var text = ""
#State var models = [String]()
var body: some View {
VStack(alignment: .leading) {
ScrollViewReader { proxy in
ScrollView (showsIndicators: false){
ForEach(models, id: \.self){ string in
Text(string)
.font(.headline)
.padding()
.background(Color("Text"))
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10).strokeBorder(.blue, style: .init(lineWidth: 1))
)
Divider()
}
.onChange(of: models){
proxy.scrollTo(models.last)
}
}
}
Spacer()
HStack{
TextField("Type here...", text: $text)
.padding(15.0)
.padding(.horizontal)
Button(){
send()
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: 40))
.foregroundColor(Color("Button"))
}
.padding(10.0)
}
.overlay(
RoundedRectangle(cornerRadius: 100)
.stroke(Color("Stroke"), lineWidth: 1)
)
}
.onAppear{
viewModel.setup()
}
.padding()
.background(/*#START_MENU_TOKEN#*//*#PLACEHOLDER=View#*/Color("Background")/*#END_MENU_TOKEN#*/)
}
func send() {
guard !text.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
models.append("Me: \(self.text)")
viewModel.send(text: text) { response in
DispatchQueue.main.async {
self.models.append("Answer: "+response)
self.text = ""
}
}
}
}
..............................
use a different overload
proxy.scrollTo(models.last,anchor:.top)

How to append something to a list?

I'm new to Swift at the moment and I'm having trouble appending to a to-do list. I am not receiving an error, so I don't know what's wrong. I hope this isn't too much code to go through, but I don't even know where the problem is.
There is a sheet modal that opens and closes fine. The only issue is that when I press the save button, the information I typed in doesn't add to the list.
I've add the ViewModel, NewTaskView(sheet), TaskView(customizes list), and a bit of code of the DetailView where the list should be.
import Foundation
import SwiftUI
class TaskViewModel : Identifiable , ObservableObject {
#Published var tasks : [Task] = [
Task(taskName: "Lab", taskDate: Date(), taskCompleted: false),
Task(taskName: "Assignment 4.02", taskDate: Date(), taskCompleted: false)
]
#Published var sortType : SortType = .alphabetical
#Published var isPresented = false
#Published var searched = ""
func addTask(task : Task){
tasks.append(task)
}
func removeTask(indexAt : IndexSet){
tasks.remove(atOffsets: indexAt)
}
func sort(){
switch sortType {
case .alphabetical :
tasks.sort(by: { $0.taskName < $1.taskName })
case .date :
tasks.sort(by: { $0.taskDate > $1.taskDate })
}
}
}
struct Task : Identifiable , Equatable {
var id : String = UUID().uuidString
let taskName : String
let taskDate : Date
var taskCompleted: Bool
}
enum SortType : String , Identifiable , CaseIterable {
var id : String { rawValue }
case alphabetical
case date
}
struct NewTaskView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var taskVM : TaskViewModel
#State var taskName = ""
#State var taskCompleted = Bool()
#State var taskDate = Date()
var body: some View {
NavigationView {
VStack(spacing: 14) {
Spacer()
TextField("Assignment Name",text: $taskName)
.padding()
.background(Color("tan"))
.cornerRadius(5)
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
.padding(.bottom, 20)
HStack{
Text("Due Date")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Image(systemName: "bell.badge")
.foregroundColor(.gray)
.frame(width: 30, height: 30, alignment: .leading)
VStack{
DatePicker("Select Date", selection: $taskDate, displayedComponents: .date)
.labelsHidden()
}
}
HStack{
Text("Mark as Completed")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Button(action : {
taskCompleted.toggle()
}){
if taskCompleted == true {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}
}
Spacer()
Button(action:{taskVM.addTask(task: .init(taskName: taskName, taskDate: taskDate, taskCompleted: taskCompleted))
presentationMode.wrappedValue.dismiss()},
label:{
Text("Save")
})
}.padding()
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack{
Image(systemName: "xmark")
.foregroundColor(Color("orange"))
}
})
}
}
}
struct NewTaskView_Previews: PreviewProvider {
static var previews: some View {
NewTaskView(taskVM: TaskViewModel())
}
}
struct TaskView: View {
var task : Task
#Environment(\.managedObjectContext) var moc
#State var currentDate: Date = Date()
#State var taskCompleted = false
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
HStack {
Button(action : {
self.taskCompleted.toggle()
try? self.moc.save()
}){
if self.taskCompleted {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}.padding()
VStack (alignment: .leading, spacing: 2){
Text("\(task.taskName)")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 18))
.foregroundColor(Color("dark"))
.padding([.leading, .trailing], 1)
if self.taskCompleted {
Text("Completed")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
else if currentDate > task.taskDate {
Text("Late")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(.white)
.frame(width: 95, height: 18)
.background(Color("lightbrown"))
.cornerRadius(4)
}
else {
let diffs = Calendar.current.dateComponents([.day], from: currentDate, to: task.taskDate )
Text("\(diffs.day!) days left")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
}
Spacer()
Text(getDateFormatString(date: task.taskDate ))
.font(Font.custom(FontNameManager.Montserrat.medium, size: 12))
.padding()
}
.padding(10)
.cornerRadius(10)
.background(
RoundedRectangle(cornerRadius: 10 , style: .continuous)
.foregroundColor(.white))
}
}
struct TaskView_Previews: PreviewProvider {
static var previews: some View {
TaskView(task: Task(id: "", taskName: "Task Name", taskDate: Date(), taskCompleted: false))
}
}
struct DetailsView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var developers : Developer
#ObservedObject var taskVM : TaskViewModel
#Environment(\.presentationMode) var presen
#Environment(\.editMode) var editButton
#State var taskCompleted = false
#State var show = false
#State var showSearch = false
#State var txt = ""
#State var currentDate: Date = Date()
#State var image : Data = .init(count: 0)
#State var indices : [Int] = []
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
NavigationView {
ZStack {
ZStack{
VStack{
Color("lightorange")
.clipShape(CustomCorners(corners: [.bottomLeft, .bottomRight], size: 70))
.ignoresSafeArea(.all, edges: .top)
.frame(width: 420, height: 175)
Spacer()
}
VStack{
HStack {
if !self.showSearch{
Button(action: {
withAnimation(.spring()){
self.presen.wrappedValue.dismiss()
}
}) {
Image(systemName: "chevron.left")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
Spacer(minLength: 0)
HStack {
if self.showSearch{
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(.horizontal, 8)
TextField("Search", text: $taskVM.searched , onEditingChanged: { (isBegin) in
if isBegin {
showSearch = true
} else {
showSearch = false
}
})
Button(action: {
withAnimation{
self.showSearch.toggle()
}
}) {
Image(systemName: "xmark").foregroundColor(.black).padding(.horizontal, 8)
}
}
else {
Button(action: {
withAnimation {
self.showSearch.toggle()
}
}) {
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
}.padding(self.showSearch ? 10 : 0)
.background(Color("lightorange"))
.cornerRadius(20)
}
ZStack{
RoundedRectangle(cornerRadius: 25)
.foregroundColor(Color("tan"))
.frame(width: 335, height: 130)
HStack {
VStack (alignment: .leading){
// Image(uiImage: UIImage(data: developers.imageD ?? self.image)!)
// .resizable()
// .frame(width: 70, height: 70)
// .clipShape(RoundedRectangle(cornerRadius: 20))
Text("\(developers.username ?? "")")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 24))
.foregroundColor(Color("dark"))
Text("\(developers.descriptions ?? "")")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 18))
.foregroundColor(.gray)
}
.padding([.leading, .trailing], 70)
.padding(.bottom, 80)
Spacer()
}
}
HStack{
Text("Assignments")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 20))
Spacer(minLength: 0)
EditButton()
}
.padding([.leading, .trailing], 20)
Spacer()
List {
ForEach (taskVM.tasks.filter {
self.taskVM.searched.isEmpty ? true : $0.taskName.localizedCapitalized.contains(self.taskVM.searched)} ){ task in
TaskView(task: task)
.listRowSeparator(.hidden)
}
.onDelete(perform: {
taskVM.removeTask(indexAt: $0)
})
}
.listStyle(InsetListStyle())
}
}
VStack {
Spacer()
HStack {
Spacer()
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
}
}
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
struct DetailsView_Previews: PreviewProvider {
static let persistenceController = PersistenceController.shared
static var developers: Developer = {
let context = persistenceController.container.viewContext
let developers = Developer(context: context)
developers.username = "Math"
developers.descriptions = "Calculus"
return developers
}()
static var previews: some View {
DetailsView(developers: developers, taskVM: TaskViewModel()).environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
struct ExpandableFAB: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var taskVM : TaskViewModel
#Binding var show: Bool
var body: some View{
VStack(spacing: 20){
if self.show{
Button(action: {
taskVM.isPresented.toggle()
}) {
Image(systemName: "list.bullet.rectangle.portrait").resizable().frame(width: 25, height: 25).padding()
}
.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
Button(action: {
self.show.toggle()
}) {
Image(systemName: "doc.badge.plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
}
Button(action: {
self.show.toggle()
}) {
Image(systemName: "plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding()
.rotationEffect(.init(degrees: self.show ? 180 : 0))
}.fullScreenCover(isPresented: $taskVM.isPresented, content: {
NewTaskView(taskVM: taskVM)
})
}
}
#burnsi I don't think I have that anywhere. Where should there be one?
Of course you have one and you need one:
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
This line is causing the issue.
You are somehow injecting a TaskViewModel in your DetailsView (You are not showing where), but not using it. Try:
ExpandableFAB(taskVM: taskVM, show: $show)
And the place you create your ViewModel ('TaskViewModel()') should have a #StateObject wrapper.
Explanation:
As you add something to your ViewModel the views depending on the ViewModel get rebuild. So your TaskViewModel ends up beeing recreated and you still have your 2 initial values in your array.

How to setup NavigationLink in SwiftUI sheet to redirect to new view

I am attempting to build a multifaceted openweathermap app. My app is designed to prompt the user to input a city name on a WelcomeView, in order to get weather data for that city. After clicking search, the user is redirected to a sheet with destination: DetailView, which displays weather details about that requested city. My goal is to disable dismissal of the sheet in WelcomeView and instead add a navigationlink to the sheet that redirects to the ContentView. The ContentView in turn is set up to display a list of the user's recent searches (also in the form of navigation links).
My issues are the following:
The navigationLink in the WelcomeView sheet does not work. It appears to be disabled. How can I configure the navigationLink to segue to destination: ContentView() ?
After clicking the navigationLink and redirecting to ContentView, I want to ensure that the city name entered in the WelcomeView textfield is rendered as a list item in the ContentView. For that to work, would it be necessary to set up an action in NavigationLink to call viewModel.fetchWeather(for: cityName)?
Here is my code:
WelcomeView
struct WelcomeView: View {
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State private var showingDetail: Bool = false
#State private var linkActive: Bool = true
#State private var acceptedTerms = false
var body: some View {
Section {
HStack {
TextField("Search Weather by City", text: $cityName)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10.0).strokeBorder(Color.gray, style: StrokeStyle(lineWidth: 1.0)))
.padding()
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
.sheet(isPresented: $showingDetail) {
VStack {
NavigationLink(destination: ContentView()){
Text("Return to Search")
}
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}.interactiveDismissDisabled(!acceptedTerms)
}
}
}.padding()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView()
}
}
ContentView
let coloredToolbarAppearance = UIToolbarAppearance()
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State var showingDetail = false
init() {
// toolbar attributes
coloredToolbarAppearance.configureWithOpaqueBackground()
coloredToolbarAppearance.backgroundColor = .systemGray5
UIToolbar.appearance().standardAppearance = coloredToolbarAppearance
UIToolbar.appearance().scrollEdgeAppearance = coloredToolbarAppearance
}
var body: some View {
NavigationView {
VStack() {
List () {
ForEach(viewModel.cityNameList) { city in
NavigationLink(destination: DetailView(detail: city)) {
HStack {
Text(city.name).font(.system(size: 32))
Spacer()
Text("\(city.main.temp, specifier: "%.0f")°").font(.system(size: 32))
}
}
}.onDelete { index in
self.viewModel.cityNameList.remove(atOffsets: index)
}
}.onAppear() {
viewModel.fetchWeather(for: cityName)
}
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}.sheet(isPresented: $showingDetail) {
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailView
struct DetailView: View {
var detail: WeatherModel
var body: some View {
VStack(spacing: 20) {
Text(detail.name)
.font(.system(size: 32))
Text("\(detail.main.temp, specifier: "%.0f")°")
.font(.system(size: 44))
Text(detail.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherModel.init())
}
}
ViewModel
class WeatherViewModel: ObservableObject {
#Published var cityNameList = [WeatherModel]()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityNameList.append(model)
}
}
catch {
print(error) // <-- you HAVE TO deal with errors here
}
}
task.resume()
}
}
Model
struct WeatherModel: Identifiable, Codable {
let id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Double = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
DemoApp
#main
struct SwftUIMVVMWeatherDemoApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
WelcomeView()
}
}
}

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.

Refresh Data in SwiftUI View pulled from HTTP Request

I've been looking for a solution to this problem but none of the ones I've found seem to fix my specific situation. Hopefully, with these details, someone can help me out. I'm new to Swift so bear with me. I'm working with a REST Api and HTTP Request from our help desk ticket system. I'm trying to find a way to refresh the data automatically when something changes and also have a way to manually refresh i.e. a pull down or actual refresh button. If I can figure this out for the two scenarios below, I think I can apply it to the rest of my scenarios.
Scenario #1 - I submit a new note for a ticket but the detail view doesn't change to reflect that new note. I have to go back and reopen the view to see the change.
Fetch Ticket Details
struct TicketDetails: Codable, Identifiable {
var id: Int
var type: String
var location: Location
var detail: String
var notes: [Notes]
var statustype: StatusType
}
struct Location: Codable {
let locationName: String
}
struct Notes: Codable {
var prettyUpdatedString: String?
var mobileNoteText: String?
}
struct StatusType: Codable {
var id: Int
var statusTypeName: String
}
class FetchTick: ObservableObject {
func getTicket(id: Int, userApi: String, completion: #escaping (TicketDetails) -> ()) {
guard let url = URL(string: "URL FOR TICKET DATA") else { return }
URLSession.shared.dataTask(with: url) {(data, _, _) in
let ticket = try! JSONDecoder().decode(TicketDetails.self, from: data!)
DispatchQueue.main.async {
completion(ticket)
}
}
.resume()
}
}
Create New Note on Ticket
class CreateTicketNote: ObservableObject {
func CreateNoteAction(ticketId: Int, userApi: String, techNote: String) {
let ticketUrl = URL(string:
"URLFORTICKET")!
var request = URLRequest(url: ticketUrl)
request.httpMethod = "POST"
let json: [String: Any] = [
"noteText": techNote,
"jobticket": [
"id": ticketId,
"type": "Ticket"
]
]
let data = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
URLSession.shared.uploadTask(with: request, from: data) { (responseData, response, error) in
if let error = error {
print("Error making POST request: \(error.localizedDescription)")
return
}
if let responseCode = (response as? HTTPURLResponse)?.statusCode, let responseData = responseData {
guard responseCode == 200 else {
print("Invalid response code: \(responseCode)")
return
}
if let responseJSONData = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) {
print("Response JSON data = \(responseJSONData)")
}
}
}.resume()
}
}
Detail View
struct DetailsView: View {
#ObservedObject var ticketStatusAction = TicketStatusAction()
#ObservedObject var createTicketNote = CreateTicketNote()
#State var ticket: [TicketDetails] = []
#State private var showingNoteAlert = false
#State private var showingOpenAlert = false
#State private var showingPendingAlert = false
#State private var showingDepotAlert = false
#State private var showingCloseAlert = false
#State private var note: String = ""
var id: Int
var displayClient: String
#Binding var userApi: String
var body: some View {
ScrollView(.vertical){
VStack(alignment: .leading){
if !ticket.isEmpty {
Text(self.ticket.first?.location.locationName ?? "")
.fontWeight(.bold)
.padding()
}
Text("\(displayClient) - \(id)")
.fontWeight(.bold)
.font(.system(size:20))
.padding()
Divider()
Text("Status")
.fontWeight(.bold)
.padding()
if !ticket.isEmpty {
Text(self.ticket.first?.statustype.statusTypeName ?? "")
.padding()
}
Text("Details")
.fontWeight(.bold)
.padding()
if !ticket.isEmpty {
Text(clearMarkdown(on:self.ticket.first?.detail ?? ""))
.padding()
.fixedSize(horizontal: false, vertical: true)
}
Divider()
Text("Most Recent Note")
.fontWeight(.bold)
.padding()
if !ticket.isEmpty {
Text(clearMarkdown(on: self.ticket.first?.notes.first?.prettyUpdatedString ?? ""))
.padding()
Text(clearMarkdown(on: self.ticket.first?.notes.first?.mobileNoteText ?? ""))
.padding()
.fixedSize(horizontal: false, vertical: true)
}
}
.onAppear {
FetchTick().getTicket(id: self.id, userApi: self.userApi) { (ticketDetails) in
self.ticket = [ticketDetails]
}}
Divider()
Section(header: Text("Create New Note")
.fontWeight(.bold)
.padding()
.padding(10)
.frame(maxWidth: .infinity)) {
TextField("Enter your note", text: $note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 350)
.padding(15)
Button(action: {
self.showingNoteAlert = true
}) {
Text("Submit Note")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.orange)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()
).actionSheet(isPresented:self.$showingNoteAlert) {
ActionSheet(
title: Text("Are you sure you want to add this note to \(displayClient)'s ticket?"),
message: Text("\(self.note)"),
buttons: [
.default(Text("Submit"))
{
self.createTicketNote.CreateNoteAction(ticketId: self.id, userApi: self.userApi, techNote: self.note);
self.note = ""
},
.cancel(){
self.note = ""
}])
}
}
Divider()
Section(header: Text("Change Ticket Status")
.fontWeight(.bold)
.padding()
.padding(10)
.frame(maxWidth: .infinity)) {
Button(action: {
self.showingOpenAlert = true
}) {
Text("Open")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.green)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingOpenAlert) {
Alert(
title: Text("Are you sure you want change \(displayClient)'s ticket to Open?"),
primaryButton: .default(Text("Open"))
{
self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 1)
},
secondaryButton: .cancel())
}
Spacer()
Button(action: {
self.showingPendingAlert = true
}) {
Text("Pending")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.yellow)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingPendingAlert) {
Alert(
title: Text("Are you sure you want to set \(displayClient)'s ticket to Pending?"),
primaryButton: .default(Text("Pending"))
{
self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 2)
},
secondaryButton: .cancel())
}
Spacer()
Button(action: {
self.showingDepotAlert = true
}) {
Text("Depot")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.blue)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingDepotAlert) {
Alert(
title: Text("Are you sure you want to depot \(displayClient)'s ticket?"),
primaryButton: .default(Text("Depot"))
{
self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi,
desiredStatus: 6)
},
secondaryButton: .cancel())
}
Spacer()
Button(action: {
self.showingCloseAlert = true
}) {
Text("Close")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.red)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingCloseAlert) {
Alert(
title: Text("Are you sure you want to close \(displayClient)'s ticket?"),
primaryButton: .destructive(Text("Close"))
{
self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 3)
},
secondaryButton: .cancel())
}
Spacer()
}
}
}
}
Scenario #2 - I'm viewing a list of tickets and want to make sure no additional tickets have opened. I would like to pull to refresh the list and show any new tickets.
Fet Tickets By Location
struct TicksByLocation: Codable, Identifiable {
public var id: Int
public var type: String
public var displayClient: String
public var shortDetail: String
}
class FetchTicksByLocation: ObservableObject {
func getTicksByLocation(ticketLocation: String, userApi: String, completion: #escaping (([TicksByLocation]) -> ())){
guard let url = URL(string: "URLFORTICKETS") else {
return
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let tickData = data {
let decodedData = try JSONDecoder().decode([TicksByLocation].self, from: tickData)
DispatchQueue.main.async {
completion(decodedData)
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
Show Tickets By Location
struct ShowLocationView: View {
#Binding var ticketLocation: String
#Binding var userApi: String
#Binding var tickets: [TicksByLocation]
#State var openTickets: [TicksByStatusAndLocation] = []
#State var green = Color.green
#State var yellow = Color.yellow
#State var blue = Color.blue
#State var purple = Color.purple
var body: some View {
NavigationView{
List(tickets) { tick in
VStack(alignment: .leading, spacing: 10) {
Text("\(tick.id)")
.font(.system(size: 11))
.cornerRadius(5)
Text(tick.displayClient)
Text(tick.shortDetail)
.font(.system(size: 11))
.foregroundColor(Color.gray)
NavigationLink(destination: DetailsView(
id: tick.id,
displayClient: tick.displayClient,
userApi: self.$userApi
)) {
Text("See Details")
.foregroundColor(Color.blue)
}
Divider()
}
}
.navigationBarTitle(
Text("\(ticketLocation) - All (\(tickets.count))"),
displayMode: .inline
)
.navigationBarItems(
trailing:
Text("Filter")
.contextMenu {
NavigationLink(
destination: ShowLocationAndStatusView(
ticketLocation: $ticketLocation,
userApi: $userApi,
status: "Open",
color: green
)
) {
Text("Open")
}
NavigationLink(
destination: ShowLocationAndStatusView(
ticketLocation: $ticketLocation,
userApi: $userApi,
status: "Pending",
color: yellow
)
) {
Text("Pending")
}
NavigationLink(
destination: ShowLocationAndStatusView(
ticketLocation: $ticketLocation,
userApi: $userApi,
status: "Depot",
color: blue
)
) {
Text("Depot")
}
NavigationLink(
destination: ShowLocationAndStatusView(
ticketLocation: $ticketLocation,
userApi: $userApi,
status: "Agi",
color: purple
)
) {
Text("Agi")
}
}
)
}
}
}
Solution #1
Section(header: Text("Create New Note")
.fontWeight(.bold)
.padding()
.padding(10)
.frame(maxWidth: .infinity)) {
TextField("Enter your note", text: $note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 350)
.padding(15)
Button(action: {
self.showingNoteAlert = true
}) {
Text("Submit Note")
.frame(width: 300)
.padding(15)
.foregroundColor(Color.white)
.background(Color.orange)
.cornerRadius(5)
}.buttonStyle(BorderlessButtonStyle()
).actionSheet(isPresented:self.$showingNoteAlert) {
ActionSheet(
title: Text("Are you sure you want to add this note to \(displayClient)'s ticket?"),
message: Text("\(self.note)"),
buttons: [
.default(Text("Submit"))
{
self.createTicketNote.CreateNoteAction(ticketId: self.id, userApi: self.userApi, techNote: self.note);
self.note = "";
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
FetchTick().getTicket(id: self.id, userApi: self.userApi) { (ticketDetails) in
self.ticket = [ticketDetails]
}
}
},
.cancel(){
self.note = ""
}])
}
}
Solution #2
Using https://github.com/phuhuynh2411/SwiftUI-PullToRefresh
import SwiftUI
import UIKit
struct ShowSelectedStatus: View {
#State private var isShowing = false
#Binding var userApi: String
#Binding var tickets: [TicksByStatus]
var ticketStatus: String
var color: Color
var body: some View {
NavigationView{
List(tickets) { tick in
VStack(alignment: .leading, spacing: 10) {
Text(tick.displayClient)
.padding(10)
.background(self.color)
.cornerRadius(5)
Text(tick.shortDetail)
.font(.system(size: 11))
.foregroundColor(Color.gray)
NavigationLink(destination: DetailsView(
id: tick.id,
displayClient: tick.displayClient,
userApi: self.$userApi
)) {
Text("See Details")
.foregroundColor(Color.blue)
}
Divider()
}
}
.pullToRefresh(isShowing: $isShowing) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.isShowing = false
}
FetchTicksByStatus().getTicksByStatus(
ticketStatus: self.ticketStatus,
userApi: self.userApi
) {
(ticks) in self.tickets = ticks
}
}
.navigationBarTitle(
Text("\(ticketStatus) (\(tickets.count))"),
displayMode: .inline
)
}
}
}