SwiftUI Edit/override all Alerts across the app - swiftui

I have a lot of Alerts in an app due to specific cases of error handling in many screens, and now I need to display the time on any Alert whenever it shows, is there a way to edit/override Alert so it includes the time only on debug?
For example I can achieve this for all prints with:
import Foundation
func print(_ items: Any..., separator: String = " ", terminator: String = "\n"){
#if DEBUG
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
Swift.print( df.string(from: Date()) )
items.forEach{
Swift.print($0, separator: separator, terminator: terminator)
}
#endif
}
But how can something like Alert be overriden without changing every single instance to another element?

I would recommend you just write a function, with a normal name such as customAlert(...). This makes it clearer that it's custom, and also the function has a good naming scheme, unlike calling it Alert. Alert is ambiguous, and with the exact same parameters, you will get a compiler-time error. Once you have renamed all instances of Alert with customAlert, you will never have to make this change again.
Example:
func customAlert(title: Text, message: Text? = nil, dismissButton: Alert.Button? = nil) -> Alert {
func alertMessage() -> Text? {
#if DEBUG
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = "Date: \(df.string(from: Date()))"
if let message = message {
return Text("\(date)\n\n") + message
} else {
return Text(date)
}
#else
return message
#endif
}
return SwiftUI.Alert(title: title, message: alertMessage(), dismissButton: dismissButton)
}
Usage:
struct ContentView: View {
#State private var presentAlert = false
var body: some View {
Button("Present alert") {
presentAlert = true
}
.alert(isPresented: $presentAlert) {
customAlert(title: Text("Title"), message: Text("Message"))
}
}
}

Related

Displaying and Logging Persistence and App Store Errors

I am trying to figure out how to display errors such as those found in the App Store Store class and Persistence.swift file in a single reporting module. Some of my errors within views use an environment object to tie the error production to the error reporting, but this isn’t feasible with errors in different classes.
So any pointers on handling these types of errors would be much appreciated.
Below is an example of my process to report an error within a view
// the save button has been pressed
struct saveButton: View {
#Environment(\.managedObjectContext) var viewContext
#EnvironmentObject var errorHandling: ErrorHandling
var body: some View {
// prepping data for save to core data
do {
try viewContext.save()
} catch {
// report: Unable to Save Transaction
self.errorHandling.handle(error: AppError.saveTransactionError)
}
The App Store purchase method (below) may produce two errors that I would like to display with my app. This is the type of logic that I need help display the error within a view
#MainActor
func purchase() {
Task.init {
guard let product = products.first else {
return
}
guard AppStore.canMakePayments else {
return
}
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
await transaction.finish()
// save to user defaults
self.purchasedIds = transaction.productID
break
case .unverified:
// throw PurchaseError.failed
// FIX: report error (Unable to purchase verification failed)
break
}
break
case .userCancelled:
break
// asked to buy (setting on phone prevents purchases)
case .pending:
break
default:
break
}
}
catch {
print(error.localizedDescription)
// FIX: report error (Unable to Complete Purchase)
}
}
}
enum AppError: LocalizedError {
case storePurchaseError
case storeVerificationError
var errorDescription: String? {
switch self {
case .storePurchaseError:
return NSLocalizedString("Store Purchase Error", comment: "")
case .storeVerificationError:
return NSLocalizedString("Store Verification Error", comment: "")
}
}
}
Below is some code I have been using to display an alert
struct ErrorAlert: Identifiable {
var id = UUID()
var message: String
var dismissAction: (() -> Void)?
}
class ErrorHandling: ObservableObject {
#Published var currentAlert: ErrorAlert?
func handle(error: Error) {
currentAlert = ErrorAlert(message: error.localizedDescription)
}
}
struct HandleErrorsByShowingAlertViewModifier: ViewModifier {
#StateObject var errorHandling = ErrorHandling()
func body(content: Content) -> some View {
content
.environmentObject(errorHandling)
.background(
EmptyView()
.alert(item: $errorHandling.currentAlert) { currentAlert in
Alert(
title: Text("Error"),
message: Text(currentAlert.message),
dismissButton: .default(Text("Ok")) {
currentAlert.dismissAction?()
}
)
}
)
}
}
extension View {
func withErrorHandling() -> some View {
modifier(HandleErrorsByShowingAlertViewModifier())
}
}

