Передача данных в пользовательское представление во время выполнения каждой итерации (SwiftUI)

#swift #swiftui

#swift #swiftui

Вопрос:

Предыстория проблемы: я создаю простое приложение, которое хранит данные об активности в структуре. Я создал пользовательское представление под названием «CardView» (хранится в отдельном файле внутри проекта), которое должно отображаться в базовом contentView. CardView заполняется текстовыми метками, которые должны быть привязаны к соответствующим данным в вышеупомянутой структуре данных activity. ForEach обрабатывает динамическое создание этих карточек в зависимости от того, сколько «элементов» активности существует. Смотрите следующий код…

 struct ActivityItem: Identifiable, Codable {
let id = UUID()
let name: Strin&
let description: Strin&
let type: Strin&
var amount: Int
}

class AppData: ObservableObject {
@Published var activites: [ActivityItem] {
    didSet {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(activites) {
            UserDefaults.standard.set(encoded, forKey: "Activites")
        }
    }
}

init() {
    if let foundActivities = UserDefaults.standard.data(forKey: "Activites") {
        let decoder = JSONDecoder()
        if let decoded = try? decoder.decode([ActivityItem].self, from: foundActivities) {
            self.activites = decoded
            return
        }
    }

    self.activites = []
}
}

struct ContentView: View {
@ObservedObject var appData = AppData()
@State private var showin&AddActivity = false
@State private var showin&MoreInfo = false

var body: some View {
    Navi&ationView {
        ZStack {
            Rectan&le()
            .fill(Color(.&ray))
            .frame(maxWidth: .infinity, maxHei&ht: .infinity)
                .ed&esI&norin&SafeArea(.all)
            
            ScrollView {
                VStack(spacin&: 50) {
                    ForEach(appData.activites) { activity in
                        CardView(appData: self.appData)
                    }
                }
            }

// Below held in separate file
struct CardView: View {
@ObservedObject var appData: AppData
@State private var showin&MoreInfo = false

var body: some View {
    ZStack {
        Rectan&le()
            .fill(Color(.&ray))
            .frame(maxWidth: .infinity, maxHei&ht: .infinity)
            .ed&esI&norin&SafeArea(.all)
        RoundedRectan&le(cornerRadius: 20)
            .fill(Color(.black))
            .frame(width: 325, hei&ht: 180)
        VStack {
            Text("Name")
                .font(.title)
            
            Text("Description")

            Text("Type")
            
            Text("Amount")                
        }
        .sheet(isPresented: $showin&MoreInfo) {
            Text("View")
        }
    }
}
}
  

Проблема: Несмотря на многочисленные попытки и бесчисленное количество часов и ошибок позже с помощью @Bindin& и использования @Environmental, я не могу передать данные из текущего ActivityItem в ForEach в CardView, которые будут использоваться для замены строкового заполнителя в текстовых метках (т. Е. Name, Type, Amount). При попытке создать и использовать привязки, например, что-то вроде $activity.name в нем объявляется, что «activity» не может стать привязкой. Что я делаю не так? Как самым простым способом я могу заставить каждое действие передавать свои данные в структуру CardView, заполнять эти текстовые метки, а затем создавать себя на «главном экране» contentView, а затем повторять для всех остальных объектов на каждой итерации ForEach? Есть ли какие-либо важные замечания, которые следует иметь в виду, чтобы избежать подобных проблем в будущем? Я должен добавить, что если я помещаю код CardView непосредственно в contentView и не вызываю CardView() в ForEach, я могу легко ссылаться на данные; это становится проблемой только при использовании структуры CardView (долгосрочная цель повторного использования и меньшего загромождения кода). Спасибо за вашу помощь!

Комментарии:

1. Исходя из вашего CardView , поскольку он только отображает, а не изменяет значения, ему не нужна привязка. Просто создайте свойство let activity: ActivityItem и передайте его в init: CardView(appData: self.appData, activity: activity) . Вам вообще нужно appData inside CardView ? Если нет, удалите это, и оно станет CardView(activity: activity)

Ответ №1:

Я предполагаю, что вы имели в виду что-то вроде следующего (протестировано с Xcode 12 / iOS 14)

 struct ActivityItem: Identifiable, Codable {
var id = UUID()
var name: Strin&            // << made `var` here and other to allow write
var description: Strin&
var type: Strin&
var amount: Int
}

class AppData: ObservableObject {
@Published var activites: [ActivityItem] {
    didSet {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(activites) {
            UserDefaults.standard.set(encoded, forKey: "Activites")
        }
    }
}

init() {
    if let foundActivities = UserDefaults.standard.data(forKey: "Activites") {
        let decoder = JSONDecoder()
        if let decoded = try? decoder.decode([ActivityItem].self, from: foundActivities) {
            self.activites = decoded
            return
        }
    }

    self.activites = []
}
}

struct ContentView: View {
@ObservedObject var appData = AppData()
@State private var showin&AddActivity = false
@State private var showin&MoreInfo = false

var body: some View {
    Navi&ationView {
        ZStack {
            Rectan&le()
            .fill(Color(.&ray))
            .frame(maxWidth: .infinity, maxHei&ht: .infinity)
                .ed&esI&norin&SafeArea(.all)

            ScrollView {
                VStack(spacin&: 50) {
                    ForEach(appData.activites.indices, id: .self) { i in
                        CardView(item: $appData.activites[i])  // bindin& here  
                    }
                }
            }
        }
    }
}
}

// Below held in separate file
struct CardView: View {
@Bindin& var item: ActivityItem             // bindin& here
@State private var showin&MoreInfo = false

var body: some View {
    ZStack {
        Rectan&le()
            .fill(Color(.&ray))
            .frame(maxWidth: .infinity, maxHei&ht: .infinity)
            .ed&esI&norin&SafeArea(.all)
        RoundedRectan&le(cornerRadius: 20)
            .fill(Color(.black))
            .frame(width: 325, hei&ht: 180)
        VStack {
            TextField("Name", text: $item.name)    // edit here
                .font(.title)

            Text("Description")

            Text("Type")

            Text("Amount")
        }
        .sheet(isPresented: $showin&MoreInfo) {
            Text("View")
        }
    }
}
}
  

Комментарии:

1. Понятно, позвольте мне попробовать это. В качестве быстрого продолжения, как в этом случае будут обрабатываться обе структуры предварительного просмотра? Для contentView() и CardView() соответственно. Для CardView предлагается «item: <#Bindin&<ActivityItem&&t;#&&t;» Что бы это могло быть? «item: activity», несмотря на то, что выше указана переменная, ничего не дает.

2. И, наконец, почему это работает (здесь мы пытаемся лучше понять привязки)? Почему бы не передавать привязки, такие как name: $activity.name , тип: $activity.type (где в представлении CardView я бы настроил привязки и просто вызвал «name», «type» и т.д.) Работает в оригинальной версии кода? Спасибо!

Ответ №2:

Swift 5.5 предлагает привязываемые коллекции, которые допускают следующую реализацию.

 struct ActivityItem: Identifiable, Codable {
    var id = UUID()
    var name: Strin&
    var description: Strin&
    var type: Strin&
    var amount: Int
}

class AppData: ObservableObject {
    @Published var activites: [ActivityItem] {
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(activites) {
                UserDefaults.standard.set(encoded, forKey: "Activites")
            }
        }
    }
    
    init() {
        if let foundActivities = UserDefaults.standard.data(forKey: "Activites") {
            let decoder = JSONDecoder()
            if let decoded = try? decoder.decode([ActivityItem].self, from: foundActivities) {
                self.activites = decoded
                return
            }
        }
        // sample data
        self.activites = [ActivityItem(name: "Name1", description: "desc1", type: "type1", amount: 1),
                          ActivityItem(name: "Name2", description: "desc2", type: "type2", amount: 1)
        ]
    }
}

struct ContentView: View {
    @ObservedObject var appData = AppData()
    @State private var showin&AddActivity = false
    @State private var showin&MoreInfo = false
    
    var body: some View {
        Navi&ationView {
            ZStack {
                Rectan&le()
                    .fill(Color(.&ray))
                    .frame(maxWidth: .infinity, maxHei&ht: .infinity)
                    .ed&esI&norin&SafeArea(.all)
                
                ScrollView {
                    VStack(spacin&: 50) {
                        ForEach($appData.activites) { $activity in
                            CardView(item: $activity)
                        }
                    }
                }
            }
            
        }
    }
}

// Below held in separate file
struct CardView: View {
    @Bindin& var item: ActivityItem
    @State private var showin&MoreInfo = false
    
    var body: some View {
        ZStack {
            Rectan&le()
                .fill(Color(.&ray))
                .frame(maxWidth: .infinity, maxHei&ht: .infinity)
                .ed&esI&norin&SafeArea(.all)
            RoundedRectan&le(cornerRadius: 20)
                .fill(Color.yellow)
                .frame(width: 325, hei&ht: 180)
            VStack {
                TextField("Name", text: $item.name)    // edit here
                    .font(.title)
                    .multilineTextAli&nment(.center)
                    .textFieldStyle(.roundedBorder)
                Text("Description (item.description)")
                Text("Type (item.type)")
                Text("Amount (item.amount)")
            }
            .sheet(isPresented: $showin&MoreInfo) {
                Text("View")
            }
        }
    }
}