How to transfer data between Parent and Child View Controllers - ios

I have tried looking at answers on similar questions to this, but I am not particularly experienced and have had trouble following them, so any help would be much appreciated! My situation is as follows: when I press a button in my Parent ViewController, the following code is used to call a Child ViewController (by the way, the Child is actually a TableViewController, but it seems to work fine "thinking" it's a normal ViewController?):
controller = (storyboard?.instantiateViewController(withIdentifier: "People"))
addChildViewController(controller!)
controller?.view.frame = CGRect(x: 10, y: 200, width: 394, height: 300)
self.view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
What I would then like is to transfer an array from the Parent to the Child, where it will be used as the TableView's data?
Secondly, when I select a cell from the Child's TableView, I would like the relevant information to be sent to the Parent, and for the Child to disappear.
In case it is of interest, I have managed to close the Child under different circumstances (when a click occurs in the Parent while the Child is displayed) using the following:
controller?.willMove(toParentViewController: nil)
controller?.view.removeFromSuperview()
controller?.removeFromParentViewController()
I would really appreciate any advice, even if it's a link to something which would help!

You can pass value from Parent to Child Controller like this
controller = (storyboard?.instantiateViewController(withIdentifier: "People"))
addChildViewController(controller!)
controller?.view.frame = CGRect(x: 10, y: 200, width: 394, height: 300)
controller.tableDataSource = // Pass your required value to child controller
self.view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
Now you want to transfer back your select value to Parent view controller. For this purpose your have a create a Delegate in ChildController like
#protocol ChildControllerDelegate : class {
func selectedValue(Value : String)
}
After that make a variable of that delegate in ChildController like this
weak var delegate : ChildControllerDelegate?
and when in rowDidSelect method add following code
if(delegate != nil) {
delegate.selectedValue(Value :"Your selected value")
}
Now step when you are going to show ChildController from ParentController at that time you have to set that delegate object to ParentController like this
controller = (storyboard?.instantiateViewController(withIdentifier: "People"))
addChildViewController(controller!)
controller?.view.frame = CGRect(x: 10, y: 200, width: 394, height: 300)
controller.delegate = self
self.view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
and after that just implement the delegate method in ParentController like that
func selectedValue(Value : String) {
// you select val
}

Try This.
First Create Public Method For Add And Remove childVC.
For Add childVC.
public class func openChildViewController(parentVC:UIViewController, with childVC:UIViewController){
parentVC.addChildViewController(childVC)
childVC.view.frame = parentVC.view.frame
parentVC.view.addSubview(childVC.view)
parentVC.didMove(toParentViewController: childVC)
}
For Remove childVC.
public class func removeChildViewController(childVC:UIViewController){
childVC.willMove(toParentViewController: nil)
childVC.view.removeFromSuperview()
childVC.removeFromParentViewController()
}
Use Above Method.
1.ParentVC.swift
class ParentVC: UIViewController , ChildVCDelegate {
var arrType = NSMutableArray()
//add ChildVC
#IBAction func btnAddChildVC(_ sender: UIButton) {
let ChildVC = self.storyboard?.instantiateViewController(withIdentifier: "ChildVC") as! ChildVC
PickerVC.arrPass = arrType //for data passing create any object in ChildVC for ex. arrPass is NSMutableArray
ChildVC.delegate = self
openChildViewController(parentVC: self, with: ChildVC)
}
// MARK: ChildVC Delegate
func SetSelectedPickerValue(strSelectOption: String) {
print(strSelectOption)
}
}
}
2.ChildVC.swift
class ChildVC: UIViewController{
// MARK: Variable for ParentVCData Passing
var arrPass = NSMutableArray()
override func viewWillAppear(_ animated: Bool) {
print(arrPass)
}
//Remove ChildVC
#IBAction func btnRemoveChildVC(_ sender: UIButton) {
self.delegate?.SetSelectedPickerValue!(strSelectOption: “any String you pass ChildVC To ParentVC”)
removeChildViewController(childVC: self)
}
}
// MARK: Create Delegate Method
#objc protocol ChildVCDelegate{
#objc optional func SetSelectedPickerValue(strSelectOption:String)
}

