private var messages = [Message]() is instantiated in the ConversationView Class
In this instance, I pass in the otherUser's email and the conversationID. I use this init once the user's chat is pressed to pass in their information in the ConversationsView.
class ConversationsView: MessagesViewController {
init(with email: String, convo_id: String?) {
self.otherUserEmail = email
self.conversationID = convo_id
super.init(nibName: nil, bundle: nil)
if let id = conversationID {
listenForMessages(convo_id: id, shouldScrollToBottom: true)
}
}
}
The issue is, is that self becomes nil somewhere in the process of the init, where it initializes the ConversationView and passes the values to the messages screen. In this case since self is nil, self.messages is not being updated. Is there any way to solve this problem?
class ConversationsView: MessagesViewController {
private func listenForMessages(convo_id: String, shouldScrollToBottom: Bool){
// Referencing a property on self in a closures, causes a retain cycle. Declaring weak self, makes the refernce weak, causing it not be a retain cycle.
DatabaseManager.shared.getAllMessagesForConversation(with: convo_id, completion: { [weak self] result in
switch result {
case .success(let messages):
print("Messages received \(messages)")
guard !messages.isEmpty else {return}
self?.messages = messages
DispatchQueue.main.async {
self?.messagesCollectionView.reloadDataAndKeepOffset()
if shouldScrollToBottom {
self?.messagesCollectionView.scrollToLastItem()
}
}
case .failure(let error):
print("failure: \(error)")
}
})
}
}
Here is where I init the params into the ConversationView
class MessagesView: UIViewController {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let model = conversations[indexPath.row]
let vc = ConversationsView(with: model.otherUserEmail, convo_id: model.id)
completionFunc()
vc.title = model.name
let targetUser = connections[indexPath.row]
dismiss(animated: true, completion: { [weak self] in
self?.completion?(targetUser)
})
}
}
Self is nil because ConversationView has been initialized.
Make sure ConversationView is retained after you initialized it.
// I'm guessing this is in some view controller, so self is the view controller
self.conversationView = ConversationView(with: "email#email.com", convo_id: "X")
Related
I want to trigger an action on button tap with my callback. Also I have presenter and coordinator. But nothing happenes. My code is not working in this closure:
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
In my ViewController I have enum:
enum StartViewControllerButton {
case registrationButtonTapped
case loginButtonTapped
}
callback:
var output: ((StartViewControllerButton) -> Void)?
and selectors:
#objc func registrationButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.registrationButtonTapped)
}
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
My Presenter
class StartModulPresenter: StartModulPresenterProtocol {
var navigationController: UINavigationController
var coordinator: CoordinatorProtocol?
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
coordinator = AuthorizationCoordinator(navigationController: navigationController)
}
//Functions
func openNextScreen() {
coordinator?.start()
}
}
My Coordinator:
class AuthorizationCoordinator: RegistrationCoordinatorProtocol {
var presenter: PresenterProtocol?
var navigationController: UINavigationController
var childCoordinators: [CoordinatorProtocol] = []
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
private func showRegistrationViewController() {
let registrationViewController = RegistrationViewController()
registrationViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(registrationViewController, animated: true)
}
private func showLoginViewController() {
let loginViewController = LoginViewController()
loginViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(loginViewController, animated: true)
}
}
Could you check if startViewController is pushed/presented or not?
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
And, is self.output is nil or not? If it is nil please check your assignment call, it needed to be called before you use this variable.
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
Honestly, I don't recommend you to use this design pattern, just a simple thing but the real result is too complicated.
Just use protocol-based MVC. View communicate with Controller via protocol/closure or Reactive-based with Combine (PassthroughSubject/CurrentValueSubject)
rookie here. I created a two tableviews and one gets its data from the previous one. So it has init objects. And I have a view controller where I have a scrollview to display some scrollable images like a catalog. It is just like a menu with categories eventually leading to brands and you tap on the brand so its content comes after.
I do the API call in didSelectRow (for a reason) and use the protocol func. to get images from API to an array then into the scrollview and its subviews. But I can't properly initialize the VC where that tableview that holds the data exists. How can I properly initialize categoriesListVC then use its delegate func?
First VC where I pass data firstly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let category = data[indexPath.row]
let vc = CategoriesList(brand: category.brand, url: category.url, brandName: category.brandSlugName)
vc?.title = category.category
navigationController?.pushViewController(vc ?? self, animated: true)
}
categoriesList VC with the TableView with init
init?(brand: [String], url: [String], brandName: [String]) {
self.brand = brand
self.url = url
self.brandName = brandName
super.init(nibName: nil, bundle: nil)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedRow2 = indexPath.row
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "CatalogView2") as! ScrollVC2
navigationController?.pushViewController(vc, animated: true)
dataCatalog() //API call - this works I checked. }
And this is how I tried to use its data on the final VC. Would this work? I printed categoriesList here and it gives nil. I probably don't understand something.
var categoriesList: CategoriesList?
override func viewDidLoad() {
super.viewDidLoad()
categoriesList?.delegate = self }
I am trying to use a delegate method in that final VC to use its object to pass data:
func catalogViewModelFromCategories(catalogViewModel: CatalogViewModel) {}
I didn't write the whole code to make it as short as possible. But delegate functions and other things are all in the real code.
In dataCatalog func there is only url session and decoder:
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
if error == nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CatalogViewData.self, from: data!)
let catalogFromCategories = CatalogViewModel(images: decodedData.catalogueimages, title: decodedData.cataloguename!)
let catalogViewModel = catalogFromCategories
DispatchQueue.main.async {
self.delegate?.catalogViewModelFromCategories(catalogViewModel: catalogViewModel)
}
} catch {
print(error)
}
return
}
}
task.resume()
}
I am getting a Unexpectedly found nil while implicitly unwrapping an Optional value in my Code error when I try to use another view controller to save a new task in a to do list. When I tap a button I open up the entry page which then has a text field where I can enter the text to then create a task item. Here is the code for the main view controller:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet var tableView: UITableView!
private var tasks = [TaskItem]()
override func viewDidLoad() {
super.viewDidLoad()
getAllTasks()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = tasks[indexPath.row].title
return cell
}
#IBAction func didTapNewTask(){
let viewContoller = storyboard?.instantiateViewController(identifier: "entry") as! EntryViewController
viewContoller.title = "New Task"
viewContoller.update = {
DispatchQueue.main.async {
self.getAllTasks()
}
}
navigationController?.pushViewController(viewContoller, animated: true)
}
//Core Data Functions
//Used to get all our tasks in our Core Data
func getAllTasks() {
do {
tasks = try context.fetch(TaskItem.fetchRequest())
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print("error getting all tasks \(error)")
}
}
//This is used to create a task, setting the properties to those in the parameters and then saving to our Core Data.
func createTask(title: String, notes: String, difficulty: Int32) {
let task = TaskItem(context: context)
task.title = title
task.notes = notes
task.difficulty = difficulty
task.dateCreated = Date()
do {
try context.save()
getAllTasks()
}
catch {
}
}
Here is the code for the entry view controller:
class EntryViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var field: UITextField!
var update: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
field.delegate = self
// Do any additional setup after loading the view.
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
saveTask()
return true
}
#IBAction func saveTask(){
let vc = storyboard?.instantiateViewController(identifier: "tasks") as! ViewController
guard let text = field.text, !text.isEmpty else {
let alert = UIAlertController(title: "Error", message: "Please input a title" , preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Confirm", style: UIAlertAction.Style.default, handler: nil))
self.present(alert,animated: true,completion: nil)
return
}
vc.createTask(title: text, notes: "Hello", difficulty: 10)
update?()
navigationController?.popViewController(animated: true)
}
The app crashes once I click save the new task but then once I reload the app the task I just created is there.
#IBAction func saveTask(){
let vc = storyboard?.instantiateViewController(identifier: "tasks") as! ViewController
guard let text = field.text, !text.isEmpty else {
let alert = UIAlertController(title: "Error", message: "Please input a title" , preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Confirm", style: UIAlertAction.Style.default, handler: nil))
self.present(alert,animated: true,completion: nil)
return
}
vc.createTask(title: text, notes: "Hello", difficulty: 10)
update?()
navigationController?.popViewController(animated: true)
}
The first line of this method is the source of your problem. What you're doing here is making a new instance of the original view controller, not the instance you first came from.
This sort of works for a moment, because you then call createTask on that view controller to make your new task. That's fine, but that method then calls getAllTasks, which then dispatches to the main queue, which then calls reload data on your table.
But your table doesn't exist, because this is a new instance of the view controller which has never had it's view loaded. The table view is an implicitly unwrapped optional, but it's nil when you hit it here.
Your best solution is to pass in a block (like you have with update) to create a new task, and in that block call methods on the original view controller.
I have this code, where I'm trying to set a self variable (self?.users) from a view model call. The code snippet looks like this.
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.global().async { [weak self] in
self?.model?.findAll() { [weak self] users, exception in // network call
guard users != nil, self?.users = users else { // Optional type ()? cannot be used as a boolean; test for !=nil instead
}
}
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
I'm capturing [weak self] twice, is that okay?, can I capture it once as weak in the enclosing closure?
Should I use this instead of guard statement?
self?.model?.findAll() { [weak self] users, exception in
if exception != nil {
self?.users = users
}
}
DispatchQueue closures don't cause retain cycles so capture lists are not necessary.
Something like this, to avoid confusion I'd recommend to rename the incoming users and the code to reload the table view must be inside the closure
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.model?.findAll() { [weak self] foundUsers, exception in // network call
guard let foundUsers = foundUsers else { return }
self?.users = foundUsers
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
And don’t forget to call super
Okay so I got my ActivityViewController to work fine when it is sharing like normal however whenever a user hits cancel after they invoked the ActivityViewController , I get the annoying fatal error.
fatal error: unexpectedly found nil while unwrapping an Optional value
So it looks like I did not unwrap my optionals well. Here's the share method :
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
activityViewController.dismissViewControllerAnimated(true, completion:{
let memevc:MemeTableViewController = MemeTableViewController()
self.presentViewController(memevc, animated: true, completion: nil)
})
}
and the share method calls the save function which generates the object called meme using an implicitly unwrapped optional which causes the error :
func save(){
var meme : MemeObject!
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
So I decided to safely unwrap the meme optional value but that invoked another problem
func save(){
var meme : MemeObject?
if let memez = meme{
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
Now when the the object is not nil , "Optionals man" is printed which shouldn't happen and the completionwithitemshandler property in the share method didn't present the table view controller which should happen directly after the user shares the object.
Code for MemeTableViewController :
import UIKit
class MemeTableViewController : UIViewController,UITableViewDelegate,UITableViewDataSource
{
var memesz: [MemeObject]!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
memesz = appDelegate.memes
//tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.hidesBarsOnSwipe = true
navigationController?.hidesBarsOnTap = true
}
//reserves the number of rows needed to display the image
func tableView(tableView : UITableView, numberOfRowsInSection section : Int)->Int
{
return memesz.count
}
//Reserves the row to be dequeued for display
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewzCell = tableView.dequeueReusableCellWithIdentifier("MemesCell", forIndexPath: indexPath) as! TableViewzCell
let memezrow = memesz[indexPath.row]
cell.label1.text = memezrow.textFieldtop
cell.label2.text = memezrow.textFieldbottom
cell.imageview.image = memezrow.memedImage
return cell
}
//Method to do something when the row is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let detailController = self.storyboard!.instantiateViewControllerWithIdentifier("FullScreenMeme") as! FullScreenMeme
detailController.meme = memesz[indexPath.row]
self.navigationController!.pushViewController(detailController, animated: true)
}
}
Any help please?
After looking your code, it seems like you haven't initialised the variable 'meme'.
func save(){
var meme : MemeObject? = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
if let memez = meme{
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
}
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
let memevc = self.storyboard!.instantiateViewControllerWithIdentifier("MemeTableViewConroller") as! MemeTableViewController
self.presentViewController(memevc, animated: true, completion: nil)
}
}