Call Function only once in UIViewController, iOS Swift4.2, Xcode10.1 - ios

I have function call (pop view) in my 1st view controller which have to be called only once in app. Since then whenever I return back to 1st View controller the function need not to be called again.
func popView() {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popView") as! popView
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
}
I have tried the following code and previous other sources in stack overflow, didn't work though..
///// Once Action in View Controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isBeingPresented || self.isMovingToParent {
// Perform an action that will only be done once
popView()
}
}

Maybe this works. In your ViewController, add a static property:
static var shouldPop = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isBeingPresented || isMovingToParent {
// Perform an action that will only be done once
if (type(of: self).shouldPop) {
type(of: self).shouldPop = false
popView()
}
}
}
Of course, depending on your setup, this won't work if you have more than one instance of this viewcontroller that should keep their own state on whether popView should be called or not.

You should call this in viewDidLoad method. It's called once per UIViewController life cycle.
Documentation here.
Just like this:
override func viewDidLoad() {
super.viewDidLoad()
if self.isBeingPresented || self.isMovingToParent {
// Perform an action that will only be done once
popView()
}
}
If your way is pop view after you was once in view controller you could do like this:
/// bool that help indicate your visit
var isViewControllerVisited = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isViewControllerVisited {
// Perform an action that will only be done once
popView()
}
//change it here
isViewControllerVisited = true
}
Hope it's help!

If you want to call that PopView function only once in you App then try this,
In App delegate, set bool value
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UserDefaults.standard.set(true, forKey: "showPop") // like so
return true
}
Then, in first view controller try this,
func hasLaunchPop() {
let isshowPop: Bool = UserDefaults.standard.bool(forKey: "showPop")
if isshowPop == true {
popView()
UserDefaults.standard.set(false, forKey: "showPop")
}
}
then in viewdidload call like this,
override func viewDidLoad() {
super.viewDidLoad()
hasLaunchPop()
}
So that PopView appears only once in your app when its launched and will never show up again.

For me I prefer to use lazy loading. This allow not to write any logic, just need to use Swift lazy var declaration. Something like this:
private lazy var viewDidAppearOnce: Bool = {
popView()
}()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_ = viewDidAppearOnce
}

Below code, try it..
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UserDefaults.standard.set(false, forKey: "isPopOverVCPopped")
if UserDefaults.standard.bool(forKey: "isPopOverVCPopped") == false {
UserDefaults.standard.set(true, forKey: "isPopOverVCPopped")
popView()
}
}

As per the view controller's lifecycle, the viewDidLoad method only gets called once. so you should call your method only there.
Or you can try the following code:
var isScreenAppeared = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isScreenAppeared {
popView()
}
isScreenAppeared = true
}

Related

Goto next ViewController right away?

I navigate to LoadUserDataViewController after a login view controller, load some user data, then automatically go to HomeViewController. Right now it only works if I use a button.
class LoadUserDataViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//this doesn't transition to the Home View Controller
transitionToHome()
}
//this does transition to the home view controller
#IBAction func gotoNextVCButton(_ sender: Any) {
transitionToHome()
}
func transitionToHome() {
let homeViewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewController) as? HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
}
}
Try performing the transitionToHome in viewDidAppear instead.
class LoadUserDataViewController: UIViewController {
//...
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
transitionToHome()
}
}
Alternatively you can provide a delay:
class LoadUserDataViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
perform(#selector(transitionToHome), with: self, afterDelay: 0.5)
}
#objc func transitionToHome() {
//...
}
}

Swift - Run code when user dismiss UIReferenceLibraryViewController