You can:
Cast the controller to the appropriate class for the child view controller (I'm using ChildViewController below, but hopefully you have a more descriptive name); and
Pass the array (which I guessed you might have called people, but use whatever your array names are in these two respective view controllers) from the current view controller (the parent) to this new child view controller.
Thus:
let child = storyboard!.instantiateViewController(withIdentifier: "People") as! ChildViewController
addChildViewController(child)
child.people = people
child.view.frame = ...
view.addSubview(child.view)
child.didMove(toParentViewController: self)
Personally, I wouldn't hard code the child coordinates like you did in your original question. I'd set translatesAutoresizingMaskIntoConstraints to false and then add the appropriate leading/trailing/top/bottom constraints, but that's up to you. It was just too painful to see hardcoded coordinates in your example.

Related

Many buttons to a single view controller

im developing an app that utilises many buttons( possibly 20 buttons) on one primary view controller that can are all able to activate a singular picker view within a pop up on a seperate view controller. i don’t think the answer is lots and lots segues. Is there a better approach I should be considering?
I’m thinking - some kind of multiuse segue that can be activated by any of the buttons, but nonidea how this is done.
Appreciate any advice
Mike
Set up all buttons to same action such as:
#IBAction func keyPressed(_ sender:UIButton){
// use button title string
self.keyString = sender.titleLabel?.text as! String
// or tag
self.keyTag= sender.tag?
self.performSegue(withIdentifier: "TheSegue", sender: self)
}
Then you would want to set up the View Controller that you are going to navigate to based on the state of the sender. So you would override the prepare:forSegue method as below.
override func prepare(for segue:UIStoryboardSegue, sender:Any?) {
let destController = segue.destination as! Dest_Controller_Class
// use tag or keyTitle to set controller attributes
// before view is shown
destController.keyTag = self.keyTag
destController.keyString = self.keyString
}
Now once you've navigated to the Dest_Controller_Class, you will have the properties of the button pressed locally in the view controller and could update the view as you see fit:
class Dest_Controller_Class: UIViewController {
var keyString: String?
var keyTag: Int?
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
if (keyString != nil) {
label.text = keyString;
// or likewise use tag
} else {
label.text = "keyString not set"
}
}
}

Passing data won't update the string

I have a table view with a tap gesture recognizer inside of it and when I double click on the cell, I wanted to pass data from the cell to another view controller. Now it works only once and doesnt update the string in the view controller. The string remains permanent. Now I need help updating the string so that when I try to double tap another cell, the string will update instead of only work once by keeping the value of the string in the view controller constant with that of the first cell tapped. Here is my code.
Code in tableview for double tap gesture recognizer.
func CellTappedTwice(sender: UITapGestureRecognizer!) {
if let index = sender.view?.tag{
if let object = objects?[index]{
if let objectId = object.objectId{
popupViewController.objectId = objectId
}
}
}
self.present(popupViewController, animated: true, completion: nil)
}
Code in view controller:
var objectId : String?
#IBOutlet weak var QrCode: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
if let object = objectId {
let qrcode = DCQRCode(info: object, size: CGSize(width: 170, height: 170))
qrcode.positionStyle = [
(UIImage(named: "RedOuterPosition")!, DCQRCodePosition.topRight),
(UIImage(named: "RedOuterPosition")!, DCQRCodePosition.topLeft),
(UIImage(named: "RedOuterPosition")!, DCQRCodePosition.bottomLeft)
]
qrcode.maskImage = UIImage(named: "RedColors")
QrCode.image = qrcode.image()
print("hello\(objectId)")
}
}
Move the code that handles objectId to your viewWillAppear(_:) method, not viewDidLoad. The viewDidLoad method only gets called once during the life of a view controller, so if you're using the same view controller over and over, you'll only every process objectId the first time it's displayed.