How to infer a generic paramater with an async/await function

I have an async/await function to make sure that the data gets passed along first;
func downloadFirebaseData() async -> String {
let group = DispatchGroup()
group.enter() // stop the thread/enter the function
let db = Firestore.firestore()
withUnsafeThrowingContinuation { continuation in
db.collection("annotations")
.getDocuments { (querySnapshot, error) in
defer {
group.leave() // << end on any return
}
if let Lng = i.document.get("lng") as? String {
DispatchQueue.main.async {
annotationLng.append(Lng) //edit the array
print("downloadLngServerData ()\(annotationLng)")
}
}
if let Lat = i.document.get("lat") as? String {
DispatchQueue.main.async {
annotationLat.append(Lat) //edit the array
print("downloadLatServerData ()\(annotationLat)")
}
}
}
}
group.wait() // clear up the thread now, exit the function
}
And its called under my view with;
.task {
try await downloadFirebaseData() //error 1
}
#State annotationLat: [String] = []
#State annotationLng: [String] = []
Inside of firebase database:
annotationLat = ["42.828392","29.18273","97.27352"]
annotationLng = ["42.828392","29.18273","97.27352"]
I have 2 errors;
Invalid conversion from throwing function of type '#Sendable () async throws -> Void' to non-throwing function type '#Sendable () async -> Void'
This was under the .task
My second error:
Generic parameter 'T' could not be inferred
This was under withUnsafeThrowingContinuation
The first error I somewhat get, but even after I modified from my original code, the error still persisted.
For the second error, I know that I might have to define that this is a string somewhere, because I don't think that the app knows that I'm trying to work with a string.
This assumes that the Firestore path for the documents is
annotations/{id}
and that each document has variables lat and lng of type String
import Foundation
import FirebaseFirestoreSwift
import FirebaseFirestore
import CoreLocation
//struct to keep the latitude and longitude together, they should not be in separate arrays
struct Annotation: Codable, Identifiable{
#DocumentID var id: String?
var lat: String?
var lng: String?
}
extension Annotation{
//Safely unwrap the Strings into doubles and then create the coordinate
var coordinate: CLLocationCoordinate2D? {
guard let latStr = lat, let lngStr = lng, let latitude = Double(latStr), let longitude = Double(lngStr) else{
print("Unable to get valid latitude and longitude")
return nil
}
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return coordinate
}
}
struct CustomFirestoreService{
let store: Firestore = .firestore()
init(){}
func getAnnotations() async throws -> [Annotation]{
let ANNOTATIONS_PATH = "annotations"
return try await retrieve(path: ANNOTATIONS_PATH)
}
///retrieves all the documents in the collection at the path
private func retrieve<FC : Codable>(path: String) async throws -> [FC]{
//Firebase provided async await.
let querySnapshot = try await store.collection(path).getDocuments()
return querySnapshot.documents.compactMap { document in
do{
return try document.data(as: FC.self)
}catch{
print(error)
return nil
}
}
}
}
Then in your View
import SwiftUI
struct AnnotationsView: View {
let service: CustomFirestoreService = CustomFirestoreService()
#State private var annotations: [Annotation] = []
var body: some View {
if annotations.isEmpty{
Text("Hello, World!")
.task {
do{
annotations = try await service.getAnnotations()
//Do any other work here, this line won't run unless the annotations are populated.
}catch{
print(error)
}
}
}else{
List(annotations){ annotation in
if let coord = annotation.coordinate{
VStack{
Text("Latitude = \(coord.latitude)")
Text("Longitude = \(coord.longitude)")
}
}else{
Text("Invalid Coordinate Value. Check firestore values for document \(annotation.id ?? "no id")")
}
}
}
}
}
struct AnnotationsView_Previews: PreviewProvider {
static var previews: some View {
AnnotationsView()
}
}
This makes some assumptions but if you paste it into your project you should get some working code.
You don't need this for your code but this is what a conversion from the "old" closures to the new async await would look like.
public func retrieve<FC : Codable>(path: String) async throws -> [FC]{
typealias MyContinuation = CheckedContinuation<[FC], Error>
return try await withCheckedThrowingContinuation { (continuation: MyContinuation) in
store.collection(path)
.getDocuments() { (querySnapshot, err) in
if let err = err {
//This throws an error
continuation.resume(throwing: err)
} else {
let array = querySnapshot?.documents.compactMap { document in
try? document.data(as: FC.self)
} ?? []
//This returns an array
continuation.resume(returning: array)
}
}
}
}
If you aren't calling continuation there is no point in returning a continuation of any kind.

