how can I pass data from one container to another, both embedded in the same uiviewcontroller in swift?
To pass data from one embedded ViewController to another embedded ViewController, have the parent handle the transfer. Here I have provided a complete example with three ViewControllers and a single StringTaker
protocol. Both the main ViewController
and the LabelViewController
implement this protocol. The main ViewController
takes a string from the ButtonViewController
and passes it on to the embedded LabelViewController
.
ViewController.swift
import UIKit
protocol StringTaker: class {
func takeString(string: String)
}
class ViewController: UIViewController, StringTaker {
weak var stringTaker: StringTaker?
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedButtonViewController" {
let dvc = segue.destinationViewController as! ButtonViewController
dvc.delegate = self
} else if segue.identifier == "EmbedLabelViewController" {
let dvc = segue.destinationViewController as! LabelViewController
stringTaker = dvc
}
}
// Receive the string from the ButtonViewController
func takeString(string: String) {
// Pass it to the LabelViewController
stringTaker?.takeString(string)
}
}
ButtonViewController.swift
import UIKit
class ButtonViewController: UIViewController {
weak var delegate: StringTaker?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func generateString(sender: UIButton) {
let cities = ["Boston", "Paris", "Sydney", "Mumbai", "Lima"]
// Pick a random city
let city = cities[Int(arc4random_uniform(UInt32(cities.count)))]
// Pass the string to the delegate
delegate?.takeString(city)
}
}
LabelViewController.swift
import UIKit
class LabelViewController: UIViewController, StringTaker {
@IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func takeString(string: String) {
myLabel.text = string
}
}
Things to note:
- The
LabelViewController
and theButtonViewController
know nothing about theViewController
that uses them. This makes it easier to reuse them. You could embed them in another viewController and as long as you properly implement theStringTaker
protocol and set up thedelegate
, everything works. - The key to hooking this up in in naming the embed segues and then properly setting up the delegates in
prepareForSegue
. The segues can be found in the Document Outline view once the Container is added to theViewController
.
What's the easiest way to pass data between two simultaneously displayed view controllers?
Creating Delegates
Start with creating delegates for both of your container ViewControllers. Don't forget to add : class
. If you didn't do it, you wouldn't be able to create weak delegate variable:
protocol TopViewControllerDelegate: class {
func sendMessage(_ string: String)
}
protocol BottomViewControllerDelegate: class {
func sendMessage(_ string: String)
}
Now for every container ViewController create weak delegate variable
class TopViewController: UIViewController {
weak var delegate: TopViewControllerDelegate?
...
}
class BottomViewController: UIViewController {
weak var delegate: BottomViewControllerDelegate?
...
}
then for TopVC implement Bottom's delegate and for BottomVC Top's.
extension TopViewController: BottomViewControllerDelegate {
func sendMessage(_ string: String) {
// do something
}
}
extension BottomViewController: TopViewControllerDelegate {
func sendMessage(_ string: String) {
// do something
}
}
Assigning Delegates
Your segues between main ViewController and containers should have their own identifiers: EmbedTop
, EmbedBottom
.
So in your WrapperViewController
create variable for your Top and Bottom ViewController and override method prepare(for:sender:)
and inside assign these variables
class WrapperViewController: UIViewController {
var topVC: TopViewController?
var bottomVC: BottomViewController?
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedTop" {
topVC = segue.destination as! TopViewController
} else if segue.identifier == "EmbedBottom" {
bottomVC = segue.destination as! BottomViewController
}
}
}
finally in viewDidAppear
set delegate of TopVC's as BottomVC and of BottomVC's as TopVC
override func viewDidAppear(_ animated: Bool) {
topVC.delegate = bottomVC
bottomVC.delegate = topVC
}
Now your two ViewControllers can speak with each other! :-)
Example:
class BottomViewController: UIViewController {
...
func speakToTop() {
delegate?.sendMessage("Hi Top!")
}
}
Passing Data to view controllers that are embedded in container views
In your Storyboard, when you embed a VC in a ContainerView, you also see a "segue" connecter. When the root VC loads, you will get a call to prepare for segue for that.
Give each storyboard-created segue an Identifier - such as "infoViewEmbedSegue", "feedViewEmbedSegue", etc.
In your root VC, I'm guessing that
var infos: BusinessProfilesDetails!
var feeds: BusinessProfilePostsFeed!
are variables to reference the content of infoView
? If so, in prepare() you want to:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// get a reference to the embedded PageViewController on load
if let vc = segue.destination as? BusinessProfilesDetails,
segue.identifier == "infoViewEmbedSegue" {
self.infos = vc
// if you already have your data object
self.infos.otherUser = theDataDict
}
if let vc = segue.destination as? BusinessProfilePostsFeed,
segue.identifier == "feedViewEmbedSegue" {
self.feeds = vc
// if you already have your data object
self.feeds.otherUser = theDataDict
}
// etc
}
Now you'll have persistent references to the actual View Controllers embedded in your Container Views, in case you want access to them in other parts of your root VC, e.g.:
@IBAction func btnTapped(_ sender: Any) {
self.feeds.otherUser = theDataDict
}
Pass data from Parent UIviewController to a Container (ChildViewController) every time the value is changed
After a deeper digging I was able to find a solution for my own question.
here I am going to post if anyone else needs it in the future
so, first of all, I need it to lunch the ChildContoller from the parent controller and not from the storyboard ( so I deleted the segue between the parent and the child.
create a variable for childController like that:
lazy var firstChildViewController: FirstChildViewController = {
let storynoard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storynoard.instantiateViewController(identifier: "firstChild") as! FirstChildViewController
self.addChild(viewController)
self.view.addSubview(viewController.view)
return viewController
}()
same thing for the other one if you have two children
and then in the viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
firstChildViewController.view.isHidden = false
secondChildViewController.view.isHidden = true
}
and then in the FirstChildViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let parent = self.parent as? ParentViewController {
parent.delegate = self
}
}
And the problem is solved
Hope it helps someone
Pass data to View Controller embedded inside a Container View Controller
Actually I feel like the correct solution is to rely on programmatic instantiation of the content view, and this is what I chose after careful and thorough thoughts.
Here are the steps that I followed:
The Table View Controller has a push segue set to
ContainerViewController
in the storyboard. It still gets performed when the user taps on a cell.I removed the embed segue from the Container View to the
ContentViewController
in the storyboard, and I added an IB Outlet to that Container View in my class.I set a storyboard ID to the Content View Controller, say…
ContentViewController
, so that we can instantiate it programmatically in due time.I implemented a custom Container View Controller, as described in Apple's View Controller Programming Guide. Now my
ContainerViewController.swift
looks like (most of the code install and removes the layout constraints):class ContainerViewController: UIViewController {
var contentViewController: UIViewController? {
willSet {
setContentViewController(newValue)
}
}
@IBOutlet private weak var containerView: UIView!
private var constraints = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
setContentViewController(contentViewController)
}
private func setContentViewController(newContentViewController: UIViewController?) {
guard isViewLoaded() else { return }
if let previousContentViewController = contentViewController {
previousContentViewController.willMoveToParentViewController(nil)
containerView.removeConstraints(constraints)
previousContentViewController.view.removeFromSuperview()
previousContentViewController.removeFromParentViewController()
}
if let newContentViewController = newContentViewController {
let newView = newContentViewController.view
addChildViewController(newContentViewController)
containerView.addSubview(newView)
newView.frame = containerView.bounds
constraints.append(newView.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor))
constraints.append(newView.topAnchor.constraintEqualToAnchor(containerView.topAnchor))
constraints.append(newView.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor))
constraints.append(newView.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor))
constraints.forEach { $0.active = true }
newContentViewController.didMoveToParentViewController(self)
}
} }In my
LetterTableViewController
class, I instantiate and setup my Content View Controller, which is added to the Container's child view controllers. Here is the code:override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
if let viewController = storyboard?.instantiateViewControllerWithIdentifier("ContentViewController"),
let contentViewController = viewController as? ContentViewController {
contentViewController.letter = letter
containerViewController.contentViewController = contentViewController
}
}
}
This works perfectly, with an entirely content-agnostic container view controller. By the way, it used to be the way one instantiated a UITabBarController
or a UINavigationController
along with its children, in the appDidFinishLaunching:withOptions:
delegate method.
The only downside of this I can see: the UI flow ne longer appears explicitly on the storyboard.
Pass data between ViewController and ContainerViewController
All you need to do is keep a reference to Container
in your master view controller.
That is, you should add an instance variable to Master
that will hold a reference to the view controller, not just the view. You'll need to set it in prepareForSegue
.
So the beginning of Master View Controller would look something like this:
class Master: UIViewController, ContainerToMaster {
@IBOutlet var containerView: UIView!
var containerViewController: Container?
@IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
And then in your button function, simply change the label using the variable you just added.
Example:
@IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
This means you can get rid of your MasterToContainer
protocol too.
I tested this code, so I know it works, but unfortunately I am an Objective-C dev, and know nothing about best practices in Swift. So I don't know if this is the best way to go about it, but it certainly works.
Edit:
Here's the exact code I've tested:
Master.swift:
import UIKit
class Master: UIViewController, ContainerToMaster {
@IBOutlet var containerView: UIView!
@IBOutlet var labelMaster: UILabel!
var containerViewController: Container?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
containerViewController!.containerToMaster = self
}
}
@IBAction func button_Container(sender: AnyObject) {
containerViewController?.changeLabel("Nice! It's work!")
}
func changeLabel(text: String) {
labelMaster.text = text
}
}
Container.swift:
import UIKit
protocol ContainerToMaster {
func changeLabel(text:String)
}
class Container: UIViewController {
@IBOutlet var labelContainer: UILabel!
var containerToMaster:ContainerToMaster?
@IBAction func button_Master(sender: AnyObject) {
containerToMaster?.changeLabel("Amazing! It's work!")
}
func changeLabel(text: String) {
labelContainer.text = text
}
}
How can I pass a data from UIViewController to a UITableViewController that is embedded in a container placed on that UIViewController?
I have a project on github (written in Objective-C) that does exactly what you want. It has the horribly unoriginal name "test".
It has a master view controller with 2 child view controllers, each of which is a subclass of UITableViewController
set up to manage static table views. (That detail doesn't really matter. It's the method of setting up connections between parent and child view controllers that addresses your question.)
The idea is that you set up your child view controller in a container view and link it in with an embed segue.
When you use an embed segue then prepareForSegue
fires when the views are loaded. In your prepareForSegue
you save a reference to your child view controller(s) if you need to call it/them, and make yourself each child's delegate if you need it to call you.
The code would be different in Swift but the concept is identical.
Passing data back from embedded view controller
You need to typecast before using it. Check with:
let myObj : MyClass = self.childViewControllers.last as ? MyClass;
let textfield: UITextField = myObj?.usernameTextField! as UITextField;
Related Topics
The Maximum Number of Apps for Free Development Profiles Has Been Reached. Xcode 11.5
Wait for Asynchronous Operation to Complete in Swift
Take a Picture on iPhone Without Showing Controls
Default Tab Bar Item Colors Using Swift Xcode 6
Avaudioplayer Throws Breakpoint in Debug Mode
Ios7 Uiswitch Its Event Valuechanged: Calling Continuously Is This Bug or What..
Missing Push Notification Entitlement Warning
How to Set Imageview in Circle Like Imagecontacts in Swift Correctly
How to Fix the "Uipopovercontroller Is Deprecated" Warning
How to Use Static Cells in Uitableview Without Using Storyboards
Barcode Generation from Within iOS App
Nsinteger and Nsuinteger in a Mixed 64Bit/32Bit Environment
How to Do Programmatically Gradient Border Color Uibutton with Swift
How to Save Nsmutablearray or Nsdictionary Data as File in iOS
Navigating to a New Screen When Stream Value in Bloc Changes