How to dismiss modal and simultaneously send data and perform segue - swift

I have a chat app and want to go from my ChatController to a newMessageVC by using a modal segue. In newMessageVC I got all my contacts shown in a table view. When clicking on a contact the newMessaenter code heregeVC should dismiss and the clicked contact should be sent to ChatController. In this moment the ChatController should perform a segue to go to the ChatVC to show the new chat with the clicked user. How can I do this?
I tried following code:
The newMessageVC is dismissing and the function showChatVC is called (It shows the print statement) but then there is the Error although the segue does exist. There is a segue from ChatController to ChatVC named showChatVC
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<WeAreFriends.ChatController: 0x7fd760e22e80>) has no segue with identifier 'showChatVC''
class ChatController : UITableViewController {
//MARK: - Outlets
#IBOutlet weak var MessageTableView: UITableView!
//MARK: - Properties
var messages = [MessageModel]()
//Mark: - Init View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
}
//MARK: - Navigation Bar
func configureNavigationBar() {
navigationItem.title = "Messages"
navigationController?.navigationBar.barTintColor = Defaults.hexStringToUIColor(hex: "#006D79")
let textAttributes = [NSAttributedString.Key.foregroundColor: Defaults.hexStringToUIColor(hex: "#FFAA01")]
navigationController?.navigationBar.titleTextAttributes = textAttributes
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(handleNewMessage))
}
#objc func handleNewMessage(){
performSegue(withIdentifier: "showNewMessage", sender: self)
}
//MARK: - Show ChatVC
func showChatVC(forUser user: UserModel){
print("Shooow, jihaaaaa")
let messageController = MessageController()
messageController.user = user
performSegue(withIdentifier: "showChatVC", sender: self)
}
//MARK: - Prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showNewMessage" {
let destinationVC = segue.destination as! NewMessageController
destinationVC.chatController = ChatController()
}
}
//MARK: - Table Config
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessagesTableViewCell", for: indexPath) as! MessagesTableViewCell
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("did select row")
}
}
newMessageVC:
class NewMessageController: UITableViewController {
//MARK: - Outlets
#IBOutlet var NewMessageTableView: UITableView!
//MARK: - Var/let
var users = [UserModel]()
var chatController : ChatController?
//Mark: - Init View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
fetchUser()
}
//MARK: - User laden
func fetchUser(){
UserApi.shared.observeUsersButCurrentUser { (user) in
self.users.append(user)
self.NewMessageTableView.reloadData()
}
}
//MARK: - Navigation Bar
func configureNavigationBar() {
navigationItem.title = "New Message"
navigationController?.navigationBar.barTintColor = Defaults.hexStringToUIColor(hex: "#006D79")
let textAttributes = [NSAttributedString.Key.foregroundColor: Defaults.hexStringToUIColor(hex: "#FFAA01")]
navigationController?.navigationBar.titleTextAttributes = textAttributes
}
//MARK: - Tableview Config
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewMessageTableViewCell", for: indexPath) as! NewMessageTableViewCell
cell.user = users[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.dismiss(animated: true) {
let user = self.users[indexPath.row]
self.chatController!.showChatController(forUser: user)
}
}
}
What am I doing wrong?

Replace
destinationVC.chatController = ChatController()
with
destinationVC.chatController = self
As ChatController() is a new different instance with no storyboard layout attached , so no segues hence the crash
func showChatVC(forUser user: UserModel){
performSegue(withIdentifier: "showChatVC", sender:user)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showNewMessage" {
let destinationVC = segue.destination as! NewMessageController
destinationVC.chatController = ChatController()
}
else if segue.identifier == "showChatVC" {
let destinationVC = segue.destination as! ChatVC
destinationVC.user = sender as! UserModel
}
}
Also make
weak var chatController : ChatController?
As not to cause retain cycles

Related

DidSelectRow function is not called

