Loading a UIViewController into a Container View programmatically - ios

This is in the latest XCode, 11.3. I've looked through the answers to similar questions, but some of the function names and parameters seem to have changed.
While working through a Udemy course, I'm trying to make a simple log in / sign up interface. In my first view controller I have a vertical stack view containing a label, a segmented control, and a container view. The segmented control can be either "Log In" or "Sign Up". I want to load the appropriate view controller when the segmented control changes.
My LogInViewController has a vertical stack view with a text field for username, another for password, and a Log In button. My SignUpViewController has a vertical stack view with text fields for email address, username, password, and password confirmation followed by a Sign Up button.
My first view controller looks like this:
class ViewController: UIViewController
{
#IBOutlet weak var logInSignUpControl: UISegmentedControl!
#IBOutlet var customContainer: UIView!
var logInVC: LogInViewController?
var signUpVC: SignUpViewController?
var activeVC = 0
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
initializeCustomControllers()
}
func initializeCustomControllers()
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
logInVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? LogInViewController
signUpVC = storyboard.instantiateViewController(withIdentifier: "SignUpViewController") as? SignUpViewController
logInVC?.willMove(toParent: self)
logInVC?.view.frame = customContainer.bounds
customContainer.addSubview(logInVC!.view)
addChild(logInVC!)
logInVC?.didMove(toParent: self)
}
Unfortunately, this only shows the LogInViewController, not the label or segmented control.
I haven't been able to find a clear description of how to do this in the latest version of XCode and Swift, so I've pieced this together from various blog posts and Stackoverflow comments about older versions. Any help is greatly appreciated.

Try the below code :)
PS: #IBAction func controlChanged(_ sender: Any) is an IBAction for the "ValueChanged" Event
class ViewController: UIViewController {
#IBOutlet weak var logInSignUpControl: UISegmentedControl!
#IBOutlet var customContainer: UIView!
var logInVC: LogInViewController?
var signUpVC: SignUpViewController?
var activeVC = 0
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
initializeCustomControllers()
}
#IBAction func controlChanged(_ sender: Any) {
changeVC()
}
func changeVC() {
if activeVC == 0 {
signUpVC?.view.removeFromSuperview()
logInVC?.willMove(toParent: self)
logInVC?.view.frame = customContainer.bounds
customContainer.addSubview(logInVC!.view)
addChild(logInVC!)
logInVC?.didMove(toParent: self)
} else {
logInVC?.view.removeFromSuperview()
signUpVC?.willMove(toParent: self)
signUpVC?.view.frame = customContainer.bounds
customContainer.addSubview(signUpVC!.view)
addChild(signUpVC!)
signUpVC?.didMove(toParent: self)
}
activeVC = activeVC == 0 ? 1 : 0
}
func initializeCustomControllers()
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
logInVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? LogInViewController
signUpVC = storyboard.instantiateViewController(withIdentifier: "SignUpViewController") as? SignUpViewController
changeVC()
}
}

Make sure this #IBOutlet var customContainer: UIView! is linked to the containerview and not to the vc's main view in storyboard

Related

I am trying to using a custom delegate in swift but when I want to call its methods, it did not get called

I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self

Calling child view controller using segmented control action?

