#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
insideCardView
? Если нет, удалите это, и оно станет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")
}
}
}
}