I'm trying to implement the didSelectRow function and perform a segue but when running the cells select and nothing happens.
I created a print statement that also doesn't run which proves that the function doesn't appear to be firing. Why would this be?
I have checked the identifier is correct and have researched these for a few hours going through many stack overflow threads but with little luck.
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let viewController = ListNameViewController()
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var itemChoosen = 0
override func viewDidLoad() {
super.viewDidLoad()
homeListsTableView.delegate = self
homeListsTableView.dataSource = self
viewController.loadList()
}
#IBOutlet weak var homeListsTableView: UITableView!
#IBAction func templatesButton(_ sender: Any) {
tabBarController?.selectedIndex = 2
}
#IBAction func allListsButton(_ sender: Any) {
tabBarController?.selectedIndex = 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewController.listName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let result = viewController.listName[indexPath.row]
cell.textLabel?.text = ("\(String(result.listName!))")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context!.delete(viewController.listName[indexPath.row])
viewController.listName.remove(at: indexPath.row)
viewController.saveList()
homeListsTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "items2", sender: self)
print("selected")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
viewController.loadList()
homeListsTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homeListsTableView.reloadData()
}
}
ListNameViewController:
import UIKit
import CoreData
class ListNameViewController: UIViewController, UITableViewDelegate {
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var listName : [ListName] = []
override func viewDidLoad() {
super.viewDidLoad()
createButtonChange.isEnabled = false
//Objective-C Line used to keep checking if the text field is vaild before enabling the submit button
listNameValue.addTarget(self, action: #selector(textValidation), for: UIControl.Event.editingChanged)
}
#IBOutlet weak var listNameValue: UITextField!
#IBOutlet weak var locationOption: UITextField!
#IBOutlet weak var createButtonChange: UIButton!
#objc func textValidation() {
//Text Field Validation check before button is enabled
if listNameValue.text!.isEmpty {
createButtonChange.isEnabled = false
} else {
createButtonChange.isEnabled = true
}
}
// Create a new List
#IBAction func createButton(_ sender: Any) {
let newList = ListName(context: context!)
newList.listName = listNameValue.text
saveList()
self.navigationController!.popViewController(animated: true)
viewWillAppear(false)
}
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
}
func loadList() {
let request : NSFetchRequest<ListName> = ListName.fetchRequest()
do{
listName = try context!.fetch(request)
} catch {
print("Error loading categories \(error)")
}
}
//Pass data to the HomeViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// let vc = segue.destination as! HomeViewController
}
}
// commented out core data and just used a normal array for testing.
Did you add segue in your storyboard for the tableview ? In this case the didSelect is not call but the prepare(for segue) of the tableview controller is called.
Ok solved it - There was a rouge tap gesture recognizer on the page. Removed it and works on one click. I have seen that if you wish to keep the gesture just add this line of code at the top of the function:
tap.cancelsTouchesInView = false
Took three days but I got there. Thanks for the help!

Is there any better way to send information to child view?

