Testing a UITableView in a UIViewController programatically instantiated - swift

I want to inject my data manager into my view controller and test it.
The ViewController:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var alert: AlertViewController?
var coreDataManager: CoreDataManagerProtocol?
init(coreDataManager: CoreDataManagerProtocol) {
self.coreDataManager = coreDataManager
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.coreDataManager = CoreDataManager()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
#IBAction func addItem(_ sender: Any) {
alert = UIStoryboard(name: Constants.alertStoryBoard, bundle: nil).instantiateViewController(withIdentifier: Constants.alerts.mainAlert) as? AlertViewController
alert?.title = "Enter your task"
alert?.presentToWindow()
alert?.delegate = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreDataManager?.getTasks().count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = coreDataManager?.getTasks()[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = data?.value(forKey: Constants.entityNameAttribute) as? String
return cell
}
}
I want to test this through injecting at mock into the view controller:
class CoreDataManagerMock: CoreDataManagerProtocol {
var storeCordinator: NSPersistentStoreCoordinator!
var managedObjectContext: NSManagedObjectContext!
var managedObjectModel: NSManagedObjectModel!
var store: NSPersistentStore!
func getTasks() -> [NSManagedObject] {
managedObjectModel = NSManagedObjectModel.mergedModel(from: nil)
storeCordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
do {
store = try storeCordinator.addPersistentStore(
ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
// catch failure here
}
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = storeCordinator
var localTasks = [NSManagedObject]()
let entityOne = NSEntityDescription.insertNewObject(forEntityName: Constants.entityName, into: managedObjectContext)
entityOne.setValue(false, forKey: Constants.entityCompletedattribute)
entityOne.setValue("Enter your task", forKey: Constants.entityNameAttribute)
localTasks.append(entityOne)
return localTasks
}
func save(task: String) {
//
}
}
But I'm struggling to test this.
I can't request a cell as I'm not instantiating from the Storyboard (and I can't, since I need to inject the mock core manager.
In every test I try to run tableView resolves to nil
Here is my attempt, thinking I can test my cellForRowAt function directly:
func testtv() {
let CDM = CoreDataManagerMock()
let viewController = ViewController(coreDataManager: CDM)
viewController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
viewController.tableView(UITableViewMock(), cellForRowAt: IndexPath(row: 0, section: 0))
let actualCell = viewController.tableView?.cellForRow(at: IndexPath(row: 0, section: 0) )
let test = viewController.tableView(UITableViewMock(), cellForRowAt: IndexPath(row: 0, section: 0))
XCTAssertEqual(actualCell?.textLabel?.text, actualCell?.textLabel?.text)
}
But as tableView is nil is nil so I can't register the cell. How can I test cellForRow(at: when I inject my dependency as above?

How I did testing with UIStoryboard instantiated UIViewControllers was by doing this.
// STEP 1
// instantiate the storyboard
let storyboard = UIStoryboard(name: "SomeStoryboard", bundle: nil)
// STEP 2
// instantiate the UIViewController using the storyboard
let sampleViewController = storyboard.instantiateViewController(withIdentifier: "SampleViewController") as! SampleViewController
// STEP 3
// set the properties you need to set
sampleViewController.xxxxx = "foo"
sampleViewController.yyyyy = "bar"
// STEP 4
// call the view so the ui objects would be instantiated
// not doing this would cause a crash
// this also calls `viewDidLoad()`
_ = sampleViewController.view
So in your case since this is how the code would look like:
func testtv() {
let storyboard = UIStoryboard(name: "INSERT STORYBOARD NAME HERE", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.coreDataManager = CoreDataManagerMock()
_ = viewController.view
// I'm not sure about the code below this comment
// but the above code should work
viewController.tableView(UITableViewMock(), cellForRowAt: IndexPath(row: 0, section: 0))
let actualCell = viewController.tableView?.cellForRow(at: IndexPath(row: 0, section: 0) )
let test = viewController.tableView(UITableViewMock(), cellForRowAt: IndexPath(row: 0, section: 0))
XCTAssertEqual(actualCell?.textLabel?.text, actualCell?.textLabel?.text)
}
ViewController
Why this is, is because calling instantiateViewController(withIdentifier: _) uses init?(coder aDecoder: NSCoder)
You can remove this snippet below because you don't need it and it won't work since the UIViewController you're instantiating is from a storyboard.
init(coreDataManager: CoreDataManagerProtocol) {
self.coreDataManager = coreDataManager
super.init(nibName: nil, bundle: nil)
}
What you could do instead is create a static method to instantiate your UIViewController with the parameters you need. Example:
class SomeViewController: UIViewController {
var someObject: SomeObject!
static func instantiate(someObject: SomeObject) -> SomeViewController {
let storyboard = UIStoryboard(name: "SomeStoryboard", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "SomeViewController")
viewController.someObject = someObject
return viewController
}
}
// usage
let vc = SomeViewController.instantiate(someObject: SomeObject())
// or
let vc: SomeViewController = .instantiate(someObject: SomeObject())

Related

Swift 5; pushViewController is not working

I'm testing Swift and I created a table list to show JSON data, now I would like to show details for each cell, clicking on it, into a new View Controller.
Testing the App I got the click on the cell but I don't see the new View.
This is the code capturing the click into the ViewController.swift:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
private let tableView: UITableView = {
let table = UITableView(frame: .zero,
style: .grouped)
table.register(UITableViewCell.self,
forCellReuseIdentifier: "cell")
return table
}()
private let tableView: UITableView = {
let table = UITableView(frame: .zero,
style: .grouped)
table.register(UITableViewCell.self,
forCellReuseIdentifier: "cell")
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
parseJSON()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
}
....
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.selectedImage = "images/logo_2-mini.png"
print("Click \(indexPath) " + vc.selectedImage!) // I see it!
navigationController?.pushViewController(vc, animated: true) // I don't see it
}
}
}
This is the DetailViewController
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
print("Pre")
if let imageToLoad = selectedImage{
imageView.image = UIImage(named: imageToLoad)
print("ok")
}
}
}
I'm too newbie with swift! Can you help me here?
If you want to show the DetailViewController you should change your code to
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: "Detail") as? DetailViewController else { return }
vc.selectedImage = "images/logo_2-mini.png"
print("Click \(indexPath) " + vc.selectedImage!) // I see it!
self.present(vc, animated: true)
}
This way you don't need to embed your ViewController into a navigation controller.
Also, I've created the storyboard variable since I don't see its declaration in your code.

