Bar button action on ViewController in ViewContainer - ios

I have a container embedded in a ViewController with a navigation bar. The ContainerView contains another ViewController with some textfields.
When the ContainerView is first displayed, the textfields are disabled.
What I would like to do is add an edit button to the navigation bar which enables the textfields.
Basically, is it possible for a bar button item in a viewcontroller to have an action on another view controller displayed in a container?

in this case you can do a little trick
the bar button will be in the ViewController but we can store the content of the ContainerView in a variable in the first ViewController
to do that i would suggest to make the content of the ContainerView has a custom class so you would write a function in it
here is an example on how to catch the content of the ContainerView
class ViewController: UIViewController{
weak var containter: ContainerViewController!
#IBAction func menuAction(_ sender: UIBarButtonItem) {
//do what you want containter.doAction()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let vc = segue.destination as? ContainerViewController {
self.containter = vc
}
}
}

Always the case. I've been looking for an answer for hours, as soon as I ask I figure it out.
I'm not sure its the best way to do it but I just made an edit bool controller by the edit button. Then used:
var child = self.childViewControllers[0]
child.viewWillAppear(false)
This reloaded the data and if it was in edit mode enabled the textfields. Works fine!

Related

Changes to navigationItem displayed with delay

I'm facing a delay when changing properties of a navigationItem inside a ViewController which is embedded in a NavigationController.
Consider the example Master-Detail-App provided by XCode: It contains a MasterViewController with a segue to a DetailViewController. I'm now trying to customize the navigation bar inside the viewDidLoad method of the DetailViewController.
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//delay if the button (or any other property) is set here
navigationItem.rightBarButtonItem = editButtonItem
}
}
The changes to the navigation bar will be displayed but with a noticeable delay. However, if the changes to the navigation bar are made from within the MasterViewController everything works perfectly fine and smooth:
class MasterViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
//no delay if the button would instead be set here
//navigationItem.rightBarButtonItem = editButtonItem
}
}
}
To me it seems not to be a good practice to modify items of the navigationBar from within the MasterViewController. Especially because
items like the title and buttons heavily depend on the DetailViewController.
What is now the most preferable way to change the navigationItem without delay and with separation of concern in mind?
Add code to viewWillApear instead of viewDidLoad
override func viewWillApear() {
super.viewWillApear()
navigationItem.rightBarButtonItem = editButtonItem
}

Having trouble "reaching" my override prepare function to programmatically change views in a tab bar controller

I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.

Find out from where ViewController was opened

I have a TableViewController and 2 ways to get there.
one is a segue(show) from a Viewcontroller that is the root controller of a Navigation Controller, which itself is a tab of my Tab Bar Controller.
second, the tableVC is also a root VC of antoher Navigation Controller, that is also a tab in that Tab Bar Controller. Here is an illustration:
Now i want to check in the viewDidLoad if my TableVC, whether it is called by the first or by the second way. How can i find that out?
You could add a property to your view controller that indicates where it came from…
class MyTableViewController: UITableViewController {
enum Source {
case productList, basket
}
var source: Source!
}
then
override func prepareForSegue(segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MyTableViewController {
vc.source = .productList
}
}
etc
very simple just add a var on your viewController which you are going to display , lets assume var vcOpenedBy = ""
now when launching this ViewController , just use this var and print whatever you feel comfortable like this
let vc = VcController(nibName:"",bundle:nil)
vc.vcOpenedBy = "NavigationMethod"
and at your segue you can also use this vcOpenedBy
and use string "SqgueMethod"
now on that viewController in viewDidLoad()
just print this vc . thats it

Cannot update embedded child VC from non-parent VC