I need to run a piece of code when user dissmisses the UIReferenceLibraryViewController. But my code that I previously coded isn't working on iOS 13.
Here is the code I wrote for iOS 12:
override func viewDidLoad() {
if UIReferenceLibraryViewController.dictionaryHasDefinition(forTerm: word) {
let ref: UIReferenceLibraryViewController =
UIReferenceLibraryViewController(term: word)
ref.reactive
.trigger(for: #selector(onboardNav.viewDidDisappear(_:)))
.observe { _ in self.handleModalDismissed() }
self.present(ref, animated: false, completion: nil)
}
}
func handleModalDismissed() { // I need to run this function when user presses "Back" button
self.showAlert(error: false, word: "")
}
I just found an answer! You need to subclass a UIReferenceLibraryViewController, and override viewWillDisappear. Like so:
class ReferenceLibraryViewControllerWithDismiss: UIReferenceLibraryViewController {
override func viewWillDisappear(_ animated: Bool) {
// your code
}
}
And then present ReferenceLibraryViewControllerWithDismiss:
let vc = ReferenceLibraryViewControllerWithDismiss()
self.present(vc, animated: true)
Happy coding!

Swift 4 One View Controller two views hide navigation bar

I have one View Controller and this View Controller contains two views/scenes in the main.storybard.
I am trying to hide the top navigation bar at first view/scene, but unhide it again on the second view/scene.
I tried with
self.navigationController?.isNavigationBarHidden = true
But this will only work with two View Controller classes.
Does anyone have a idea to manage it?
Hide navigationbar in viewWillAppear & unhide in viewWillDisappear
var shouldHideNavBar = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(shouldHideNavBar, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if shouldHideNavBar == true {
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
And when you perform segue set shouldHideNavBar as true
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "show") {
let viewController = segue!.destinationViewController as! ViewController
viewController.shouldHideNavBar = true
}
}
You should use extra control. Or you can create an IBInspactable variable and assing it's value on Interface Builder. Like this:
#IBDesignable class myViewController: UIViewController{
#IBInspectable var isNavbarHidden: Bool = true{
didSet{
self.navigationController?.isNavigationBarHidden = isNavBarHidden
}
}
override func viewDidLoad(){
super.viewDidLoad()
//I am not sure if this line is necessary
self.navigationController?.isNavigationBarHidden = isNavBarHidden
}
}
After then go to InterfaceBuilder(your storyboard file) and set its value for your Scenes on your viewControllers properties.

Finish editing UITextField on back button tap

I have 2 controllers inside NavigationController. First pushes the second one to the stack and user can interact with the text field there. Then (in one scenario) user will tap on back button to be taken to the previous screen. Assuming that loading of second one is 'heavy', so I will be keeping only one instance of it once it is needed.
Expected:
I would like to have keyboard hidden once back button is pressed.
Actual:
First responder keeps being restored when I go back to the second for the second time. How to prevent that? Resigning first responder also doesn't do the trick there...
Problem demo:
https://gitlab.com/matrejek/TestApp
Major code parts:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
}
This does seem odd --- and it seems like your approach should work.
Apparently (based on quick testing), since you are not allowing the Navigation Controller to release the SecondVC, the text field is remaining "active."
If you add this to SecondViewController, it will prevent the keyboard from "auto re-showing" the next time you navigate to the controller - not sure it will be suitable for you, but it will do the job:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.view.endEditing(true)
}
}
Edit: Jan 25 2020
Based on new comments, yes, this seems to be a bug.
My previous work-around answer worked -- sort of. The result was the keyboard popping up and then disappearing on subsequent pushes of child.
Following is a better work-around. We have SecondViewController conform to UITextFieldDelegate and add a BOOL class var / property that will prevent the text field from becoming first responder. Comments should be clear:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
// conform to UITextFieldDelegate
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
// bool var to prevent text field re-becoming first responder
// when VC is pushed a second time
var bResist: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// assign text field delegate
textField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// view has appeared, so allow text field to become first responder
bResist = false
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return !bResist
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// end editing on this view
view.endEditing(true)
// we want to resist becoming first responder on next push
bResist = true
}
}

Show a View on First Launch Only - Swift 3

I am implementing a Terms & Conditions view into my app and the user has to accept them to proceed, then when they do accept them, they no longer have to go through the Terms & Conditions view. I followed a tutorial on how to integrate UserDefaults and store the value locally if someone does accept the terms. However I am stuck with implementing it into the root view controller. Specifically stuck on my viewDidAppear function. What goes in the if and else statements?
class TermsAndConditionsViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var termsTextView: UITextView! {
didSet {
termsTextView.delegate = self
}
}
#IBOutlet weak var acceptButton: UIButton! {
didSet {
acceptButton.isHidden = true
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
acceptButton.isHidden = scrollView.contentOffset.y + scrollView.bounds.height < scrollView.contentSize.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if UserDefaults.standard.bool(forKey: "termsAccepted") {
} else {
}
}
#IBAction func acceptButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "toPeekView", sender: sender)
}
}
Probably, you mean "Show a ViewController on First Launch Only".
Using UserDefaults for this purpose is a good idea, however, checking if the term accepted should not be at the TermsAndConditionsViewController layer, instead, it should be in AppDelegate - application:didFinishLaunchingWithOptions, you can decide in it whether the root ViewController should be the TermsAndConditionsViewController or the other ViewController (HomeViewController for example).
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let rootViewController = storyboard.instantiateViewController(withIdentifier: UserDefaults.standard.bool(forKey: "termsAccepted") ? "termsViewControllerID" : "homeViewControllerID")
window?.rootViewController = rootViewController
return true
}
TermsAndConditionsViewController:
class TermsAndConditionsViewController: UIViewController {
//...
#IBAction func acceptButtonTapped(_ sender: Any) {
UserDefaults.standard.set(true, forKey: "termsAccepted")
performSegue(withIdentifier: "toPeekView", sender: sender)
}
//...
}
Hope this helped.

Resources