Swift iOS -storyboard.instantiateViewController(withIdentifier:) causing retain cycle - ios

I'm using my physical device and not the simulator.
I'm instantiating a vc using storyboard.instantiateViewController(withIdentifier:) and presenting it modally. I dismiss it using presentingViewController?.dismiss(animated: true, completion: nil). Inside the presented vc I have a print method inside Deinit that never runs.
I went to Instruments > Allocations > Statistics > Allocation Summary > MyApp.ThePresenedController and it shows 2 faces saying something is wrong. When I clicked them it took me to the presenting vc's code where I instantiated the vc to present and highlighted it green. After the presented vc is dismissed it's not removed from the Allocation Summary list. Inside the presented vc there isn't a reference to the presenting vc so it's not a weak var problem.
How come storyboard.instantiateViewController(withIdentifier:) is causing me a problem?
Presenting VC:
#IBAction func forgotPasswordButtonTapped(_ sender: UIButton) {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let forgotPasswordVC = mainStoryboard.instantiateViewController(withIdentifier: "ForgotPasswordController") as! ForgotPasswordController
let navVC = UINavigationController(rootViewController: forgotPasswordVC)
present(navVC, animated: true, completion: nil)
}
Presented VC:
#IBAction func cancelButtonTapped(_ sender: UIButton) {
presentingViewController?.dismiss(animated: true, completion: nil)
}
deinit{
print("I've been dismissed")
}
I'm also using the same storyboard.instantiateViewController(withIdentifier:) code inside AppDelegate and the same 2 faces and highlighted green error is occurring.
AppDelegate didFinishLaunching:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if userDoesThis {
// if true this first line will highlight green
let thisVC: ThisController = mainStoryboard.instantiateViewController(withIdentifier: "ThisController") as! ThisController
let nav = UINavigationController(rootViewController: thisVC)
} else {
// if false this first line will highlight green
let thatVC: ThatController = mainStoryboard.instantiateViewController(withIdentifier: "ThisController") as! ThatController
let nav = UINavigationController(rootViewController: thatVC)
}
window?.rootViewController = nav
window?.makeKeyAndVisible()
return true

As #StevenFisher suggested inside the comments the problem wasn’t the green highlighted line itself but instead was a closure that I overlooked and didn’t declare with [weak self]. I’ve read articles that says that pressing the faceless faces takes you to the offending line of code but Steve pointed it that might not be the issue and it can/will instead take you to where the problem starts. In my situation it let me know that I had a problem somewhere in the file as soon as it was instantiated and not the line itself.

Related

Swfit4. Initial ViewController is the wrong for a split second, then it fixes

In my app, the first time the app is used, the user is taken to a specific view controller (TutorialPageViewController). Every other time its used, the user is taken to a different view controller (HomeViewController).
To do this, I created a third view controller (FirstBlankViewController), where I show the logo of the app for one second. After that second, I call the appropriate view controller. This works, however when the app is opened, for a split second, you see the wrong view controller (ie TutorialPageViewController or HomeViewController) then the first one opens, and after a second it does the right thing.
But why is anything appearing before the FirstBlankViewController, which is set as the initial view controller. It shouldn't appear, even for a split second.
Here is the code in the FirstBlankViewController:
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
if launchedBefore {
print("Not first launch.")
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
self.present(newViewController, animated: false, completion: nil)
} else {
print("First launch")
self.dismiss(animated: false)
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "TutorialPageViewController") as! TutorialPageViewController
self.present(newViewController, animated: false, completion: nil)
}
})
}
I hope its clear!
Thanks
The flicker is an iOS bug/feature that happens with every app, including system applications. It is a caching system that shows a screencap from the application. It tries to replicate the last state of the app, however, it's far from perfect. Note that the flicker occurs regardless you've set a launch screen/launch image or not.
Just ignore it and move on.

Attempt to present UIImagePickerController on ViewController whose view is not in the window hierarchy