SwiftUI Using MapKit for Address Auto Complete

I have a form where the user enters their address. While they can always enter it manually, I also wanted to provide them with an easy solution with auto complete so that they could just start typing their address and then tap on the correct one from the list and have it auto populate the various fields.
I started by working off of jnpdx's Swift5 solution - https://stackoverflow.com/a/67131376/11053343
However, there are two issues that I cannot seem to solve:
I need the results to be limited to the United States only (not just the continental US, but the entire United States including Alaska, Hawaii, and Puerto Rico). I am aware of how MKCoordinateRegion works with the center point and then the zoom spread, but it doesn't seem to work on the results of the address search.
The return of the results provides only a title and subtitle, where I need to actually extract all the individual address information and populate my variables (i.e. address, city, state, zip, and zip ext). If the user has an apt or suite number, they would then fill that in themselves. My thought was to create a function that would run when the button is tapped, so that the variables are assigned based off of the user's selection, but I have no idea how to extract the various information required. Apple's docs are terrible as usual and I haven't found any tutorials explaining how to do this.
This is for the latest SwiftUI and XCode (ios15+).
I created a dummy form for testing. Here's what I have:
import SwiftUI
import Combine
import MapKit
class MapSearch : NSObject, ObservableObject {
#Published var locationResults : [MKLocalSearchCompletion] = []
#Published var searchTerm = ""
private var cancellables : Set<AnyCancellable> = []
private var searchCompleter = MKLocalSearchCompleter()
private var currentPromise : ((Result<[MKLocalSearchCompletion], Error>) -> Void)?
override init() {
super.init()
searchCompleter.delegate = self
searchCompleter.region = MKCoordinateRegion()
searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address])
$searchTerm
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
.removeDuplicates()
.flatMap({ (currentSearchTerm) in
self.searchTermToResults(searchTerm: currentSearchTerm)
})
.sink(receiveCompletion: { (completion) in
//handle error
}, receiveValue: { (results) in
self.locationResults = results
})
.store(in: &cancellables)
}
func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> {
Future { promise in
self.searchCompleter.queryFragment = searchTerm
self.currentPromise = promise
}
}
}
extension MapSearch : MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
currentPromise?(.success(completer.results))
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
//currentPromise?(.failure(error))
}
}
struct MapKit_Interface: View {
#StateObject private var mapSearch = MapSearch()
#State private var address = ""
#State private var addrNum = ""
#State private var city = ""
#State private var state = ""
#State private var zip = ""
#State private var zipExt = ""
var body: some View {
List {
Section {
TextField("Search", text: $mapSearch.searchTerm)
ForEach(mapSearch.locationResults, id: \.self) { location in
Button {
// Function code goes here
} label: {
VStack(alignment: .leading) {
Text(location.title)
.foregroundColor(Color.white)
Text(location.subtitle)
.font(.system(.caption))
.foregroundColor(Color.white)
}
} // End Label
} // End ForEach
} // End Section
Section {
TextField("Address", text: $address)
TextField("Apt/Suite", text: $addrNum)
TextField("City", text: $city)
TextField("State", text: $state)
TextField("Zip", text: $zip)
TextField("Zip-Ext", text: $zipExt)
} // End Section
} // End List
} // End var Body
} // End Struct
Since no one has responded, I, and my friend Tolstoy, spent a lot of time figuring out the solution and I thought I would post it for anyone else who might be interested. Tolstoy wrote a version for the Mac, while I wrote the iOS version shown here.
Seeing as how Google is charging for usage of their API and Apple is not, this solution gives you address auto-complete for forms. Bear in mind it won't always be perfect because we are beholden to Apple and their maps. Likewise, you have to turn the address into coordinates, which you then turn into a placemark, which means there will be some addresses that may change when tapped from the completion list. Odds are this won't be an issue for 99.9% of users, but thought I would mention it.
At the time of this writing, I am using XCode 13.2.1 and SwiftUI for iOS 15.
I organized it with two Swift files. One to hold the class/struct (AddrStruct.swift) and the other which is the actual view in the app.
AddrStruct.swift
import SwiftUI
import Combine
import MapKit
import CoreLocation
class MapSearch : NSObject, ObservableObject {
#Published var locationResults : [MKLocalSearchCompletion] = []
#Published var searchTerm = ""
private var cancellables : Set<AnyCancellable> = []
private var searchCompleter = MKLocalSearchCompleter()
private var currentPromise : ((Result<[MKLocalSearchCompletion], Error>) -> Void)?
override init() {
super.init()
searchCompleter.delegate = self
searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address])
$searchTerm
.debounce(for: .seconds(0.2), scheduler: RunLoop.main)
.removeDuplicates()
.flatMap({ (currentSearchTerm) in
self.searchTermToResults(searchTerm: currentSearchTerm)
})
.sink(receiveCompletion: { (completion) in
//handle error
}, receiveValue: { (results) in
self.locationResults = results.filter { $0.subtitle.contains("United States") } // This parses the subtitle to show only results that have United States as the country. You could change this text to be Germany or Brazil and only show results from those countries.
})
.store(in: &cancellables)
}
func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> {
Future { promise in
self.searchCompleter.queryFragment = searchTerm
self.currentPromise = promise
}
}
}
extension MapSearch : MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
currentPromise?(.success(completer.results))
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
//could deal with the error here, but beware that it will finish the Combine publisher stream
//currentPromise?(.failure(error))
}
}
struct ReversedGeoLocation {
let streetNumber: String // eg. 1
let streetName: String // eg. Infinite Loop
let city: String // eg. Cupertino
let state: String // eg. CA
let zipCode: String // eg. 95014
let country: String // eg. United States
let isoCountryCode: String // eg. US
var formattedAddress: String {
return """
\(streetNumber) \(streetName),
\(city), \(state) \(zipCode)
\(country)
"""
}
// Handle optionals as needed
init(with placemark: CLPlacemark) {
self.streetName = placemark.thoroughfare ?? ""
self.streetNumber = placemark.subThoroughfare ?? ""
self.city = placemark.locality ?? ""
self.state = placemark.administrativeArea ?? ""
self.zipCode = placemark.postalCode ?? ""
self.country = placemark.country ?? ""
self.isoCountryCode = placemark.isoCountryCode ?? ""
}
}
For testing purposes, I called my main view file Test.swift. Here's a stripped down version for reference.
Test.swift
import SwiftUI
import Combine
import CoreLocation
import MapKit
struct Test: View {
#StateObject private var mapSearch = MapSearch()
func reverseGeo(location: MKLocalSearchCompletion) {
let searchRequest = MKLocalSearch.Request(completion: location)
let search = MKLocalSearch(request: searchRequest)
var coordinateK : CLLocationCoordinate2D?
search.start { (response, error) in
if error == nil, let coordinate = response?.mapItems.first?.placemark.coordinate {
coordinateK = coordinate
}
if let c = coordinateK {
let location = CLLocation(latitude: c.latitude, longitude: c.longitude)
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
guard let placemark = placemarks?.first else {
let errorString = error?.localizedDescription ?? "Unexpected Error"
print("Unable to reverse geocode the given location. Error: \(errorString)")
return
}
let reversedGeoLocation = ReversedGeoLocation(with: placemark)
address = "\(reversedGeoLocation.streetNumber) \(reversedGeoLocation.streetName)"
city = "\(reversedGeoLocation.city)"
state = "\(reversedGeoLocation.state)"
zip = "\(reversedGeoLocation.zipCode)"
mapSearch.searchTerm = address
isFocused = false
}
}
}
}
// Form Variables
#FocusState private var isFocused: Bool
#State private var btnHover = false
#State private var isBtnActive = false
#State private var address = ""
#State private var city = ""
#State private var state = ""
#State private var zip = ""
// Main UI
var body: some View {
VStack {
List {
Section {
Text("Start typing your street address and you will see a list of possible matches.")
} // End Section
Section {
TextField("Address", text: $mapSearch.searchTerm)
// Show auto-complete results
if address != mapSearch.searchTerm && isFocused == false {
ForEach(mapSearch.locationResults, id: \.self) { location in
Button {
reverseGeo(location: location)
} label: {
VStack(alignment: .leading) {
Text(location.title)
.foregroundColor(Color.white)
Text(location.subtitle)
.font(.system(.caption))
.foregroundColor(Color.white)
}
} // End Label
} // End ForEach
} // End if
// End show auto-complete results
TextField("City", text: $city)
TextField("State", text: $state)
TextField("Zip", text: $zip)
} // End Section
.listRowSeparator(.visible)
} // End List
} // End Main VStack
} // End Var Body
} // End Struct
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
If anyone is wondering how to generate global results, change the code from this:
self.locationResults = results.filter{$0.subtitle.contains("United States")}
to this in Address Structure file:
self.locationResults = results