Access UI properties across different View Controllers in swift

In my app, I have a summary page which will display the summary of payers and recipients list of each payer. That is, the summary page will have a list in the left side and clicking on each list will show its details on the right side(iPad). In order to re-use the same code for iPhone version too, I have it as separate view controllers. That is, I have a base ViewController(SummaryViewController). In this i subview the ViewController of the list(SummaryListViewController) and ViewController of the details(SummaryDetailViewController). Now, when the base view controller SummaryViewController loads, i subview the list and detail view controllers like this
//ListView is a view in the base ViewController to which i subview the list ViewController
let listViewController = SummaryListViewController(nibName:"SummaryListViewController", bundle: nil)
addChildViewController(listViewController)
listViewController.view.frame = CGRect(x: 0, y: 0, width: self.ListView.frame.size.width, height: self.ListView.frame.size.height)
ListView.addSubview(listViewController.view)
listViewController.didMove(toParentViewController: self)
//DetailView is a view in the base ViewController to which i subview the Detail ViewController
let detailViewController = SummaryDetailViewController(nibName: (UIDevice.current.userInterfaceIdiom == .pad ? "SummaryDetailViewController" : "SummaryDetailViewController_iPhone"), bundle: nil)
addChildViewController(detailViewController)
DetailView.frame = CGRect(x: DetailView.frame.origin.x, y: DetailView.frame.origin.y, width: self.DetailView.frame.size.width, height: self.DetailView.frame.size.height)
DetailView.addSubview(detailViewController.view)
detailViewController.didMove(toParentViewController: self)
Now, the prob is, I have to call a method in the SummaryDetailViewController from the tableView-didSelectRow of SummaryListViewController which will update the UI elements according to the data i send.
I have tried the following things to achieve this,
I tried using addObserver in NotificationCenter. When i click on the list, i added an observer. And this observer triggers the method in the detailViewController that will update the UI elements. But this doesn't work well all the time. When i come to Summary page, go back and if i come again to summary page and do the same, the observer is called twice even after removing the observer in ViewDidDisapper. And also i learnt in few websites that NotificationCenter should not be used for this kind of a situation.
Second, Am trying to use protocols. I thought i would write a protocol in the SummaryListViewController
protocol SummaryDetailProtocol {
func setSummaryDetails()
}
class SummaryListViewController: UIViewController
{
var summaryDetailsDelegate : SummaryDetailProtocol?
func delegateFromSummaryDetails(delegate: SummaryDetailProtocol)
{
self.summaryDetailsDelegate = delegate
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.summaryDetailsDelegate?.setSummaryDetails()
}
func delegateFromSummaryDetails(delegate: SummaryDetailProtocol)
{
self.summaryDetailsDelegate = delegate
}
}
Now in the ViewDidLoad of the SummaryDetailViewController, I would like to send the reference of the delegate to the listViewController so that the listViewController can call the setSummaryDetails method.
override func viewDidLoad()
{
super.viewDidLoad()
sendDelegateReferenceToListPage()
}
func sendDelegateReferenceToListPage()
{
let summaryListObj = SummaryListView()
//This is where the error occurs. It throws the error since i try to cast SummaryDetailViewController to parameter of type SummaryDetailProtocol of SummaryListViewController
summaryListObj.delegateFromSummaryDetails(delegate: self as! SummaryDetailProtocol)
}
Can anyone help me to get out of this
Protocol solution is the best. You can use like this
SummaryListViewController file:
protocol SummaryDetailProtocol {
func setSummaryDetails()
}
class SummaryListViewController: UIViewController
{
var summaryDetailsDelegate : SummaryDetailProtocol?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.summaryDetailsDelegate?.setSummaryDetails()//use here
}
}
SummaryDetailViewController file:
class SummaryDetailViewController: UIViewController,SummaryDetailProtocol
{
internal func setSummaryDetails() {
//return whatever you want
}
}
And finally set SummaryListViewController delegate to SummaryDetailViewController:
let detailViewController = SummaryDetailViewController(nibName: (UIDevice.current.userInterfaceIdiom == .pad ? "SummaryDetailViewController" : "SummaryDetailViewController_iPhone"), bundle: nil)
addChildViewController(detailViewController)
DetailView.frame = CGRect(x: DetailView.frame.origin.x, y: DetailView.frame.origin.y, width: self.DetailView.frame.size.width, height: self.DetailView.frame.size.height)
DetailView.addSubview(detailViewController.view)
detailViewController.didMove(toParentViewController: self)
let listViewController = SummaryListViewController(nibName:"SummaryListViewController", bundle: nil)
addChildViewController(listViewController)
listViewController.view.frame = CGRect(x: 0, y: 0, width: self.ListView.frame.size.width, height: self.ListView.frame.size.height)
ListView.addSubview(listViewController.view)
listViewController.didMove(toParentViewController: self)
listViewController.summaryDetailsDelegate = detailViewController // set delegate detail view controller

