Update second view controller in NSSplitViewController - swift

I have a NSSplitViewController in which first viewcontroller displays a table and second viewcontroller should display a viewcontroller from a list of viewcontrollers based on the selection of table row.
I'm using tableViewSelectionDidChange() to get the selected row.
Now I have a bunch of viewcontrollers(storyboard ID given as row number) that I should add and remove to second viewcontroller in NSSplitViewController
How can I do that?

You are on the right path.
Within tableViewSelectionDidChange() you need to instantiate a new viewController from your storyboard using NSStoryboards instantiateController(withIdentifier: String) method. Then you can set this as your splitViews second view controller.
Then you need to create a splitViewItem. You can use the init method which takes a viewController for this (NSSplitViewItem(viewController:)).
Finally you have two possibilities to add the new viewController.
Either you use the add/removeSplitViewItem methods or you set the splitViewItems array directly.
My words in code:
guard let splitViewController = self.parent as? NSSplitViewController,
let viewController = self.storyboard?.instantiateController(withIdentifier: "yourIdentifier") as? NSViewController
else { return }
let item = NSSplitViewItem(viewController: viewController)
// Method one
splitViewController.removeSplitViewItem(splitViewController.splitViewItems[1])
splitViewController.addSplitViewItem(item)
// OR method two
var items = splitViewController.splitViewItems
items[1] = item
splitViewController.splitViewItems = items

Objective C representation of the first method.
NSStoryboard * mainStoryboard = [NSStoryboard storyboardWithName:#"Main" bundle:nil];
NSViewController * sourceViewController = [mainStoryboard instantiateControllerWithIdentifier:identrifier];
NSSplitViewItem * item = [NSSplitViewItem splitViewItemWithViewController:sourceViewController];
[self removeSplitViewItem:[self.splitViewItems objectAtIndex:1]];
[self addSplitViewItem:item];

Related

InstantiateViewController doesn't load the #IBOutlet

I have two views. The first one has a UITextView and a button. The second has a UILabel. When I click on the button I would like the text label of the second views to be filled with what was in the textfield of the first view.
I did the following:
#IBAction func tapButton() {
let vc = storyboard?.instantiateViewController(identifier: "second") as! SViewController
vc.l.text = t.text
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
l is the UILabel and t is the UITextField. When I run this code it's telling me I am trying to unwrapped l whereas l is nil.
But why l is nil? I mean if I instantiate my second view then it should create the UILabel l and thus l is not nil?
This happens because your second view controller's view hierarchy hasn't loaded yet, that's why you're getting nil. Simple solution is to keep a string variable in the second view controller and set it in the button tap action. And then in viewDidLoad of the second view controller set the label text.

Pass data to TabBarControllers after intialization?

How would I do something like this?
viewControllers!.forEach
{
$0.view
$0.m = self.m // error here
}
In each tabbarcontroller I defined m, yet this is not working.
I need it done through this as I initialize every tab by this.
Thanks.
The main problem is $0, the shorthand for the first parameter (i.e. a viewController in this case), is always immutable in a closure. There are a few other things to address to...
First of all you will need to subClass UIViewController to allow you to create/access the m property. At the most basic level this will be:
class MyVC: UIViewController {
var m: Int = 0 //giving a default value to save a .init in the example
}
The you will need to create MyVC view controllers, rather than standard UIViewControllers within your AppDelegate / SceneDelegate.
At which point you can adapt your original code to set the m property within each view controller:
if let count = tabBarController.viewControllers?.count {
for i in 0 ..< count {
if let vc = tabBarController.viewControllers?[i] as? MyVC {
vc.view // as per the original, but can't see any point in it
vc.m = m
}
}
}

how can I move a data array between tab bar viewControllers when I have navigationControllers involved

I have a tab bar controller and 3 viewControllers connected to it and when I move data between the viewControllers I use the following code, which works perfect:
let secondTab = tabBarController?.viewControllers![1] as! ImageViewController
secondTab.imageArray = images
Now I added navigation to the second viewController with EDITOR->Embed In....so, there is now a navigationController between my ImageViewController and the tabBarController. How can I still get the data to the ImageViewController?
You can try
if let nav = tabBarController?.viewControllers![1] as? UINavigationController {
if let let secondTab = nav.topViewController as? ImageViewController
secondTab.imageArray = images
}
}

Opening window + view from an other view controller

I've got a ViewControllerOne. ViewControllerOne is connected via Ctrl-Drag (in storyboard) to a menu-button mBtn (which means I don't know how it is implemented programmatically).
Clicking on this mBtn, a ViewOne appears (present modal). This ViewOne is bound to ViewControllerOne. ViewOne has a button btnOne.
Clicking on btnOne I want ViewOne to be dismissed and ViewTwo to be shown. ViewTwo belongs to ViewControllerTwo and to WindowControllerTwo.
The WindowControllerTwo-ViewControllerTwo-binding is the standard case as created on a new project.
I have the following code in the IBAction for button btnOne in ViewControllerOne:
#IBAction func onbtnOnePressed(sender: AnyObject){
let m_WindowControllerTwo = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("WindowControllerTwo")) as! NSWindowController // I have no custom class for the window controller as I don't really know what I can use it for ...
let m_ViewTwo = WindowControllerTwo.contentViewController as! ViewControllerTwo // my custom class for ViewTwo
m_ViewTwo.attributeIWantToPassToThisView = self.x // an attribute I want to pass from view a to view b
m_WindowControllerTwo.contentViewController = m_ViewTwo // passing the attribute from a to b
m_WindowControllerTwo.showWindow(self) // this does not work
self.dismiss(nil) // see NOTE
}
This code actually does not work. On debugging it step by step, I'm seeing the window/view flickering but not appearing...
NOTE: I could connect the button btnOne with a ctrl-drag to ViewControllerTwo. This works. But then the current ViewOne does not get dismissed!
Question: What am I doing wrong here? In iOS swift this also works. I don't quite get the WindowController stuff, so I'll need your advice on this.
Instead of this: m_WindowControllerTwo.showWindow(self)
use:
let application = NSApplication.shared()
application.runModal(for: wordCountWindow) //this will present WindowControllerTwo modally.
then to close your present controller add this line: PresentWindowControllerName.close()