Up to date Xcode/Swift/iOS.
I have a Master VC (called StartVC) that contains a Child VC (called TopBarVC) via and embedded segue. The Child VC contains a button, that, when pressed, modally segues to a 3rd VC (called CategoryPickerOverlayVC) (the view in this VC serves as a dropdown box for picking a category).
#IBAction func CategoryFilterButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "toCategoryPickerOverlay", sender: self)
}
When an option is selected from the dropdown box, which itself is composed of three buttons, the title of the selected button should be used to replace the title text of the button in the Child VC.
In the Master VC, I use prepareforsegue to store a reference to the Child VC in a variable - "topBarReference" - at the moment when the embed segue takes place.
var topBarReference: TopBarVC?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TopBarPane"{
topBarReference = segue.destination as? TopBarVC
}
}
Then, in the 3rd VC, when I click on one of the button options in the dropdown box, the button title is sent via a prepareforsegue to update the button in the Child VC (via "topBarReference").
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.filterButtonText = ((sender as! UIButton).titleLabel?.text)!
}
The 3rd VC then unwind segues back to the Master VC. I should add that when the button in the Child VC is changed, a variable (filterButtonText) in Child VC is first set with the title text and then this variable is then used to set the button title text via the viewDidAppear method of Child VC.
When using the debugger, I also note that viewDidAppear in the Master VC does not seem to execute after unwinding (I placed a diagnostic print-to-console in viewDidAppear and nothing prints after the unwind segue). I realise this would explain the button not getting updated but I've got no idea why viewDidAppear does not run.
I have also tried using a delegate protocol and instantiateViewController(withString:) to no avail. All of the methods produce the same result, which is that the button in the Child VC does not get updated. No errors are shown. Everything else happens as expected.
Any ideas as to what I am doing wrong?
Do you mean something like this?
If so, the solution I used was very simple: the third VC uses prepareForSegue to set a property of the embedded VC, and the embedded VC picks up that property in the unwind method.
In my implementation, the three view controllers are called ViewController, ChildViewController, and ThirdViewController. This is the entire code (everything else is configured in the storyboard):
class ChildViewController: UIViewController {
#IBOutlet weak var theButton: UIButton!
var buttonTitle : String?
#IBAction func unwind(_:UIStoryboardSegue) {
self.theButton.setTitle(self.buttonTitle, for: .normal)
}
}
class ThirdViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
(segue.destination as! ChildViewController).buttonTitle = (sender as! UIButton).currentTitle
}
}
Ok, so I have found that my original code works fine bar one line in the prepareforsegue of the Child VC. If I change that prepareforsegue from:
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.CategoryFilterButton.titleLabel?.text = ((sender as! UIButton).titleLabel?.text)!
}
to this:
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.CategoryFilterButton.setTitle((sender as! UIButton).titleLabel?.text, for: .normal)
}
it works just fine. The use of the .setTitle method seems to make a difference although I am not sure why.
Thanks to Matt for giving me the idea to change it to that. Matt's method did work when i tried it, although, as I am unwinding to the Master VC and not the Child VC, I had to edit the code accordingly, in terms of where I placed it.
As my little "discovery" equates to the smallest change to the original code, I'll mark this as the answer.
Thanks to all for taking the time to respond!

Swift Hide Button In Another View Controller

Currently working on my first IOS application. I have a purchase button, on success this currently sets a test button on the same view controller to hidden. Code is as follows
Decleration
#IBOutlet weak var Test: UIButton!
hide button on successful purchase
Test.isHidden = true
Now this works on my Test button, which is sat in the PurchaseViewController,class is the MasterViewController.Swift. (Purchase button that initiates this method is also in the same view controller)
PlanViewController also has a button, and class is also linked to MasterViewController.Swift. This has a separate button that i wish to hide on success of the purchase button.
When I utilise the same code as above for the button, it crashes, is their a limitation on manipulating other view controllers while you are not in it? I would have thought this worked given that they both have the Masterviewcontroller.swift as the class
Thanks
Although sometimes possible, it's generally not a good idea to directly manipulate one view controller's view from another view controller, as you are trying to do. Here is how I would do what you are trying to do.
First, set a segue identifier between your two view controllers by clicking on the segue in the storyboard and going to the attributes inspector. I suggest goToMasterViewController
In both MasterViewController.swift and PurchaseViewController.swift declare a variable var buttonHidden = false
In PurchaseViewController.swift add the following code, which will be called just before your segue to MasterViewController is performed:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "goToMasterViewController") {
let destinationController = segue.destination as! MasterViewController
destinationController.buttonHidden = buttonHidden
}
}
When you hide the button in PurchaseViewController, also set buttonHidden = true
And finally in MasterViewController.swift:
override func viewWillAppear(_ animated: Bool) {
testButton.isHidden = buttonHidden
}

Resources