I have a view controller which has a child view. I want to convey information from the view to the child view. To do so, I did this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "visualize", sender: self)
invoiceNumber = indexPath.row
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
But the problem is that the value of invoiceNumber is not updated on the first iteration but on the second. I tried to see what the problem was and found that "invoiceNumber = indexPath.row" runs after "vc.invoiceNumber = invoiceNumber". Please help ! Thanks
Here is the code of printing :
import UIKit
class ViewController: UIViewController {
var invoiceNumber: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
}
}
Sorry I have mistaken :
var customerData: [[customerInformation]] = []
var itemsData: [[Item]] = []
var totalData: [TotalInformation] = []
var invoiceNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
let tabBar = tabBarController as! baseTabBarController
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return customerData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let invoiceCell = tableView.dequeueReusableCell(withIdentifier: "invoice", for: indexPath) as! invoiceTableViewCell
invoiceCell.textLabel?.text = (customerData[indexPath.row][0]).input
invoiceCell.detailTextLabel?.text = "Invoice n°" + String(indexPath.row)
invoiceCell.totalLabel.text = (totalData[indexPath.row]).total
return invoiceCell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let tabBar = tabBarController as! baseTabBarController
tabBar.customerData.remove(at: indexPath.row)
//itemData.remove(at: indexPath.row)
tabBar.totalData.remove(at: indexPath.row)
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
print(invoiceNumber, "yes")
performSegue(withIdentifier: "visualize", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
}
It is the "vc.invoiceNumber = invoiceNumber" which comes before the "invoiceNumber = indexPath.row" as I tried to change the value of invoiceNumber a variable in the view and print out the value in the child view and if showed its value first (I tested -1).
I added the Preview view controller which receives the invoiceNumber :
import UIKit
import WebKit
class PreviewViewController: UIViewController {
#IBOutlet var webPreview: UIWebView!
var invoiceComposer: InvoiceComposer!
var HTMLContent: String!
var invoiceNumber: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
createInvoiceAsHTML()
}
func createInvoiceAsHTML() {
invoiceComposer = InvoiceComposer()
if let tabBar = tabBarController as? baseTabBarController {
if let invoiceHTML = invoiceComposer.renderInvoice(invoiceNumber: String(invoiceNumber), invoiceDate: "", recipientInfo: tabBar.customerData[invoiceNumber][0].input, items: tabBar.itemsData[invoiceNumber], totalAmount: tabBar.totalData[invoiceNumber].total) {
webPreview.loadHTMLString(invoiceHTML, baseURL: NSURL(string: invoiceComposer.pathToInvoiceHTMLTemplate!)! as URL)
HTMLContent = invoiceHTML
}
}
else {
print("tabBarController is not of type baseTabBarController or either nil ")
}
}
}
Change the order of execution, like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
performSegue(withIdentifier: "visualize", sender: self)
}
I should have connected the segue from the view controller to the child view and not from the cell to the child view.

How to use NSPredicate to fetch request and populate second viewController with data when UItableView roll is pressed in first viewController

I'm coding a Note App in Swift 4. The root ViewController (NoteListViewController) gets populated when secondViewController (ComposeNoteViewController) Textfield and TextView are populated.
The problem is when I press a populated TableView cell, rather than fetch and display the content, it opens a fresh instance of theComposeNoteViewController.
import UIKit
import CoreData
class NoteListTableViewController: UITableViewController {
var noteListArray = [NoteListItem]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
loadNoteListItem()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return noteListArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoteListItemCell", for: indexPath)
cell.textLabel?.text = noteListArray[indexPath.row].title
return cell
}
//MARK: - TABLEVIEW DELEGATE METHODS
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToComposeNote", sender: self)
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ComposeNoteViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedNoteList = noteListArray[indexPath.row]
}
}
import UIKit
import CoreData
class ComposeNoteViewController: UIViewController {
var noteComposeItemsArray = [ComposeNote]()
var noteListArray = [NoteListItem]()
// let noteListController = NoteListTableViewController()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var selectedNoteList : NoteListItem? {
didSet {
loadComposeItem()
}
}
#IBOutlet weak var noteTextView: UITextView!
#IBOutlet weak var noteTextField: UITextField!
#IBAction func noteSavePressed(_ sender: UIBarButtonItem) {
let newNoteTitleItem = NoteListItem(context: context)
let newComposeNote = ComposeNote(context: context)
newNoteTitleItem.title = noteTextField.text!
newComposeNote.note = noteTextView.text!
newComposeNote.parentTitleNote = selectedNoteList
noteComposeItemsArray.append(newComposeNote)
noteListArray.append(newNoteTitleItem)
saveComposeItems()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func saveComposeItems() {
do {
try context.save()
}catch {
print("Error saving context \(error)")
}
reloadInputViews()
}
func loadComposeItem() {
let request : NSFetchRequest<ComposeNote> = ComposeNote.fetchRequest()
let predicate = NSPredicate(format: "parentTitleNote.title MATCHES %#", selectedNoteList!.title!)
request.predicate = predicate
do {
noteComposeItemsArray = try context.fetch(request)
}catch {
print("Can't load Items")
}
reloadInputViews()
}
}

