At the moment I have a ViewController class containing a UIScrollView, within the scroll view I have another view controller, where I can currently receive gesture recognition. My goal is to be able to perform a segue to a different view controller, based on which subViewController I tap.
let scrollView = UIScrollView(frame: CGRect(x:0,y:0, width: self.view.frame.width, height:self.view.frame.height-106))
scrollView.delegate = self;
self.view.addSubview(scrollView);
let subView11 = subView(nibName: nil, bundle: nil);
subView1.view.frame = CGRect(x:0,y:0, width: self.view.frame.width, height: CGFloat(openReelHeight));
self.addChildViewController(subView1);
scrollView.addSubview(subView1.view);
subView.didMove(toParentViewController: self);
Then in the subView class I have a basic touch recognition function:
#IBAction func tapOnView(_ sender: UITapGestureRecognizer) {
//change main View controller
}
I would suggest letting the parent perform the segue. So you need a mechanism to let the child inform the parent that the button has been tapped. Here are two approaches:
The child view controller can define a protocol and then have its #IBAction for the button invoke that in the parent view controller.
protocol ChildViewControllerDelegate {
func child(_ child: ChildViewController, didTapButton button: Any)
}
class ChildViewController: UIViewController {
#IBAction func didTapButton(_ sender: Any) {
if let parent = parent as? ChildViewControllerDelegate {
parent.child(self, didTapButton: sender)
}
}
}
Clearly, the parent view controller needs to conform to that protocol:
extension ViewController: ChildViewControllerDelegate {
func child(_ child: ChildViewController, didTapButton button: Any) {
// now segue to whatever you want
}
}
You can alternatively follow an explicit protocol-delegate pattern, rather than relying upon the view controller containment relationships of the parent:
protocol ChildViewControllerDelegate: class {
func didTapButton(_ sender: Any)
}
class ChildViewController: UIViewController {
weak var delegate: ChildViewControllerDelegate?
#IBAction func didTapButton(_ sender: Any) {
delegate?.didTapButton(sender)
}
}
And then, when the parent adds the child, it would have to explicitly set the delegate:
let child = storyboard!.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController
addChildViewController(child)
child.delegate = self
// add the child's view to your view hierarchy however appropriate for your app
child.didMove(toParentViewController: self)
And, of course, the parent again has to conform to this protocol:
extension ViewController: ChildViewControllerDelegate {
func didTapButton(_ sender: Any) {
// segue to next scene
}
}
Note, with both of these approaches, you can change your protocol's func to include whatever parameters you want (e.g. passing back contents of some UITextField or whatever). Likewise, you might use method names that make the functional intent of the child a little more explicit. I used somewhat generic method and protocol names because I don't know what the various children are doing.
Related
I want to pass my data from child view controller to parent view controller.
I'm just moving the child view to the top of the parent view, here is my code:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let toChildView = storyBoard.instantiateViewController(identifier: "toChildView")
// show child view
self.addChild(toChildView)
toChildView.willMove(toParent: self)
toChildView.view.frame = self.view.bounds
self.view.addSubview(toChildView.view)
toChildView.didMove(toParent: self)
Here is the delegate:
protocol DataDelegate {
func printTextField(string: String)
}
I added variable delegate to my Child View
var delegate: DataDelegate?
And send the data to Parent View
#IBAction func btnSend(_ sender: Any) {
delegate?.printTextField(string: textField.text)
}
And close the Child View, like:
#IBAction func btnClose(_ sender: Any) {
// back to parent view
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.removeFromParent()
}
Calling the DataDelegate to Parent View
class ParentView: UIViewController, DataDelegate {
func printTextField(string: String) {
print(string)
}
}
Im planning to pass the data of my text field from child view to parent view, I try this codes:
https://jamesrochabrun.medium.com/implementing-delegates-in-swift-step-by-step-d3211cbac3ef
https://www.youtube.com/watch?v=aAmvQU9HccA&t=438s
but it didn't work, any help thanks :)
EDITED: Added my delegate implementation code...
So as per the code you have done with declaration but not initialized the delegate. So go to you ParentView and assign the delegate in viewDidLoad or in the function where you prepare to move on child like:-
toChildView.delegate = self
And try again. :-)
you will not get delegate property in parent view as it is member of childView. You can do following
self.addChild(toChildView)
toChildView.delegate = self
This question might be asked already about hiding buttons, but I was wondering if I could just click a button which would affect the variables in another view controller. For example, I have firstViewController and endViewController. There's a button in endViewController that the user presses which should change a variable in the firstViewController. Is there a way to access the endViewController button from the firstViewController?
Edit
I haven't tried much so far except control clicking the endViewController button into the firstViewController (which didn't work).
class firstViewController: UIViewController {
#IBAction func nextButton(_ sender: Any) { //button that sits in endViewController
}
}
You can use the DELEGATE PATTERN to pass data back:
Here's a little help on delegates between two view controllers:
Step 1: Make a protocol in the UIViewController that you will be removing/will be sending the data.
protocol FooTwoViewControllerDelegate:class {
func myVCDidFinish(_ controller: FooTwoViewController, text: String)
}
Step2: Declare the delegate in the sending class (i.e. UIViewcontroller)
class FooTwoViewController: UIViewController {
weak var delegate: FooTwoViewControllerDelegate?
[snip...]
}
Step3: Use the delegate in a class method to send the data to the receiving method, which is any method that adopts the protocol.
#IBAction func saveColor(_ sender: UIBarButtonItem) {
delegate?.myVCDidFinish(self, text: colorLabel.text) //assuming the delegate is assigned otherwise error
}
Step 4: Adopt the protocol in the receiving class
class ViewController: UIViewController, FooTwoViewControllerDelegate {
Step 5: Implement the delegate method
func myVCDidFinish(_ controller: FooTwoViewController, text: String) {
colorLabel.text = "The Color is " + text
controller.navigationController.popViewController(animated: true)
}
Step 6: Set the delegate in the prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" {
let vc = segue.destination as! FooTwoViewController
vc.colorString = colorLabel.text
vc.delegate = self
}
}
And that should work. This is of course just code fragments, but should give you the idea. For a long explanation of this code you can go over to my blog entry here:
segues and delegates
If you are interested in what's going on under the hood with a delegate I did write on that here:
under the hood with delegates
original answer
First View Controller
The code for the First View Controller is
import UIKit
class FirstViewController: UIViewController, DataEnteredDelegate {
#IBOutlet weak var label: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destinationViewController as! SecondViewController
secondViewController.delegate = self
}
}
func userDidEnterInformation(info: String) {
label.text = info
}
}
Note the use of our custom DataEnteredDelegate protocol.
Second View Controller and Protocol
The code for the second view controller is
import UIKit
// protocol used for sending data back
protocol DataEnteredDelegate: class {
func userDidEnterInformation(info: String)
}
class SecondViewController: UIViewController {
// making this a weak variable so that it won't create a strong reference cycle
weak var delegate: DataEnteredDelegate? = nil
#IBOutlet weak var textField: UITextField!
#IBAction func sendTextBackButton(sender: UIButton) {
// call this method on whichever class implements our delegate protocol
delegate?.userDidEnterInformation(textField.text!)
// go back to the previous view controller
self.navigationController?.popViewControllerAnimated(true)
}
}
Note that the protocol is outside of the View Controller class.
That's it. Running the app now you should be able to send data back from the second view controller to the first.
Original post: https://stackoverflow.com/a/33229483/13783496
There are 2 methods:
You can create a segue from button in endviewcontroller to firstviewcontroller in storyboard. You can configure the func prepare(for segue: UIStoryboardSegue, sender: Any?) for it.
let endVC = endViewcontroller()
endVC.color = "blue"
You can keep the variable whose value needs to be changed as static datatype.
On the click action of button, you can access variable as,
EndViewController.color = "Red".
Kindly use static variables only if you want other Viewcontrollers to access it directly.
I've set up a simple Swift project to try and wrap my head around delegates & protocols. The goal is to pass data between two classes (SendingClass & ReceivingClass). Two buttons in the SendingClass are linked to the delegate which should trigger the Protocol conforming function in the ReceivingClass to execute. This doesn't work unfortunately, I suspect it has to do with where and how I am declaring the ReceivingClass as the delegate.
Appreciate your insights, i'm just starting out!
I've tried setting the delegate in various locations (presently within viewDidLoad, but cant get it to work).
let vc = SendingClass()
vc.statusDelegate = self
SendingClass.swift
import UIKit
protocol StatusDelegate {
func statusChanged(state: Bool, sender: String)
}
class SendingClass: UIViewController {
var statusDelegate : StatusDelegate?
#IBAction func button1Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: true, sender: "Button 1")
}
#IBAction func button2Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: false, sender: "Button 2")
}
}
ReceivingClass.swift
import Foundation
import UIKit
class ReceivingClass: UIViewController, StatusDelegate {
override func viewDidLoad() {
let vc = SendingClass()
vc.statusDelegate = self
}
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
Expected: the ReceivingClass protocol conforming function (func statusChanged) should execute each time the buttons are pressed within the SendingClass.
Actual: Nothing happens
I am using this..
// create extension in your receiving class
extension ReceivingClass: PopUpVCDelegate {
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
// on sending class, when you present your receiving class on any button click
eg.
let resultController = self.storyboard?.instantiateViewController(withIdentifier: "PopUpVCID") as? PopUpVC
resultController?.delegate = self
self.present(resultController!, animated: true, completion: nil)
//or if not have button add on viewdidload in receiving class
// here is full eg
How to get data from popup view controller to custom table view cell?
For protocol and delegate, you use it when u want to bring a value from 2nd VC (presented by 1st or pushed by 1st VC) to 1st VC, which is the original.
From your code, I dont see you presenting or pushing your 2nd VC. that's why it's not working. Hopefully I answered your doubt.
However if you still want to bring a value over from 1st VC to 2nd VC. In second VC, create a variable to receive it
var ReceivedData = String()
then from your first VC, when u are going to push it,
let vc = SendingClass()
vc.ReceivedData = "Whatever you want it to receive"
If you're using storyboard segues, maybe the view controller is instantiated from there so probably you have to use the prepareForSegue and get the destination view controller (which is already instantiated for you) in the ReceivingClass view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? SendingClass {
destination.delegate = self
}
}
Also be careful with delegate patter: the delegate property should be declared as a weak property to avoid retain-cycle
weak var delegate: MyDelegate?
In my app I want to have multiple container views stored in my main view controller. From there I want to be able to modify the constraints of the container views from both the main view controller and from the container views.
I've made a simple Xcode project and made an example GIF that shows what I want to be able to do.
The "Reset" button is stored in a container view in my main view controller. The container view is centered vertically and horizontally using constraints. The "Up", "Right", "Down", and "Left" buttons modify existing constraints of the container view. I'd like to be able to modify the constraints of the container view using the "Reset" button inside of the container view.
Every time I try to access/modify the constraint IBOutlets stored in the main view controller from within the container view it crashes saying "fatal error: unexpectedly found nil".
What is the best way to achieve this functionality? I'm all ears to your suggestions.
Thanks in advance.
ViewController.swift (main view controller)
class ViewController: UIViewController {
#IBOutlet weak var ContainerViewVerticalConstraint: NSLayoutConstraint!
#IBOutlet weak var ContainerViewHorizontalConstraint: NSLayoutConstraint!
#IBAction func upButtonPressed(_ sender: Any) {
ContainerViewVerticalConstraint.constant = -100
}
#IBAction func rightButtonPressed(_ sender: Any) {
ContainerViewHorizontalConstraint.constant = 50
}
#IBAction func downButtonPressed(_ sender: Any) {
ContainerViewVerticalConstraint.constant = 100
}
#IBAction func leftButtonPressed(_ sender: Any) {
ContainerViewHorizontalConstraint.constant = -50
}
}
ContainerViewController.swift (container view)
class ContainerViewController: UIViewController {
#IBAction func resetButtonPressed(_ sender: Any) {
// Modify the constraints of the container view when this button is pressed
}
}
The container view is embedded with an embed segue. You could do the following:
Find the embed segue in the Document Outline and give it an identifier such as "embedResetView".
In prepareForSegue, pass the two constraints to properties in the destination (Reset) view if segue.identifier is "embedResetView". The constraints are objects, so they are passed by reference.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "embedResetView" {
if let dvc = segue.destination as? ContainerViewController {
dvc.horizontalConstraint = ContainerViewHorizontalConstraint
dvc.verticalConstraint = ContainerViewVerticalConstraint
}
}
}
When the reset button is pressed, modify the constant property in those constraints.
class ContainerViewController: UIViewController {
weak var horizontalConstraint: NSLayoutConstraint?
weak var verticalConstraint: NSLayoutConstraint?
#IBAction func resetButtonPressed(_ sender: Any) {
horizontalConstraint?.constant = 0
verticalConstraint?.constant = 0
}
}
You can use a delegate pattern to pass events or data between your view controllers.
First of all, you should create a protocol in your ContainerViewController like that:
protocol ContainerViewControllerDelegate: class {
func containerControllerDidRequestViewReset()
}
After that, add a delegate property to ContainerViewController:
weak var delegate: ContainerViewControllerDelegate?
At your reset button function call the protocol method like that:
#IBAction func resetButtonPressed(_ sender: Any) {
// Modify the constraints of the container view when this button is pressed
delegate?.containerControllerDidRequestViewReset()
}
Than, navigate to your Storyboard file and add an identifier to your segue from ViewController to ContainerViewController, for example "ToContainerViewSegueID".
In your main ViewController class implement following method:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToContainerViewSegueID" {
if let containerVC = segue.destination as? ContainerViewController {
containerVC.delegate = self
}
}
}
After that, your should implement ContainerViewControllerDelegate into your main ViewController class. For example, you can do this using extension in the same file like that:
class ViewController: UIViewController {
//your code goes here
}
extension ViewController: ContainerViewControllerDelegate {
func containerControllerDidRequestViewReset() {
//here is a good place to reset your constraints values
ContainerViewVerticalConstraint.constant = 0
ContainerViewHorizontalConstraint.constant = 0
}
}
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)
}
}