I have two child view controllers in my parent view controller, I want to call them upon a value change in the segmented control, and want to set the value of the parent imageView through child view controllers.
protocol UserEdittedPhoto {
func UserIsDone(image:UIImage)
}
class ControllerFinal:UIViewController, UserEdittedPhoto{
func UserIsDone(imageEditted: UIImage){
self.usedImage=imageEditted
self.imageView.image=self.usedImage
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentAction(sender:UISegmentedControl){
if (segmentedControl.selectedSegmentIndex==0){
performSegueWithIdentifier("EditIAm", sender: nil)
}
else if (segmentedControl.selectedSegmentIndex==1){
performSegueWithIdentifier("EditIAm", sender: nil)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditIAm"{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("ControllerEdit")
self.presentViewController(controller, animated: true, completion: nil)
let nextView = segue.destinationViewController as! ControllerEdit
nextView.originalImage=self.imageView.image!
nextView.delegate=self
}
else if segue.identifier == "FilterIAm"{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("ControllerFilters")
self.presentViewController(controller, animated: true, completion: nil)
let nextView = segue.destinationViewController as! ControllerFilters
nextView.toBeFilter=self.imageView.image!
}
}
class ControllerEdit:UIViewController{
var delegate: UserEdittedPhoto? = nil
if (delegate != nil){
self.originalImage = UIImage(CIImage: CIImage(image: self.originalImage)!.exposureAdjustFilter(sliderValue.value)!)
self.delegate!.UserIsDone(self.originalImage)
print("I am Called!")
}
}
class ControllerFilters:UIViewController{
override func viewDidLoad() {
print("SAHASHhqdwiuhiuhsaiuhsaiudhiuash")
controllerFinal?.imageView.image=toBeFilter
print("SAHASHhqdwiuhiuhsaiuhsaiudhiuash")
super.viewDidLoad()
}
}
UPDATE
To reflect our discussion in the comments below, I don't think you really need Containers and View Controllers to manage your custom controls (Edit / Filters). It's overkill.
Instead, I think you should be creating custom Views, and then adding them to your main Storyboard.
Then you could simply hide/show your custom Views when users tap on the Segmented Control as well as passing values to them, for example:
CustomEditView.valueY = newValueY
CustomFiltersView.valueX = newValueX
Regarding:
I need to call it forcefully through segmentedControl action, so that
my values in the childView be updated
Then you need to map the target View Controllers to local variables and use them to update the target View Controller variables when users presses the segments.
I've update the code and "demo" in my answer to reflect that.
(Notice that I'm just putting random Strings in the labels to make a point.)
Now to the complete answer...
In the setup you described in your other question, which is based on containers, the View Controllers are already there, in the Storyboard. You absolutely don't need to present them again (you can remove performSegueWithIdentifier calls).
If I understood correctly, you just want to show different "controllers" to the user based on what they choose via a Segmented Control.
There are some ways for doing that, but the easiest one would be to hide and to show the containers of the ControllerEdit / ControllerFilters View Controllers -- by changing the containers isHidden variable state.
Like this:
Storyboard setup:
Code (based on my other answer):
import UIKit
protocol UpdateImageProtocol {
func userIsDone(image: UIImage)
}
class ViewController: UIViewController, UpdateImageProtocol {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var changeImageContainer: UIView!
var controllerEdit: ControllerEdit?
#IBOutlet weak var applyFilterContainer: UIView!
var controllerFilters: ControllerFilters?
var image = UIImage(named: "hello")
override func viewDidLoad() {
super.viewDidLoad()
userIsDone(image: image!)
}
func userIsDone(image: UIImage) {
imageView.image = image
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "controllerEdit" {
let nextView = segue.destination as! ControllerEdit
nextView.delegate = self
controllerEdit = nextView
} else if segue.identifier == "controllerFilters" {
let nextView = segue.destination as! ControllerFilters
controllerFilters = nextView
}
}
#IBAction func segmentAction(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
changeImageContainer.isHidden = false
applyFilterContainer.isHidden = true
controllerEdit?.customLabel.text = String(arc4random_uniform(999))
} else {
changeImageContainer.isHidden = true
applyFilterContainer.isHidden = false
controllerFilters?.customLabel.text = String(arc4random_uniform(999))
}
}
}
class ControllerEdit: UIViewController {
#IBOutlet weak var customLabel: UILabel!
var image = UIImage(named: "newHello")
var delegate: UpdateImageProtocol?
#IBAction func changeImage(sender: UIButton) {
delegate?.userIsDone(image: image!)
}
}
class ControllerFilters: UIViewController {
#IBOutlet weak var customLabel: UILabel!
// TODO
}
Put a breakpoint in this function:
#IBAction func segmentAction(sender:UISegmentedControl){
if (segmentedControl.selectedSegmentIndex==0){
performSegueWithIdentifier("EditIAm", sender: nil)
}
else if (segmentedControl.selectedSegmentIndex==1){
performSegueWithIdentifier("EditIAm", sender: nil)
}
}
If it's not getting called, then you probably didn't connect it to an action in IB (is the circle to the left of the the #IBAction filled in?)
If it is getting called, then make sure the segue names are right -- also, fix the one in the else if, because it looks like you want "FilterIAm" there.
Then, put a breakpoint in prepareForSegue:... -- is that getting called? If not, recheck the names are the same as in IB.
EDIT: based on comment
Your prepareForSegue is not supposed to create the ViewController. The destination view controller is created as a consequence of performing the segue and passed to this function.
if segue.identifier == "EditIAm"{
let nextView = segue.destinationViewController as! ControllerEdit
nextView.originalImage=self.imageView.image!
nextView.delegate=self
}
You don't need to present anything -- the destinationViewController is going to be presented. You can set any of its variables (as you have) - that is what is meant by preparing for the segue.

how to send data from popover view controller to main view controller. Pass a string so that i can make that as the label text in the main view

I tried many methods to send data from my popup view controller to main view controller. but failed. can anybody help me with this.
i am using a "present as popover" segue. i want to the text entered in textfield of popover view as the label text of main view.
From Popup View, Data send to Main ViewController using protocol in Swift 3.
enter image description here
Complete Details are given below...
1. View Controller Implementing with Protocol named sendDataToViewProtocol.
import UIKit
class ViewController: UIViewController,sendDataToViewProtocol {
#IBOutlet weak var lshowDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnShowPopUpDialog(_ sender: Any) {
let popUpVc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PopupVIewController") as! PopupVIewController
//Don't forget initialize protocal deletage
popUpVc.delegate = self
self.addChildViewController(popUpVc)
popUpVc.view.frame = self.view.frame
self.view.addSubview(popUpVc.view)
popUpVc.didMove(toParentViewController: self)
}
func inputData(data: String) {
lshowDataLabel.text = data
}
}
Popup View Controller With Protocol named sendDataToViewProtocol below.
3.protocol declare outside the PopupVIewController.
Don't forget to assign ViewController to PopupVIewController .
In viewController withIdentifier: "PopupVIewController" , "PopupVIewController" is PopupVIewController storyborad Id.
Please see the attached image.
import UIKit
protocol sendDataToViewProtocol {
func inputData(data:String)
}
class PopupVIewController: UIViewController {
//Protocol object
var delegate:sendDataToViewProtocol? = nil
#IBOutlet weak var txtInputFieldText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor
.black.withAlphaComponent(0.8)
}
#IBAction func btnSendDataToViewController(_ sender: Any) {
//"Check Delegate nil"
if(delegate != nil){
//Check textField is empty
if(txtInputFieldText.text != ""){
//set textField Data to protocol Function
delegate?.inputData(data: txtInputFieldText.text!)
self.view.removeFromSuperview()
}
}
}
#IBAction func btnClose(_ sender: Any) {
self.view.removeFromSuperview()
}
}
First of all, keep a temporary variable in your Main ViewController. Let's call it:
var somethingCool: String?
Then, in your popup ViewController code, assuming you have your segue trigger there, you will need to add in a new method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "your_segue_identifier" {
if let vc = segue.destination as? MainViewController {
vc.somethingCool = "whatever_you_want"
}
}
}

