I have swiftUI scene for Error.
That error can be uses for Network , login etc anything.
Buttons and their actions on that screen varies as per the errortype.
enum ErrorItem: String {
case subscriptionError = "Subscription"
case networkError = "NetworkError"
}
init(errorType: ErrorItem) {
super.init()
if errorType == ErrorItem.subscriptionError {
titleString = "Login Error"
numOfButton = 1 // I do not want to use this . I want to create some closure and enum of buttons and create array of buttons
}
else {
titleString = "Something Went Wrong"
numOfButton = 2
}
}
I am handling number of buttons display by "numofButtons".
But for each case number of buttons, their titles and their actions will also be different.
What are the best approach to handle that ?
Can i create something like this and have array of ErrorButtonTypes. So if one screen contain 2 buttons i.e close and action button i can use both and if one screen contain 1button only close button i can use only 1 button
enum ErrorButtonType {
case close(String) // String = title
case action(String, (() -> Void) // String = title, function = action to take with that button
}
The easy and efficient way to declare var inside the enum and set value for each case. Like this
enum ErrorItem: String {
case subscriptionError = "Subscription"
case networkError = "NetworkError"
var title: String {
switch self {
case .subscriptionError:
return "Login Error"
case .networkError:
return "Something Went Wrong"
}
}
var numOfButton: Int {
switch self {
case .subscriptionError:
return 1
case .networkError:
return 2
}
}
}
And your init is
init(errorType: ErrorItem) {
super.init()
titleString = errorType.title
numOfButton = errorType.numOfButton
}
EDIT
As per your edited question, you can use enum with action like this.
Your enum
enum ErrorButtonType {
case close(String) // String = title
case action(String, (() -> Void)) // String = title, function = action to take with that button
}
Declare your enum
var errorType: ErrorButtonType = ErrorButtonType.close("Close")
set enum value
let action: (() -> Void) = {
// Your button action
}
errorType = ErrorButtonType.action("Action", action)
Get enum value
switch errorType {
case .close(let title):
print(title)
case .action(let title, let action):
print(title)
action() // Assign this action to button
}
Related
I have a fairly complex document type to work with. It is basically a bundle containing a set of independent documents of the same type, with various pieces of metadata about the documents. The data structure that represents the bundle is an array of structs, similar to this (there are several more fields, but these are representative):
struct DocumentData: Equatable, Identifiable, Hashable {
let id = UUID()
var docData: DocumentDataClass
var docName: String
var docFileWrapper: FileWrapper?
func hash(into hasher: inout Hasher) {
id.hash(into: &hasher)
}
static func ==(lhs: KeyboardLayoutData, rhs: KeyboardLayoutData) -> Bool {
return lhs.id == rhs.id
}
}
The window for the bundle is a master-detail, with a list on the left and, when one is selected, there is an edit pane for the document on the right. The FileWrapper is used to keep track of which files need to be written for saving, so it gets initialised on reading the relevant file, and reset when an undoable change is made. That is largely the only way that the DocumentData structure gets changed (ignoring explicit things like changing the name).
I've reached a point where a lot of things are working, but I'm stuck on one. There's a view inside the edit pane, several levels deep, and when I double-click it, I want a sheet to appear. It does so, but then disappears by itself.
Searching for ways to work this out, I discovered by using print(Self._printChanges()) at various points that the edit pane was being refreshed after showing the sheet, which meant that the parent disappeared. What I found was that the dependency that changed was the DocumentData instance. But, I then added a print of the DocumentData instance before the _printChanges call, and it is identical. I have also put in didSet for each field of DocumentData to print when they get set, and nothing gets printed, so I'm not sure where the change is happening.
So the question comes down to how I can work out what is actually driving the refresh, since what is claimed to be different is identical in every field.
There are some other weird things happening, such as dragging and dropping text into the view causing the whole top-level document array of DocumentData items to change before the drop gets processed and the data structures get updated, so there are things I am not understanding as clearly as I might like. Any guidance is much appreciated.
ADDED:
The view that triggers the sheet is fairly straightforward, especially compared to its enclosing view, which is where most of the interface code is. This is a slightly simplified version of it:
struct MyView: View, DropDelegate {
#EnvironmentObject var keyboardStatus: KeyboardStatus
#Environment(\.displayFont) var displayFont
#Environment(\.undoManager) var undoManager
var keyCode: Int
#State var modifiers: NSEvent.ModifierFlags = []
#State private var dragHighlight = false
#State private var activeSheet: ActiveSheet?
#State private var editPopoverIsPresented = false
// State variables for double click and drop handling
...
static let dropTypes = [UTType.utf8PlainText]
var body: some View {
ZStack {
BackgroundView(...)
Text(...)
}
.onAppear {
modifiers = keyboardStatus.currentModifiers
}
.focusable(false)
.allowsHitTesting(true)
.contentShape(geometry.contentPath)
.onHover { entered in
// updates an inspector view
}
.onTapGesture(count: 2) {
interactionType = .doubleClick
activeSheet = .doubleClick
}
.onTapGesture(count: 1) {
handleItemClick()
}
.sheet(item: $activeSheet, onDismiss: handleSheetReturn) { item in
switch item {
case .doubleClick:
DoubleClickItem(...) ) {
activeSheet = nil
}
case .drop:
DropItem(...) {
activeSheet = nil
}
}
}
.popover(isPresented: $editPopoverIsPresented) {
EditPopup(...)
}
.onDrop(of: KeyCap.dropTypes, delegate: self)
.contextMenu {
ItemContextMenu(...)
}
}
func handleItemClick() {
NotificationCenter.default.post(name: .itemClick, object: nil, userInfo: [...])
}
func handleEvent(event: KeyEvent) {
if event.eventKind == .dropText {
interactionType = .drop
activeSheet = .drop
}
else if event.eventKind == .replaceText {
...
handleItemDoubleClick()
}
}
func handleSheetReturn() {
switch interactionType {
case .doubleClick:
handleItemDoubleClick()
case .drop:
handleItemDrop()
case .none:
break
}
}
func handleItemDoubleClick() {
switch itemAction {
case .state1:
...
case .state2:
...
case .none:
// User cancelled
break
}
interactionType = nil
}
func handleItemDrop() {
switch itemDropAction {
case .action1:
...
case .action2:
...
case .none:
// User cancelled
break
}
interactionType = nil
}
// Drop delegate
func dropEntered(info: DropInfo) {
dragHighlight = true
}
func dropExited(info: DropInfo) {
dragHighlight = false
}
func performDrop(info: DropInfo) -> Bool {
if let item = info.itemProviders(for: MyView.dropTypes).first {
item.loadItem(forTypeIdentifier: UTType.utf8PlainText.identifier, options: nil) { (textData, error) in
if let textData = String(data: textData as! Data, encoding: .utf8) {
let event = ...
handleEvent(event: event)
}
}
return true
}
return false
}
}
Further edit:
I ended up rewiring the code so that the sheet belongs to the higher level view, which makes everything work without solving the question. I still don't understand why I get a notification that a dependency has changed when it is identical to what it was before, and none of the struct's didSet blocks are called.
Try removing the class from the DocumentData. The use of objects in SwiftUI can cause these kind of bugs since itβs all designed for value types.
Try using ReferenceFileDocument to work with your model object instead of FileDocument which is designed for a model of value types.
Try using sheet(item:onDismiss:content:) for editing. I've seen people have the problem you describe when they try to hack the boolean sheet to work with editing an item.
After lots of trial and error, I ended up with the following implementation to do real-time formatting for numeric entry in a text field. Various attempts to use a SwiftUI TextField() resulted in many anomalies. The approach below seems to be solid but even here I struggled with the proper approach to sub-classing NSTextField as I couldn't find any documentation on how to handle the designated initializer such that it would be compatible with SwiftUI's frame modifier.
The one minor remaining anomaly is that when placing the cursor in the middle of an entered number then typing non-numeric characters, the cursor advances even though no changes occur in the text. This is livable but I would prefer to keep that from happening.
Is there a better, more "proper" way to implement this?
import Foundation
import SwiftUI
struct NumberField : NSViewRepresentable {
typealias NSViewType = NumberText
var defaultText : String
var maxDigits : Int
var numberValue : Binding<Int>
func makeNSView(context: Context) -> NSViewType {
// Create text field
let numberTextField = NumberText()
numberTextField.isEditable = true
// numberTextField.numberBinding = numberValue
numberTextField.configure(text: defaultText, digits: maxDigits, intBinding: numberValue)
return numberTextField
}
func updateNSView(_ nsView: NSViewType, context: Context) {
// nsView.stringValue = "This is my string"
}
}
/// NumberText draws an NSTextField that will accept only digits up to a maximum number specified when calling Configure. Apple implements some nice integration between SwiftUI's frame and padding modifiers and the NSTextField's designated initializer. Rather than having to figure out how to fix/preserve this integration, this class provides a "configure()" function that is effectively it's initializer. As a result, it is MANDATORY that this class's configure() function be called immediately after initializing the class.
class NumberText : NSTextField {
// Code below jumps through a couple of hoops to avoid having to write a custom initializer since that gets in the middle of Apple's configuration of the text field using standard SwiftUI modifiers.
// NOTE THAT A USER OF NumberText MUST CALL CONFIGURE() IMMEDIATELY AFTER CREATING IT
var numberBinding : Binding<Int> = Binding( // This is initialized with a garbage Binding just to avoid having to write an initializer
get: {return -1},
set: {newValue in return}
)
var defaultText = "Default String"
var maxDigits = 9
private var decimalFormatter = NumberFormatter()
func configure(text: String, digits: Int, intBinding: Binding<Int>) { // Configure is used here instead of attempting to override init()
// Configure values
decimalFormatter.numberStyle = .decimal
defaultText = text
self.placeholderString = defaultText
maxDigits = digits
numberBinding = intBinding
// Set up TextField values
self.integerValue = numberBinding.wrappedValue
if self.integerValue == 0 {self.stringValue = ""}
}
override func textDidChange(_ notification: Notification) {
self.stringValue = numberTextFromString(self.stringValue)
if self.stringValue == "0" {self.stringValue = ""}
}
func numberTextFromString(_ inputText: String, maxLength: Int = 9) -> String {
// Create a filtered and trucated version of inputText
let filteredText = inputText.filter { character in
character.isNumber
}
let truncatedText = String(filteredText.suffix(maxLength))
// Make a number from truncated text
let myNumber = Int(truncating: decimalFormatter.number(from: truncatedText) ?? 0 )
// Set binding value
numberBinding.wrappedValue = myNumber
// Create formatted string for return
let returnValue = decimalFormatter.string(from: myNumber as NSNumber) ?? "?"
return returnValue
}
After some additional trial and error, I was able to fix the cursor problems mentioned in my initial question. The version here is, to the best of my knowledge, bullet proof (though the test team will have a whack at it so perhaps it will change).
Would still welcome any improvement suggestions.
import Foundation
import SwiftUI
struct NumberField : NSViewRepresentable {
typealias NSViewType = NumberText
var defaultText : String
var maxDigits : Int
var numberValue : Binding<Int>
func makeNSView(context: Context) -> NSViewType {
// Create text field
let numberTextField = NumberText()
numberTextField.isEditable = true
numberTextField.configure(text: defaultText, digits: maxDigits, intBinding: numberValue)
return numberTextField
}
func updateNSView(_ nsView: NSViewType, context: Context) {
}
}
/// NumberText draws an NSTextField that will accept only digits up to a maximum number specified when calling Configure. Apple implements some nice integration between SwiftUI's frame and padding modifiers and the NSTextField's designated initializer. Rather than having to figure out how to fix/preserve this integration, this class provides a "configure()" function that is effectively it's initializer. As a result, it is MANDATORY that this class's configure() function be called immediately after initializing the class.
class NumberText : NSTextField {
// Code below jumps through a couple of hoops to avoid having to write a custom initializer since that gets in the middle of Apple's configuration of the text field using standard SwiftUI modifiers.
// NOTE THAT A USER OF NumberText MUST CALL CONFIGURE() IMMEDIATELY AFTER CREATING IT
// The following variable declarations are all immediately initialized to avoid having to write an init() function
var numberBinding : Binding<Int> = Binding( // This is initialized with a garbage Binding just to avoid having to write an initializer
get: {return -1},
set: {newValue in return}
)
var defaultText = "Default String"
var maxDigits = 9
private var decimalFormatter = NumberFormatter()
func configure(text: String, digits: Int, intBinding: Binding<Int>) { // Configure is used here instead of attempting to override init()
// Configure values
decimalFormatter.numberStyle = .decimal
defaultText = text
self.placeholderString = defaultText
maxDigits = digits
numberBinding = intBinding
// Make sure that default text is shown if numberBinding.wrappedValue is 0
if numberBinding.wrappedValue == 0 {self.stringValue = ""}
}
override func textDidChange(_ notification: Notification) {
self.stringValue = numberTextFromString(self.stringValue, maxLength: maxDigits) // numberTextFromString() also sets the wrappedValue of numberBinding
if self.stringValue == "0" {self.stringValue = ""}
}
/// Takes in string from text field and returns the best number string that can be made from it by removing any non-numeric characters and adding comma separators in the right places.
/// Along the way, self.numberBinding.warppedValue is set to the Int corresponding to the output string and self's cursor is reset to account for the erasure of invalid characters and the addition of commas
/// - Parameters:
/// - inputText: Incoming text from text field
/// - maxLength: Maximum number of digits allowed in this field
/// - Returns:String representing number
func numberTextFromString(_ inputText: String, maxLength: Int) -> String {
var decrementCursorForInvalidChar = 0
var incomingDigitsBeforeCursor = 0
// For cursor calculation, find digit count behind cursor in incoming string
// Get incoming cursor location
let incomingCursorLocation = currentEditor()?.selectedRange.location ?? 0
// Create prefix behind incoming cursor location
let incomingPrefixToCursor = inputText.prefix(incomingCursorLocation)
// Count digits in prefix
for character in incomingPrefixToCursor {
if character.isNumber == true {
incomingDigitsBeforeCursor += 1
}
}
// Create a filtered and trucated version of inputText
var characterCount = 0
let filteredText = inputText.filter { character in
characterCount += 1
if character.isNumber == true {
return true
} else { // character is invalid or comma.
if character != "," { // character is invalid,
if characterCount < inputText.count { // Only decrement cursor if not at end of string
// Decrement cursor
decrementCursorForInvalidChar += 1
}
}
return false
}
}
// Decrement cursor as needed for invalid character entries
currentEditor()!.selectedRange.location = incomingCursorLocation - decrementCursorForInvalidChar
let truncatedText = String(filteredText.prefix(maxLength))
// Make a number from truncated text
let myNumber = Int(truncating: decimalFormatter.number(from: truncatedText) ?? 0 )
// Set binding value
numberBinding.wrappedValue = myNumber
// Create formatted string for return
let outgoingString = decimalFormatter.string(from: myNumber as NSNumber) ?? "?"
// For cursor calculation, find character representing incomingDigitsBeforeCursor.lastIndex
var charCount = 0
var digitCount = 0
var charIndex = outgoingString.startIndex
while digitCount < incomingDigitsBeforeCursor && charCount < outgoingString.count {
charIndex = outgoingString.index(outgoingString.startIndex, offsetBy: charCount)
charCount += 1
if outgoingString[charIndex].isNumber == true {
digitCount += 1
}
}
// Get integer corresponding to current charIndex
let outgoingCursorLocation = outgoingString.distance(from: outgoingString.startIndex, to: charIndex) + 1
currentEditor()!.selectedRange.location = outgoingCursorLocation
return outgoingString
}
}
I tried to get a string from an enum based on other enum, how can i do it?
I want to get the icon : string of GenderIcon based on GenderString
My playground :
import UIKit
enum GenderString: String {
case man = "man"
case woman = "woman"
}
enum GenderIcon: String {
case man = "πββοΈ"
case woman = "πββοΈ"
}
var gender: GenderString = .man
var genderIcon: GenderIcon = .woman // case is wowan at start and it's normal
print("ππππ \(gender)")
// I tried these :
print("ππππ \(genderIcon.rawValue(gender))") // < - I want here the man icon based on gender
print("ππππ \(genderIcon.rawValue(gender.rawValue))")
print("ππππ \(genderIcon(gender))")
print("ππππ \(genderIcon(gender.rawValue))")
You have two ways to do this. The first is to create a property on your enum that returns a value from the other. The second is to create an init on the enum that uses the other as the property.
Example of the first:
enum GenderString: String {
case man = "man"
case woman = "woman"
var icon: GenderIcon {
switch self {
case .man: return .man
case .woman: return .woman
}
}
}
enum GenderIcon: String {
case man = "πββοΈ"
case woman = "πββοΈ"
}
var gender: GenderString = .man
gender.icon.rawValue // πββοΈ
Example of the second.
enum GenderString: String {
case man = "man"
case woman = "woman"
}
enum GenderIcon: String {
case man = "πββοΈ"
case woman = "πββοΈ"
init(gender: GenderString) {
switch gender {
case .man: self = .man
case .woman: self = .woman
}
}
}
var gender: GenderString = .woman
var icon = GenderIcon(gender: gender)
icon.rawValue // πββοΈ
However, because you can add properties to enums, you can skip having a separate enum for the icon and simplify your example into:
enum Gender: String {
// Implicitly uses the case name as the raw string value
case man
case woman
var icon: String {
switch self {
case .man: return "πββοΈ"
case .woman: return "πββοΈ"
}
}
}
var man: Gender = .man
man.icon // πββοΈ
I am exploring SwiftUI+Combine with a demo app BP Management.
Homescreen has a provision to take bp readings(systolicBP, diastolicBP, pulse & weight).
Button "Next" is enabled only when all 4 fields are filled.
control should fall to the next textfield when a valid input is entered. (input is valid when it falls between the range specified by the placeholder - refer the image below)
On tapping next, on the detail screen user can edit the bp values (taken in the HomeScreen), additionally he can add recorded date, notes...
Thought enums would be best model this so I proceeded like
enum SBPInput: CaseIterable {
//name is a Text to indicate the specific row
typealias Field = (name: String, placeholder: String)
case spb, dbp, pulse, weight, note, date
var field: Field {
switch self {
case .dbp: return ("DBP", "40-250")
case .spb: return ("SBP", "50-300")
case .pulse: return ("Pulse", "40-400")
case .weight: return ("Weight", "30-350")
case .note: return ("Note", "")
case .date: return ("", Date().description)
}
}
// Here I am getting it wrong, - I can't bind a read only property
var value: CurrentValueSubject<String, Never> {
switch self {
case .date:
return CurrentValueSubject<String, Never>(Date().description)
case .spb:
return CurrentValueSubject<String, Never>("")
case .dbp:
return CurrentValueSubject<String, Never>("")
case .pulse:
return CurrentValueSubject<String, Never>("")
case .weight:
return CurrentValueSubject<String, Never>("70")
case .note:
return CurrentValueSubject<String, Never>("")
}
}
}
class HomeViewModel: ObservableObject {
#Published var aFieldsisEmpty: Bool = true
var cancellable: AnyCancellable?
var dataSoure = BPInput.allCases
init() {
var bpPublishers = (0...3).map{ BPInput.allCases[$0].value }
//If a field is empty, we need to disable "Next" button
cancellable = Publishers.CombineLatest4(bpPublishers[0], bpPublishers[1], bpPublishers[2], bpPublishers[3]).map { $0.isEmpty || $1.isEmpty || $2.isEmpty || $3.isEmpty }.assign(to: \.aFieldsisEmpty, on: self)
}
}
The idea is to create HStacks for each datasorce(sbp,dbp,pulse,weight) to look like this
struct HomeScreen: View {
#ObservedObject var viewModel = HomeViewModel()
var body: some View {
VStack {
ForEach(Range(0...3)) { index -> BPField in
BPField(input: self.$viewModel.dataSoure[index])
}
Button("Next", action: {
print("Take to the Detail screen")
}).disabled(self.viewModel.aFieldsisEmpty)
}.padding()
}
}
struct BPField: View {
#Binding var input: BPInput
var body: some View {
//implicit HStack
Text(input.field.name)
BPTextField(text: $input.value, placeHolder: input.field.name)//Error:- Cannot assign to property: 'value' is a get-only property
// input.value being read only I can't bind it. How to modify my model now so that I can bind it here?
}
}
And my custom TextField
struct BPTextField: View {
let keyboardType: UIKeyboardType = .numberPad
var style: some TextFieldStyle = RoundedBorderTextFieldStyle()
var text: Binding<String>
let placeHolder: String
// var onEdingChanged: (Bool) -> Void
// var onCommit: () -> ()
var background: some View = Color.white
var foregroundColor: Color = .black
var font: Font = .system(size: 14)
var body: some View {
TextField(placeHolder, text: text)
.background(background)
.foregroundColor(foregroundColor)
.textFieldStyle(style)
}
}
your problems are not there, what SwiftUI tells you.
but you should first compile "small parts" of your code and simplify it, so the compiler will tell you the real errors.
one is here:
BPTextField(text: self.$viewModel.dataSoure[index].value, placeHolder: viewModel.dataSoure[index].field.placeholder)
and the error is:
Cannot subscript a value of type 'Binding<[BPInput]>' with an argument of type 'WritableKeyPath<_, _>'
and of course you forgot the self ....
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