I have the following setup:
StartViewController has a ContainerView that contains ContainerViewController
I try to find a way to hidden an element in StartViewController after a task is performed in ContainerViewController.
For this I try to use delegation method like this:
StartViewController
class StartViewController: UIViewController, showBannerAdDelegate {
#IBOutlet weak var bannerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
bannerView.hidden = false
}
func bannerAdHidden(status: Bool) {
bannerView.hidden = status
}
}
ContainerViewController
protocol showBannerAdDelegate: class {
func bannerAdHidden(status: Bool)
}
class ContainerViewController: UIViewController {
weak var delegate: showBannerAdDelegate! = nil
#IBAction func buttonPressed(sender: UIButton) {
delegate.bannerAdHidden(true)
}
}
If I presented the ContainerViewController I could do in prepareForSegue
let destination = segue.destinationViewController as! ContainerViewController
destination.delegate = self
But in this case both View Controller are always present.
What code should I add to the View Controller to make it work?
Thank you,
If one of the view controllers is inside a container view then it is loaded with an embed segue, which fires when the containing view controller is first loaded. The prepareForSegue method still gets called, so you can set up a delegate exactly as you've described. I always thought embed segues were a little odd (it's not really a segue, more like loading a child view controller) but that's how it works.
Related
I have an app written in Swift for iOS 13 where I present a view modally using storyboards. Once the new view is being presented, I want the parent to call a method which is located inside the child view controller (of my custom class which inherits from UIViewController).
To do this, I plan to have a method inside my parent view controller that gets the modal view controller being presented as its child. Once I get this reference, I will call the child's function from my parent view controller.
I realise this is probably a bad design decision, but I haven't found a way to avoid this approach. I have looked all over stackoverflow to find an answer, but I haven't found any yet. Any help would be much appreciated.
You can instantiate the child view controller and set its properties before presenting it. Then the code that changes the child view controller based on the data is put in the viewDidLoad() method.
class ParentViewController: UIViewController {
func goToChildViewController(object: CustomObject) {
guard let childViewController = self.storyboard?.instantiateViewController(withIdentifier: "child") as? ChildViewController else { fatalError("Cannot instantiate child view controller!") }
childViewController.myProperty = true
childViewController.myObject = myObject // Example of how to pass data from a data model
self.present(childViewController, animated: true)
}
}
class ChildViewController: UIViewController {
var myProperty = false
var myObject: CustomObject? = nil
override viewDidLoad() {
if myProperty {
// Conditional code here
}
{
}
Alternatively, you could trigger a segue in code instead of presenting the child view controller directly.
In this case, you would set up the child view controller inside the parent view controller’s overridden prepare(for:sender:) method, where the child view controller can be accessed using segue.destinationViewController.
Through Segue
When segue triggered maybe through a button press or a table view selection prepare(for:) method will be called on your view controller, at this point you can configure your DestinationViewController by setting some properties.
RootViewController.Swift
#IBOutlet weak var textFieldFirstName: UITextField!
#IBOutlet weak var labelFullname: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let firstVC = segue.destination as? FirstViewController else { return }
firstVC.firstname = textFieldFirstName.text
}
After typing the firstname and tap enter button , firstname value is passed to firstViewController and assigned to related UILabel in viewDidLoad() Method.
FirstViewController.Swift
#IBOutlet weak var textFieldLastName: UITextField!
#IBOutlet weak var labelFirstName: UILabel!
var firstname: String?
override func viewDidLoad() {
super.viewDidLoad()
labelFirstName.text = “Firstname: \(firstname ?? “”)”
}
You can achieve same thing through closure and Delegates
does anyone know how to pass an action from ViewController to its ContainerViewController. I try to hide the container view by an action that is triggered by itself. The ContainerViewController is embedded in the container view.
ViewController:
#IBOutlet weak var ChoseLanguageContainer: UIView!
**ContainerViewController:**
#IBAction func action(_ sender: Any) {
ViewController().containerView.isHidden = true
} //I know this does not work
I had the similar requirement, I created my own delegate methods which were implemented in ContainerViewController.
protocol ContainerViewControllerDelegate :class{
func notifyItemChange(any params you need to pass.)
}
In my container ViewController, I created a variable for delegate.
var changeContainerDelegate :ContainerViewControllerDelegate?
In my parent View Controller which contains the container, I did the following.
To get the instance of view controller which is embedded in the container.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let containerViewController = segue.destination as? ContainerViewController{
containerViewController.changeContainerDelegate = self
}
Create IBOutlet of container view in your view controller.
Conform to this protocol and write the implementation in View controller.
func changeContainerVC(containerVCName : String ,dataToBePassed:[AnyObject]?) {
containerView.isHidden = true
}
In the button action inside the ContainerViewController call the delegate like :
changeContainerDelegate?.notifyItemChange()
This works for me. Hope helps ou too!
In your code:
ViewController().containerView.isHidden = true
You are making a new instance of the ViewController and using it to hide the containerView. This won't work. Instead you need to hide the containerView of the current instance of ViewController, i.e self.
Here is the code you can try:
class ViewController: UIViewController
{
#IBOutlet weak var containerView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func hideContainerView(_ sender: UIButton)
{
self.containerView.isHidden = true
}
}
View Hierarchy:
Let me know if you face any other issue regarding this.
App has two View Controllers: ViewController (this is the main View Controller that displays the majority of the app's content) and SecondViewController (accessible via a UIButton on ViewController; SecondViewController is only used to display a user's inventory, and a UIButton within SecondViewController allows the user to return to the original view, ViewController). Currently, the app uses the "Show" action segue to switch between View Controllers when the user presses the appropriate UIButton. However, after switching from ViewController to SecondViewController, and then pressing the UIButton to return to ViewController, the properties of ViewController have been reverted to the properties that occur when the app launches (background color is changed, certain text fields appear that shouldn't).
So, how do I "save the state" of ViewController when the user moves to SecondViewController, so that the user resumes where they left off when they return to ViewController?
What you are looking for is an unwind segue. Here's the simplest way of how to create it:
In your ViewController (or, basically any other view controller you are willing to pop to) create an IBAction that accepts an instance of a segue (function name doesn't really matter):
#IBAction func unwindToThisVC(segue: UIStoryboardSegue) { }
In the storyboard, go to SecondViewController, and control + drag from your UIButton to the Exit outlet of ViewController and then select the IBAction you've created in step 1:
More on Unwind Segues
The way you are doing it now (using Show from the second to get back to the first) actually brings up a third VC.
What you want to do is dismiss the second view controller.
The normal way is to implement a protocol for the second one that the first one implements and then to have a function in that protocol for the second one to let the first one know it is done.
When the function is called, the first one dismisses the second and then it will be shown again with its state intact.
Here is a simple example of segue and unwind that you can adapt to your problem... Assume that you have ViewController with label and a button and a SecondViewController with label and a button.
For the first ViewController...
import UIKit
//steps to receive data back from SecondViewController...
//1. create protocol in the SecondViewController (see SecondViewController code)
//2. conform to the protocol
class ViewController: UIViewController, UnwindSegue {
//3. method that gets triggred.
func dataReceived(dataSegued: String) {
labelOne.text = dataSegued
}
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var labelOne: UILabel!
var textReceived : String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btPressed(_ sender: Any) {
performSegue(withIdentifier: "goToSecondController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondController" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.textSegued = textField.text!
//4. create delegate in the SecondViewController (see SecondViewController code)
//5. set ourselves up as delegate of SecondViewController
destinationVC.delegate = self
//6. then dismiss the SecondViewController (see SecondViewController code)
}
}
}
Then for your SecondViewController...
import UIKit
//1. create protocols and delegates to transfer data back
protocol UnwindSegue {
//single required method with a single parameter
func dataReceived(data:String)
}
class SecondViewController: UIViewController {
var textSegued : String?
//4. create delegate of the protocol of type CanReceive that can be a nil. If it is nil, it doesn't go anywhere when BT is pressed
var delegate : UnwindSegue?
#IBOutlet weak var label: UILabel!
#IBOutlet weak var secondTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.text = textSegued
}
#IBAction func btTwoPressed(_ sender: Any) {
//this is not triggered if var delegate is nil (as defined as optional)
delegate?.dataReceived(data: secondTextField.text!)
//6. dismiss the 2nd VC so you can see the fist VC
dismiss(animated: true, completion: nil)
}
}
I'm using the following code to goto another view programmatically in swift 3. There is no error while running. But don't know why it is not going to that view
Code I used:
let images = self.storyboard?.instantiateViewController(withIdentifier:"Collection") as! UICollectionViewController
self.navigationController?.pushViewController(images, animated: true)
I want to goto CollectionView.swift
In order to navigate to between view controllers you use UINavigationController.
I will provide you a basic example of navigation, hopefully it will help you to make navigation work in your project.
Result
ViewController passes an image to DetailViewController between navigation:
Setting up your Views
First ensure that your root controller is embedded with a navigation controller control so that you can navigate using segues:
Connect your views that are being used to navigate.
Code
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// showDetail Segue
if segue.identifier == "showDetail" {
// Sending the image to DetailViewController
// Before appears in the screen.
let detailViewController = segue.destination as! DetailViewController
detailViewController.image = sender as? UIImage
}
}
#IBAction func loginButton(_ sender: AnyObject) {
// Go to another view controller
performSegue(withIdentifier: "showDetail", sender: imageView.image)
}
}
class DetailViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
if let imageSent = image {
imageView.image = imageSent
}
}
}
I think it would be best instead of programmatically calling the Storyboard ID of the controller, to use Segues instead. You can find out how to use them here.
But if you have to use the SBID, here is an example snippet from one of my projects...
let vc = self.storyboard?.instantiateViewController(withIdentifier: "UpdateFilesViewController")
self.navigationController?.present(vc!, animated: true, completion: nil)
Notice that presenting might be the solution you are after instead of pushing. Additionally, you seem to be casting the UIViewController to a UICollectionViewController, which may cause problems as well. Keep note that a UICollectionViewController is a subclass of a UIViewController so the casting is unnecessary.
As we found out in the comments, your current viewController does not have a navigationController.
This means that
self.navigationController?.pushViewController(images, animated: true)
does nothing. You need to set up the initial viewController with a navigationController for this to have any effect.
Change your storyboard Identifier
You just need to change your storyboard identifier as "CollectionOfImages" in identity inspector as Storybroad identity so it will move on there.
Note : Please make sure that your current view controller is embed with navigation controller otherwise it will not push new controller during movement.
If you don't want to use navigation controller, you can simply present your controller using:
let images = self.storyboard?.instantiateViewController(withIdentifier:"Collection") as! UICollectionViewController
self.present(images, animated: true, completion: nil)
I have a view controller which is nested inside of another view controller using a container view. Is it possible for me to segue from the view which is currently in the container view and replace it with another view controller in the same container view. I.e. the content that is around the container view is not removed by another view controller taking up the entire view.
Yes it is. You can read about that in the Apple Docs.
Considering your containerView currently only has one viewcontroller, here is a very basic example:
func loadVCWithId(idToLoad: String){
childViewControllers[0].willMoveToParentViewController(nil)
childViewControllers[0].view.removeFromSuperview()
childViewControllers[0].removeFromParentViewController()
let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier(idToLoad)
UIView.transitionWithView(yourContainer, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {self.yourContainer.addSubview((secondViewController?.view)!)}, completion: nil)
secondViewController!.view.frame = firstContainer.bounds
// do initialization of secondViewController here
secondViewController?.didMoveToParentViewController(self)
}
loadVCWithId(idToLoad:String)is a method within your host viewcontroller.
In this code fragment I delete the current content of the container (probably not the best way to just access index 0, but for the sake of this example, this should be enough), instantiate a new ViewController by ID (this one is present in my storyboard but not accessbile yet), animate the transition and actually add the new VC to the container.
Hope this helps.
this my solution maybe helpful for
first i create a protocol on childViewController
protocol ChildViewControllerDelaget
{
func performForSegue(SegueIdentifier:String)
}
class ChildViewController: UIViewController {
var delaget:ChildViewControllerDelaget?
override func viewDidLoad() {
super.viewDidLoad()
}
init()
{
}
#IBAction func myAction(sender: AnyObject) {
if delaget != nil {
deleget.performForSegue("mySegueIdentifier")
}
}
and on MainViewController
class ViewController: UIViewController,ChildViewControllerDelaget {
override func viewDidLoad()
{
super.viewDidLoad()
let child = ChildViewController()
child.delaget = self
}
func performForSegue(segueIdentifier:String)
{
self.performSegueWithIdentifier(segueIdentifier, sender: nil)
}
}