I'm trying to load all controllers in the main controller. Trying to use main controller as a navigation controller.
I try modal segue but I can only load the first one because even if i can see the navigation on the main controller I'm not able to press it.
I think that the top controller have to be resized to contrains but I have not being able to find how.
Here is the screenshot of what I'm trying to achieve.
The controllers need to go in the orange section.
Here is the answer. Found an old tutorial and just made some adjustments.
Maybe helps someone else.
MenuCV
import UIKit
class MenuCV: UIViewController {
#IBOutlet weak var tableView: UITableView!
var containerView: ContainerVC?
var menuItems = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
menuItems = ["Dashboard", "Companies", "Makes"]
}
}
extension MenuCV: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell") as? MenuTVC else { return UITableViewCell() }
let image = UIImage(named: "logo")
cell.configureCell(menuIcon: image!, menuLbl: menuItems[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
containerView?.segueIdentifierReceivedFromParent(button: menuItems[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "container" {
containerView = segue.destination as? ContainerVC
}
}
}
ContainerVC
import UIKit
class ContainerVC: UIViewController {
var vc : UIViewController!
var segueIdentifier : String!
var lastViewController: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
segueIdentifierReceivedFromParent(button: "Dashboard")
}
func segueIdentifierReceivedFromParent(button: String){
self.segueIdentifier = button
self.performSegue(withIdentifier: self.segueIdentifier, sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == segueIdentifier{
//Avoids creation of a stack of view controllers
if lastViewController != nil{
lastViewController.view.removeFromSuperview()
}
//Adds View Controller to Container
vc = segue.destination
self.addChildViewController(vc)
vc.view.frame = CGRect(x: 0,y: 0, width: self.view.frame.width,height: self.view.frame.height)
self.view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
lastViewController = vc
}
}
}
DashboardVC
import UIKit
class DashboardVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
CompaniesVC
import UIKit
class CompaniesVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
MakesVC
import UIKit
class MakesVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
EmptyVC
import UIKit
class EmptyCV: UIStoryboardSegue {
override func perform() {
}
}
Related
I have an array in a tableView that shows that contents of that array, and I am able to append values to the array. the problem is that whenever I go to another page, it automatically empties, but I want the values to stay there. Here is my code for the first view controller:
// AddFoodViewController.swift
// grosseries
//
// Created by Amish Tyagi on 6/2/20.
// Copyright © 2020 grosseries. All rights reserved.
//
import UIKit
class AddFoodViewController: UIViewController {
#IBOutlet weak var foodTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func doneTapped(_ sender: Any) {
}
func transitionToNext() {
let homeViewController = storyboard?.instantiateViewController(identifier: "TableViewController") as? TableViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "toTableViewController") {
let homeViewController = segue.destination as? TableViewController
homeViewController?.food.append(foodTextField.text!)
view.window?.rootViewController = homeViewController
}
}
}
Here is the code for my second view controller:
// TableViewController.swift
// grosseries
//
// Created by Amish Tyagi on 5/29/20.
// Copyright © 2020 grosseries. All rights reserved.
//
import UIKit
class TableViewController: UIViewController{
#IBOutlet var tableView: UITableView!
var food : [String]! = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
}
#IBAction func addItemTapped(_ sender: Any) {
transitionToNext()
}
func transitionToNext() {
let nextViewController = storyboard?.instantiateViewController(identifier: "AddFoodViewController") as? AddFoodViewController
view.window?.rootViewController = nextViewController
view.window?.makeKeyAndVisible()
}
}
extension TableViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me :)")
}
}
extension TableViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return food.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = food[indexPath.row]
return cell
}
}
Here is a picture of my storyboard layout:
Any Help would be greatly appreciated!
In the storyboard, select your TableViewController storyboard and select Editor->Embed in->Navigation Controller:
Below your tableView (or anywhere else you'd like) add a button with a segue to show the AddFoodViewController.
Now, on your AddFoodViewController add a button to confirm the food you put on textField:
#IBAction func confirmAddedFood(_ sender: Any) {
guard let tableViewVC = navigationController?.viewControllers.first as? TableViewController else { return }
tableViewVC.food.append(foodTextField.text!)
navigationController?.popViewController(animated: true)
}
You don't need the food array on your AddFoodViewController anymore, you still need the one on TableViewController though.
Don't forget to reload the tableView when you go back to it after adding a food, in your TableViewController add:
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
Your TableViewController:
class TableViewController: UIViewController{
#IBOutlet var tableView: UITableView!
var food: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// In the case when not using prototype cell.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
}
extension TableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return food.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = food[indexPath.row]
return cell
}
}
And FoodViewController:
class AddFoodViewController: UIViewController {
#IBOutlet weak var foodTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func confirmAddedFood(_ sender: Any) {
guard let tableViewVC = navigationController?.viewControllers.first as? TableViewController else { return }
tableViewVC.food.append(foodTextField.text!)
navigationController?.popViewController(animated: true)
}
}
It's not very cool to just give the already done code, but I think in this case is just a silly mistake, whether from you or me.
In this split view was not displaying on the iPad screen if I drag it was displaying and if I select an index it is not displaying on the label
class ListTableViewController: UITableViewController {
let names = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath)
cell.isSelected = true
cell.textLabel?.text = names[indexPath.row]
return cell
}
// MARK:- Storyboard segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "ShowDetailIdentifier") {
var detail: DetailsViewController
if let navigationController = segue.destination as? UINavigationController {
detail = navigationController.topViewController as! DetailsViewController
} else {
detail = segue.destination as! DetailsViewController
}
if let path = tableView.indexPathForSelectedRow {
detail.selectedIndex = path.row + 1
}
}
}
the code in master view controller
#IBOutlet weak var numberLabel: UILabel!
var selectedIndex:Int = 1
override func viewDidLoad() {
super.viewDidLoad()
numberLabel?.text = "\(selectedIndex)"
print(selectedIndex)
if splitViewController?.responds(to: #selector(getter: UISplitViewController.displayModeButtonItem)) == true {
navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
navigationItem.leftItemsSupplementBackButton = true
}
the code in details view controller
class SplitViewController: UISplitViewController,UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
splitViewController?.preferredDisplayMode = .primaryOverlay
splitViewController?.delegate = self
// Do any additional setup after loading the view.
}
func splitViewController(_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController: UIViewController,
onto primaryViewController: UIViewController) -> Bool {
return true
}
the code for split view controller
you are missing thisatableView.reloadData()on yourviewDidLoadorviewDidAppear
I can't see where are you initializing the splitviewcontroller you need to pass the TableViewController, and detailViewController.
You need to pass it in the viewDidLoad of the class inheriting form UISplitViewController
self.viewControllers = [masterNav, detail]
and to always show splitviewcontroller you need this
self.displayMode = .allVisible
see this gif
when I choose the city Med , it passed to the TableVC not to the FirstVC (MainVC)
can I do that ? segue to the mainVC with the data passed through
the container (TableVC) ?
here what I did so far
MainVC
Empty
TableVC
import UIKit
class passedViewController: UITableViewController {
#IBOutlet weak var passcelltow: UITableViewCell!
#IBOutlet weak var passcell: UITableViewCell!
var passedCity1 = "اختر المدينة الاولى"
var passedCity2 = "اختر المدينة الثانية"
override func viewDidLoad() {
super .viewDidLoad()
passcell.textLabel?.text = passedCity1
passcelltow.textLabel?.text = passedCity2
}
}
Table 1 with data to pass to the TableVC
import UIKit
class city2ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
var city2 = ["RUH" , "Med" , "Jed"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return city2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
print(indexPath.row)
cell.textLabel?.text = city2[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "show", sender: city2[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let passing = segue.destination as! passedViewController
passing.passedCity2 = sender as! String
}
}
Table 2 is the same ..
commend error
0 1 2 Could not cast value of type 'UIViewController' (0x107a10288) to
'table_view_test_pass.passedViewController' (0x105dbfdf8). (lldb)
You can pass data via segues or protocols. Since you are using segues i will show you a complete example and how to do it the right way in Swift 3. Using only two ViewControllers.
Create two UITextFields in the main "ViewController".
Create a new view controller of type UIViewController call it "MainTabelViewController" and add a tableView in it. Select content Dynamic prototypes Style Grouped and create 1 prototype cell and add a UILabel to it for the city name. "Don't forget the put the cell identifier name". I called it "cell".
Add the delegates and data sources to the class and add its functions like in code.
Create a segue from the main view controller to the main table view controller. And create another segue the opposite direction. "Don't forget the put the segue identifier names" I called them "toCity" & "toMain"
Create a "CityTableViewCell" controller of type UITableViewCell and create an IBOutlet of UILabel type where you will save the city name in as a text.
Edit this part in the AppDelegate.swift To delete the city names saved using in the UserDefaults every time the app is launched. So i wont populate the UITextFields randomly every time.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var userDefaults: UserDefaults!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
userDefaults = UserDefaults.standard
userDefaults.removeObject(forKey: "City One")
userDefaults.removeObject(forKey: "City Two")
return true
}
This is the ordinary main ViewController.swift where you have your UITextFields in. I distinguish which UITextField did the user click on using the tags. You need to add also the UITextFieldDelegate protocol to be able to use the the textFieldDidBeginEditing function. And i also save the selected city names using UserDefaults class to call them when user chooses the other city.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var cityOneLabel: UITextField!
#IBOutlet var cityTwoLabel: UITextField!
#IBOutlet var continueButton: UIButton!
var selectedCityOne = ""
var selectedCityTwo = ""
var userDefaults: UserDefaults!
override func viewDidLoad() {
super.viewDidLoad()
cityOneLabel.delegate = self
cityTwoLabel.delegate = self
cityOneLabel.tag = 1
cityTwoLabel.tag = 2
continueButton.isEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
userDefaults = UserDefaults.standard
cityOneLabel.text = selectedCityOne
cityTwoLabel.text = selectedCityTwo
if selectedCityOne != "" {
userDefaults.set(selectedCityOne, forKey: "City One")
} else {
cityOneLabel.text = userDefaults.string(forKey: "City One")
}
if selectedCityTwo != "" {
userDefaults.set(selectedCityTwo, forKey: "City Two")
} else {
cityTwoLabel.text = userDefaults.string(forKey: "City Two")
}
if cityOneLabel.text != "" && cityTwoLabel.text != "" {
continueButton.isEnabled = true
} else {
continueButton.isEnabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func continueButtonAction(_ sender: UIButton) {
//Later on continue after selecting the cities
}
func textFieldDidBeginEditing(_ textField: UITextField) {
performSegue(withIdentifier: "toCity", sender: textField.tag)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toCity" {
guard let cityVC = segue.destination as? MainTableViewController else {
return
}
cityVC.selectedTextField = sender as! Int
}
}
}
In the CityTabelViewCell.swift add the IBOutlet UILabel for the city name.
import UIKit
class CityTableViewCell: UITableViewCell {
#IBOutlet var cityNameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
For the MainTabelViewController.swift write this:
Here is where i create an array of strings to populate my table view UILabels with.
import UIKit
class MainTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var cityTabelView: UITableView!
var cityNamesArray = ["Cairo", "Alexandria", "Suez"]
var selectedTextField = Int()
var selectedCityName = ""
override func viewDidLoad() {
super.viewDidLoad()
cityTabelView.delegate = self
cityTabelView.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CityTableViewCell
cell.cityNameLabel.text = cityNamesArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cityNamesArray.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCityName = cityNamesArray[indexPath.row]
performSegue(withIdentifier: "toMain", sender: self)
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
var title = ""
if selectedTextField == 1 {
title = "City One"
} else if selectedTextField == 2 {
title = "City Two"
}
return title
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMain" {
guard let mainVC = segue.destination as? ViewController else {
return
}
if selectedTextField == 1 {
mainVC.selectedCityOne = selectedCityName
} else if selectedTextField == 2 {
mainVC.selectedCityTwo = selectedCityName
}
}
}
}
This is how my layout looks like. Try it. I just added a continue button too if the user will have to go to another UIViewController after selecting the two cities.
If you want to segue to MainVC, you should instantiate a view controller from that class in prepare for segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let passing = segue.destination as! ViewController
passing.passedCity2 = sender as! String
}
Change ViewController to whatever the name of your class is for MainVC.
If you want to go back to the Parent View, you should be using an unwind-segue.
For that you must create the unwind segue method in the Parent View like this
#IBAction func unwindSegueFromChild(segue: UIStoryboardSegue){
// This code executes when returning to view
}
And in your child view you must create the unwind segue ctrl+dragging
There a dropdown appears and you select unwindSegueFromChild
Once you've done that, you must assign the unwind segue an identifier and programmatically perform it like a normal segue.
I'm dealing with following problem: On main VC I have a TableView, and on container view controller I have a textField. I want to add every text, I'm typing in container automatically appears as a new row on tableView in main VC
By now I'm using segue to send data from main VC to container. But what should I implement to do the same in a reverse order? I though of implementing delegate of main VC in my container view, but I have no idea how to do that properly. Or maybe there is exist more common solution.
Anyway, here is my code:
class MessageViewController: UIViewController {
var currentUser: User!
var containerViewController: InputTextViewController?
#IBOutlet weak var enterMessageView: UIView!
#IBOutlet weak var messageTableView: UITableView!
}
extension MessageViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "moveToInputText" {
let connectContainerViewController = segue.destination as? InputTextViewController
containerViewController = connectContainerViewController
containerViewController?.userSendMessageTo = currentUser
}
}
}
extension MessageViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currentUser.mesaageHistory.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as! ChatMessageTableViewCell
let data = currentUser.mesaageHistory[indexPath.row]
cell.messageLabel.text = data.messageText
return cell
}
}
class InputTextViewController: UIViewController {
#IBOutlet weak var messageTextField: UITextField!
var userSendMessageTo: User!
weak var delegate = MessageViewController()
#IBAction func sendMessge(_ sender: Any) {
handleSend()
userSendMessageTo.mesaageHistory.append(message)
print(userSendMessageTo.mesaageHistory[0].messageText)
let row = userSendMessageTo.mesaageHistory.count - 1
let insertIndexPath = IndexPath(item: row, section: 0)
print(userSendMessageTo.mesaageHistory.count)
delegate?.messageTableView.beginUpdates()
delegate?.messageTableView.insertRows(at: [insertIndexPath], with: UITableViewRowAnimation.automatic)
delegate?.messageTableView.endUpdates()
}
}
Here's how to use the 'delegate' pattern properly
Protocol declaration & delegate member
protocol InputTextViewControllerDelegate: class {
func someFunc()
func anotherFunc()
}
class InputTextViewController: UIViewController {
weak var delegate: InputTextViewControllerDelegate?
}
Protocol implementation & setting the delegate property
extension MessageViewController, InputTextViewControllerDelegate {
// MARK: InputTextViewControllerDelegate
//
func someFunc()
{
}
func anotherFunc()
{
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "moveToInputText" {
let connectContainerViewController = segue.destination as? InputTextViewController
containerViewController = connectContainerViewController
containerViewController
}
}
}
This is my TableViewController: Cells will segue to First controller
import UIKit
class SubjectsTableViewCell: UITableViewCell {
#IBOutlet weak var subjectImage: UIImageView!
#IBOutlet weak var subjectName: UILabel!
}
class AddQuestionTableViewController: UITableViewController {
var Subjects = ["Geology", "Mathematics", "Computer", "English", "History", "Science"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (Subjects.count)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pickedSubjectSegue", sender: Subjects[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let next = segue.destination as! SubViewController
next.myLabel = sender as! String
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SubjectsTableViewCell
cell.subjectName?.text = Subjects[indexPath.row]
cell.subjectImage?.image = UIImage(named: (Subjects[indexPath.row] + ".jpeg"))
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
How can I move to second view controller using the Add navigation bar item?
What I tried: ctrl + dragging then modal - failed.
Should I use action or segue? But i already use segue in the table cells.
Any help is greatly appreciated. Thanks
Connect seque to second view controller and give it a name other than the segue that is connected to first view controller i.e. pickedSubjectSegue
Example: "secondSegue"
Then in prepareSegue method check the identifiers of both segues before performing any operation:
Code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier = "pickedSubjectSegue"
{
let next = segue.destination as! SubViewController
next.myLabel = sender as! String
}
else
{
//Write your code here
}
}