I am trying to get data from a different screen but for some reason, the variable does not update. here is the code so far
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController: UIViewController = UIViewController()
switch (indexPath.row) {
case 0:
print("hello")
let vc = AddViewController()
vc.chosenDaily = true
dismiss(animated: true, completion: nil)
here I created a tableView and once the user clicks on the first index of my tableView I want to set a boolean variable to true and return to another screen. I declared this boolean variable on the other screen here.
var chosenDaily:Bool = false
on the same screen, I have a save button and want to print the result.
#IBAction func didTapSaveButton() {
if chosenDaily == true {
print("it's true")
} else if chosenDaily == false {
print("it's false")
}
}
but for some reason, the code doesn't update and returns false instead of true. can anyone help? thanks for any help that i get
I don't know if you are using navigation controller during the transition between screens. But I suppose you are using it. So let's say you have two ViewController. FirstViewController and SecondViewController. And they are also using navigation controller when you make a transition from FirstViewController to the SecondViewController. In the SecondViewController you have your tableView and when you click a row in that tableView you want to send some value to the FirstViewController before dismiss. Add this function in the SecondViewController and call it before dismiss :
func changeValue(myValue : Bool) {
if let navController = SecondViewController as? UINavigationController {
let getfirstVC = navController.topViewController as! FirstViewController
getfirstVC.chosenDaily = myValue
}
}
and your tableview function will be like this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController: UIViewController = UIViewController()
switch (indexPath.row) {
case 0:
print("hello")
changeValue(true)
dismiss(animated: true, completion: nil)
Don't forget to change FirstViewController and SecondViewController according to your own.
You can use Delegate to interactive between view.
protocol tableViewControllerDelegate {
func change(value:Bool)
}
class tableViewController: UIViewController {
var del:tableViewControllerDelegate!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (indexPath.row) {
case 0:
del?.change(value:true)
dismiss(animated: true, completion: nil)
break;
default:
break
}
}
}
Class is reference type, You can also processing data by class.
class AddViewController:UIViewController,tableViewControllerDelegate{
var chosenDaily = false
func change(value:Bool){
chosenDaily = value
}
}
class tableViewController: UIViewController {
var data:Sample!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch (indexPath.row) {
case 0:
data?.chosenDaily = true
dismiss(animated: true, completion: nil)
break;
default:
break
}
}
}
class Sample{
var chosenDaily = false
}
class AddViewController:UIViewController{
var data = Sample()
func open(){
let view = tableViewController()
view.data = data
}
}
Related
I want to avoid duplicate rewriting same code and create reusable UITableViewController.
I have ExerciseViewController with 3 buttons. Each button push a UITableViewController on the navigation stack. There are three UITableViewControllers: 1) CategoryUITableVC, 2) EquipmentUITableVC, 3) MusclesUITableVC.
All of these three view controllers have almost exactly same layout - cells with labels and accessory buttons. The only difference is that first view controller has got image next to title. Is it worth doing one reusable VC and instantiate it 3 times or maybe better solution create 3 separated VC (but It will be just rewriting almost same code).
I use coordinator pattern.
class ExerciseCoordinator: NSObject, Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
...
//unnecessary code to show
...
// *HERE I INSTANTIATE VIEW CONTROLLERS, I PRESENT THEM MODALLY BUT I WANT TO HAVE NAVIGATION BAR, SO I NEED TO CREATE NEW NAVIGATION CONTROLLERS*
lazy var equipmentVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var equipmentNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: equipmentVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var categoryVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var categoryNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: categoryVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
lazy var muscleVC: ReusableTableViewController = {
let vc = AppStoryboard.Main.instantiate(ReusableTableViewController.self)
vc.delegate = self
return vc
}()
lazy var muscleNavController: UINavigationController = {
let navController = UINavigationController(rootViewController: muscleVC)
navController.navigationItem.largeTitleDisplayMode = .always
return navController
}()
}
extension ExerciseCoordinator: CustomExerciseDelegate {
func selectCategory() {
navigationController.viewControllers.last?.present(categoryNavController, animated: true, completion: nil)
categoryVC.dataType = .category
}
func selectEquipment() {
navigationController.viewControllers.last?.present(equipmentNavController, animated: true, completion: nil)
equipmentVC.dataType = .equipment
}
func selectMuscles() {
navigationController.viewControllers.last?.present(muscleNavController, animated: true, completion: nil)
muscleVC.dataType = .muscle
}
}
I assign data type to know from where it comes from (CategoryVC/EquipmentVC/MuscleVC) when I will dismiss UITableVC.
Here it is my reusable UITableViewController:
import UIKit
import RealmSwift
class ExerciseCategoryTableViewController: UITableViewController {
var delegate: ExerciseSelectionCriteriaDelegate?
//I use it delegate to send data back after dismiss view
var dataType: DataType?
var data = [String]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData() }}
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
if dataType == .category {
let allCategories = RealmService.shared.realm.objects(Category.self)
data = allCategories.compactMap({$0.category})
} else if dataType == .equipment {
let allEquipment = RealmService.shared.realm.objects(Equipment.self)
data = allEquipment.compactMap({$0.equipment})
} else {
let allMuscles = RealmService.shared.realm.objects(Muscles.self)
data = allMuscles.compactMap({$0.muscles})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// below is my own shortcut for dequeue cell, it works
let cell: ExerciseSelectionTableViewCell = tableView.dequeueResuableCell(for: indexPath)
cell.category.text = data[indexPath.row]
if let image = UIImage(named: "\(data[indexPath.row].lowercased())") {
cell.categoryImage.image = image
cell.categoryImage.isHidden = false
} else {
cell.categoryImage.isHidden = true
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
#IBAction func closeViewController(_ sender: UIBarButtonItem) {
closeViewController()
}
#IBAction func saveSelectedCategories(_ sender: UIBarButtonItem) {
saveSelectedData()
}
func saveSelectedData() {
let selectedIndexes = tableView.indexPathsForSelectedRows
if let selectedData = selectedIndexes?.compactMap({data[$0.row]}) {
dismiss(animated: true) {
self.delegate?.selectedFields(for: selectedData, dataType: self.dataType)
}
} else {
dismiss(animated: true) {
self.delegate?.selectedFields(for: nil, dataType: self.dataType)
}
}
}
func closeViewController() {
guard let _ = tableView.indexPathsForSelectedRows else { return dismiss(animated: true, completion: nil)}
Alert.showAlertWithCompletion(on: self, with: "TEXT", message: "TEXT") { _ in
self.dismiss(animated: true, completion: nil)
}
}
}
I will be thankful if you tell me if this approach is correct or maybe better solution are separated view controllers or create protocol with default implementation?
Let's say we have two view controllers, a parent with a label and a modally presented child with a table view. How would I pass the user's selection in the table view back to the parent using delegation?
ViewController1:
var delegate: vc2delegate?
override func viewDidLoad {
super.viewDidLoad()
let label.text = ""
}
ViewController2:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
let selections = ["1", "2", "3", "4", "5"]
cell.selections.text = selections[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? Cell {
cell.didSelect(indexPath: indexPath as NSIndexPath)
}
dismiss(animated: true, completion: nil)
}
//wherever end of class is
protocol vc2delegate {
// delegate functions here
}
Do I even have the right approach? I never really got down this pattern and I think it's crucial for me to learn for iOS. Another tricky caveat may be that viewDidLoad() doesn't get called when you dismiss a modal view controller.
Take a look at the UIViewController life cycle docs: ViewDidLoad only gets called once.
There are plenty of guides on how to do this, just do a quick search.
You'll need to update the dataSource logic as I added a quick string array, and you'll most likely have something a bit more complex, but the idea is still the same.
BTW, I used your naming convention of vc1/vc2, but I hope you have more meaningful names for your controllers.
In your code you have the delegate on the wrong VC. Here is a quick code sample of what it should look like:
class VC1: UIViewController {
let textLabel = UILabel()
// whenever you're presenting the vc2
func presentVC2() {
var vc2 = VC2()
vc2.delegate = self
self.present(vc2, animated: true, completion: nil)
}
}
extension VC1: VC2Delegate {
func updateLabel(withText text: String) {
self.textLabel.text = text
}
}
protocol VC2Delegate: class {
func updateLabel(withText text: String)
}
class VC2: UIViewController {
weak var delegate: VC2Delegate?
let dataSource = ["string 1", "tring 2"]
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let string = dataSource[indexPath.row]
self.delegate?.updateLabel(withText: string)
dismiss(animated: true, completion: nil)
}
}
You can use callback function also to update your label from tableview:
1) Declare callback function into your VC2:
var callback:((String) -> Void)?
2) Call this function in your tableview CellForRowAt method in VC2:
let dataSource = ["string 1", "tring 2"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let string = dataSource[indexPath.row]
var cell = tableView.dequeueReusableCell(withIdentifier: "yourCell") as! YourCell
//here you can call callback function & pass string to VC1
cell.callback?(dataSource[indexPath.row])
}
3) Now you can call callback closure in VC1 in anywhere you call your VC2:
class VC1: UIViewController {
let textLabel = UILabel()
//I'm calling this(presentVC2()) function on ViewDidLoad you can call anywhere you want
func viewDidLoad() {
super.viewDidLoad()
presentVC2()
}
// whenever you're presenting the vc2
func presentVC2() {
var vc2 = VC2()
vc2.callback = { text in
self.textLabel.text = text
}
self.present(vc2, animated: true, completion: nil)
}
}
I have the following class:
import UIKit
import CloudKit
class FirstViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var listTableView: UITableView!
var list: TLListModel!
var specificList: CKRecord!
override func viewDidLoad()
{
super.viewDidLoad()
let myContainer = CKContainer.default()
list = TLListModel(container: myContainer, viewController: self)
if(listTableView != nil){
listTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("number of items: %i", list.lists.count)
return list.lists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = listTableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
let list: CKRecord = self.list.lists[(indexPath as NSIndexPath).row]
cell.textLabel?.text = list.value(forKey: "ListName") as? String
cell.textLabel?.font = UIFont (name: "Slim Joe", size: 20)
cell.accessoryType = .disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("This object has been selected")
print(self.list.lists[(indexPath as NSIndexPath).row])
specificList = self.list.lists[(indexPath as NSIndexPath).row]
performSegue(withIdentifier: "TLSpecificListSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TLSpecificListSegue"{
if let destinationVC = segue.destination as? TLSpecificListViewController{
destinationVC.listObject = specificList
}
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
let cloudkit = TLCloudKitHelper()
cloudkit.deleteListItem(self.list.lists[(indexPath as NSIndexPath).row], callback: { (listName) in
TLAlertHelper.notifyUser("List Deleted", message: NSString(format: "List for %# successfully deleted", listName) as String, sender: self)
let myContainer = CKContainer.default()
self.list = TLListModel(container: myContainer, viewController: self)
DispatchQueue.main.async {
self.listTableView.reloadData()
}
})
}
}
}
When I call it from another view controller using the following method:
#IBAction func createListAction(_ sender: AnyObject) {
let cloudkit = TLCloudKitHelper()
let listArray = createListFromTextField(textInputArea.text)
if(!(listNameTextField.text?.isEmpty)!){
cloudkit.createList(listNameTextField.text!) { (response) in
let listId = response
if (!listArray.isEmpty){
for item in listArray{
cloudkit.saveItemRecord(item, listId: listId, recordName: response)
}
}
let fvc: FirstViewController = FirstViewController()
DispatchQueue.main.async {
self.present(fvc, animated: true, completion: nil)
}
}
}else{
TLAlertHelper.notifyUser("Give the list a name", message: "You need to give you list a name...", sender:self)
}
}
I get an error saying fatal error: unexpectedly found nil while unwrapping an Optional value
I don't understand why I am getting this error. I've tried looking at the answers here: Simple UITableView in Swift - unexpectedly found nil but I none of those answers helped. Can someone tell me why this this crashing and how I can fix it?
The problem is this line:
let fvc: FirstViewController = FirstViewController()
This creates a blank FirstViewController instance — one completely unconnected with the interface you designed in the storyboard. Its view is empty. It has no table view in it. Therefore, since there is no table view, there is no outlet connection from any table view, and listTableView remains nil.
What you want to do is get the FirstViewController instance from the storyboard, the one whose interface you have already designed in the storyboard. You can do that by talking to the storyboard and using the FirstViewController's identifier, i.e., call instantiateViewController(withIdentifier:). (You might have to give the FirstViewController in the storyboard an identifier for this purpose.)
EDIT This is such a common mistake that I've written a blog post about it: http://www.programmingios.net/dont-make-a-new-instance-by-mistake/
I have a navigation bar with a table view controller that display 4 images (4 cells). I have 4 viewControllers that I need to show when the user clicks on the cell ( each image show a specific VC ) ??
any ideas would be much appreciated
here is my code :
class MenuTable: UITableViewController {
#IBOutlet weak var imgtable: UITableView!
var menuImage: [UIImage] = [
UIImage(named: "image1")!,
UIImage(named: "image2")!,
UIImage(named: "image3")!,
UIImage(named: "image4")!
]
override func viewDidLoad() {
super.viewDidLoad()
imgtable.dataSource = self
imgtable.delegate = self
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuImage.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("menucell") as! Menucell
cell!.imageview.image = menuImage[indexPath.row]
return cell
}
}
You need to implement didSelectRowAtIndexPath delegate method of UITableView like this
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var viewController: UIViewController = UIViewController()
switch (indexPath.row) {
case 0:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("viewController1") as! viewController1
case 1:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("viewController2") as! viewController2
case 2:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("viewController3") as! viewController3
case 3:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("viewController4") as! viewController4
default:
print("default")
}
self.navigationController?.pushViewController(viewController, animated: true)
}
I turn on the editing table in the FirstViewController
#IBAction func editButtonPressed(sender: UIBarButtonItem) {
self.tableView.allowsMultipleSelectionDuringEditing = true
if self.editing {
let popoverEditMenu = self.storyboard?.instantiateViewControllerWithIdentifier("popoverEditMenu") as! EditMenuTableViewController
popoverEditMenu.modalPresentationStyle = .Popover
popoverEditMenu.popoverPresentationController!.delegate = self
let popover: UIPopoverPresentationController = popoverEditMenu.popoverPresentationController!
popover.barButtonItem = sender
presentViewController(popoverEditMenu, animated: true, completion: nil)
} else {
editButton.image = UIImage(named: "profile_more")
self.editing = !self.editing
}
}
Editing table is included successfully. After the above actions, I want to finish editing, by clicking on a table cell in a popover, code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let firstTableVC = self.storyboard?.instantiateViewControllerWithIdentifier("firstTableVC") as! FirstTableViewController
tableView.deselectRowAtIndexPath(indexPath, animated: true)
switch indexPath.row {
case 0:
self.dismissViewControllerAnimated(true, completion: nil)
firstTableVC.editing = false // Disable Editing
firstTableVC.editButton.image = UIImage(named: "1461294921_15.Pencil")
default:
break
}
}
But there is no change in the button image, and table editing mode not disabled
Solution found!
The problem was solved by the use of delegation. Thanks to #pbasdf for the tip
import UIKit
protocol SecondTableViewControllerDelegate {
func endEditing()
}
class SecondTableViewController: UITableViewController {
var delegate: SecondTableViewControllerDelegate?
...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
switch indexPath.row {
case 0:
self.dismissViewControllerAnimated(true, completion: nil)
delegate?.endEditing()
default:
break
}
}
}
Delegate function in FirstViewController. You need to specify a delegate inheritance in FirstViewController
func endEditing() {
self.editing = false
editButton.image = UIImage(named: "1461294921_15.Pencil")
}
Try to use
firstTableVC.editButton.setImage(UIImage(named:"1461294921_15.Pencil"), forState: UIControlState.Normal)
It must works.