I created a Webkit view that passes a String from an embedded Youtube video via the ID--with static data the app works perfectly.
Youtube View
struct YoutubeView: UIViewRepresentable {
let videoID: String
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let youtubeURL = URL(string: "https://www.youtube.com/embed/\(videoID)") else {return}
uiView.scrollView.isScrollEnabled = false
uiView.load(URLRequest(url: youtubeURL))
} }
Data Model
var youtubeID: String
However, the issue I am having is when I attempt to launch the app after using the Contentful API - I am getting Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value The other lines of code from Contentful works, it's just this one.
class PdcstStore: ObservableObject {
#Published var pdcsts: [Pdcst] = pdcstData
init() {
DispatchQueue.main.async {
self.refreshView()
}
}
func refreshView() {
getArray(id: "pdcstsc") { (items) in
items.forEach { (item) in
self.pdcsts.append(
Pdcst(
title: item.fields["title"] as! String,
duration: item.fields["duration"] as! Int,
episode: item.fields["episode"] as! Int,
youtubeID: item.fields["youtubeID"] as! String,
description: item.fields["description"] as! String,
date: item.fields["date"] as! String,
image: item.fields.linkedAsset(at: "image")?.url ?? URL(string: ""),
new: item.fields["new"] as? Bool ?? false,
pdcstUrl: URL(string: "https://images.squarespace-cdn.com/content/v1/617ff7d966d585586902b7be/1640279341707-5I5P75CAJZMZ41M9H0XM/BarrettSquare.jpg?format=750w")!)
)
}
}
}
}
How can I solve this? I am fairly new to coding and SwiftUI. So, this is kinda new to me and I am learning as I go!
Thank you for your assistance in advance!
Edit
YoutubeView(videoID: pdcst.youtubeID)
.frame(minHeight: 0, maxHeight: UIScreen.main.bounds.height * 0.3)
.frame(maxWidth: .infinity)
Print
The_Grdnt/PdcstStore.swift:55: Fatal error: Unexpectedly found nil while unwrapping an Optional value
2022-02-19 20:58:28.445983-0500 The Grdnt[48129:1407025] The_Grdnt/PdcstStore.swift:55: Fatal error: Unexpectedly found nil while unwrapping an Optional value
PRINT
enter image description here
Items
struct Pdcst: Identifiable {
var id = UUID()
var title: String
var duration: Int
var episode: Int
var youtubeID: String
var description: String
var date: String
var image: URL?
var new: Bool
var pdcstUrl : URL
}
let pdcstData = [
Pdcst(title: "Barrett Miller II",
duration: 69,
episode: 010,
youtubeID: "xtLvfVXu_JY",
description: "Barrett Miller II is what some would call a protege, a pupil, a student to his craft. This college student filmmaker is making waves through the consistent and persistent work he is putting out.\n\nBarrett is the descendant of a Bahamian ancestor. His lineage is coded all through his DNA. As we converse, we become conscious of his upbringing in 757, stepping into filmmaking, and the genesis of his ideas.",
date: "Jan, 4, 2022",
image: URL(string: "https://images.squarespace-cdn.com/content/v1/617ff7d966d585586902b7be/1640279341707-5I5P75CAJZMZ41M9H0XM/BarrettSquare.jpg?format=750w")!,
new: true,]
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 have implemented a MKMapView in SwiftUI and I am showing a list of annotations (stops) as well as the user's location.
I wanted to add the tap functionality to the "stop pins" but I wasn't able to find anything helpful to achieve this.
The problem with this code is that it changes the view of the user location pin and eventually crashes with the following error.
2021-07-10 18:31:21.434538+0900 Bus Finder[5232:2086940] *** Terminating app due to uncaught exception 'NSGenericException', reason: '<Bus_Finder.Stops: 0x2816c4cc0> must implement title, or view (null) must have a non-nil detailCalloutAccessoryView when canShowCallout is YES on corresponding view <MKAnnotationView: 0x13137cd60; frame = (-20 -20; 40 40); opaque = NO; layer = <CALayer: 0x2832e9e20>>'
*** First throw call stack:
(0x196f2a754 0x1ab9f17a8 0x1a6566410 0x1a65655bc 0x1a656464c 0x1a65641d0 0x1982fd458 0x196ea522c 0x196ea4e28 0x196ea4278 0x196e9e02c 0x196e9d360 0x1ae4db734 0x199918584 0x19991ddf4 0x19ddf3370 0x19ddf32fc 0x19d8ebb6c 0x100eacf54 0x100eacff4 0x196b59cf8)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSGenericException', reason: '<Bus_Finder.Stops: 0x2816c4cc0> must implement title, or view (null) must have a non-nil detailCalloutAccessoryView when canShowCallout is YES on corresponding view <MKAnnotationView: 0x13137cd60; frame = (-20 -20; 40 40); opaque = NO; layer = <CALayer: 0x2832e9e20>>'
terminating with uncaught exception of type NSException
I only want to change the view of the "stops pin" and add the tap functionality.
I pass a list of Stops to the MapView on appear. The Stops structure is at the end.
A visual concept of my problem:
(When commenting the viewFor annotation function)
I want to change the style of the stops pin and add tap functionality to it not the user's location pin.
When I use the viewFor annotation function (the same code as in this question) the user location view changes and then the app crashes.
The MapView file:
// MARK: MapView
struct MapView: UIViewRepresentable {
// MARK: Variables
#Binding var stops: [Stops]
#Binding var centerCoordinate: MKCoordinateRegion
#Binding var action: Action
// MARK: Action Lists
enum Action {
case idle
case reset(coordinate: MKCoordinateRegion)
case changeType(mapType: MKMapType)
}
// MARK: First Time Only
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
mapView.isUserInteractionEnabled = true
mapView.centerCoordinate = self.centerCoordinate.center
mapView.setRegion(self.centerCoordinate, animated: true)
return mapView
}
// MARK: Updating UI
func updateUIView(_ view: MKMapView, context: Context) {
switch action {
case .idle:
break
case .reset(let newCoordinate):
view.delegate = nil
DispatchQueue.main.async {
self.centerCoordinate.center = newCoordinate.center
self.action = .idle
view.setRegion(self.centerCoordinate, animated: true)
view.delegate = context.coordinator
}
case .changeType(let mapType):
view.mapType = mapType
}
view.addAnnotations(stops)
}
// MARK: Setting Coordinator
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate.center = mapView.centerCoordinate
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "TESTING NOTE")
annotationView.canShowCallout = true
annotationView.image = UIImage(systemName: "location.circle")?.withTintColor(.systemGreen, renderingMode: .alwaysOriginal)
let size = CGSize(width: 40, height: 40)
annotationView.image = UIGraphicsImageRenderer(size:size).image {
_ in annotationView.image!.draw(in:CGRect(origin:.zero, size:size))
}
return annotationView
}
}
}
Stops structure file:
// MARK: StopPinPoint
final class Stops: NSObject, Codable, Identifiable, MKAnnotation {
var id: String?
var name: BusStopName
var images: [String]?
var landMarks: [String]?
var coordinate: CLLocationCoordinate2D
var prevNexStop: [String]?
init(id: String?, name: BusStopName, images: [String]?, landMarks: [String]?, coordinates: CLLocationCoordinate2D, prevNextStop: [String]?) {
self.id = id
self.name = name
self.coordinate = coordinates
self.images = images
self.landMarks = landMarks
self.prevNexStop = prevNextStop
}
var location: CLLocation {
return CLLocation(latitude: self.coordinate.latitude, longitude: self.coordinate.longitude)
}
func distance(to location: CLLocation) -> CLLocationDistance {
return location.distance(from: self.location)
}
}
I would appreciate it a lot if someone could help me! I have been working on this problem for weeks now!
So basically after a bit more searching, I found out the answer.
In order to not change the user's location pin, I have to check the type of annotation and if the type is MKUserLocation I should return nil.
Following that the reason for the crash was that I had to make the Stops structure confirm to MKPointAnnotation and remove or override the coordinate variable then when I am making a list of Stops I can simply define the title, subtitle and coordinates.
I'm new to SwiftUI. Currently struggling with fetching and presenting photos and places names to my List from Google Places (Maps) API. I do know that they provide explanation of how to do it, but since I'm not that experienced yet, i cannot get the code provided just for Swift (not Swift UI). Tried to use the method below:
struct Response: Codable {
var results: [Place]
}
struct Place: Codable {
var placeId: Int
var placeName: String
var placePic: String
var placeTime: String
var placePhone: String
var placeLocation: String
}
#State var results = [Place] ()
var body: some View {
List(results, id: \.placeId) { item in
VStack{
Text(item.placeName)
}
}.onAppear(perform: loadData)
}
func loadData(){
guard let url = URL(string: "https://maps.googleapis.com/maps/api/place/photo?parameters") else{
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request){data, responce, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.results = decodedResponse.results
}
return
}
}
print("fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
Question:
Is there any ideas of what i do wrong? Because I tried to search for some tutorials on the web, using SwiftUI, but haven't found any.
Scenario:
I'm using an Observable Class to acquire data from the network.
In this case some elementary weather data.
Problem:
I don't know how to display this data in the calling View.
For the time-being, I merely am trying to populate a Textfield (and worry about more-eleborate layout later).
I get the following:
.../StandardWeatherView.swift:22:13: Cannot invoke initializer for
type 'TextField<_>' with an argument list of type '(Text, text:
Sample?)'
Here's is my calling View which is the receiver of #ObservedObject data:
import SwiftUI
struct StandardWeatherView: View {
#EnvironmentObject var settings: Settings
#ObservedObject var standardWeatherReportLoader = StandardWeatherReportLoader()
init() {
self.standardWeatherReportLoader.doStandard()
}
var body: some View {
ZStack {
Color("FernGreen").edgesIgnoringSafeArea(.all)
TextField(Text("Weather Data"), text: standardWeatherReportLoader.weatherReport)
}
}
}
struct StandardWeatherView_Previews: PreviewProvider {
static var previews: some View {
StandardWeatherView()
}
}
Here's the publisher, acquiring data:
import Foundation
class StandardWeatherReportLoader: ObservableObject {
#Published var networkMessage: String?
#Published var hasAlert = false
#Published var weatherReport: Sample?
#Published var hasReport = false
func doStandard() {
let url = EndPoint.weather.path()
var request = URLRequest(url: EndPoint.weather.path()!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: url!) { (data: Data?, _: URLResponse?, error: Error?) -> Void in
DispatchQueue.main.async {
guard error == nil else {
self.networkMessage = error?.localizedDescription
self.hasAlert = true
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Sample.self, from: data!)
self.weatherReport = result
self.hasReport = true
print("\n Standard Weather ----------------")
print(#function, "line: ", #line, "Result: ",result)
print("\n")
} catch let error as NSError {
print(error)
}
}
}
task.resume()
}
}
What's the simplest way of passing a string of data to the View via #Published var?
Log:
Standard Weather ---------------- doStandard() line: 38 Result:
Sample(coord: DataTaskPubTab.Coord(lon: -0.13, lat: 51.51), weather:
[DataTaskPubTab.Weather(id: 300, main: "Drizzle", description: "light
intensity drizzle")], base: "stations", main:
DataTaskPubTab.Main(temp: 280.32, pressure: 1012, humidity: 81,
tempMin: 279.15, tempMax: 281.15), visibility: 10000, wind:
DataTaskPubTab.Wind(speed: 4.1, deg: 80), clouds:
DataTaskPubTab.Clouds(all: 90), dt: 1485789600.0, id: 2643743, name:
"London")
But I'm getting nil at the TextField:
(lldb) po standardWeatherReportLoader.weatherReport nil
One option is to set a binding within your body to track whenever the TextField has updated. From within this binding, you can then edit your Published variable as you wish:
#ObservedObject var reportLoader = StandardWeatherReportLoader()
var body: some View {
// Binding to detect when TextField changes
let textBinding = Binding<String>(get: {
self.reportLoader.networkMessage
}, set: {
self.reportLoader.networkMessage = $0
})
// Return view containing the text field
return VStack {
TextField("Enter the Network Message", text: textBinding)
}
}
Edit: Also in your original post, you were passing an object of optional type Sample into the TextField which was expecting a binding String type which could cause some issues.
I am passing data from one view controller to another. If I print the text to the console it will show. However when I try to add the text to my label on the view controller I get an error about Nil being found when unwrapping the optional.
Here is my code from the initial View Controller
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedLady = DataService.instance.getLadies()[indexPath.row]
performSegue(withIdentifier: "LadyView", sender: selectedLady)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let ladyView = segue.destination as? LadyView {
assert(sender as? Lady != nil)
ladyView.initLadies(selectedLady: sender as! Lady)
//ladyView.selectedLadyName = selectedLady["name"]
}
}
And this is the code that is waiting in the second controller
func initLadies(selectedLady: Lady) {
print(selectedLady.name)
//This is the line that says it's Nil
LadyName.text = selectedLadyName
}
This is my lady struct
struct Lady {
private(set) public var name: String
private(set) public var imageName: String
private(set) public var subTitle: String
private(set) public var body: String
init(name: String, imageName: String, subTitle: String, body: String) {
self.name = name
self.imageName = imageName
self.subTitle = subTitle
self.body = body
}
}
Thank you for your help.
I have looked at the other answer in this post Passing data from table view to view controller and although it's similar his answer seems to be slightly different.
func initLadies(selectedLady: Lady) {
print(selectedLady.name)
LadyName.text = selectedLadyName.name
}
try this #CL Maciel