GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

why can't I append to my list

Forums > SwiftUI

I am struggling to understand why this works. similar to the iExpense app where we created a struct then a class intialising the array of that struct and finally intialising that class in ContentView and passing that over to our Addview, i did the same thing here. I do not understand why this works but if i were to just simply create a class Photo and in my ContentView i initalise an array of that and pass that over to my PhotoView why can i not append anything to that array. i get this error, Cannot use mutating member on immutable value: 'self' is immutable. Why does the first method work and not this. Thanks in advance

struct Photo: Identifiable {
    var id = UUID()
    var image: Image?
    var name: String

}

@ Observable
class PhotoItem {
    var photoList = [Photo]()
}
struct PhotoView: View {
    @Environment(\.dismiss) var dismiss
    var photos: PhotoItem
    
    @State private var selectedPhoto: PhotosPickerItem?
    @State private var photoName = ""
    @State private var processedImage: Image?
    
    
    
    var body: some View {
        NavigationStack {
            VStack {
                TextField("Enter a name for the photo", text: $photoName)
                PhotosPicker(selection: $selectedPhoto ) {
                    if let processedImage {
                        processedImage
                            .resizable()
                            .scaledToFill()
                    } else {
                        ContentUnavailableView("No picture", systemImage: "photo.badge.plus", description: Text("Tap to import a photo"))
                    }
                }
                .onChange(of: selectedPhoto, loadImage)
                
                Button("Save") {
                    photos.photoList.append(Photo(image: processedImage, name: photoName))
                    dismiss()
                }
            }
        }
    }
    func loadImage() {
        Task {
            if let loaded = try? await selectedPhoto?.loadTransferable(type: Image.self) {
                processedImage = loaded
            } else {
                print("failed")
            }
        }
    }
}
struct ContentView: View {
    @State private var selectedPhoto: PhotosPickerItem?
    @State private var processedImage: Image?
    
    @State private var showingSheet = false
    @State private var photoName = ""
    
    @State private var photos = PhotoItem()
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack {
                    ForEach(photos.photoList) { photo in
                        
                        Text(photo.name)
                        if let image = photo.image {
                            image
                                .resizable()
                                .scaledToFit()
                                .frame(width: 300, height: 300)
                        }
                        
                    }
                }
            }
            .navigationTitle("People photo")
            .sheet(isPresented: $showingSheet) {
                PhotoView(photos: photos)
                
            }
            .toolbar {
                Button("New photo", systemImage: "plus") {
                    showingSheet = true
                }
            }
            
        }
    }
    
    
}

   

Try @State var photos: PhotoItem

   

the code works correctly but i am struggling to understand why it works.

struct Photo: {
  var name: String
  var image: Image?

  }
struct ConentView {
  @State private var photoList = [Photo]()
}

and passing this into my new view and trying to append that to my list doesnt seem to work

struct PhotoView {
  var photoList: [Photo]
}

and making it @State updates the array in PhotoView but not ContentView

so i am confused as to why this works

struct Photo: {
  var name: String
  var image: Image?

  }
class PhotoItem {
  var photoList = [Photo]()

}
struct ConentView {
  @State private var photos = PhotoItem()
}
struct PhotoView {
   var photos: PhotoItem
}

i am confused as to why the second approach works and not the first, and what exactly is the benefit of the second approach

   

Sorry, I didn't read your code to the end and I missed the second View.

Structs in SwiftUI are by default immutable. That's a language feature which we have to accept. When you use variables that have to change you have to mark them with a property wrapper like @State , @Binding or @Bindable.

So in your example we have to adapt the code which I gave to you (because I didn't see the Content View).

ContentView is the owner of your photos variable and you should mark it @State private var photos = PhotoItem()

Your PhotoView should access the exact same variable. Now in your PhotoView you mark your variable with @Bindable var photos

On the call side you have to use PhotoView(photos: $photos)

This should do the trick and your ContentView should update.

   

Thanks a lot for all the help i really appreciate it, i just have have one last doubt. When using @Bindable how do i preview my code because the preview required a $Bindable object.

Also if i understand correctly, my [Photo] could not be appended to because of structs being immutable, but what if i made it into a class, it still doesnt work

class Photo: {
  var name: String
  var image: Image?

  }

and then in my contentView i made an array of Photo and pass that into my PhotoView, shouldn't it work because classes are mutable ?

   

The View is immutable not your Photo and the View has to be a struct. You can't make it into a class. You need the property wrapper in the View (and they only work in a View).

In your preview you should be able to use

PhotoView(photos: .constant(PhotoItem()))

   

Hacking with Swift is sponsored by Alex.

SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!

Try for free!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.