How to open data which in tableView in new controller, data from FIrebase

I want to send text to firebase, and then retrieve it to tableView, but some data too long and don't fit in one line, so i want that data can be open in new controller, how to release it?
My code:
class UserVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference?
var databaseHandle:DatabaseHandle?
var postData = [String]()
var postOneData = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
ref = Database.database().reference()
databaseHandle = ref?.child("Child").observe(.childAdded, with: { (snapshot) in
let post = snapshot.value as? String
if let actualPost = post {
self.postData.append(actualPost)
self.tableView.reloadData()
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "MySell") as UITableViewCell!
cell.textLabel?.text = self.postData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showText", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showText" {
if let destinationVC = segue.destination as? ShowText {
}
}
}
Assuming that you have a text label in you ShowText
private var textForShowText = String() // a private class variable
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.textForShowText = self.postData[indexPath.row]
performSegue(withIdentifier: "showText", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showText" {
if let destinationVC = segue.destination as? ShowText {
destinationVC.textLabel.text = textForShowText
}
}
}
Here I am assuming that your ShowText has a UITextLabel named textLabel. Also make sure that the numberOfLines of the text label is set to 0. You can also use a UITextView if you want !!

Data not passing in Swift

I'm working on a journal app and now I want people to be able to click on a cell/entry and then go to a more detailed view where they can read/edit their entry. The thing is, the strings I'm trying to pass to the next view aren't coming through.
Edit:
This is the code on the TableViewController:
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items: [Entry] = []
var passedEntry:String = ""
func fetchData() {
do {
items = try context.fetch(Entry.fetchRequest())
print(items)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Couldn't fetch data")
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
let date = items.reversed()[indexPath.row].date
cell.textLabel?.text = items[indexPath.row].title
if let date = date {
let dateStamp = "Added on \(date)"
cell.detailTextLabel?.text = dateStamp
}
return cell
}
#objc(tableView:editActionsForRowAtIndexPath:) func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
// delete item at indexPath
let item = self.items[indexPath.row]
self.context.delete(item)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
self.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
return [delete]
}
public func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "showEntry") {
// initialize new view controller and cast it as your view controller
let viewController = EntryViewController()
// your new view controller should have property that will store passed value
viewController.passedEntry = passedEntry
}
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
passedEntry = items[indexPath.row].title!
self.performSegue(withIdentifier: "showEntry", sender: self)
}
override func viewWillAppear(_ animated: Bool) {
fetchData()
}
This is the code on the EntryViewController which still prints nil:
class EntryViewController: UIViewController {
var passedEntry:String!
#IBOutlet weak var dreamTitle: UITextView!
#IBOutlet weak var dreamPost: UITextView!
#IBAction func saveChanges(_ sender: Any) {
}
override func viewWillAppear(_ animated: Bool) {
print(passedEntry)
}
I'm sorry for being a newbie, but I need some more detailed instructions...
Two issues:
The signature of prepareForSegue is wrong for Swift 3.
Never create view controllers with the default initializer when using storyboard
This main issue occurs in this line:
let viewController = EntryViewController()
You are creating a brand new instance of EntryViewController which is not the controller in the storyboard. You have to use the destination view controller of the segue:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showEntry" {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! EntryViewController
// your new view controller should have property that will store passed value
let title = items[indexPath.row].title
viewController.passedEntry = title
}
}
And I recommend to declare the string in the destination view controller as non-optional empty string rather than an implicit unwrapped optional. Non-optional types will never crash the app.
var passedEntry = ""
You are defining prepareForSegue() as a local function inside the tableView-function. It will not be used. Put a debugger breakpoint into it and see yourself if it will be reached.