I want to modalViewController's table cell data pass to the main View.
Using Protocol delegate. but
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This error occur where i remark at the ModalViewController.
Using Protocol method is wrong?.
I use this protocol code another mini project textField's text data pass back and forward. In there it works well.
So i use it in cell data pass
What am i Miss ? Please help.
Bottom Link is my Simulator.
ModalTableViewDataPass
**MainViewController**
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var show: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
show.placeholder = "HAHA"
show.delegate = self
show.inputView = UIView()
show.addTarget(self, action: #selector(textFieldDidBeginEditing(_:)), for: .touchUpInside)
// Do any additional setup after loading the view.
}
#objc func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == show {
moveToFindAdrVC()
print("주소 검색해라잉")
}
}
func moveToFindAdrVC() {
let modalVC = storyboard?.instantiateViewController(identifier: "ModalViewController") as? ModalViewController
self.present(modalVC!, animated: true, completion: nil)
}
}
extension ViewController: PassDataToVc {
func passData(str: String){
show.text = str
}
}
ModalTableView
import UIKit
class ModalViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var data = ["as","df","qw","er"]
var delegate: PassDataToVc!
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = data[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let passData = data[indexPath.row]
let sb = storyboard?.instantiateViewController(identifier: "ViewController") as? ViewController
//sb?.show.text = passData
delegate.passData(str: passData)//Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
self.dismiss(animated: true, completion: nil)
}
}
protocol PassDataToVc {
func passData(str: String)
}
delegate in ModalViewController is never set, hence it's nil.
But there is a more convenient solution, protocol/delegate is not needed at all.
Delete the code related to protocol/delegate
extension ViewController: PassDataToVc {
func passData(str: String){
show.text = str
}
}
var delegate: PassDataToVc!
and replace
delegate.passData(str: passData)
with
(presentingViewController as? ViewController)?.show.text = passData
Related
I have two view controllers, one with a table view (first VC) and a second one that can be navigated to via a button in the navigation bar. The second view controller is supposed to be able to add a table view cell into the tableview of the first view controller.
First View Controller:
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
var data = ["mars", "earth", "jupiter", "venus", "saturn"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(tableView)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = data[indexPath.row]
return cell
}
Second View Controller:
import UIKit
class SecondViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
#IBOutlet var button: UIButton!
#IBOutlet var addPostButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
title = "Second Screen"
// Do any additional setup after loading the view.
}
#IBAction func didTapButton(){
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.allowsEditing = true
picker.delegate = self
present(picker, animated: true)
}
#IBAction func didTapAddPostButton(){
let FVC = FirstViewController()
FVC.data.append("New data added!")
DispatchQueue.main.async{
FVC.tableView.beginUpdates()
FVC.tableView.performBatchUpdates({
FVC.tableView.insertRows(at: [IndexPath(row: FVC.data.count - 1,
section: 0)],
with: .automatic)
}, completion: nil)
FVC.tableView.endUpdates()
}
print("TapAddPostButton pressed")
print(FVC.data)
}
}
extension SecondViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
return
}
imageView.image = image
}
}
What's supposed to happen: upon pressed the button in the second view controller, a new table cell is supposed to be added and then the tableview reloaded.
What's actually happening: the tableview is not being reloaded. When i swipe back to the first view controller, the tableview is the same as before and has not reloaded and updated with the new cell.
Images:
This is the first view controller
This is the second view controller
When you do:
let FVC = FirstViewController()
... you're creating a new, unrelated instance of FirstViewController. This won't work. Look into delegates + protocols or closures (or show code for where you are presenting SecondViewController and I'll update my answer).
If you need to achieve it, it's better to use protocols to be able to link these two viewControllers in the correct way.
Creating a protocol In anywhere you need:
protocol PassingProtocol {
func saveData(withText myText: String)
}
then in FirstViewController:
extension FirstViewController: PassingProtocol {
func saveData(withText myText: String) {
data.append(myText)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
In the Action of NewEntry Barbutton :
let myVC = self.storyboard?.instantiateViewController(identifier: "SecondViewController") as! SecondViewController
myVC.textDelegate = self
self.navigationController?.pushViewController(myVC, animated: true)
}
In the SecondViewController:
var textDelegate: PassingProtocol!
Then in the action of didTapAddPostButton :
textDelegate.saveData(withText: "New data added!")
If you need to use outlets follow the below steps:
FirstViewController.Swift:
import UIKit
class FirstViewController: UIViewController {
var data = ["mars", "earth", "jupiter", "venus", "saturn"]
#IBOutlet weak var tableViewOutlet: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func newEntryAction(_ sender: Any) {
let myVC = self.storyboard?.instantiateViewController(identifier: "SecondViewController") as! SecondViewController
myVC.textDelegate = self
self.navigationController?.pushViewController(myVC, animated: true)
}
}
extension FirstViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewOutlet.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
cell.lblData.text = data[indexPath.row]
return cell
}
}
extension FirstViewController: PassingProtocol {
func saveData(withText myText: String) {
data.append(myText)
DispatchQueue.main.async {
self.tableViewOutlet.reloadData()
}
}
}
SecondViewController.Swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var textFieldData: UITextField!
var textDelegate: PassingProtocol!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func addEntryButtonAction(_ sender: Any) {
textDelegate.saveData(withText: textFieldData.text!)
}
}
PassingProtocol.Swift
import Foundation
protocol PassingProtocol {
func saveData(withText myText: String)
}
MyCell.Swift
import UIKit
class MyCell: UITableViewCell {
#IBOutlet weak var lblData: UILabel!
}
It will works , I've tried it myself and worked fine
After taking photo from a gallery. pass a delegate function to FirstVC.
FirstVC - function contain the data and Data count. Load Tableview in a dispatch queue method.
I'm trying to change the values of a variable in two different view controllers from the value of a textField but I don't understand how to use the delegate so that it works.
My Storyboard:
My Code:
MainView:
class GameCreatingViewController: UIViewController {
var newGame = Game()
override func viewDidLoad() {
super.viewDidLoad()
newGame = Game()
newGame.playerBook.NumberOfPlayers = 2
if let vc = self.children.first(where: { $0 is PlayersTableViewController }) as? PlayersTableViewController {
vc.currentGame = self.newGame
vc.tableView.reloadData()
}
if let vc = self.children.first(where: { $0 is GameViewController }) as? GameViewController {
vc.currentGame = self.newGame
}
}
func changeName(name: String, number: Int) {
self.newGame.playerBook.players[number].name = name
}
}
tableViewController:
class PlayersTableViewController: UITableViewController, UITextFieldDelegate {
var currentGame = Game()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
func changeName(name: String, number: Int) {
self.currentGame.playerBook.players[number].name = name
}
}
The Cell:
protocol changeNameDelegate: class {
func changeName(name: String, number: Int)
}
class PlayerNameTableViewCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: changeNameDelegate? = nil
#IBOutlet weak var playerName: UITextField!
var numberOfPlayer: Int = Int()
#IBAction func changeName(_ sender: UITextField) {
delegate?.changeName(name: sender.text!, number: numberOfPlayer)
}
}
It seems like the action from the button executes but the fonctions from the other viewcontrollers don't.
Use the delegate to notify the other viewController.
Make sure isn't nil.
Usually protocols name the first letter is capitalized.
A good practice is to implement protocols in extensions.
Implement the changeNameDelegate protocol.
class PlayersTableViewController: UITableViewController, UITextFieldDelegate, changeNameDelegate {
And in the cell configuration set the delegate.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
cell.delegate = self // This line is missing.
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
So I have been trying to get my custom cells to show up on this tableview, but I am not sure as to why they are not showing up
I have already checked other stack overflow questions and tried their fixes, to no avail. Please ignore the aws stuff as you can see I have the text hard coded so I can just get them to appear for now.
This is the code within the class holding the tableview
import Foundation
import AWSDynamoDB
import AWSCognitoIdentityProvider
import UIKit
// this will be the main feed class showing the user data
class UserDetailTableViewController : UITableViewController {
// attributes for the custome cell
#IBOutlet weak var testing: UITextField!
#IBOutlet var Table: UITableView!
var response: AWSCognitoIdentityUserGetDetailsResponse?
var user: AWSCognitoIdentityUser?
var pool: AWSCognitoIdentityUserPool?
var questiondata : Array<Phototext> = Array()
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
super.viewDidLoad()
self.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
if (self.user == nil) {
self.user = self.pool?.currentUser()
}
// grabbing data from our aws table
updateData()
self.refresh()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setToolbarHidden(true, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setToolbarHidden(false, animated: true)
}
#IBAction func Questions(_ sender: Any) {
performSegue(withIdentifier: "ask", sender: self)
}
// MARK: - IBActions
#IBAction func signOut(_ sender: AnyObject) {
self.user?.signOut()
self.title = nil
self.response = nil
self.refresh()
}
// reloads the prior view
func refresh() {
self.user?.getDetails().continueOnSuccessWith { (task) ->
AnyObject? in
DispatchQueue.main.async(execute: {
self.response = task.result
self.title = self.user?.username
// saving the user name from the main menu
username123 = self.user?.username! ?? "broken"
})
return nil
}
}
// function that calls to our aws dynamodb to grab data from the
// user
//and re update questions
// the array list
func updateData(){
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
// testing to grabt the table data upon startup
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDBObjectMapper.scan(Phototext.self, expression:
scanExpression).continueWith(block: {
(task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
// passes down an array of object
for Photo in paginatedOutput.items as! [Phototext] {
// loading in the arraylist of objects
// adding the objects to an arraylist
self.questiondata.append(Photo)
}
DispatchQueue.main.async {
//code for updating the UI
}
}
return ()
})
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}
}
}
Here is the code for the QuestionCell class
import UIKit
class QuestionCell: UITableViewCell {
#IBOutlet weak var Subject: UILabel!
#IBOutlet weak var QuestionText: UITextView!
}
The cell class is called QuestionCell and the identifier I left on the cell in the storyboard is Questionpost
Here is a photo of my story board:
I have fixed it by declaring an extension with the proper types.
extension UserDetailTableViewController: UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// returning the number of rows
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Questionpost", for: indexPath) as! QuestionCell
cell.QuestionText.text = "call it"
cell.Subject.text = "a day"
return cell
}}
good explanation of what's going on, you need to conform to the UITableViewDataSource and UITableViewDelegate when you inbed a tableview.
Redundant conformance of TableView to protocol UITableViewDataSource with Xib Files
I have one table view and inside that i placed one main view. And inside that main view i placed one button.And when ever use click on my cell button. I need to get the cell title label.This is what i need. But i tried following below code. Not sure what i am missing out. It not at all calling my cell.add target line.
Code in cell for row at index:
cell.cellBtn.tag = indexPath.row
cell.cellBtn.addTarget(self, action:#selector(self.buttonPressed(_:)), for:.touchUpInside)
#objc func buttonPressed(_ sender: AnyObject) {
print("cell tap")
let button = sender as? UIButton
let cell = button?.superview?.superview as? UITableViewCell
let indexPath = tableView.indexPath(for: cell!)
let currentCell = tableView.cellForRow(at: indexPath!)! as! KMTrainingTableViewCell
print(indexPath?.row)
print(currentCell.cellTitleLabel.text)
}
I even added a breakpoint, still it not at calling my cell.addTarget line
Tried with closure too. In cell for row at index:
cell.tapCallback = {
print(indexPath.row)
}
In my table view cell:
var tapCallback: (() -> Void)?
#IBAction func CellBtndidTap(_ sender: Any) {
print("Right button is tapped")
tapCallback?()
}
Here that print statement is getting print in console.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var list = [String]()
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.saveButton.tag = indexPath.row
//cell.saveButton.accessibilityIdentifier = "some unique identifier"
cell.tapCallback = { tag in
print(tag)
}
return cell
}
}
class MyTableViewCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var saveButton: UIButton!
// MARK: - IBActions
#IBAction func saveTapped(_ sender: UIButton) {
tapCallback?(sender.tag)
}
// MARK: - Actions
var tapCallback: ((Int) -> Void)?
}
Actually this is not a good programming practice to add the button (which contains in table view cell) target action in view controller. We should follow the protocol oriented approach for it. Please try to under stand the concept.
/*This is my cell Delegate*/
protocol InfoCellDelegate {
func showItem(item:String)
}
/*This is my cell class*/
class InfoCell: UITableViewCell {
//make weak reference to avoid the Retain Cycle
fileprivate weak var delegate: InfoCellDelegate?
//Outlet for views
#IBOutlet var showButton: UIButton?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//This is the public binding function which will bind the data & delegate to cell
func bind(with: DataModel?, delegate: InfoCellDelegate?, indexPath: IndexPath) {
//Now the bind the cell with data here
//.....
//Assign the delegate
self.delegate = delegate
}
//Button action
#IBAction func rowSelected(sender: UIButton) {
self.delegate?.showItem(item: "This is coming from cell")
}
}
/*Now in your ViewController you need to just confirm the InfoCellDelegate & call the bind function*/
class ListViewController: UIViewController {
//Views initialisation & other initial process
}
//Table view Delegate & Data source
extension ListViewController: UITableViewDataSource, UITableViewDelegate {
/**
Configure the table views
*/
func configureTable() {
//for item table
self.listTable.register(UINib.init(nibName: "\(InfoCell.classForCoder())", bundle: nil), forCellReuseIdentifier: "\(InfoCell.classForCoder())")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoCell
cell.bind(with: DataModel, delegate: self, indexPath: indexPath)
return cell
}
}
extension ListViewController: InfoCellDelegate {
func showItem(item) {
print(item)
}
}
I'm trying to find a "cleaner-elegant" way to pass data between UIViewControllers. So, I decided to proceed using Delegates and Protocols. However, I failed on receive the data provided by my Protocol. What am I doing wrong?
Trying to receive the protocol data and use it to populate a UITableView:
class ViewController: UIViewController, CLLocationManagerDelegate, UITableViewDataSource, dataReceivedDelegate {
func dataReceived(nome: String, foto: UIImage, qtd: Int) {
nomeReceived = nome
self.qtd = qtd
self.itensTableView.reloadData()
}
#IBOutlet weak var itensTableView: UITableView!
var arrayNomes = NSMutableArray()
var nomeReceived = ""
var qtd:Int = 0
var objetos = [Objeto]()
//TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let item = objetos[indexPath.row]
let cell = itensTableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! tableviewCell
cell.nameCell.text = nomeReceived //Nil value
// cell.imageViewCell.image = item.foto //Nil value
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return qtd
}
override func viewDidAppear(_ animated: Bool) {
let controller = storyboard?.instantiateViewController(withIdentifier: "addVc") as! adicionarNovoItemVc
controller.delegate = self
}
Creating and setting value to the Protocol:
import UIKit
protocol dataReceivedDelegate {
func dataReceived(nome:String,foto:UIImage,qtd:Int)
}
class adicionarNovoItemVc: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {
#IBOutlet weak var textFieldNome: UITextField!
let imagePicker = UIImagePickerController()
#IBOutlet weak var namePreview: UILabel!
#IBOutlet weak var imagePreview: UIImageView!
let picker = UIImagePickerController()
var delegate:dataReceivedDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.textFieldNome.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func botaoAdcItem(_ sender: UIButton) {
if (self.namePreview!.text != nil) && (self.imagePreview!.image != nil) {
delegate?.dataReceived(nome: self.namePreview.text!, foto: self.imagePreview.image!, qtd: 1)
self.navigationController?.popViewController(animated: true)
}
else {return}
}
In your ViewController add an action to button,
func buttonAction(sender: UIButton!) {
let storyboard = UIStoryboard.init(name: "yourStoryboarName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "addVc") as! adicionarNovoItemVc
controller.delegate = self
self.navigationController?.pushViewController(controller, animated: true)
}
Once new controller is pushed on screen, you can execute 'botaoAdcItem' action and rest will get you expected result.