How to show one tableview data in two segments in swift

I am using XLPagerTabStrip for segment
i want to show one tableview data in two segment with sorted, for that i am using below code
SegmentViewController code: if i add child_2.isSorted = false then error
Value of type 'UIViewController' has no member 'isSorted'
import UIKit
import XLPagerTabStrip
class SegmentViewController: ButtonBarPagerTabStripViewController {
#IBOutlet weak var testSeg: ButtonBarView!
override func viewDidLoad() {
super.viewDidLoad()
// change selected bar color
// Sets the background colour of the pager strip and the pager strip item
settings.style.buttonBarBackgroundColor = .white
settings.style.buttonBarItemBackgroundColor = .yellow
// Sets the pager strip item font and font color
settings.style.buttonBarItemFont = UIFont(name: "Helvetica", size: 15.0)!
settings.style.buttonBarItemTitleColor = .gray
changeCurrentIndexProgressive = { [weak self] (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void in
guard changeCurrentIndex == true else { return }
oldCell?.label.textColor = .gray
newCell?.label.textColor = .blue
}
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.buttonBarView.frame = testSeg.frame
}
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
let child_1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SegTableViewController")
let child_2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SegTableViewController")
child_2.isSorted = false
return [child_1, child_2]
}
}
SegTableViewController code: here in both segments showing only sortedArray i need in first segment sortedArray and second segment namesArray.. how to do that.. could anyone please suggest me
import UIKit
import XLPagerTabStrip
class SegTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, IndicatorInfoProvider {
var sortedArray = [String]()
var isSorted = true
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
if isSorted{
return IndicatorInfo(title: "Sorted")
}else{
return IndicatorInfo(title: "Normal")
}
}
#IBOutlet weak var tableView: UITableView!
var namesArray = ["afsdf","ddsfsdf", "hjhgjh", "trytryr", "nvbmvnb", "yuertyri", "bmvmncb", "jgfhk", "ytuioi", "sdfgsfdsh", "mkhjgijik", "gfuyru"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
sortedArray = namesArray.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedDescending }
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSorted {
return sortedArray.count
}
else{
return namesArray.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
if isSorted{
cell.textLabel?.text = sortedArray[indexPath.row]
}
else{
cell.textLabel?.text = namesArray[indexPath.row]
}
return cell
}
}
You need to cast your view controller like this
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
let child_1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SegTableViewController")
let child_2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SegTableViewController")
(child_2 as? SegTableViewController).isSorted = false
return [child_1, child_2]
}
Also, no need for an array for displaying data. Use only one array namesArray like this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if isSorted {
namesArray = namesArray.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedDescending }
}
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}