Swift iOS -How to pop to different SegmentControl Index using a NavigationController

//Index=0 //contains buttonToSwitchTab to pop to TabOneVC
TabZeroNavCntrlr ----TabZeroVC --- TabChangeVC
//segmntCntrl /
NavCntlr--ContainerView---
\ //Index=1
TabOneNavCntrlr ---- TabOneVC
I have a NavController that hasContainerView connected to it. Inside the containerView there is a segmentControl. The containerView has 2 more nav controllers connected to it that each lead to their own child controllers: TabZeroVC and TabOneVC. Initially the first view shown is TabZeroVC (selectedSegmentIndex = 0). There is a button inside there that leads to TabChangeVC which has a buttonToSwitchTab inside of it. Pressing that button I want to pop to TabOneVC.
I tried this code below by using my navigation controller to make the switch but on the line tabOneVC.segmentedControl.selectedSegmentIndex = 1 I get a crash: unexpectedly found nil while unwrapping an Optional value
I put a break point on it and the containerVC gets initialized but it's 3 properties segmentedControl, tabZeroVC, and tabOneVC are all coming up as nil. Since segmentedControl is nil that's where the crash is happening.
class TabChangeVC: UIViewController{
#IBAction func buttonToSwitchTab(sender: UIButton){
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let containerVC = mainStoryboard.instantiateViewControllerWithIdentifier("ContainerViewController") as! ContainerViewController
containerVC.segmentedControl.selectedSegmentIndex = 1 //crash happens here
self.navigationController?.popToViewController(containerVC, animated: true)
}
}
ContainerView:
class ContainerViewController: UIViewController{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tabZeroVC: UIView!
#IBOutlet weak var tabOneVC: UIView!
...
}
TabOneVC is selectedSegmentIndex = 1
My question is how do I pop to TabOneVC from the TabChangeVC and more importantly why is the containerVC's segmentedControl coming up as nil?
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil
let tabOneVC = mainStoryboard.instantiateViewControllerWithIdentifier("TabOneNavigationController") //TabOneNavCntrlr identifire
tabOneVC.segmentedControl.selectedSegmentIndex = 1
appDelegate.window!.rootViewController = tabOneVC
I don't know why the segmentedControl is coming up as nil but I was able to pop to tabOneVC by using NSNotificationCenter. I created a func named tabSwitch() with the segment I wanted to show and then I added a listener inside the viewDidLoad of the ContainerViewController:
class ContainerViewController: UIViewController{
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tabZeroVC: UIView!
#IBOutlet weak var tabOneVC: UIView!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(tabSwitch), name: "tabSwitch", object: nil)
}
func tabSwitch(){
self.segmentedControl.selectedSegmentIndex = 1
self.tabZeroVC.hidden = true
self.tabOneVC.hidden = false
}
}
Inside the TabChangeVC I added a notifier to trigger it and navigationController?.popToRootViewControllerAnimated(true) to get back to the root vc:
class TabChangeVC: UIViewController{
#IBAction func buttonToSwitchTab(sender: UIButton){
NSNotificationCenter.defaultCenter().postNotificationName("tabSwitch", object: nil)
self.navigationController?.popToRootViewControllerAnimated(true)
}
}