PageMenu how to add view controllers dynamically (programmatically)?

I want my app to look like the image below.
I am using PageMenu to acomplish this result. The data is fetcehd from a web API. I want to dynamically create view controllers based on the number of days that the web API responds with so that each day can have a view controller so that it can display schedule for that day. How can I accomplish this?
reading from the link you provided yourself it has:
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller : UIViewController = UIViewController(nibName: "controllerNibName", bundle: nil)
controller.title = "SAMPLE TITLE"
controllerArray.append(controller)
So what you have to do is : have a function that returns a [dailyModel].
Your dailyModel would look something like this:
struct dailyModel {
let programStartingTime : String // 6:45
let item : [item] // [(Maghrib & Isha Prayer, 20, 0),(Ziarat-e-Ashura, 20, 1), ...otherItems...]
}
struct item{
let duration : Int? //minutes
let name : String // Maghrib and Isha prayers
let row : Int? // 0
}
Then loop through that dailyModel and using its parameters you instantiate & populate viewcontroller and then append each of them as the tutorial is doing.
This isn't the best code but I hope you get the idea.
Simply create a loop add same controller multiple times. i created addPageMenu function. call it when you get the response.
func addPageMenu(count: Int) {
var controllerArray : [UIViewController] = []
for i in count {
var controller : UIViewController = UIViewController(nibName: "controllerNibName", bundle: nil)
if i == 0 {
controller.title = "SUNDAY"
} else if i == 1 {
controller.title = "MONDAY"
}...
controllerArray.append(controller)
}
let pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}

Delegate loosing View Controller objects swift

All,
I have set up a protocol, and the view controller is the delegate of this protocol, like so :
import Foundation
protocol PayButtonProtocol {
func enablePayButton()
func disablePayButton()
}
And the view controller is the delegate :
class ViewController: UIViewController, PayButtonProtocol
The protocol functions are as follows :
func enablePayButton() {
println("Button enabled")
PAYBarButton.enabled = false
}
func disablePayButton() {
PAYBarButton.enabled = false
}
I set a class and assign the delegate :
class Trigger
{
var delegate:PayButtonProtocol?
func EnablePayButton()
{
delegate?.enablePayButton()
}
}
Then I set the trigger to run the function :
let localtrigger = Trigger()
localtrigger.delegate = ViewController()
localtrigger.EnablePayButton()
This works and the 'button enabled' is displayed in the console. But the Bar Button (PAYBarButton) is nil and it seems that the view controller has lost its hieracy as I cannot access any of the view controllers objects. The View Controller was built with interface builder. Anyone got any ideas ? Is it
localtrigger.delegate = ViewController()
that rebuilds the viewconotroller and makes the original one not accessible ? If so how do i do this ?
if you are creating the localTrigger object inside your ViewController class you can just do:
let localtrigger = Trigger()
localtrigger.delegate = self // self is an instance of ViewController
localtrigger.EnablePayButton()

Resources