how to call/show action sheet for taking or choose photo from gallery on Main VC after tapped on TableViewCell Image(swift)

Here I Try MVVM pattern to achieve TableView but for showing alert I face problems , it compiles successfully but not showing alert.
[Result] on the tapped on profile pic which is in tableview cell, I want to show alert
TableViewCell
import Foundation
import UIKit
class ParentsImageCell : UITableViewCell {
weak var myVC : ProfileOfParentsDetailsViewController?
var parentProfileVC = ProfileOfParentsDetailsViewController()
#IBOutlet weak var imageProfile : UIImageView!
var items : ParentProfileViewModelItem? {
didSet {
guard let items = items as? ParentProfileViewModelProfileItem else {
return
}
imageProfile?.image = UIImage(named: items.profileImg)
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
super.awakeFromNib()
imageProfile?.layer.cornerRadius = 62
imageProfile?.clipsToBounds = true
imageProfile?.contentMode = .scaleAspectFill
imageProfile?.backgroundColor = UIColor.lightGray
//Add Tapped Gesture
imageProfile.isUserInteractionEnabled = true
let gesture = UITapGestureRecognizer(
target: self,
action: #selector(didTappedChangeProfilePic))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
imageProfile.addGestureRecognizer(gesture)
}
#objc private func didTappedChangeProfilePic(){
print("tapped on imageView")
presentPhotoActionSheet()
}
override func prepareForReuse() {
super.prepareForReuse()
imageProfile?.image = nil
}
}
extension ParentsImageCell : UIImagePickerControllerDelegate ,UINavigationControllerDelegate {
func presentPhotoActionSheet(){
let actionSheet = UIAlertController(title: "Profile Picture", message: "How would you write to select a picture", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: nil))
actionSheet.addAction(UIAlertAction(title: "Take Photo", style: .default, handler: {[weak self] _ in
self?.presentCamera()
}))
actionSheet.addAction(UIAlertAction(title: "Choose Photo", style: .default, handler: { [weak self]_ in
self?.presentPhotoPicker()
}))
myVC?.present(actionSheet , animated: true)
}
func presentCamera(){
let vc = UIImagePickerController()
vc.sourceType = .camera
vc.delegate = self
vc.allowsEditing = true
myVC?.present(vc , animated: true)
}
func presentPhotoPicker(){
let vc = UIImagePickerController()
vc.sourceType = .photoLibrary
vc.delegate = self
vc.allowsEditing = true
myVC?.present(vc , animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:
[UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
return
}
self.imageProfile.image = selectedImage
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
ViewModel
class ParentProfileViewModel: NSObject {
var items = [ParentProfileViewModelItem]()
var reloadSections: ((_ section: Int) -> Void)?
override init() {
super.init()
guard let data = dataFromFile("ServerData"),
let profile = Profile(data: data) else {
return
}
// initialization code will go here
if let profile = profile.pictureUrl {
let profileItem = ParentProfileViewModelProfileItem(profileImg: profile)
items.append(profileItem)
}
if let name = profile.fullName {
let nameItem = ParentProfileViewModelNameItem(name: name)
items.append(nameItem)
}
if let email = profile.email {
let emailItem = ParentProfileViewModelEmailItem(email: email)
items.append(emailItem)
}
let coach = profile.coach
if !coach.isEmpty {
let coachItem = ParentProfileViewModelCoachItem(coach: coach)
items.append(coachItem)
}
let candidate = profile.candidate
if !candidate.isEmpty {
let candidateItem = ParentProfileViewModelCandidateItem(candidate: candidate)
items.append(candidateItem)
}
}
}
//MARK:- TableviewDatasource & Delegates
extension ParentProfileViewModel: UITableViewDataSource {
//Number of section
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
//Number of RowInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let item = items[section]
guard item.isCollapsible else {
return item.rowCount
}
if item.isCollapsed {
return 0
} else {
return item.rowCount
}
}
//Cell for row at indexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we will configure the cells here
let item = items[indexPath.section]
switch item.type {
case .profileImg:
let vc = ProfileOfParentsDetailsViewController()
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsImageCell.identifier, for: indexPath) as? ParentsImageCell {
cell.items = item
cell.myVC = vc
return cell
}
case .fullName:
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsFulNameCell.identifier, for: indexPath) as? ParentsFulNameCell {
cell.items = item
return cell
}
case .email:
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsEmailCell.identifier, for: indexPath) as? ParentsEmailCell {
cell.items = item
return cell
}
case .candidate:
if let item = item as? ParentProfileViewModelCandidateItem, let cell = tableView.dequeueReusableCell(withIdentifier: CandidatCell.identifier, for: indexPath) as? CandidatCell {
let candidate = item.candidate[indexPath.row]
cell.item = candidate
return cell
}
case .coach:
if let item = item as? ParentProfileViewModelCoachItem, let cell = tableView.dequeueReusableCell(withIdentifier: ParentCoachCell.identifier, for: indexPath) as? ParentCoachCell {
cell.item = item.coach[indexPath.row]
return cell
}
}
return UITableViewCell()
}
}
ViewController
import Foundation
import UIKit
class ProfileOfParentsDetailsViewController: UIViewController {
let viewModel = ParentProfileViewModel()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
viewModel.reloadSections = { [weak self] (section: Int) in
self?.tableView?.beginUpdates()
self?.tableView?.reloadSections([section], with: .fade)
self?.tableView?.endUpdates()
}
tableView?.dataSource = viewModel
tableView.delegate = viewModel
tableView?.estimatedRowHeight = 250
tableView?.rowHeight = UITableView.automaticDimension
tableView?.register(ParentsImageCell.nib, forCellReuseIdentifier: ParentsImageCell.identifier)
tableView?.register(ParentsEmailCell.nib, forCellReuseIdentifier: ParentsEmailCell.identifier)
tableView?.register(ParentsFulNameCell.nib, forCellReuseIdentifier: ParentsFulNameCell.identifier)
tableView?.register(CandidatCell.nib, forCellReuseIdentifier: CandidatCell.identifier)
tableView?.register(ParentCoachCell.nib, forCellReuseIdentifier: ParentCoachCell.identifier)
tableView?.register(ParentsHeaderView.nib, forHeaderFooterViewReuseIdentifier: ParentsHeaderView.identifier)
}
I try to get called alert Sheet , but I Failed and also comment about my approach towards MVVM
I try to called the tableview data source and delegate in VM
I try to get called alert Sheet , but I Failed and also comment about my approach towards MVVM
I try to called the tableview data source and delegate in VM