I'm in trouble, after that I'm moving from View Controller to a second View Controller and then switching back to the View Controller and clicking on the UI Image Picker it's not appearing and bringing me back to the second View Controller. On the debugger it's appearing:
Attempt to present on whose view is not in the window hierarchy!
I'm really becoming crazy. My code that is bringing me back from second View Controller to the first View Controller is inside a button and it's below:
#IBAction func profileButton(_ sender: Any) {
let webVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
webVC.profileLink = linkProfilo
self.present(webVC, animated: false, completion: nil)
}
in second view controller add this variable:
var firstVC: ViewController?
and this setter function to set it
setup(firstVC: ViewController) {
self.firstVC = firstVC
}
in the first view controller, when presenting second view controller pass in a reference to self (first view controller)by calling the setup function
let secondVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as! ViewController
secondVC.setup(firstVC: self)
self.present(secondVC, animated: false, completion: nil)
now when button is clicked in secondVC,
guard let first = self.firstVC else {return}
first.profileLink = linkProfilo
self.dismiss(animated: false, completion: nil)
I finally found the way for fix all of this:
#IBAction func profileButton(_ sender: Any) {
if let webVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as? ViewController {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
webVC.profileLink = linkProfilo
dismiss(animated: false, completion: nil)
appDelegate.window?.rootViewController!.present(webVC, animated: false, completion: nil)
}
}
it's enough to add the appDelegate sharing method and dismiss before to present the webVC for share the data in the right way and don't cause problems on the first view when presented.

Swift tried to present modally an active controller [Current view controller] even though the presented controller is another one

I have read some answers like dismissing the current ViewController but my situation is different because I am presenting another ViewController.
This code presents the view controller with its navigation controller although I cannot access it's properties:
#IBAction func didTouchAddFriend(_ sender: UIButton) {
DispatchQueue.main.async {
let storyBoard = UIStoryboard(name: "CustomContact", bundle: nil)
let customContactVC = storyBoard.instantiateViewController(withIdentifier: "CustomContact")
}
}
While this one doesn't work
#IBAction func didTouchAddFriend(_ sender: UIButton) {
DispatchQueue.main.async {
let navigation = UIStoryboard.init(name: "CustomContact", bundle: nil).instantiateInitialViewController() as! UINavigationController
let pickerUserVC = navigation.viewControllers.first as! CustomContactsViewController
self.present(pickerUserVC, animated: true, completion: nil)
}
}
I have another story board named CustomContact.storyboard which containts a CustomContactsViewController
When I click the didTouchAddFriend, it just shows me this error:
'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller <Main.MainViewController: 0x7ff29f06ac00>.'
I cannot understand the error cos when I printed the value of pickerUserVC, it show the right view controller:
<Main.CustomContactsViewController: 0x7fd1b7922400>
MainViewController is the current view controller implementing the didTouchAddFriend function
You ought to present the navigation controller (which hosts the CustomContactsViewController as it's root view controller).
Moreover, you can skip the Dispatch call, since we are in an #IBAction which already runs in the main thread:
#IBAction func didTouchAddFriend(_ sender: UIButton) {
let navigation = UIStoryboard.init(name: "CustomContact", bundle: nil).instantiateInitialViewController() as! UINavigationController
let pickerUserVC = navigation.viewControllers.first as! CustomContactsViewController
self.present(navigation, animated: true, completion: nil)
}

RevealViewController not working after programmatically set a view in iOS / Swift

