Swift 3 - How do I insert TableViewCells from another view? - swift

I am using Swift 3, Xcode 8.2.
I am trying to figure out how to insert cells into my table view from another tab of my tabbed view controller.
MyTableView
class MyTableView: UITableViewController {
var items = ["Item 1", "Item 2", "Item 3"]
...
func insertCell() {
items.append("Item \(items.count + 1)")
tableView.reloadData()
}
If I create a bar button item on that table view page, I can successfully insert items, no problem. It's when I try to trigger it from another view controller that nothing happens.
Another Tab
#IBAction func triggerInsert(_ sender: Any) {
//MyTableView().insertCell() // this doesn't work
//MyTableView().items.append("Item") // these two lines don't work either
//MyTableView().tableView.reloadData()
// also tried setting a boolean in here to be read from MyTableView but that didn't work either
}
I'm not quite sure what the "correct" way to do this is. Any help would be greatly appreciated.

The problem is this expression you keep trying to use:
MyTableView()
That creates a completely new and separate table view controller. But that obviously is not what you want to do. You want to talk to the existing table view controller, the one that is, as you say, another tab in the tab bar controller.
For example, if MyTableView is the first tab and OtherViewController is the second tab, then OtherViewController might say:
self.tabBarController!.viewControllers![0] as! MyTableView
...as a way of getting a reference to the existing table view controller.

Related

Does the segue create an instance of the target ViewController? Should I remove them from the stack If I am done with them? If so, how? [duplicate]

