iosswiftcocoa-touchviperviper-architecture

How to pass IBOutlet value from view to interactor in VIPER iOs?


I have a textView that loads with the view. Then the user can edit and modify the value, and when the user press back in the navigation button, then viewWillDisappear() is launched and the value of the textView outlet is passed to the interactor, that should fire a method from dataManager to save that value to core data.

My question is what is the best way to pass this parameter without violating the viper rules?

I did it that way, but I'm almost sure I'm doing it wrong. This is my very first experience with the VIPER architechture and any help would be appreciated.

View file:

import Foundation
import UIKit
class NoteDetailView : UIViewController  {

    var presenter: NoteDetailPresenterProtocol?
    @IBOutlet weak var detailNoteText: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter?.viewDidLoad()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        presenter?.updatedText = detailNoteText.text
        presenter?.viewWillDisappear()
    }



}
extension NoteDetailView : NoteDetailViewProtocol {
    func showNote(noteDetail note: NoteModel) {
        detailNoteText.text = note.noteText
    }
}

presenter:

import Foundation
class NoteDetailPresenter : NoteDetailPresenterProtocol {
    var view: NoteDetailViewProtocol?
    var updatedText: String?
    var note: NoteModel?

    var router: NoteDetailRouterProtocol?

    var interactor: NoteDetailInteractorProtocol?

    func viewDidLoad(){
        view?.showNote(noteDetail: note!)
    }

    func viewWillDisappear() {
        guard note?.noteText.isEmpty == false, let text = updatedText else {return}
        interactor?.retrieveNote(note: note!, updatedText : text)
    }
}

interactor:

import Foundation
class NoteDetailInteractor : NoteDetailInteractorProtocol {
    var dataManager: NewNoteDataManagerProtocol?
    var localDataManager: NoteListLocalDataManagerInputProtocol?

    func retrieveNote(note: NoteModel, updatedText : String) {
        do {
            if let noteList = try localDataManager?.retrieveNoteList() {

                let noteModelList = noteList.map(){
                    return NoteModel(noteText : $0.noteText != nil ? $0.noteText! : "", creationDate : $0.creationDate != nil ? $0.creationDate! : "")
                }
                if !noteModelList.isEmpty {
                    var noteTextExist = false
                    var indexPath = 0
                    for (index,newnote) in noteModelList.enumerated(){
                        if newnote == note {
                            noteTextExist = true
                            indexPath = index
                        }
                    }
                    if noteTextExist == true {
                        do {
                            noteList[indexPath].noteText = updatedText
                            try dataManager?.updateNote(note: noteList[indexPath])
                        }
                        catch {
                            print(error)
                        }
                    }
                }
            }

        }
        catch {
            print(error)
        }
    }

}

DataManager:

import Foundation
import CoreData
import UIKit
class NewNoteDataManager : NewNoteDataManagerProtocol{

    func deleteNote(note: Note) throws {
        guard let managedOC = CoreDataStore.managedObjectContext else {
            throw PersistenceError.managedObjectContextNotFound
        }
        managedOC.delete(note)
        do {
            try managedOC.save()
        }
        catch let error as NSError {
            print(error)
        }
    }

    func updateNote(note: Note) throws {
        guard let managedOC = CoreDataStore.managedObjectContext else {
            throw PersistenceError.managedObjectContextNotFound
        }
        do {
            try managedOC.save()
        }
        catch let error as NSError {
            print(error)
        }

    }

    func saveNote(noteText: String) throws {
        guard let managedOC = CoreDataStore.managedObjectContext else {
            throw PersistenceError.managedObjectContextNotFound
        }

        if let entity = NSEntityDescription.entity(forEntityName: String("Note"), in: managedOC) {
            do {
                let newNote = Note(entity: entity, insertInto: managedOC)
                newNote.noteText = noteText

                let date = Date()
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
                newNote.creationDate = formatter.string(from: date)
                try managedOC.save()
            }catch let error as NSError {
                print(error)
            }
        }else {
            throw PersistenceError.couldNotSaveObject
        }
    }


}

the full project can be found here: https://github.com/AlfroMlg/Notes


Solution

  • To be honest, I think your approach is okay. One could argue it's better not to store data like "updatedText" and "note" in presenter but rather pass this data through presenter with methods.

    You should make a reference to view in presenter weak to avoid retain cycle. And since both interactor and router are not gonna be nil when presenter is alive you can declare them as non-optional and initialize them within init. Same goes for dataManager references in interactor.