I have an ObservableObject class that fetches the remote data based on month and year value. Initially it works fine because I use current month and year values however I want to update and re run the getAttendanceRecord2 when I select some other month from picker. What is the best practice for achieving this?
struct AttendanceRecord: View {
#ObservedObject var item_data: getAttendanceRecord2
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"]
init(id: String) {
self.item_data = getAttendanceRecord2(id: id,month: getMonth(), year: getYear())
}
var body: some View {
VStack {
Picker(selection: $selectedMonth, label: Text("Months"), content: {
ForEach(months, id: \.self) {
Text($0)
}
}).pickerStyle(MenuPickerStyle())
Text("You selected: \(selectedMonth)")
}
}
}
Here is my function:
class getAttendanceRecord2: ObservableObject {
#Published var attlist = [EAttendance]()
init(id: String, month: String, year: String) {
let url = URL(string: "http://superteclabs.com/apis2/AttendanceRecord2.php")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Accept")
let formdata: Data = "id=\(id)&month=\(month)&year=\(year)".data(using: .utf8)!
request.httpBody = formdata
URLSession.shared.dataTask(with: request) { (response_data, response, error) in
guard error == nil else {
print("Error: error calling POST")
print(error!)
return
}
guard let data = response_data else {
print("Error: Did not receive data")
return
}
guard let response = response as? HTTPURLResponse, (200 ..< 299) ~= response.statusCode else {
print("Error: HTTP request failed")
return
}
do {
let decodedData = try JSONDecoder().decode([EAttendance].self, from:data)
DispatchQueue.main.async {
self.attlist = decodedData
}
} catch {
print("Error: Trying to convert JSON data to string")
return
}
}.resume()
}
}
It can be done in onChange(of:, like
Picker(selection: $selectedMonth, label: Text("Months"), content: {
ForEach(months, id: \.self) {
Text($0)
}
}).pickerStyle(MenuPickerStyle())
.onChange(of: selectedMonth) { newMonth in // << here !!
// process here new selected month as needed
}
Related
Summary:
I have a list loaded from an API. Each list item have a button. On click of button, a unique ID associated with the list item is sent to server which in response provides a pdf directly there is no other response just a pdf file, the api is like :
http://myhost/api/DownloadPDF/uniqueID=67198287_239878092_8089
I have created the list and also able to download the pdf in documentDirectory by calling download task. However, I am unable to open the pdf automatically in app itself after downloading. I have created DisplayPDF struct which uses PDFKit to display as follows:
struct DisplayPDF: View {
var url:URL
var body:some View
{
PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable{
func updateUIView(_ uiView: UIView, context:
UIViewRepresentableContext<PDFKitRepresentedView>) {
}
let url: URL
init(_ url:URL)
{
self.url = url
}
func makeUIView(context:
UIViewRepresentableContext<PDFKitRepresentedView>) ->
PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}}
I need to pass the url into the above struct. The url can be the saved location or API directly. However, the url is not passed when the DisplayPDF view is called.
What I have tried so far
1> Pass the DisplayPDF into navigationlink in ReportList(where list is loaded) struct and than either call getFile func in onAppear in DisplayPDF struct or ReportRow struct.
2> Call getFile() on ReportRow in onAppear and pass the url in DisplayPDF() there.
3> Call getFile() on DisplayPDF() onAppear and pass the url there
4> Also tried, sheet method blank sheet pops up
All failed, no value is sent to DisplayPDF(url) the moment it is called from any of the listed method.
ReportList struct
import SwiftUI
struct ReportList: View {
#ObservedObject var reportLink : ReportViewModel
var body: some View {
List{
ForEach(reportLink.trackReport)
{report in
VStack {
ReportRow(report: report)
}
if(reportLink.trackReport.isEmpty)
{
Text("No Report Found")
.foregroundColor(.accentColor)
.fontWeight(.semibold)
}
}
}
}
}
ReportRow struct:
struct ReportRow: View {
var report : ReportResponse
#StateObject var pdfDownload = PDFDownload()
var body: some View {
VStack{
HStack{
Text(report.name)
.font(.system(size: 16))
.foregroundColor(.black)
.padding(.bottom,1)
.padding(.top,1)
}.frame(maxWidth: .infinity, alignment: .leading)
HStack{
Text("P.Id:")
.foregroundColor(.black)
.font(.system(size: 14))
Text(report.patientID)
.foregroundColor(.purple)
.font(.system(size: 14))
Spacer()
Button(action: {
pdfDownload.uniqueReportId = report.uniqueID
pdfDownload.patientName = report.name
pdfDownload.getFile()
}, label:
{
Text("\(report.status)")
.foregroundColor(.blue)
.font(.system(size: 14))
.padding(.trailing,2)
}).frame(maxWidth: .infinity, alignment: .trailing)
}
}
}}
I have made this PDFDownload model in which openURL is declared a published var which should provide updated url to a view(like DisplayPDF() view):
class PDFDownload : UIViewController, ObservableObject
{
#Published var uniqueReportId:String = String()
#Published var patientName:String = String()
#Published var isNavigate:Bool = false
#Published var openURL:URL = URL(fileURLWithPath: "")
func getFile()
{
var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "myHost"
urlComponents.port = 80
urlComponents.path = "/api/Reports/DownloadReport"
urlComponents.queryItems = [URLQueryItem(name: "uniquePackageId",
value: uniqueReportId)]
let url = urlComponents.url
print(url?.absoluteString)
let downloadTask = URLSession.shared.downloadTask(with: url!)
{
urlOrNil, responseOrNil, errorOrNil in
guard let fileURL = urlOrNil else {return}
do{
let documentURL = try FileManager.default.url(for:
.documentDirectory, in: .userDomainMask, appropriateFor:
nil, create: false)
let savedURL = documentURL.appendingPathComponent("\
(self.patientName)_\(UUID().uuidString).pdf")
print(savedURL)
try FileManager.default.moveItem(at: fileURL, to:
savedURL)
DispatchQueue.main.async {
self.openURL = savedURL
}
}
catch{
print("Error while writting")
}
}
downloadTask.resume()
}}
So what is the correct way of solving this problem that the correct URL can be passed to DisplayPDF() view.
Extra: ReportResponse model:
struct DownReport : Codable, Identifiable {
let id = UUID()
let success : Bool
let message : String
let reportResponse : [ReportResponse]
enum CodingKeys: String, CodingKey{
case success = "IsSuccess"
case message = "Message"
case reportResponse = "ResponseData"
}}
struct ReportResponse : Codable, Identifiable {
var id:String {uniqueID}
let patientID : String
let name : String
let status : String
let crmNo : String?
let recordDate : String
let uniqueID : String
let testCount : Int
enum CodingKeys: String, CodingKey {
case patientID = "PatientId"
case name = "Name"
case status = "Status"
case crmNo = "CrmNo"
case recordDate = "RecordDate"
case uniqueID = "UniquePackageId"
case testCount = "NoOfTests"
}
}
The above response is from POST request which is sent to generate list. To get pdf only unique id as Query is sent as I have posted on top.
The above structure successfully downloads the file but fail to open the file automatically. How to do that?
Here is some sample code that shows how to download a pdf document (wikipedia),
copy it to a local file, and display it on the screen by passing the savedURL to the View. You should be able to adapt the sample code for your purpose.
import Foundation
import SwiftUI
import PDFKit
struct ContentView: View {
#StateObject var downloader = PDFDownload()
var body: some View {
VStack (spacing: 30) {
Button("download1", action: {
downloader.patientName = "patient-1"
downloader.uniqueReportId = "astwiki-Homo_heidelbergensis-20200728.pdf/astwiki-Homo_heidelbergensis-20200728.pdf"
downloader.getFile()
}).buttonStyle(.bordered)
Button("download2", action: {
downloader.patientName = "patient-2"
downloader.uniqueReportId = "rowiki-Biban_european-20200728.pdf/rowiki-Biban_european-20200728.pdf"
downloader.getFile()
}).buttonStyle(.bordered)
if downloader.isDownloading { ProgressView("downloading ...") }
}
.fullScreenCover(item: $downloader.openURL) { siteUrl in
DisplayPDF(url: siteUrl.url)
}
}
}
struct DisplayPDF: View {
#Environment(\.dismiss) var dismiss
let url: URL
var body:some View {
VStack {
#if targetEnvironment(macCatalyst)
Button("Done", action: {dismiss()})
#endif
PDFViewer(url: url)
}
}
}
struct PDFViewer: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> PDFView {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: url)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: PDFView, context: Context) { }
}
class PDFDownload : ObservableObject {
#Published var uniqueReportId = ""
#Published var patientName = ""
#Published var isNavigate = false
#Published var openURL: SiteURL?
#Published var isDownloading = false
func getFile() {
isDownloading = true
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "ia803207.us.archive.org"
urlComponents.path = "/0/items/\(uniqueReportId)" // <-- just for testing
// urlComponents.port = 80
// urlComponents.queryItems = [URLQueryItem(name: "uniquePackageId", value: uniqueReportId)]
guard let url = urlComponents.url else {return}
let downloadTask = URLSession.shared.downloadTask(with: url) { urlOrNil, responseOrNil, errorOrNil in
guard let fileURL = urlOrNil else { return }
do {
let documentURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let savedURL = documentURL.appendingPathComponent("\(self.patientName)_\(UUID().uuidString).pdf")
try FileManager.default.moveItem(at: fileURL, to: savedURL)
DispatchQueue.main.async {
self.openURL = SiteURL(url: savedURL)
self.isDownloading = false
}
}
catch {
print("Error \(error)")
}
}
downloadTask.resume()
}
}
struct SiteURL: Identifiable {
let id = UUID()
var url: URL
}
Updated Answer with your update question: the row will update when the file is downloaded, it will then be a navigation link to display pdf
struct DownReport : Codable, Identifiable {
let id = UUID()
let success : Bool
let message : String
let reportResponse : [ReportResponse]
enum CodingKeys: String, CodingKey{
case success = "IsSuccess"
case message = "Message"
case reportResponse = "ResponseData"
}
}
struct ReportResponse : Codable, Identifiable {
var id:String {uniqueID}
let patientID : String
let name : String
let status : String
let crmNo : String?
let recordDate : String
let uniqueID : String
let testCount : Int
// This URL is only set when report has been downloaded and it does not need to be part of the response
var localFileUrl: URL?
enum CodingKeys: String, CodingKey {
case patientID = "PatientId"
case name = "Name"
case status = "Status"
case crmNo = "CrmNo"
case recordDate = "RecordDate"
case uniqueID = "UniquePackageId"
case testCount = "NoOfTests"
}
}
class ReportViewModel: ObservableObject {
// some dummy value
#Published var trackReport: [ReportResponse] = [ReportResponse(patientID: "0001", name: "patient-1", status: "status", crmNo: nil, recordDate: "today", uniqueID: "010001", testCount: 1),ReportResponse(patientID: "0002", name: "patient-2", status: "status", crmNo: nil, recordDate: "today", uniqueID: "010002", testCount: 3)]
// Update the report in the array ussing report unique ID
func updateReport(withId reportId: String, url: URL) {
guard let index = trackReport.firstIndex(where: {$0.uniqueID == reportId}) else {return}
var report = trackReport[index]
report.localFileUrl = url
trackReport[index] = report
}
}
// no need for any observation on pdfDownload object as the completion will do the jobs
class PDFDownload {
var uniqueReportId: String
var patientName: String
init(uniqueReportId:String, patientName:String) {
self.uniqueReportId = uniqueReportId
self.patientName = patientName
}
func getFile(completion: #escaping (URL) -> ())
{
var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "myHost"
urlComponents.port = 80
urlComponents.path = "/api/Reports/DownloadReport"
urlComponents.queryItems = [URLQueryItem(name: "uniquePackageId",value: uniqueReportId)]
let url = urlComponents.url
// print(url?.absoluteString)
let downloadTask = URLSession.shared.downloadTask(with: url!)
{
urlOrNil, responseOrNil, errorOrNil in
// Simulation of downloading
sleep(3)
DispatchQueue.main.async {
completion(URL(fileURLWithPath: "report\(self.patientName).pdf"))
}
guard let fileURL = urlOrNil else {return}
do{
let documentURL = try FileManager.default.url(for:
.documentDirectory, in: .userDomainMask, appropriateFor:
nil, create: false)
let savedURL = documentURL.appendingPathComponent("\(self.patientName)_\(UUID().uuidString).pdf")
print(savedURL)
try FileManager.default.moveItem(at: fileURL, to:
savedURL)
// Update the report url
DispatchQueue.main.async {
completion(savedURL)
}
}
catch{
print("Error while writting")
}
}
downloadTask.resume()
}
}
struct ReportList: View {
#ObservedObject var reportLink : ReportViewModel
var body: some View {
NavigationView{
List{
ForEach(reportLink.trackReport) { report in
if let url = report.localFileUrl {
NavigationLink {
DisplayPDF(url: url)
} label: {
Text(report.name)
}
} else {
ReportRow(report: report, updateReport: updateReport)
}
}
// Moved out of ForEach
if(reportLink.trackReport.isEmpty)
{
Text("No Report Found")
.foregroundColor(.accentColor)
.fontWeight(.semibold)
}
}
}
}
func updateReport(withId reportId: String, url: URL) {
reportLink.updateReport(withId: reportId, url: url)
}
}
struct ReportRow: View {
var report: ReportResponse
var updateReport: (String, URL) -> ()
var body: some View {
VStack{
HStack{
Text(report.name)
.font(.system(size: 16))
.foregroundColor(.black)
.padding(.bottom,1)
.padding(.top,1)
}.frame(maxWidth: .infinity, alignment: .leading)
HStack{
Text("P.Id:")
.foregroundColor(.black)
.font(.system(size: 14))
Text(report.patientID)
.foregroundColor(.purple)
.font(.system(size: 14))
Spacer()
Button(action: {
let pdfDownload = PDFDownload(uniqueReportId: report.uniqueID, patientName: report.name)
pdfDownload.getFile(completion: updateReportUrl)
}, label:
{
Text("\(report.status)")
.foregroundColor(.blue)
.font(.system(size: 14))
.padding(.trailing,2)
}).frame(maxWidth: .infinity, alignment: .trailing)
}
}
}
func updateReportUrl(url: URL) {
updateReport(report.uniqueID, url)
}
}
struct DisplayPDF: View {
var url:URL
var body:some View
{
// Stub as I can not download
Text(url.absoluteString)
// PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable{
func updateUIView(_ uiView: UIView, context:
UIViewRepresentableContext<PDFKitRepresentedView>) {
}
let url: URL
init(_ url:URL)
{
self.url = url
}
func makeUIView(context:
UIViewRepresentableContext<PDFKitRepresentedView>) ->
PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
}
I want to call "getTestCounts" before displaying "Text(testCounts.counts[index].number)".
However, if I use onAppear, I end up with an infinite loop.
I think that since we are creating unique IDs with UUID and spinning them around in ForEach, we end up with an infinite loop.
Infinite loop when using onAppear in SwiftUI
I tried to solve this problem using init() with reference to
"Cannot use instance member 'calendar' within property initializer; property initializers run before 'self' is available"
The following error occurs.
How can I solve this problem? Thanks for suggestions.
Here is the UI I want to create
I want to display the API response value under the date.
Here is the source code where the infinite loop occurs.
struct CalendarView: View {
#Binding var year: String
#Binding var month: String
#Binding var day: String
#EnvironmentObject var testCounts: TestCounts
var body: some View {
let calendar = Calendar(identifier: .gregorian)
let selectedDate = calendar.date(from: DateComponents(year: Int(year), month: Int(month), day: Int(day)))
let calendarDates = generateDates(selectedDate!)
LazyVGrid(columns: Array(repeating: GridItem(.fixed(60.0), spacing: 0), count: 7), spacing: 0) {
ForEach(calendarDates) { date in
Button(action: {}, label: {
VStack(spacing: 0) {
if let date = date.date, let day = Calendar.current.day(for: date) {
Text("\(day)").fontWeight(.semibold).frame(width: 45, alignment: .leading).foregroundColor(Color("dayTextBrown"))
ForEach(0 ..< testCounts.counts.count, id: \.self) { index in
if testCounts.counts[index].date == DateTime.dateToStr(date) {
Text(testCounts.counts[index].number)
Text(testCounts.counts[index].hcount)
}
}
} else {
Text("").frame(width: 45, alignment: .leading)
}
}.frame(width: 60, height: 60).onAppear {
getTestCounts(date.date ?? Date(), "all")
}
}).background(.white).border(Color("drabBrown"), width: 1)
}
}
}
func getTestCounts(_ date: Date, _ timeType: String) {
let since = Calendar.current.startOfMonth(for: date)
let stringSince = DateTime.dateToStr(since!)
let until = Calendar.current.endOfMonth(for: date)
let stringUntil = DateTime.dateToStr(until!)
TestCountsApi(LoginInformation.shared.token, shopId: LoginInformation.shared.defaultShopId, since: stringSince, until: stringUntil, timeType: timeType).request { json, error, result in
switch result {
case .success, .successWithMessage:
TestCounts.shared.setTestCounts(json!)
case .apiError:
errorMessage = json!.message!
case .communicationError:
errorMessage = error!.localizedDescription
case .otherError:
errorMessage = "otherError"
}
}
}
}
struct CalendarDates: Identifiable {
var id = UUID()
var date: Date?
}
func generateDates(_ date: Date) -> [CalendarDates] {
var days = [CalendarDates]()
let startOfMonth = Calendar.current.startOfMonth(for: date)
let daysInMonth = Calendar.current.daysInMonth(for: date)
guard let daysInMonth = daysInMonth, let startOfMonth = startOfMonth else {
return []
}
for day in 0 ..< daysInMonth {
days.append(CalendarDates(date: Calendar.current.date(byAdding: .day, value: day, to: startOfMonth)))
}
}
guard let firstDay = days.first, let lastDay = days.last,
let firstDate = firstDay.date, let lastDate = lastDay.date,
let firstDateWeekday = Calendar.current.weekday(for: firstDate),
let lastDateWeekday = Calendar.current.weekday(for: lastDate)
else { return [] }
let firstWeekEmptyDays = firstDateWeekday - 1
let lastWeekEmptyDays = 7 - lastDateWeekday
for _ in 0 ..< firstWeekEmptyDays {
days.insert(CalendarDates(date: nil), at: 0)
}
for _ in 0 ..< lastWeekEmptyDays {
days.append(CalendarDates(date: nil))
}
return days
}
class TestCounts: ObservableObject {
struct TestCount {
var date: String
var number: Int
var hcount: Int
}
static let shared = TestCounts()
#Published var counts: [TestCount] = []
func setTestCounts(_ json: TestCountsJson) {
counts = []
if let countsJsons = json.counts {
for countJson in countsJsons {
counts.append(TestCount(
date: countJson.date ?? "",
number: countJson.number ?? 0,
hcount: countJson.hcount ?? 0
))
}
}
}
}
Here is the source code that tries to use init().
struct CalendarView: View {
#Binding var year: String
#Binding var month: String
#Binding var day: String
#EnvironmentObject var testCounts: TestCounts
let calendar = Calendar(identifier: .gregorian)
let selectedDate = calendar.date(from: DateComponents(year: Int(year), month: Int(month), day: Int(day)))
let calendarDates = generateDates(selectedDate!)
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.fixed(60.0), spacing: 0), count: 7), spacing: 0) {
ForEach(calendarDates) { date in
Button(action: {}, label: {
VStack(spacing: 0) {
if let date = date.date, let day = Calendar.current.day(for: date) {
Text("\(day)").fontWeight(.semibold).frame(width: 45, alignment: .leading).foregroundColor(Color("dayTextBrown"))
ForEach(0 ..< testCounts.counts.count, id: \.self) { index in
if testCounts.counts[index].date == DateTime.dateToStr(date) {
Text(testCounts.counts[index].number)
Text(testCounts.counts[index].hcount)
}
}
} else {
Text("").frame(width: 45, alignment: .leading)
}
}.frame(width: 60, height: 60)
}).background(.white).border(Color("drabBrown"), width: 1)
}
}
}
}
class TestViewModel {
var date: Date
var timeType: String
var errorMessage = ""
init (date: Date, timeType: String) {
self.date = date
self.timeType = timeType
getTestCounts(date, timeType)
}
func getTestCounts(_ date: Date, _ timeType: String) {
let since = Calendar.current.startOfMonth(for: date)
let stringSince = DateTime.dateToStr(since!)
let until = Calendar.current.endOfMonth(for: date)
let stringUntil = DateTime.dateToStr(until!)
TestCountsApi(LoginInformation.shared.token, shopId: LoginInformation.shared.defaultShopId, since: stringSince, until: stringUntil, timeType: timeType).request { json, error, result in
switch result {
case .success, .successWithMessage:
print("success")
TestCounts.shared.setTestCounts(json!)
case .apiError:
self.errorMessage = json!.message!
print("errorMessage")
case .communicationError:
self.errorMessage = error!.localizedDescription
print("errorMessage")
case .otherError:
self.errorMessage = "otherError"
print("errorMessage")
}
}
}
}
I know I can use for each for this, but every time I try to implement according to documentation it throws some kind of error regarding syntax.
Here is my view:
import SwiftUI
import Combine
struct HomeTab: View {
#StateObject var callDevices = CallDevices()
var body: some View {
NavigationView {
devices
.onAppear {
callDevices.getDevices()
}
}
}
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(content: callDevices.getDevices(), id: \.self) { device in
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
}
struct HomeTab_Previews: PreviewProvider {
static var previews: some View {
HomeTab()
}
}
Here is my Call Devices which works without issue in other views:
class CallDevices: ObservableObject {
private var project_id: String = "r32fddsf"
#Published var devices = [Device]()
func getDevices() {
guard let url = URL(string: "www.example.com") else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Authorization")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {print(error!.localizedDescription); return }
// guard let data = data else {print("empty data"); return }
let theData = try! JSONDecoder().decode(Welcome.self, from: data!)
DispatchQueue.main.async {
self.devices = theData.devices
}
}
.resume()
}
}
is the issue in the way I am calling my function?
try this:
(you may need to make Device Hashable)
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(callDevices.devices, id: \.self) { device in // <-- here
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
If Device is Identifiable, you can remove the id: \.self.
struct Device: Identifiable, Hashable, Codable {
let id = UUID()
var Name = ""
var Status = ""
// ... any other stuff you have
}
I'm currently developing an application using SwiftUI.
I want to close a sheet after an API connection finish using a closure method.
So I tried to do that with the code below, but in the case of these codes, they don't work well...
How could I solve this problem?
Here are the codes:
TestSheet.swift
import SwiftUI
struct TestSheet: View {
#EnvironmentObject var appState: AppState
#State var id:Int = 1
#State var memo:String = "new Memo"
#State var isFinish:Bool = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView{
VStack{
Button(action: {
appState.makeUpDate(
pk:id, memo:memo, finish: {returnData in
isFinish = returnData
}
)
if(isFinish){
self.presentationMode.wrappedValue.dismiss()
}
})
{
Text("UPDATE")
}
}
}
}
}
AppState.swift
...
func makeUpDate(pk the_pk:Int, memo the_memo:String, finish:#escaping(Bool)->Void) {
var isFinish:Bool = false
let endpoint: String = "https://sample.com/api/info/\(the_pk)/"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
urlRequest.httpMethod = "PUT"
urlRequest.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let newInfo:[String:Any]=["memo":the_memo]
let jsonInfo: Data
do {
jsonInfo = try JSONSerialization.data(withJSONObject: newInfo, options: [])
urlRequest.httpBody = jsonInfo
} catch {
print("Error: cannot create JSON from newInfo")
return
}
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print("error calling Put")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard let response = response as? HTTPURLResponse else {
print("Error: did not response data")
return
}
print("The response code is \(response.statusCode)")
// parse the result as JSON, since that's what the API provides
do {
guard let receivedData = try JSONSerialization.jsonObject(with: responseData,
options: []) as? [String: Any] else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The request is: " + receivedData.description)
} catch {
print("error parsing response from PUT")
return
}
DispatchQueue.main.async {
isFinish = true
finish(isFinish)
}
}
task.resume()
}
...
Xcode:Version 12.0.1
The isFinish is updated asynchronously, so we should react on it in different place
var body: some View {
NavigationView{
VStack{
Button(action: {
appState.makeUpDate(
pk:id, memo:memo, finish: {returnData in
isFinish = returnData
}
)
})
{
Text("UPDATE")
}
}
.onChange(of: isFinish) { result in
if result {
self.presentationMode.wrappedValue.dismiss() // << here !!
}
}
}
}
I'm currently developing an application using SwiftUI.
I want to show two kinds of data in two lists in a view.
(1)start_date
(2)the temperatures of each day from start_date(1) to today
in the case of my code, (1)start_date is shown well, but (2)the temperatures have a problem because each list should show different data but they show the same data in each list...
Although I can check when each method is called, they make deferent data in the console like below, the simulator shows the same data...
The result in the console:
temp_info
25.7
24.9
temp_info
25.6
25.7
24.9
24.1
23.5
25.7
26.4
23.7
23.0
24.4
26.1
How could I resolve this problem?
Here are the codes:
JsonModel.swift
import Foundation
struct DbVegetableInfos: Codable,Identifiable {
var id: Int
var start_date: String
}
struct WeatherAveInfos:Codable,Identifiable {
var id: Int
var ave_temp: Float
}
AppState.swift
import SwiftUI
import Foundation
import Combine
import UIKit
class AppState: ObservableObject {
#Published var arrayDbVegetableInfos:[DbVegetableInfos]?
#Published var weatherAveInfos:[WeatherAveInfos]?
func makeGetCallVegetableInfos() {
// Set up the URL request
let endpoint: String = "https://sample.com/api/info/"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
DispatchQueue.main.async {
do{ self.arrayDbVegetableInfos = try JSONDecoder().decode([DbVegetableInfos].self, from: responseData)
}catch{
print("Error: did not decode")
return
}
}
}
task.resume()
}
}
func makeGetCallWeatherAveTemp(start_date:String ) {
// Set up the URL request
let endpoint: String = "https://sample.com/api/weather_ave/?start_date=\(start_date)"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET")
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
DispatchQueue.main.async {
do{ self.weatherAveInfos = try JSONDecoder().decode([WeatherAveInfos].self, from: responseData)
print("temp_info")
for info in self.weatherAveInfos!{
print(info.ave_temp)
}
}catch{
print("Error: did not decode")
return
}
}
}
task.resume()
}
HomeView.swift
import SwiftUI
struct HomeView: View {
#EnvironmentObject var appState: AppState
var body: some View {
NavigationView{
VStack{
ForEach(appState.arrayDbVegetableInfos ?? []){ info in
VStack{
VegetableInfoRow(info:info)
}.background(Color(.secondarySystemFill))
.cornerRadius(10)
.padding(.top)
.padding(.leading)
.padding(.bottom)
}
}.onAppear(){
appState.makeGetCallVegetableInfos()
}
}
}
}
VegetableInfoRow.swift
import SwiftUI
struct VegetableInfoRow: View {
#EnvironmentObject var appState: AppState
var info:DbVegetableInfos
var body: some View {
ScrollView(.horizontal) {
HStack{
VStack{
VStack{
Text("start_dateļ¼").padding()
Text(stringToStringDate(stringDate: info.start_date, format: "yyyy-MM-dd"))
}
}
Divider()
.padding()
VStack{
VStack{
Text("progress_temperatureļ¼").padding()
ForEach(appState.weatherAveInfos ?? []){ info in
Text(String(info.ave_temp))
}
}
}
}
}.onAppear(){
appState.makeGetCallWeatherAveTemp(start_date: info.start_date)
}
}
}
func stringToStringDate(stringDate: String, format:String) -> String {
let formatter: DateFormatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.dateFormat = "yyyy/MM/dd"
let newDate = formatter.date(from: stringDate)!
formatter.dateFormat = format
return formatter.string(from: newDate)
}
Xcode:Version 12.0.1