First of all, let me introduce what I'm trying to do. Second... I'm new to Swift/iOS development.
What I'm trying to do is:
When I open the APP, it loads the LoginVC. When the user logs in, the TableSelectionVC loads. If the user selects a cell, it goes to the Home screen with the selected values. If the user taps on the bell (blue arrows) the app should go to AlarmVC.
It should work, but it doesn't. The AlarmVC says that self.revealViewController() is nil. BUT If I go to alarmVC through the menu option (red arrows) it loads normally and the Menu is shown. Also, if I choose the option in green, it goes to the TableSelectionVC and if I tap the icon, it goes to alarmVC and it doesn't crash. The problem is if I go from LoginVC to TableSelectionVC and tap on the icon, the it will crash.
I think it is the way that I'm setting the views, instantiating a controller and setting the window.rootViewController.
In my AppDelegate I have the following functions:
func changeRootViewControllerToSWRevealViewController () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SWRevealViewController")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
func changeRootViewControllerToPlantSelectionVC () {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
controller.modalTransitionStyle = .flipHorizontal
if let window = self.window {
window.rootViewController = controller
}
}
When the user logs in the app, the following function is executed:
static func goToPlantSelection() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
//there should be an alert error
return
}
appDelegate.changeRootViewControllerToPlantSelectionVC()
}
After that, the PlantSelectionVC is shown and if the user taps on an cell the appDelegate.changeRootViewControllerToSWRevealViewController() is executed and the HomeVC is shown.
If the user taps on the icon, it tries to go to the AlarmVC but it crashes, like I said. I think I'm doing something wrong with binding the SWRevealViewController with LoginVC and TableSelectionVC.
The following code in AlarmVC tries to execute:
static func setupMenuToggle(button: UIBarButtonItem, viewController: UIViewController) {
button.target = viewController.revealViewController()
viewController.revealViewController().rearViewRevealWidth = viewController.view.bounds.size.width * 0.9
button.action = #selector(SWRevealViewController.revealToggle(_:))
}
But is shows the error in the third line: found nil while unwrapping an Optional value
Can anyone help ?
I fixed it. I set the login screen as sw_front for the swRevealViewController and after that, when the user logs in I would change the swRevealViewController front view controller with
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "navPlantSelectionVC")
self.revealViewController().setFront(controller, animated: true)
It is working now.

Swift: Prevent flicker when presenting login screen at startup

We have two storyboards in our app. The default view in storyboard #1 assumes the user has previously supplied valid credentials. If they have not done so we "redirect" to the login screen in storyboard #2:
let storyboard = UIStoryboard(name: "Authentication", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("LoginController") as UIViewController
presentViewController(controller, animated: true, completion: nil)
This works - but there is a flicker when this code runs where the default view briefly appears prior to the login view being displayed. How can you perform this action without a flicker?
Instead of presenting LoginController you have to set the respective ViewController as rootviewcontroller to the window in Appdelegate itself as folllows:
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
//Check for credentials,if value available make LoginStatus as true
if LoginStatus == true{
//Change Storyboard name "Main" to your "storyboard #1" name.
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
//Change Viecontroller name "My_Offer" to your "DefaultViewController name" name.
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("My_Offer") as UIViewController
let navigationController = UINavigationController(rootViewController: vc)
self.window!.rootViewController = navigationController
}else{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Authentication", bundle: nil)
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("LoginController") as UIViewController
let navigationController = UINavigationController(rootViewController: vc)
self.window!.rootViewController = navigationController
}
self.window!.makeKeyAndVisible()
This will prevent from displaying default view before presenting LoginController.
Or else
In storyboard #1 create a DummyViewController(UIViewController) and make it as InitialViewController and set a Background image(Your SplashScreen Image) to DummyViewController .In viewdidload check for credentials,If You have value
let storyboard = UIStoryboard(name: "storyboard #1", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("default view ") as UIViewController
presentViewController(controller, animated: true, completion: nil)
if no credentials go with your given code
let storyboard = UIStoryboard(name: "Authentication", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("LoginController") as UIViewController
presentViewController(controller, animated: true, completion: nil)
this too prevents flicker but it is not good way to do.I suggest First Method setting rootviewcontroller.This may help you.Give it a try.
My suggestion is to set launch screen viewcontroller as rootviewcontroller of the application until the all your deciding factor get executed. This is one of the finest approach for async operation.

Resources