How to get the previous viewcontroller that pushed my current view

The home page of my app has UIButtons, btnIncome and btnExpense. Pressing on this buttons pushes IncomeVC and ExpenseVC respectevely,which are two UIViewControllers with UITabBar added via xib. The tabBar have 4 items. Selecting on each tab item adds same four view controllers(which contains UITableViews) as the subview of IncomeVC and ExpenseVC,like for eg, DailyVC,WeeklyVC,MonthlyVC,YearlyVC.(ie,for Income ,there is daily,weekly etc and same for Expense) (I have done like that because the IncomeVC and ExpenseVC have a UIButton and a UIView which is common for all tabs).
So the problem is that, if click the btnIncome I have to populate those tableviews with the arrays related to Income and vice versa for Expense. How can I find from which viewController I selected the different tabs(I need to get it from the 4 Views I added as the subview of IncomeVC and ExpenseVC). Do I have to make 8 different views 4 each for Income and expense ?
Thanx.
In Swift 3,
if let navController = self.navigationController, navController.viewControllers.count >= 2 {
let viewController = navController.viewControllers[navController.viewControllers.count - 2]
}
You can get the previous viewController like following code,
NSLog(#"%#",[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2]);
This will displays the previous viewController name...
In Swift:
let n: Int! = self.navigationController?.viewControllers?.count
let myUIViewController = self.navigationController?.viewControllers[n-2] as! UIViewController
Swift 3
Here is a mashup of the previous answers that can be put into an extension:
extension UIViewController{
var previousViewController:UIViewController?{
if let controllersOnNavStack = self.navigationController?.viewControllers, controllersOnNavStack.count >= 2 {
let n = controllersOnNavStack.count
return controllersOnNavStack[n - 2]
}
return nil
}
}
Edit:
When fetching the previousViewController of a given view controller, call it VC1, in viewWillDisappear, VC1 is already popped of the Navigation Controller Stack. So in this scenario, the above code does not end up fetching the View controller directly above VC1(call it VC2), but the view controller above VC2 (if it exists).
To avoid this problem I just check if VC1 is still on the stack when previousViewController is requested. Here is the updated code:
extension UIViewController{
var previousViewController:UIViewController?{
if let controllersOnNavStack = self.navigationController?.viewControllers{
let n = controllersOnNavStack.count
//if self is still on Navigation stack
if controllersOnNavStack.last === self, n > 1{
return controllersOnNavStack[n - 2]
}else if n > 0{
return controllersOnNavStack[n - 1]
}
}
return nil
}
}
This code assumes that view controller you are sending the previousViewController message to will either be at the top of the navigation stack or not at all.
Swift 5.1 🔸
(Based on previous answers but simpler)
extension UINavigationController {
var previousViewController: UIViewController? {
viewControllers.count > 1 ? viewControllers[viewControllers.count - 2] : nil
}
}
If the reason for needing access to the previous view controller is to know what data to get, I would suggest that you instead give the new view controller the data before you push it on the stack. Or at least enough data so that the view controller know how to get the right data, e.g. a enum or constant or something.
This could be done with a custom initializer, or a property.
Take a look at this blog post for an example: "Passing Data Between View Controllers"
If you are using a storyboard, you can use prepareForSegue:sender to pass the right data. A good tutorial on that can be found here: Beginning Storyboards in iOS 5 Part 2
UIViewController *previousViewController = [[[self navigationController]viewControllers] objectAtIndex:([viewControllers indexOfObject:self]-1)];
Where self will be current view controller.
[viewControllers.count - 2] approach is buggy
If stated view controller is not last view controller of the navigation stack,[viewControllers.count - 2] returns incorrect result.
New crash safe solution:
Swift
extension UIViewController {
var previousViewController: UIViewController? {
guard
let viewControllers = navigationController?.viewControllers,
let index = viewControllers.firstIndex(of: self),
index > 0
else { return nil }
return viewControllers[index - 1]
}
}
Swift
extension UINavigationController {
func getPreviousViewController() -> UIViewController? {
let count = viewControllers.count
guard count > 1 else { return nil }
return viewControllers[count - 2]
}
}
You can certainly reuse the view controllers. I would suggest instead of pushing UIViewController (which contains UITabBar added from IB) when the button is pressed, you can push UITabBarController which contains 4 View Controllers (daily, weekly, monthly, and yearly).
You UITabBarController could have an enum property (Income and Expense) and this can be assigned when you push this UITabBarController based on the button pressed. From there, you can assign another enum property (Income and Expense) to the 4 UIViewControllers to show the correct type of data in each view controller.
Check out this post: How to identify previous view controller in navigation stack
Tony is right, you can get the previous VC from the viewControllers array but at index n-2.