iOS 6 and Xcode 4.5 has a new feature referred to as "Unwind Segue":
Unwind segues can allow transitioning to existing instances of scenes in a storyboard
In addition to this brief entry in Xcode 4.5's release notes, UIViewController now seem to have a couple of new methods:
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier
How do unwind segues work and what they can be used for?
In a Nutshell
An unwind segue (sometimes called exit segue) can be used to navigate back through push, modal or popover segues (as if you popped the navigation item from the navigation bar, closed the popover or dismissed the modally presented view controller). On top of that you can actually unwind through not only one but a series of push/modal/popover segues, e.g. "go back" multiple steps in your navigation hierarchy with a single unwind action.
When you perform an unwind segue, you need to specify an action, which is an action method of the view controller you want to unwind to.
Objective-C:
- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue
{
}
Swift:
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
}
The name of this action method is used when you create the unwind segue in the storyboard. Furthermore, this method is called just before the unwind segue is performed. You can get the source view controller from the passed UIStoryboardSegue parameter to interact with the view controller that initiated the segue (e.g. to get the property values of a modal view controller). In this respect, the method has a similar function as the prepareForSegue: method of UIViewController.
iOS 8 update: Unwind segues also work with iOS 8's adaptive segues, such as Show and Show Detail.
An Example
Let us have a storyboard with a navigation controller and three child view controllers:
From Green View Controller you can unwind (navigate back) to Red View Controller. From Blue you can unwind to Green or to Red via Green. To enable unwinding you must add the special action methods to Red and Green, e.g. here is the action method in Red:
Objective-C:
#implementation RedViewController
- (IBAction)unwindToRed:(UIStoryboardSegue *)unwindSegue
{
}
#end
Swift:
#IBAction func unwindToRed(segue: UIStoryboardSegue) {
}
After the action method has been added, you can define the unwind segue in the storyboard by control-dragging to the Exit icon. Here we want to unwind to Red from Green when the button is pressed:
You must select the action which is defined in the view controller you want to unwind to:
You can also unwind to Red from Blue (which is "two steps away" in the navigation stack). The key is selecting the correct unwind action.
Before the the unwind segue is performed, the action method is called. In the example I defined an unwind segue to Red from both Green and Blue. We can access the source of the unwind in the action method via the UIStoryboardSegue parameter:
Objective-C:
- (IBAction)unwindToRed:(UIStoryboardSegue *)unwindSegue
{
UIViewController* sourceViewController = unwindSegue.sourceViewController;
if ([sourceViewController isKindOfClass:[BlueViewController class]])
{
NSLog(#"Coming from BLUE!");
}
else if ([sourceViewController isKindOfClass:[GreenViewController class]])
{
NSLog(#"Coming from GREEN!");
}
}
Swift:
#IBAction func unwindToRed(unwindSegue: UIStoryboardSegue) {
if let blueViewController = unwindSegue.sourceViewController as? BlueViewController {
println("Coming from BLUE")
}
else if let redViewController = unwindSegue.sourceViewController as? RedViewController {
println("Coming from RED")
}
}
Unwinding also works through a combination of push/modal segues. E.g. if I added another Yellow view controller with a modal segue, we could unwind from Yellow all the way back to Red in a single step:
Unwinding from Code
When you define an unwind segue by control-dragging something to the Exit symbol of a view controller, a new segue appears in the Document Outline:
Selecting the segue and going to the Attributes Inspector reveals the "Identifier" property. Use this to give a unique identifier to your segue:
After this, the unwind segue can be performed from code just like any other segue:
Objective-C:
[self performSegueWithIdentifier:#"UnwindToRedSegueID" sender:self];
Swift:
performSegueWithIdentifier("UnwindToRedSegueID", sender: self)
As far as how to use unwind segues in StoryBoard...
Step 1)
Go to the code for the view controller that you wish to unwind to and add this:
Objective-C
- (IBAction)unwindToViewControllerNameHere:(UIStoryboardSegue *)segue {
//nothing goes here
}
Be sure to also declare this method in your .h file in Obj-C
Swift
#IBAction func unwindToViewControllerNameHere(segue: UIStoryboardSegue) {
//nothing goes here
}
Step 2)
In storyboard, go to the view that you want to unwind from and simply drag a segue from your button or whatever up to the little orange "EXIT" icon at the top right of your source view.
There should now be an option to connect to "- unwindToViewControllerNameHere"
That's it, your segue will unwind when your button is tapped.
Unwind segues are used to "go back" to some view controller from which, through a number of segues, you got to the "current" view controller.
Imagine you have something a MyNavController with A as its root view controller. Now you use a push segue to B. Now the navigation controller has A and B in its viewControllers array, and B is visible. Now you present C modally.
With unwind segues, you could now unwind "back" from C to B (i.e. dismissing the modally presented view controller), basically "undoing" the modal segue. You could even unwind all the way back to the root view controller A, undoing both the modal segue and the push segue.
Unwind segues make it easy to backtrack. For example, before iOS 6, the best practice for dismissing presented view controllers was to set the presenting view controller as the presented view controller’s delegate, then call your custom delegate method, which then dismisses the presentedViewController. Sound cumbersome and complicated? It was. That’s why unwind segues are nice.
Something that I didn't see mentioned in the other answers here is how you deal with unwinding when you don't know where the initial segue originated, which to me is an even more important use case. For example, say you have a help view controller (H) that you display modally from two different view controllers (A and B):
A → H
B → H
How do you set up the unwind segue so that you go back to the correct view controller? The answer is that you declare an unwind action in A and B with the same name, e.g.:
// put in AViewController.swift and BViewController.swift
#IBAction func unwindFromHelp(sender: UIStoryboardSegue) {
// empty
}
This way, the unwind will find whichever view controller (A or B) initiated the segue and go back to it.
In other words, think of the unwind action as describing where the segue is coming from, rather than where it is going to.
Swift iOS:
Step 1: define this method into your MASTER controller view. in which you want to go back:
//pragma mark - Unwind Seques
#IBAction func goToSideMenu(segue: UIStoryboardSegue) {
println("Called goToSideMenu: unwind action")
}
Step 2: (StoryBoard) Right click on you SLAVE/CHILD EXIT button and Select "goToSideMenu" As action to Connect you Button on which you will click to return back to you MASTER controller view:
step 3: Build and Run ...
For example if you navigate from viewControllerB to viewControllerA then in your viewControllerA below delegate will call and data will share.
#IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
        if sender.source is ViewControllerB  {
            if let _ = sender.source as? ViewControllerB {
                self.textLabel.text = "Came from B = B->A , B exited"
            }
            
        }
}
Unwind Seague Source View Controller ( You Need to connect Exit Button to VC’s exit icon and connect it to unwindseague:
Unwind Seague Completed -> TextLabel of viewControllerA is Changed.

How to get the navigation bar to show large title upon a segue back?

I have a tab bar app where one of the views is a UITableViewController containing static cells as content with 1 section and 1 row.
I want the Large Title to be set to "Always," so I made the selection on the storyboard and the title was large on the simulator. Now when the user taps "Start Chat," the app will segue to the Virtual Assistant View Controller, where the Large Title is set to "Never" on the storyboard. Now the problem is that when the user segues back to the previous view controller with the "Start Chat" table view cell, the title is not large anymore.
It is interesting that when I set the table view to be scrollable, the title becomes large again upon dragging down the table view. I made sure the navigation bar on the Navigation Controller storyboard is checked with the "Prefers Large Titles." I am using Xcode 11, and this was not a problem when using Xcode 10.
I tried creating a custom class for the view with the start chat button and this code did not work in making the title large from a segue back:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
What else could I do? Any help will be greatly appreciated!
I'd use willMove(toParent:) to change the title back before the segue is performed.
override func willMove(toParent parent: UIViewController?) {
navigationController?.navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
Set the properties when setting up the UINavigationController, before presenting it. If you already presented the navigation controller, try doing this to force-update the navigation bar:
navigationController?.navigationItem.prompt = ""
navigationController?.navigationItem.prompt = nil
I took this workaround from this question.
In your particular case, it would be better to subclass the navigation controller and set those properties in its viewDidLoad method, so its properties (largeTitleDisplayMode and prefersLargeTitles) are set in a self-contained code.

Segueing to a single view controller that will display some number of UITextFields depending on index selected in table view controller

In my current application, I have a UITableViewController that allows me to segue to three different indices depending on the row selected. If I select row one, I segue to a view controller with three UITextFields. If I select row two, I segue to a NEW view controller with one UITextField. If I select row three, I segue to ANOTHER NEW view controller with five UITextFields. I am looking for a way to condensing the view controllers I am going to into one view controller that will dynamically hide/unhide or remove/add the needed number of UITextFields depending on the index selected in the table view controller. I need the text fields to display in a vertical stack view.
Although it would be very valuable, I am not looking for a solution/example from someone, but rather letting me know some topics I can research would be extremely helpful.
Thank you for your time,
Tony
There might be different approaches for this problem, but I would create a stackView in the NEW view controller with the maximum possible number of textFields embedded. Lets say the VC with most textFields can contain 3 textFields. Create your stackView with 3 textFields inside.
Then segue the indexPath.row of tableView to the NEW VC, it can hold a variable of Integer like:
var index: Int?
In didSelectRowAt method of tableView:
performSegue(withIdentifier: "yourIdentifier", sender: indexPath.row)
In prepareForSegue method:
if segue.identifier == "yourIdentifier"
let vc = segue.destination as! NEWViewController
vc.index = sender
Set tag to your textFields in your NEW View controller, so you can know which textField you will want to delete depending on index.
Then in viewDidLoad of your newViewController, you can remove textFields from your stackView according to the index (Then you might run into which ):
var counter = 0
while counter < index {
answerStackView.subviews.forEach { (view) in
if counter < index {
if view is UITextField {
if view.tag == /*something here to delete specific textFields depending on index. */ {
view.removeFromSuperview()
}
}
}
counter += 1
}
}

Change views inside NSSplitViewController

I'm trying to migrate my Objective-C and now improving Swift knowledge to an application for Mac OS X. Steep learning curve!
I'm trying to load a NSSplitViewController with different views in the "detail view" depending on buttons pressed on the "master view" if you will. Following tutorials and searching for hours has led me to nothing.
I currently have:
import Cocoa
class MainSplitView: NSSplitViewController, BlissWindowDelegate {
var masterViewController: vcMainMenu {
let masterItem = splitViewItems[0] as! NSSplitViewItem
return masterItem.viewController as! vcMainMenu
}
override func viewDidLoad() {
super.viewDidLoad()
masterViewController.delegate = self
}
func userDidSelectFunction(function: String) {
switch function {
case "app":
println("You have selected to load the appointment screen")
case "cust":
println("You have selected to load the customer screen")
case "login":
println("I think I am here and you've clicked login?")
let detailItem = splitViewItems[1] as! NSSplitViewItem
// Trying to load the views here ... but no idea how to
case "admin":
println("You've clicked admin")
default:
println("Nothing here ...")
}
}
}
I'm using BlissWindowDelegate to tell me which button was pressed. I am then trying to load into the splitViewItem[1] various views from a Storyboard. But having no luck. Can anyone point me in the right direction please? Even for a decent reference? Nothing on Google is seeming to help.
Thanks.
Since it sounds like you have a specific set of detail panes that can be shown, using an NSTabViewController is probably best way to accomplish this.
Basically, your NSSplitViewController has two children: the master view controller, and a NSTabViewController. And the tab view controller has its own children for each of the detail panes. Since tab view controller shouldn't present its own tab selection UI (the master pane is doing that), you would set the tabStyle to be .Unspecified. The storyboard would look something like this:
Your MainSplitViewController would also have a reference to the tab view controller, detailController. Then on userDidSelectFunction(), you would set the detailController's selectedTabViewItemIndex to be that of the corresponding detail pane. NSTabViewController will take care of the view transition, including animating between the panes if setup to do so.

How to have several buttons all segue to the same view controller?

I am new in iOS and storyboard and I need some advice on the possible ways of designing the architecture of the new app. I will have some data saved in the database and I will have a UIViewController page with just some buttons that have some names of some categories. When let's say 'All' is clicked or 'Business' is clicked, it will fetch data show according to that category to a UITableViewController.
Now I am confused on one part:
In story board is it allowed to create multiple segues from different buttons to the same ViewController Class? So if that is not allowed, do I have to create 6 different UITableViewController classes to make segues for 6 categories?
And if it is allowed to make segues from multiple buttons to a single UIViewController, how do I send a parameter in storyboard to make it realize that I clicked a specific button to go to the UIViewController.
Should I make a custom constructor in the UITableViewController which takes a parameter and I will initial that constructor from the other button click methods? So that it displays results in terms of category? Or is there a way storyboard does this more easily?
Sure, you can do that.
After creating all your buttons, control-drag from each button to the next view controller and create (for example) a "push" segue. Next, click on the segue graphic to select the segue. Switch to the Attributes inspector and give segue an identifier, like "All" or "Business". You can look at the segue's identifier in your -prepareForSegue: method to figure out which segue caused the transition, and hence which category to display.
I don't recommend that approach, though. There's no need to create half a dozen segues that all do exactly the same thing. I'd use an action that triggers a single segue instead:
Set the tag property of each button to a unique value so that you can differentiate between the buttons.
Add an action method to your first view controller (the one that manages the buttons) named something like -(IBAction)switchToCategoryFromButton:(id)sender.
Connect all the buttons to that single action.
Implement the action such that it looks at sender.tag to figure out which button was pressed, uses that to determine which category to load, and then triggers the one segue to the next view controller programmatically.
Having a single segue triggered by an action seems a lot neater than many segues. Code to do that might look like this:
- (IBAction)switchToCategory:(UIControl*)sender
{
// map the tapped button to a category
switch (sender.tag) {
case kAllButton: {
self.category = kAllCategories;
break;
}
case kBusinessButton: {
self.category = kBusinessCategory;
break;
}
default: {
self.category = kAllCategories;
break;
}
}
// start the segue
[self performSegueWithIdentifier:kCategorySegue sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// tell the destination view controller what category to display
if ([segue.identifier isEqualToString:kCategorySegue]) {
[(NextViewController*)segue.destinationViewController setCategory:self.category];
}
}
Use
self.performSegueWithIdentifier("yourViewSegue", sender: sender) under your event for handling button's click like :
#IBAction func redButtonClicked(sender: AnyObject) {
self.performSegueWithIdentifier("redView", sender: sender)
}
redView is the segue Identifier .
You can use same function for your button event handling & can distinguish button touch by assigning them different tags.