SwiftUI Помогите определить причину ошибки Index out of range

Пожалуйста, разъясните начинающему в чем ошибка. Пытаюсь из Core Data получить данные и вывести их как текстовые поля. В onAppear делаю заполнение массива с типом MyMessage. Для контроля вывожу на печать через print. Вижу, что массив заполнен корректными данными. Но при попытке вывести элементы массива в текстовые поля получаю ошибку:

Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444

Вот код:

import SwiftUI
import CoreData

struct MyMessage: Codable, Hashable {
    let msgHeader: String
    let msgBody: String
    let msgFooter: String
}

struct MyMessageView: View {
    @Environment(\.managedObjectContext) var managedObjectContext

    @State private var showMsg = [MyMessage]()
    
    var body: some View {
        VStack {
            Text(self.showMsg[0].msgHeader)
                .padding(.bottom, 10)

            Text(self.showMsg[0].msgBody)
                .padding(.bottom, 10)
            
            Text(self.showMsg[0].msgFooter)
        }
        .padding()
        .navigationBarTitle(Text("Message"), displayMode: .inline)
        .onAppear(){
            // from Core Data
            let requestM = NSFetchRequest<NSFetchRequestResult>(entityName: "Messages")
            let sortDescriptorM = NSSortDescriptor(key: "msgId", ascending: true)
            requestM.predicate = NSPredicate(format: "msgId == %@", NSNumber(integerLiteral: 1))
            requestM.sortDescriptors = [sortDescriptorM]
            do {
                let resultM = try self.managedObjectContext.fetch(requestM) as NSArray
                
                self.showMsg.append(MyMessage(
            msgHeader: (resultM[0] as AnyObject).msgHeader,
            msgBody: (resultM[0] as AnyObject).msgBody,
            msgFooter: (resultM[0] as AnyObject).msgFooter
                ))

                Print(self.showMsg[0])

            } catch {
                print(error)
            }

        } //onAppear
    } //body
}

Если я отказываюсь от использования массива и ввожу отдельные переменные для каждого элемента, то все работает отлично. Но у меня есть чувство, что так делать не совсем правильно.

Это код второго варианта

import SwiftUI
import CoreData

struct MyMessageView: View {
    @Environment(\.managedObjectContext) var managedObjectContext

    @State var msgHeader = ""
    @State var msgBody = ""
    @State var msgFooter = ""


    var body: some View {
        VStack {
            Text(self.msgHeader)
                .padding(.bottom, 10)

            Text(self.msgBody)
                .padding(.bottom, 10)

            Text(self.msgFooter)

        }
        .padding()
        .navigationBarTitle(Text("Message"), displayMode: .inline)
        .onAppear(){
            // from Core Data
            let requestM = NSFetchRequest<NSFetchRequestResult>(entityName: "Messages")
            let sortDescriptorM = NSSortDescriptor(key: "msgId", ascending: true)
            requestM.predicate = NSPredicate(format: "msgId == %@", NSNumber(integerLiteral: 1))
            requestM.sortDescriptors = [sortDescriptorM]
            do {
                let resultM = try self.managedObjectContext.fetch(requestM) as NSArray
                
                msgHeader = (resultM[0] as AnyObject).msgHeader
                msgBody = (resultM[0] as AnyObject).msgBody
                msgFooter = (resultM[0] as AnyObject).msgFooter
            } catch {
                print(error)
            }

        } //onAppear
    } //body
}

Как решить проблему Index out of range для первого варианта?

UPD Как вариант решения на уровне костыля, это делать перед выводом проверку на пустоту массива через if isEmpty.


Ответы (1 шт):

Автор решения: schmidt9

Здесь проблема в том, что onAppear срабатывает позже, чем впервые выполняется код внутри VStack (можете проверить, установив там точку останова), когда массив showMsg еще пустой. Исправить можно например так (упрощенный вариант без класса для модели Core Data)

struct MyMessageView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State private var showMsg = [MyMessage]()
    
    var body: some View {
        VStack {
            Text(self.showMsg.first?.msgHeader ?? "")
                .padding(.bottom, 10)
            
            Text(self.showMsg.first?.msgBody ?? "")
                .padding(.bottom, 10)
            
            Text(self.showMsg.first?.msgFooter ?? "")
        }
        .padding()
        .navigationBarTitle(Text("Message"), displayMode: .inline)
        .onAppear(){
            // from Core Data
            let requestM = NSFetchRequest<NSFetchRequestResult>(entityName: "Messages")
            let sortDescriptorM = NSSortDescriptor(key: "msgId", ascending: true)
            requestM.predicate = NSPredicate(format: "msgId == %@", NSNumber(integerLiteral: 1))
            requestM.sortDescriptors = [sortDescriptorM]
            do {
                let resultM = try self.managedObjectContext.fetch(requestM) as! [NSManagedObject]
                
                self.showMsg.append(MyMessage(
                    msgHeader: resultM.first?.value(forKey: "msgHeader") as! String,
                    msgBody: resultM.first?.value(forKey: "msgBody") as! String,
                    msgFooter: resultM.first?.value(forKey: "msgFooter") as! String
                ))
                
                print(self.showMsg[0])
                
            } catch {
                print(error)
            }
            
        } //onAppear
    } //body
}
→ Ссылка