Navigate to ViewController from #IBDesignable UIView didSelectRowAt UITableView

I have created a #IBDesignable View file and implemented tableview in it. Data is coming and tableView in working but only I am facing issue at the time of select cell which has to open to other ViewController. Because its a #IBDesignable view file, not a viewController.swift
How to navigate to viewController from #IBDesignable UIView tableview cell select with passing values?
Error: has no member 'navigationController' if I use self.
Code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let actionType = AppData?.items?[indexPath.row].actionType
switch actionType {
case 5:
print(actionType ?? 0)
let urlLink = AppData?.items?[indexPath.row].actionUrl
let titleText = AppData?.items?[indexPath.row].textValue
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "WebViewController") as! WebViewController
vc.url = urlLink ?? ""
vc.titleText = titleText ?? ""
self.navigationController.pushViewController(vc, animated: true)
default:
print(actionType ?? 0)
}
}
You can access the parent view controller using this extension:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.next
}
return nil
}
}
You can use it like this in your code:
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "WebViewController") as! WebViewController
vc.url = urlLink ?? ""
vc.titleText = titleText ?? ""
self.viewcontroller?.navigationController?.pushViewController(vc, animated: true)
Note: Although you can get the parent controller of UIView, it is not
recommended as the UIView should not be aware of the controller. I
suggest using Delegates.
Your UITableView is a UIView so it's not a UIViewController and has not UINavigationController.
You should create a delegate in your UITableView that will be implemented by your UIViewController.
Something like that :
protocol MyTableViewDelegate: class {
func didSelectRow(url: String, titleText: String)
}
Add a delegate variable in your table view like :
weak var delegate: MyTableViewDelegate?
and call your delegate like this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let actionType = AppData?.items?[indexPath.row].actionType
switch actionType {
case 5:
print(actionType ?? 0)
let urlLink = AppData?.items?[indexPath.row].actionUrl
let titleText = AppData?.items?[indexPath.row].textValue
self.delegate.didSelectRow(url: url, titleText: titleText)
default:
print(actionType ?? 0)
}
}
In your view controller :
you should add your table view custom class into your ViewController,
then add delegate : yourTableView.delegate = self
implement delegate :
extension MyViewController: MyTableViewDelegate {
func didSelectRow(url: String, titleText: String) {
let vc = storyboard.instantiateViewController(withIdentifier: "WebViewController") as! WebViewController
vc.url = urlLink
vc.titleText = titleText
self.navigationController.pushViewController(vc, animated: true)
}
}

