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
Related
I have a todo list that allows the user to add todos to a table view. On a separate view controller (CompletedViewController) the user can see previously-completed todos.
There is an itemBarButton in the CompletedViewController that should allow the user to clear the list of completed items, as well as clear the array of completed todos (completedThings) on the initial ViewController.
Creating an instance of CompletedViewController and setting the completedTodos array in ViewController.swift:
#IBAction func viewCompletedTapped(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "Completed") as? CompletedViewController {
vc.completedTodos = completedThings
navigationController?.pushViewController(vc, animated: true)
}
}
the protocol in CompletedViewController.swift:
protocol CompletedCleared {
func didClearCompleted()
}
the method called when clicking the 'clear' itemBarButton in CompletedViewController.swift
#objc func clearCompleted() {
completedTodos = []
tableView.reloadData()
let vc = ViewController()
vc.didClearCompleted()
}
conforming to the protocol in ViewController.swift
func didClearCompleted() {
completedThings.removeAll()
}
This does not clear the list on the previous view controller. What am I doing wrong?
You forgot to set the delegate of the CompletedViewController. In viewCompletedTapped add this line after instantiating the view controller:
vc.delegate = self
Also make sure you use this delegate method in CompletedViewController. clearCompleted should probably look like this:
#objc func clearCompleted() {
completedTodos = []
tableView.reloadData()
delegate?.didClearCompleted()
}
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.
I have the following class below. The idea is it will use a custom Progress Window View Controller to handle progress of various different events. The problem is since this is in a class and not a view controller it's self, I'm not sure how to make the progressWindow actually show up after I instantiate it from the storyboard?
How do I do this? Currently I get an error that the application tried to present model view controller on itself.
import Foundation
import UIKit
class StatusProgress{
static var cancelCode = {}
static var runCode = {}
static var theProgressWindowController = ProgressWindowViewController()
static var returningViewControllerIdentifier = ""
static let storyboard = UIStoryboard(name: "Main", bundle: nil)
static func run(){
// This will run in parralel but on main queue. Has to be on this Queue because it might involve UI
dispatch_async(dispatch_get_main_queue(), {
// Update the UI on the main thread.
StatusProgress.runCode()
});
}
static func cancel(){
dispatch_async(dispatch_get_main_queue(), {
StatusProgress.cancelCode()
dispatch_sync(dispatch_get_main_queue(),{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(returningViewControllerIdentifier)
vc.presentViewController(vc, animated: true, completion: nil)
})
});
}
static func show(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil) //use own instance to show it's self? (throws error! application tried to present modal view controller on itself. Presenting controller is <Inventory_Counter.ProgressWindowViewController: 0x1466ea390>.')
})
}
}
My problem is essentially I need a replacement for this line of code.
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil)
I forgot to mention here is the code that runs it inside another view controller.
SyncViewController.swift
import UIKit
class SyncViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func yesSyncButtonAction(sender: UIButton) {
StatusProgress.returningViewControllerIdentifier = "syncWindow"
StatusProgress.runCode = {
print("run code test")
}
StatusProgress.cancelCode = {
print("cancel code test")
}
StatusProgress.show()
}
#IBAction func noSyncActionButton(sender: UIButton) {
tabBarController?.selectedIndex = 1 //assume back to inventory section
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The biggest problem is that your StatusProgress class is instantiating and showing a view controller. View controllers should instantiate and show other view controllers, model objects should not. So you need to move the logic for presenting the new view controller into you SyncViewController. Then use delegation to communicate to the SyncViewController that the syncing is done.
protocol StatusProgressDelegate {
func statusProgress(status: StatusProgress, shouldShow: Bool)
func statusProgress(status: StatusProgress, shouldCancel: Bool)
}
Your StatusProgress object would have a delegate that conforms to that protocol and call that delegate inside of its show and cancel methods. This means that you need to make the static functions instance methods, and write an initializer for the class so you can instantiate it.
If the view life cycle events are not much important for you, you may just add the view of your progress controller to view of your current controller. or it's even better if you supply the UIView parameter in your show() function.
static func show(attachToView: UIView ){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
attachToView.addSubview(theProgressWindowController.view)
})
}
After all you'd better to remove your progress view from superview
static func cancel(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.view.removeFromSuperview()
})
}
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)
}
}
My delegate protocol never called
My first controller - ViewController
class ViewController: UIViewController,testProtocol {
#IBAction func btInit(sender: AnyObject) {
println("Bt Init")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initViewController: UIViewController = storyBoard.instantiateViewControllerWithIdentifier("viewTarget") as targetViewController
self.presentViewController(initViewController,animated: false, nil)
}
var targetController = targetViewController();
override func viewDidLoad() {
super.viewDidLoad()
self.targetController.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func testDelegate(){
println(" in my view controller delegate ")
}
}
In my second view controller - targetViewController
protocol testProtocol {
func testDelegate() // this function the first controllers
}
class targetViewController: UIViewController {
#IBAction func BtTarget(sender: AnyObject) {
println("bt target pressed")
delegate?.testDelegate()
}
var delegate : testProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func testDelegate(){
println(" in my target view controller delegate ")
}
}
Why is testDelegate() never called on ViewController? What am I doing wrong? Thanks.
I have read a lot of posts about this, but all of the examples are given with segue transition, and I don't want use a segue.
Typically you set a new view controller's delegate property in prepareForSegue:. You said you're not using a segue, so you'll need to instantiate the second view controller and present it somehow. You can do this by doing something like:
let storyboard = UIStoryboard(name: "AStoryboardName", bundle: nil)
let secondVC = storyboard.instantiateViewControllerWithIdentifier(anIdentifier) as! targetViewController
secondVC.delegate = self
presentViewController(secondVC, animated: true, completion: nil)
You have a testDelegate() method in both view controllers, but you only want it in the first view controller. Then your second view controller can call delegate?.testDelegate() at the appropriate time.
Finally, you typically want to make delegate properties weak, so I would recommend changing var delegate : testProtocol? to weak var delegate: testProtocol?
I would read up on delegation. Here is a relatively simple 5 step process to delegation that may help you:
Delegation in 5 Steps:
object A is the delegate for object B, and object B will send out the messages:
Define a delegate protocol for object B.
Give object B an optional delegate variable. This variable should be weak.
Make object B send messages to its delegate when something interesting happens, such as the user pressing the Cancel or Done buttons, or when it needs a piece of information.
Make object A conform to the delegate protocol. It should put the name of the protocol in its class line and implement the methods from the protocol.
Tell object B that object A is now its delegate (in prepareForSegue(sender)).