SwiftUI combine nil data

I have created a class to perform a network request and parse the data using Combine. I'm not entirely certain the code is correct, but it's working as of now (still learning the basics of Swift and basic networking tasks). My Widget has the correct data and is works until the data becomes nil. Unsure how to check if the data from my first publisher in my SwiftUI View is nil, the data seems to be valid even when there's no games showing.
My SwiftUI View
struct SimpleEntry: TimelineEntry {
let date: Date
public var model: CombineData?
let configuration: ConfigurationIntent
}
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var widgetFamily
var body: some View {
VStack (spacing: 0){
if entry.model?.schedule?.dates.first?.games == nil {
Text("No games Scheduled")
} else {
Text("Game is scheduled")
}
}
}
}
Combine
import Foundation
import WidgetKit
import Combine
// MARK: - Combine Attempt
class CombineData {
var schedule: Schedule?
var live: Live?
private var cancellables = Set<AnyCancellable>()
func fetchSchedule(_ teamID: Int, _ completion: #escaping (Live) -> Void) {
let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Schedule.self, decoder: JSONDecoder())
//.catch { _ in Empty<Schedule, Error>() }
//.replaceError(with: Schedule(dates: []))
let publisher2 = publisher
.flatMap {
return self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
}
Publishers.Zip(publisher, publisher2)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in
}, receiveValue: { schedule, live in
self.schedule = schedule
self.live = live
completion(self.live!)
WidgetCenter.shared.reloadTimelines(ofKind: "NHL_Widget")
}).store(in: &cancellables)
}
func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error /*Never if .catch error */> {
let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Live.self, decoder: JSONDecoder())
//.catch { _ in Empty<Live, Never>() }
.eraseToAnyPublisher()
}
}
Like I said in the comments, it's likely that the decode(type: Live.self, decoder: JSONDecoder()) returns an error because the URL that you're fetching from when link is nil doesn't return anything that can be decoded as Live.self.
So you need to handle that case somehow. For example, you can handle this by making the Live variable an optional, and returning nil when link is empty (or nil).
This is just to set you in the right direction - you'll need to work out the exact code yourself.
let publisher2 = publisher1
.flatMap {
self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
.map { $0 as Live? } // convert to an optional
.replaceError(with: nil)
}
Then in the sink, handle the nil:
.sink(receiveCompletion: {_ in }, receiveValue:
{ schedule, live in
if let live = live {
// normal treatment
self.schedule = schedule
self.live = live
//.. etc
} else {
// set a placeholder
}
})
SwiftUI and WidgetKit work differently. I needed to fetch data in getTimeline for my IntentTimelineProvider then add a completion handler for my TimelineEntry. Heavily modified my Combine data model. All credit goes to #EmilioPelaez for pointing me in the right direction, answer here.

