TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: DAY 77 Challenge

Forums > 100 Days of SwiftUI

Hello world! I have a little bit of struggling with updating my main view wich is ContentView. Structure of app is little another then I found on forum, so here is my files. Please help with that <_<

P.S. I tried several hints for manual update but ContentView not react to changing of @State-like objects at all. When app is manually reopens - new positions in List is appeared

ContentView.swift

import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    @State var refresh: Bool = false
    @State private var showingAddView = false

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.meetup) { person in
                    PersonRowView(person: person)
                        .padding(-15)
                }
                .onDelete { index in
                    viewModel.removeRows(at: index)
                }
            }
            .navigationTitle("Meetup")
            .navigationBarTitleDisplayMode(.large)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button {
                        showingAddView = true
                    } label: {
                        ZStack {
                            Circle()
                                .fill(.black.opacity(0.65))
                                .frame(width: 64, height: 64)
                            Image(systemName: "plus")
                                .foregroundColor(.white)
                                .font(.title)
                        }
                        .padding(.bottom)
                    }
                }
            }
            .sheet(isPresented: $showingAddView) {
                AddPersonView()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

PersonRowView.swift

import SwiftUI
import UIKit

struct PersonRowView: View {
    let person: Person
    @State private var image: Image?

    var body: some View {
        HStack {
            image?
                .resizable()
                .scaledToFit()
                .clipShape(Circle())
                .frame(width: 100, height: 100)
                .shadow(radius: 2)
            Text(person.name)
                .font(.title2)
            Spacer()
        }
        .onAppear(perform: loadImage)
    }

    func loadImage() {
        image = person.convert()
    }
}

struct PersonRowView_Previews: PreviewProvider {
    static var previews: some View {
        PersonRowView(person: Person.example)
            .previewLayout(.sizeThatFits)
            .padding()
    }
}

AddPersonView.swift

import SwiftUI

struct AddPersonView: View {
    @Environment(\.dismiss) var dismiss
    @ObservedObject var viewModel = ViewModel()

    @State private var image: Image?
    @State private var inputUIImage: UIImage?
    @State private var savingUIImage: UIImage?
    @State private var showingImagePicker = false
    @State private var personName = ""

    var body: some View {
        NavigationView {
            VStack {
                TextField("Enter the name", text: $personName)
                    .textFieldStyle(.roundedBorder)
                    .padding()

                ZStack {
                    Rectangle()
                        .fill(.secondary)
                        .frame(width: 300, height: 300)
                    Text("Tap to select photo")
                        .foregroundColor(.white)
                        .font(.headline)
                    image?
                        .resizable()
                        .scaledToFit()
                        .frame(width: 300, height: 300)
                }
                .onTapGesture {
                    showingImagePicker = true
                }
            }
            .onChange(of: inputUIImage) { _ in loadImage() }
            .sheet(isPresented: $showingImagePicker) {
                ImagePicker(image: $inputUIImage)
            }
            .toolbar {
                ToolbarItem(placement: .automatic) {
                    Button("Save") {
                        viewModel.addNewPerson(name: personName, inputUIImage: savingUIImage)
                        viewModel.updateView()
                        dismiss()
                    }
                    .disabled(inputUIImage == nil)
                    .disabled(personName == "")
                }
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", role: .cancel) {
                        dismiss()
                    }
                }
            }
        }
    }

    func loadImage() {
        guard let inputUIImage = inputUIImage else { return }
        image = Image(uiImage: inputUIImage)
        savingUIImage = inputUIImage
    }
}

struct AddPersonView_Previews: PreviewProvider {
    static var previews: some View {
        AddPersonView()
    }
}

Person.swift

import Foundation
import UIKit
import SwiftUI

struct Person: Equatable, Identifiable, Codable {
    var id: UUID
    var name: String
    var avatar: Data

    init(name: String, avatar: Data) {
        self.id = UUID()
        self.name = name
        self.avatar = avatar
    }

    func convert() -> Image {
        let uiImageData = avatar
        if let uiImage = UIImage(data: uiImageData) {
            return Image(uiImage: uiImage)
        }
        return Image(systemName: "questionmark.square.dashed")
    }

    static let exampleAvatarData = UIImage(named: "Steve_Jobs")!.jpegData(compressionQuality: 0.8)
    static let example = Person(name: "Steve Jobs", avatar: exampleAvatarData!)

    static func <(lhs: Person, rhs: Person) -> Bool {
        lhs.name < rhs.name
    }
}

ViewModel.swift

import Foundation
import UIKit

@MainActor class ViewModel: ObservableObject {
    @Published var isUnlocked = true
    @Published var authError = ""
    @Published private(set) var meetup: [Person]

    let savePathMeetup = FileManager.documentsDirectory.appendingPathComponent("SavedPersons")

    init() {
        do {
            let personsData = try Data(contentsOf: savePathMeetup)
            meetup = try JSONDecoder().decode([Person].self, from: personsData)
        } catch {
            meetup = []
        }
    }

    func addNewPerson(name: String, inputUIImage: UIImage?) {
        guard let imageData = inputUIImage?.jpegData(compressionQuality: 0.8) else { return }
        let newPerson = Person(name: name, avatar: imageData)
        self.meetup.append(newPerson)
        saveMeetup()
    }

    func saveMeetup() {
        do {
            let personsData = try JSONEncoder().encode(self.meetup)
            try personsData.write(to: savePathMeetup, options: [.atomic, .completeFileProtection])
            print("saved meetup")
        } catch {
            print("unable to save data")
        }
    }

    func updateView() {
        self.objectWillChange.send()
    }

    func removeRows(at offsets: IndexSet) {
        self.meetup.remove(atOffsets: offsets)
        saveMeetup()
        print("deleted")
    }
}

ImagePicker and FileManager-DocumentsDirectory are pretty standart

2      

I'm not sure I fully understand your problem. But I'm curious if this might help at all.

Currently you have this in AddPersonView.swift

Button("Save") {
        viewModel.addNewPerson(name: personName, inputUIImage: savingUIImage)
        viewModel.updateView()
        dismiss()
}

And these functions in ViewModel.swift

func addNewPerson(name: String, inputUIImage: UIImage?) {
        guard let imageData = inputUIImage?.jpegData(compressionQuality: 0.8) else { return }
        let newPerson = Person(name: name, avatar: imageData)
        self.meetup.append(newPerson)
        saveMeetup()
}
func updateView() {
    self.objectWillChange.send()
}

So, addNewPerson() is called, and the change to your meetup array is happening before objectWillChange.send() is called.

Does it make any difference if you move the line that calls addNewPerson() after updateView() is called?

2      

Hello , in AddPersonView, change this @ObservedObject var viewModel = ViewModel() to @ObservedObject var viewModel: ViewModel then just ensure where ever you use viewModel it, it gets passed a valid ViewModel() instance, avoid creating an ObservedObject that initialises to some thing like ViewModel() this resets every thing ...

2      

yepp, in AddPersonView changed from

@ObservedObject var viewModel = ViewModel()

to

@ObservedObject var viewModel: ViewModel

and also added initializator to AddPersonView

init(viewModel: ViewModel) {
        self.viewModel = viewModel
    }

and now thats working!

2      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.