not getting navigationController?.pushViewController to work but present does? - swift4

I do have a non-Storyboard, 100% coded UIViewController, UICollectionView and UICollectionViewCell - works perfect.
here's the code in question:
SceneDelegate
not sure if this is relevant, tho.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let myController = MyViewController(collectionViewLayout: layout)
window?.rootViewController = myController
window?.makeKeyAndVisible()
}
.
.
ViewController
very simple and straight forward...
class MyViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let data = loadOnboardingData()
.
.
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
collectionView?.register(MyPageCell.self, forCellWithReuseIdentifier: "cellId")
collectionView?.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView?.tag = myPageControl.currentPage
setupMyPageControl()
}
ViewControllerExtention
here's the problem: the pushViewController method just doesn't do anything but the modal present works like a charm and I'm not getting what's wrong and why:
extension MyViewController: MyPageCellDelegate {
.
.
func didTabOnActionButton(title: String) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let homeViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else {
print("Coun't find controller")
return
}
navigationController?.pushViewController(homeViewController, animated: true) <- NO EFFECT
//present(homeViewController, animated: true, completion: nil) <- WORKS PERFECT!
}
MyPageCell
I set up the Delegate via protocol and it seems that's fine too
protocol MyPageCellDelegate {
func didTabOnActionButton(title: String)
}
class MyPageCell: UICollectionViewCell {
var delegate: MyPageCellDelegate?
let myActionButton: UIButton = {
let button = UIButton(type: .system)
return button
}()
myActionButton.addTarget(self, action: #selector(self.doAction), for: .touchUpInside)
.
.
#objc private func doAction(_ sende: Any) {
delegate?.didTabEndOnboardingActionButton(title: "end Onboarding")
}
so, any Idea what's wrong with:
navigationController?.pushViewController(homeViewController, animated: true)
EDIT --------------------------------------------------------------------
As pointed out by #Michcio this here: window?.rootViewController = UINavigationController(rootViewController: myController) works half way and as far as I understand it, I'm embedding myController into an UINavigationController which adds the Navigation Bar to the current and following controllers.
But that's not what I need!
What I need is a clean and simple one for the onboarding i.e. MyViewController and the HomeViewController should be one with a Tab- and Navigation Bar
Basically starting from scratch after onboarding.
I used to solve this in the previous version editing the AppDelegate first Method like this (in this example I used Storyboards):
extension AppDelegate {
func showOnboarding() {
if let window = UIApplication.shared.keyWindow, let onboardingViewController = UIStoryboard(name: "Onboarding", bundle: nil).instantiateInitialViewController() as? OnboardingViewController {
onboardingViewController.delegate = self
window.rootViewController = onboardingViewController
}
}
func hideOnboarding() {
if let window = UIApplication.shared.keyWindow, let mainViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
mainViewController.view.frame = window.bounds
UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {
window.rootViewController = mainViewController
}, completion: nil)
}
}
}
and in the Delegate itself like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let isFirstRun = true // logic to determine goes here
if isFirstRun {
showOnboarding()
}
return true
}
but I'm seriously not getting the new SceneDelegate or simply don't understand it
Really would appreciate if someone could past some code here for re-use.

It didn't work, because you are set MyViewController as window.rootViewController. Just change line in SceneDelegate to:
window?.rootViewController = UINavigationController(rootViewController: myController)

Related

Send user to a view controller from one that was made in code