How to inject presenter to CustomTableViewCell as dependancy injection

i'm learning Dependency Injection and created an app by MVP.
I could inject presenters for a few VCs in AppDelegate using storyboard.
But now I created CustomTableViewCell with .xib that has UIButton on the cell so that I would like to process the detail of it in the presenter file.
I tried to create UINib(nibName~ in AppDelegate but when the presenter in CustomTableViewCell is called, it is nil.
I heard that an instance of a cell called in 'AppDelegate' is going to be disposed of. However I don't know any ways not to make presenter nil.
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let mainTabVC = UIStoryboard(name: "MainTab", bundle: nil).instantiateInitialViewController() as! MainTabViewController
let userDefault = UserDefault()
let presenter = MainTabPresenter(view: mainTabVC, udManager: userDefault)
mainTabVC.inject(presenter: presenter, userDefaultManager: userDefault)
let addTabVC = UIStoryboard(name: "AddTab", bundle: nil).instantiateInitialViewController() as! AddTabViewController
let alertHandller = AlertHandller()
let addPresenter = AddTabPresenter(view: addTabVC, mainView: mainTabVC, alertHandller: alertHandller)
addTabVC.inject(presenter: addPresenter)
let settingTabVC = UIStoryboard(name: "SettingTab", bundle: nil).instantiateInitialViewController() as! SettingTabViewController
let settingPresenter = SettingTabPresenter(view: settingTabVC)
settingTabVC.inject(presenter: settingPresenter, alertHandller: alertHandller)
let vcs = [mainTabVC, addTabVC, settingTabVC]
let mainTabBar = UIStoryboard(name: "MainView", bundle: nil).instantiateInitialViewController() as! MainTabBarController
mainTabBar.setViewControllers(vcs, animated: false)
// no probs until here (inject functions work)
// this is disposed??
let listCell = UINib(nibName: "ListCell", bundle: nil).instantiate(withOwner: ListCell.self, options: nil).first as! ListCell
let cellPresenter = ListCellPresenter(view: mainTabVC)
listCell.inject(presenter: cellPresenter)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = mainTabBar
window?.makeKeyAndVisible()
return true
}
VC that tableViewDataSource is written
class MainTabViewController: UIViewController {
private let shared = Sharing.shared
private var presenter: MainTabPresenterInput!
private var userDefaultManager: UserDefaultManager!
func inject(presenter: MainTabPresenterInput, userDefaultManager: UserDefaultManager) {
self.presenter = presenter
self.userDefaultManager = userDefaultManager
}
override func viewDidLoad() {
super.viewDidLoad()
tableViewSetup()
}
func tableViewSetup(){
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "ListCell", bundle: nil), forCellReuseIdentifier: "ListCell")
}
}
extension MainTabViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shared.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
if let item = presenter.item(row: indexPath.row) {
cell.configure(item: item)
}
return cell
}
}
customTableViewCell
class ListCell: UITableViewCell {
private let shared = Sharing.shared
private var presenter: ListCellPresenterInput!
func inject(presenter: ListCellPresenterInput) {
self.presenter = presenter
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func favButtonTapped(_ sender: Any) {
let row = self.tag
switch shared.items[row].fav {
case true:
favButton.setImage(UIImage(named: "favIconNon"), for: .normal)
case false:
favButton.setImage(UIImage(named: "favIcon"), for: .normal)
}
presenter.countFavIcon(rowAt: row) // this presenter is nil
}
func configure(item: Item) {
    ・・・
}
}
cell presenter
protocol AddTabPresenterInput {
func addButtonTapped(item item: Item?)
}
protocol AddTabPresenterOutput: AnyObject {
func clearFields()
func showAlert(alert: UIAlertController)
}
final class AddTabPresenter: AddTabPresenterInput {
private weak var view: AddTabPresenterOutput!
private weak var mainView: MainTabPresenterOutput!
private weak var alertHandller: AlertHandllerProtocol!
let shared = Sharing.shared
init(view: AddTabPresenterOutput, mainView: MainTabPresenterOutput, alertHandller: AlertHandllerProtocol) {
self.view = view
self.mainView = mainView
self.alertHandller = alertHandller
}
func addButtonTapped(item item: Item?) {
print("called")
mainView.updateView()
}
}
How Can I solve the issue that presenter is nil?
Hopefully some of you would help me out.
Thank you.
UITableView uses reusable cells, you can find a lot of info in google, here is one of the articles
https://medium.com/ios-seminar/why-we-use-dequeuereusablecellwithidentifier-ce7fd97cde8e
So creating an instance of UITableViewCell in AppDelegate you're doing nothing, it is immediately dispose
Create presenter
// somewhere in your MainTabViewController
let cellPresenter = ListCellPresenter(view: self)
To inject presenter you should use tableView(_:cellForRowAt:) method
// later in code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! ListCell
cell.inject(presenter: cellPresenter)
if let item = presenter.item(row: indexPath.row) {
cell.configure(item: item)
}
return cell
}