Custom View Label Won't Change

My view contain a button which bring a custom view which contain a ContainerView and it has a label. When I bring the custom view to the front I get the label text empty. So please where would be my issue?
View Controller Code:
#IBAction func PushAlert(sender: UIButton) {
let alert = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("alertViewController") as! AlertViewController
var alertText = AlertTextViewController()
alertText.lblText = "DONE"
alert.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
alert.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
self.presentViewController(alert, animated: true, completion: nil)
}
class AlertTextViewController: UIViewController {
#IBOutlet weak var lblAlertText: UILabel!
var lblText = ""
override func viewDidLoad() {
super.viewDidLoad()
lblAlertText.text = lblText
}
}
I have created a link for the source code which will be much easier to understand source code
What about var lblText: String? in your AlertTextViewController.
Maybe it's overriden by your declaration (for some reason I wouldn't be able to explain)?
Or wait, I'm confused by
var alertText = AlertTextViewController()
as you are presenting creating and presenting the ViewController alert. How is alertText and alert related?
Edit:
class AlertViewController: UIViewController {
#IBOutlet weak var container: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var alertText: AlertTextViewController = self.childViewControllers[0] as! AlertTextViewController
alertText.lblAlertText.text = "Test"
// Do any additional setup after loading the view.
}
Do this in your AlertViewController, and it works. I don't know what your final purpose is though, and this might not be the most elegant solution. I've created an outlet, the containerView to your class.
I think you may have a order of events problem. Try setting lblAlertText.text in viewWillAppear instead of viewDidLoad.

Resources