I have a view controller on the storyboard that I'm trying to send the user to (TitleInfoVC), but the view controller I'm trying to send the user from was created in code, in TabBarVC, which is being set as the rootViewController in the SceneDelegate.
When I try either of the attempts in the DiscoverVC I'm getting the following error:
Attempt to present <x.TitleInfoVC: 0x12b417630> on <x.DiscoverVC: 0x12b411310> (from <xDiscoverVC: 0x12b411310>) whose view is not in the window hierarchy.
How do I send the user to the TitleInfoVC when the DiscoverVC isn't in the storyboard?
SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = TabBarVC()
window?.makeKeyAndVisible()
}
TabBarVC:
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = UINavigationController(rootViewController: DiscoverVC())
let vc2 = UINavigationController(rootViewController: SearchVC())
vc1.tabBarItem.image = UIImage(systemName: "house")
vc2.tabBarItem.image = UIImage(systemName: "magnifyingglass")
vc1.title = "Discover"
vc2.title = "Search"
setViewControllers([vc1, vc2], animated: true)
}
DiscoverVC (Where I'm trying to send the user from) :
func sendToView(titleID: Int) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let titleInfoVC = storyBoard.instantiateViewController(withIdentifier: "TitleInfoVC") as! TitleInfoVC
titleInfoVC.titleID = titleID
self.present(titleInfoVC, animated: true, completion: nil)
let titleInfoVC = TitleInfoVC()
titleInfoVC.titleID = titleID
self.navigationController?.pushViewController(titleInfoVC, animated: true)
self.present(titleInfoVC, animated: true, completion: nil)
}
TitleInfoVC (Where I'm trying to send the user to) :
#IBOutlet weak var titleIDLabel: UILabel!
var titleID = 0
override func viewDidLoad() {
super.viewDidLoad()
titleIDLabel.text = "Title ID \(titleID)"
}
//EDIT:
Where sendToView is being called from (completely different view controller)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedTitleID = titles[indexPath.row].id
let discoverVC = DiscoverVC()
discoverVC.sendToView(titleID: selectedTitleID)
}
From what you show you need to work with a shared DiscoverVC.
in TabBarVC :
// use the share discoverVC
let vc1 = UINavigationController(rootViewController: DiscoverVC.shared)
in DiscoverVC declare the shared instance :
class DiscoverVC: UIViewController {
static var shared = DiscoverVC()
in TableCellVC, use the shared instance
let discoverVC = DiscoverVC.shared
discoverVC.sendToView(titleID: selectedTitleID)

Black Screen when loading View from SceneDelegate

I get an nearly black screen when I try to load this view. But if I add view.backgroundColor = .red it shows the color red correctly. Just the settings I did in Storyboard wont get shown.
Here is the code I used:
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
window?.rootViewController = mainStoryboard.instantiateViewController(withIdentifier: "ResultsViewController") as! ResultsViewController
window?.makeKeyAndVisible()
}
import UIKit
class ResultsViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}
You seem to have created the ResultsViewController in the storyboard. You'll have to load it from the storyboard to set it as rootViewController.
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
window?.rootViewController = mainStoryboard.instantiateViewController(withIdentifier: "ResultsViewController") as! ResultsViewController
Note: Don't forget to set the identifier for ResultsViewController as "ResultsViewController" in the storyboard.

Different view controller display at start app

I try to explain situation:
I have two view controllers: viewHome and viewStartTest.
When student start app first time, don't have any data about his test in table.
In this situation should be display viewStartTest controller after launch screen.
But when he start app again and condition "test is finished" is true, viewHome controller should be display at start.
I try to put this code in AppDelegate.swift and simulate finished test but still not working, thanks for help:
// 0 - false, 1 - True
var conditionTest = 1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if conditionTest == 1 {
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewStartTest: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "viewStartTest")
self.window?.rootViewController = viewStartTest
self.window?.makeKeyAndVisible()
} else {
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ViewHome: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "viewHome")
self.window?.rootViewController = ViewHome
self.window?.makeKeyAndVisible()
}
}
Correct Scene delegate code after discussion below:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
if conditionTest == 1 {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewStartTest: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "viewStartTest")
self.window?.rootViewController = viewStartTest
self.window?.makeKeyAndVisible()
} else {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ViewHome: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "viewHome")
self.window?.rootViewController = ViewHome
self.window?.makeKeyAndVisible()
}
}
guard let _ = (scene as? UIWindowScene) else { return }
}
You need to store the value of conditionTest somewhere. I would suggest using UserDefaults . This is an example of how you could implement it:
NavigationController:
class MainNavigationControllerViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if isLoggedIn() {
let homeController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeVC")
viewControllers = [homeController]
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
}
Extension for UserDefaults:
extension UserDefaults {
func setIsLoggedIn(value: Bool) {
set(value, forKey: "isLoggedIn")
synchronize()
}
func isLoggedIn() -> Bool {
return bool(forKey: "isLoggedIn") }
}
How to use:
with the above code you can simply login/logout the user. Make sure to call .synchronize()
Login:
UserDefaults.standard.setIsLoggedIn(value: true)
UserDefaults.standard.synchronize()
Logout:
UserDefaults.standard.setIsLoggedIn(value: false)
UserDefaults.standard.synchronize()

Swift Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeedbbaff8)

