I want to navigate view controller conditionally. so before view controller load i need to check condition for navigate. I do same as below. also this is not my initial view else i can put condition in appDelegate.
class LoginController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(false)
if(!fileMgr.getCacheData(constants.defaultsKeys.KEY).isEqual("")){
dispatch_async(dispatch_get_main_queue()) {
let listing = self.storyboard?.instantiateViewControllerWithIdentifier("listing") as! ListingController
self.showViewController(listing as UIViewController, sender: listing )
}
}
}
}
but it loads the base controller first then load listing controller. Any idea how to deal with this situation ?
Related
I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.
I am new in Programming and swift, and I have tried to read some solutions in stack overflow, but to be honest I don't really grasp with the answer :(
I have 2 view controllers. a homeVC and a LoginVC. homeVC is my initial view controller. in viewDidLoad I have firebase function that can check if the user has logged in before or not. if not, then the user will be send to loginVC. here is my simplified code in the HomeVC
import UIKit
import Firebase
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
}
and here is my loginVC
import UIKit
import Firebase
import GoogleSignIn
class LoginVC : UIViewController, GIDSignInUIDelegate {
#IBOutlet weak var googleButton: GIDSignInButton!
#IBOutlet weak var emailButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// delegate declaration
GIDSignIn.sharedInstance().uiDelegate = self
}
#IBAction func googleButtonDidPressed(_ sender: Any) {
GIDSignIn.sharedInstance().signIn()
}
}
the app can perform as I expected. but there is a warning in my debugging area :
Warning: Attempt to present LoginVC: 0x7fc315714f40 on
HomeVC: 0x7fc3155095c0 whose view is not in the window
hierarchy!
of course the problem is in this lines of code
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
as far as I know, if the view is stacked in the layer of navigation controller, then if I want to move to another view controller I have to use perform segue method.
But for this case, between homeVC and LoginVC are not stacked in the same navigation controller. so no hierarchy. thats why I use that line of code to move to another view controller (loginVC). but I don't understand why it is said "view is not in the window hierarchy!"
So what should I do to omit that warning?
Move code to viewDidAppear
override func viewDidAppear(_ animated:Bool) {
super.viewDidAppear(true)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
Your LoginVC is perfectly fine.
However, you need to change your HomeVC as #Sh_Khan suggested and move the testing code from viewDidLoad to viewDidAppear:
import UIKit
import Firebase
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// HomeVC.view was added to a view hierarchy
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
}
}
Explanation
Your viewDidLoad method gets called before the viewController gets presented, so it at that moment it cannot really present another view controller (since it itself is not presented), viewDidLoad documentation:
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
In that moment the viewController is not in the window hierarchy yet.
viewDidAppear however gets called when the view is presented and becomes a part of the window hierarchy, viewDidAppear documentation:
Notifies the view controller that its view was added to a view hierarchy.
You can override this method to perform additional tasks associated with presenting the view. If you override this method, you must call super at some point in your implementation.
Don't forget to call super.viewDidAppear during overriding it.
TLDR; You should move your code to viewDidAppear
viewDidLoad()
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
Apple docs
So the view is only in memory yet and not in the hierarchy. You should move it to viewDidAppear
viewDidAppear()
Notifies the view controller that its view was added to a view hierarchy. Apple docs
As Sh_Khan said, move the lines:
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
in viewDidAppear() method; when viewDidLoad() gets called, the view controller it's not added on the view hierarchy yet, it's not visible and it cannot present another view controller.
The reason for such kind of error is: You are trying present (open) two view controllers simultaneously (view of first presenting view controller is just started and you may be trying to present second view controller).
You should move your code (for view controller presentation/navigation) to viewDidAppear. Your main view of existing view controller (from where you are presenting new view controller) is not ready/loaded.
You should move it to viewDidAppear.
Here is sample code:
Swift 4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// instantiate your view controller either using storyboard or class or any other way....
if let newVC = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController {
self.present(newVC, animated: true, completion: nil)
}
}
In your case/code, solution is:
override func viewDidLoad() {
super.viewDidLoad()
// Move your code from here (viewDidLoad) to viewDidAppear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
Look at the difference between both view controller life cycle.
viewDidLoad
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
See more about: viewDidLoad
viewDidAppear
Notifies the view controller that its view was added to a view hierarchy.
See more about: viewDidAppear
At this point in your code, the view controller's view has only been created but not added to any view hierarchy. If you want to present from that view controller as soon as possible you should do it in viewDidAppear to be safest.
I have some problem to present a view controller within details view controller of master details view controller. Is it possible to do that?
I want to present a view controller when the user taps a button on details view controller exactly like details view controller though it is not.
If it is possible then help me. If not then guide me some way to do that.
You can try it from Storyboard like,
In the storyboard, select the Segue as present modally, and go to the Identity Inspector, and choose Current Context for the Presentation option.
Hope it helps.
It's possible, but your details VC must be a navigation Controller, so you can present.
Try this :
class func presentViewCoontroller(vc : UIViewController) -> Void{
let viewController : UIViewController = ((UIApplication.shared.delegate as! AppDelegate).window?.rootViewController)!
if (viewController.presentedViewController != nil) {
viewController.presentedViewController?.present(vc, animated: true, completion: nil)
}
else{
viewController.present(vc, animated: true, completion: nil)
}
}
Calling
self.presentViewCoontroller(vc: yourcontroller)
create a static instance of master view controller and then try to present a new view controller with that reference.
class MasterViewController : UIViewController{
static var masterVC : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
MasterViewController.masterVC = self
}
}
class DetailViewController : UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didTapButton(_ sender: UIButton) {
MasterViewController.masterVC?.present(<newViewController>, animated: true, completion: nil)
}
}
I'm using XLPagerTabStrip to switch among a collection of view controllers. I have three view controllers and I would like that middle view controller is shown by default as first.
I could use
let parentViewController = self.parent! as! ParentViewController
parentViewController.moveToViewControllerAtIndex(1)
inside my first view controller, but that first view controller loads some data from the server and if I switch to another view controller while it is loading data, that first view controller will freeze and it won't load data.
Is there a way to show middle view controller as first by default?
jump to the defenition of 'currentIndex' and change it to public from private. then you can select your current controller by this code:
currentIndex = 1
In function:
override func viewControllers(for pagerTabStripController:
PagerTabStripViewController) -> [UIViewController] {
// This line will help you achieve the requirement
pagerTabStripController.currentIndex = /* required index */
}
It will work smoothly after you make currentIndex in PagerTabStripViewController as public.
To prevent loading the first tab, moveToViewControllerAtIndex() must be called before viewDidLoad() is called in your PagerTabStripViewController subclass.
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
pagerTabStripController.moveToViewController(at: 0) // required index
}
For Move Specific Tab XLPagerTabStrip in swift 5
override func viewDidAppear(_ animated: Bool) {
if nowFrom == "sendvc"
{
self.moveToViewController(at: 3,animated: false)
}
}
You have to use the following lines:
override func viewDidAppear(_ animated: Bool) {
self.moveToViewController(at: 2)
reloadPagerTabStripView()
}
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)
}
}