put observedObject in List

I get the data from my api and create a class for them. I can use swifyJSON to init them correctly. The problem is that when I put my observedObject in a List, it can only show correctly once. It will crashed after I changed the view. It's very strong because my other List with similar data struct can work.(this view is in a tabView) Is somebody know where my getAllNotification() should put view.onAppear() or List.onAppear()? Thanks!!
class ManagerNotification : Identifiable, ObservableObject{
#Published var id = UUID()
var notifyId : Int = 0
var requestId : Int = 0
var requestName: String = ""
var groupName : String = ""
// var imageName: String { return name }
init(jsonData:JSON) {
notifyId = jsonData["notifyId"].intValue
requestId = jsonData["requestId"].intValue
requestName = jsonData["requestName"].stringValue
groupName = jsonData["groupName"].stringValue
}
}
import SwiftUI
import SwiftyJSON
struct NotificationView: View {
var roles = ["userNotification", "managerNotification"]
#EnvironmentObject var userToken:UserToken
#State var show = false
#State private var selectedIndex = 0
#State var userNotifications : [UserNotification] = [UserNotification]()
#State var managerNotifications : [ManagerNotification] = [ManagerNotification]()
var body: some View {
VStack {
Picker(selection: $selectedIndex, label: Text(" ")) {
ForEach(0..<roles.count) { (index) in
Text(self.roles[index])
}
}
.pickerStyle(SegmentedPickerStyle())
containedView()
Spacer()
}
.onAppear(perform: getAllNotification)
}
func containedView() -> AnyView {
switch selectedIndex {
case 0:
return AnyView(
List(userNotifications) { userNotification in
UserNotificationCellView(userNotification: userNotification)
}
)
case 1:
return AnyView(
List(managerNotifications) { managernotification in
ManagerNotificationCellView(managerNotification : managernotification)
}
.onAppear(perform: getManagerNotification)
)
default:
return AnyView(Text("22").padding(40))
}
}
func getAllNotification(){
// if (self.userNotifications.count != 0){
// self.userNotifications.removeAll()
// }
// I think the crash was in here, because when i don't use removeAll().
// It works fine, but i don't want every times i change to this view. my array will be longer and
// longer
if (self.managerNotifications.count != 0){
self.managerNotifications.removeAll()
}
NetWorkController.sharedInstance.connectApiByPost(api: "/User/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.userNotifications.append(UserNotification(jsonData: notification))
}
}
}
}
}
NetWorkController.sharedInstance.connectApiByPost(api: "/Manager/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.managerNotifications.append(ManagerNotification(jsonData: notification))
}
}
}
}
}
}
func getManagerNotification(){
// if (self.managerNotifications.count != 0){
// self.managerNotifications.removeAll()
// }
print(self.managerNotifications.count)
NetWorkController.sharedInstance.connectApiByPost(api: "/Manager/email", params: ["token": "\(self.userToken.token)"])
{(jsonData) in
if let result = jsonData["msg"].string{
print("eeee: \(result)")
if(result == "you dont have any email"){
}else if(result == "success get email"){
if let searchResults = jsonData["mail"].array {
for notification in searchResults {
self.managerNotifications.append(ManagerNotification(jsonData: notification))
}
}
}
}
}
}
}
error message
Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. reason: 'attempt to delete section 0, but there are only 0 sections before the update'
I think you are confused about the role of #State and #ObservebableObject; it's not like MVC where you replace the ViewController with a SwiftUI.View as it appears you are trying to do in your example. Instead the view should be a function of either some local #State and/or an external #ObservedObject. This is closer to MVVM where your #ObservedObject is analogous to the ViewModel and the view will rebuild itself in response to changes in the #Published properties on the ObservableObject.
TLDR: move your fetching logic to an ObservableObject and use #Published to allow the view to subscribe to the results. I have an example here: https://github.com/joshuajhomann/TVMaze-SwiftUI-Navigation