I am trying to get a pod to work.
It is from here:
https://github.com/Ahmadalsofi/SOTabBar
I get no errors until runtime, where I get a long list of what seems like a thread loop of sorts.
Images:
1.Tread Stack
2.Line where error happens
3.Error Message
Where I can trace the error to:
My scene delegate entry to application: (I am not using storyboards)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
window?.rootViewController = MainTabBarVC()
window?.makeKeyAndVisible()
}
}
My MainTabBarVC:
import UIKit
import SOTabBar
class MainTabBarVC: SOTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = SearchVC(nibName: "Main", bundle: nil)
let secondVC = TestVC(nibName: "Main", bundle: nil)
firstVC.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "mappin.and.ellipse"), selectedImage: UIImage(systemName: "star"))
secondVC.tabBarItem = UITabBarItem(title: "Chat", image: UIImage(named: "secondImage"), selectedImage: UIImage(systemName: "mappin.and.ellipse"))
viewControllers = [firstVC, secondVC]
}
override func loadView() {
super.loadView()
SOTabBarSetting.tabBarHeight = 60.0
SOTabBarSetting.tabBarTintColor = UIColor.red
SOTabBarSetting.tabBarBackground = UIColor.purple
SOTabBarSetting.tabBarCircleSize = CGSize(width: 50.0, height: 50.0)
SOTabBarSetting.tabBarSizeImage = CGFloat(20)
SOTabBarSetting.tabBarSizeSelectedImage = CGFloat(40)
SOTabBarSetting.tabBarAnimationDurationTime = 2
}
}
extension MainTabBarVC: SOTabBarControllerDelegate {
func tabBarController(_ tabBarController: SOTabBarController, didSelect viewController: UIViewController) {
print("Hello")
}
}
My SearchVC and TestVC are just simple VCs with a different background color like:
import UIKit
class SearchVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemRed
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
The view controllers themselves are fine, the issue seems to be the pod. I tried looking at their github source but I don't know what could be causing it. So the issue is either the pod or my implementation of it.

loading childViewControllers first time logging in

I have been struggling with an issue for loading child ViewControllers. The first time I log in it shows the containerView but without the childViewControllers loaded. When I close the app and re-open the app with the logged in state saved the childViews in the containerView are displayed. I know it has something to do with my window hierarchy and rootviewController. I have researched this a bunch but still am having issues solving. Thanks in advance!
// app delegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = MainNavigationController()
return true
}
// mainNavigationController - RootViewController
class MainNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
homeController.firstViewController = vc1
homeController.secondViewController = vc2
viewControllers = [homeController]
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// log in function
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
mainNavigationController.viewControllers = [HomeController()]
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
let homeController = HomeController()
homeController.firstViewController = vc1
homeController.secondViewController = vc2
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}
// HomeController
class HomeController: UIViewController, FBSDKLoginButtonDelegate {
// child view controllers to put inside content view
var firstViewController: TravelersFeedVC?
var secondViewController: ProfileVC?
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(inactiveViewController: oldValue)
updateActiveViewController()
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMove(toParentViewController: nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = contentView.bounds
contentView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMove(toParentViewController: self)
}
}
// UI elements
lazy var contentView: UIView = {
let tv = UIView()
tv.backgroundColor = UIColor.blue
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.masksToBounds = true
return tv
}()
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
activeViewController = firstViewController
checkIfUserIsLoggedIn()
view.addSubview(contentView)
setupProfileScreen()
let items = ["Travelers", "Me"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
segmentedController.tintColor = UIColor.black
segmentedController.selectedSegmentIndex = 0
// Add function to handle Value Changed events
segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
// reference to collectionViewController
var travelersFeedVC: TravelersFeedVC!
func segmentedValueChanged(_ sender:UISegmentedControl!)
{
switch segmentedController.selectedSegmentIndex {
case 0:
activeViewController = firstViewController
case 1:
activeViewController = secondViewController
default: // Do nothing
break
}
}
In your finishLoggingIn() it looks like you are using 2 separate HomeController instances.
Something like this should fix it.
func finishLoggingIn() {
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
guard let mainNavigationController = rootViewController as? MainNavigationController else { return }
// create it
let homeController = HomeController()
// use it
mainNavigationController.viewControllers = [homeController]
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
// use it again
homeController.firstViewController = vc1
homeController.secondViewController = vc2
UserDefaults.standard.setIsLoggedIn(value: true)
dismiss(animated: true, completion: nil)
}