I know there are already a couple issues on this, but I can't figure it out. The previous solved issues would suggest that 'profileViewController' is nil, but I don't know why that would be the case. The UI is completely programmatic, no IB. Getting:
"fatal error: Can't unwrap Optional.None"
on pushViewController() in the following code:
class FavoritesViewController: UIViewController {
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Custom initialization
self.title = "Favorites"
self.tabBarItem.image = UIImage(named: "MikeIcon")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.redColor()
let profileButton = UIButton.buttonWithType(.System) as UIButton
profileButton.frame = CGRectMake(60, 300, 200, 44)
profileButton.setTitle("View Profile", forState: UIControlState.Normal)
profileButton.addTarget(self, action: "showProfile:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(profileButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showProfile(sender: UIButton) {
let profileViewController = ProfileViewController(nibName: nil, bundle: nil)
self.navigationController.pushViewController(profileViewController, animated: true)
}
Here's the relevant portion of AppDelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
// Override point for customization after application launch.
let feedViewController = FeedViewController(nibName: nil, bundle: nil)
let favoritesViewController = FavoritesViewController(nibName: nil, bundle: nil)
let profileViewController = ProfileViewController(nibName: nil, bundle: nil)
let tabBarController = UITabBarController()
self.window!.rootViewController = tabBarController
tabBarController.viewControllers = [feedViewController, favoritesViewController, profileViewController]
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
Is the Navigation Controller object self.navigationController nil?
The variable type on the navigationController property is an unwrapped optional. If your View Controller is not inside a UINavigationController that would be the problem.
To prevent the crash code along the following lines should be written:
if (self.navigationController)
{
self.navigationController.pushViewController(profileViewController, animated: true)
}
Alternatively you could write the code as:
self.navigationController?.pushViewController(profileViewController, animated: true)
The ? operator will prevent any further code from being executed, if self.navigationController evaluates to nil.
Related
i created two projects to learn how the delegate method is working..
one project created WITHOUT storyboard, just via code and my delegate is working just fine.
i built the other Project WITH storyboard, which means all ViewControllers are visible in the Interfacebuilder..
i am sure the issue lays in the definition of the ViewControllers in the code file:
let homeVC = HomeViewController()
Can someone please tell what is wrong here?
import UIKit
protocol HomeViewControllerDelegate: AnyObject {
func showMenu()
}
class HomeViewController: UIViewController {
var delegate: HomeViewControllerDelegate?
override func viewDidLoad() {
title = "App"
super.viewDidLoad()
configureNaviBar()
}
func configureNaviBar() {
// Left Bar Button Item
let burgerButton = UIImage(systemName: "line.horizontal.3")
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: burgerButton, style: .plain, target: self, action: #selector(showMenu))
}
#objc func showMenu(sender: AnyObject) {
print("show Menu (home)")
// homeDelegate is nil?
delegate!.showMenu() // throws an error!
}
}
import UIKit
class MainViewController: UIViewController {
let naviVC:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NaviVC") as! NaviVC
let menuVC:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SideMenuID") as! SideMenuViewController
let homeVC = HomeViewController()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
setupContainerView()
}
func setupContainerView() {
// menu
addChild(menuVC)
self.view.addSubview(menuVC.view)
menuVC.view.frame = CGRect(x: 0, y: 0, width: 200, height: 896)
menuVC.didMove(toParent: self)
// Home
homeVC.delegate = self
addChild(naviVC)
self.view.addSubview(naviVC.view)
naviVC.view.frame = self.view.bounds
naviVC.didMove(toParent: self)
}
}
extension MainViewController: HomeViewControllerDelegate {
func showMenu() {
// does not get called
print("did tap menu")
}
}
Error:
Debug_project/HomeViewController.swift:49: Fatal error: Unexpectedly found nil while unwrapping an Optional value
i am already searching for days now, and just can't find the solution for this...
please help me out guys
I found the solution!
Tanks to Phillip Mills and all others for helping me find this..
the solution is:
change
let homeVC = HomeViewController()
to
override func viewDidLoad() {
super.viewDidLoad()
let homeVC = naviVC.viewControllers.first as! HomeViewController // working: this is it!
}
class MainViewController: UIViewController {
let naviVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NaviVC") as! NaviVC
let menuVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SideMenuID") as! SideMenuViewController
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
setupContainerView()
}
func setupContainerView() {
// menu
addChild(menuVC)
self.view.addSubview(menuVC.view)
menuVC.view.frame = CGRect(x: 0, y: 0, width: 200, height: 896)
menuVC.didMove(toParent: self)
// Home
let homeVC = naviVC.viewControllers.first as! HomeViewController // working: this is it!
homeVC.delegate = self
addChild(naviVC)
self.view.addSubview(naviVC.view)
naviVC.view.frame = self.view.bounds
naviVC.didMove(toParent: self)
}
}
extension MainViewController: HomeViewControllerDelegate {
func showMenu() {
// does not get called
print("did tap menu")
}
}
I am building an authentication system using the Firebase prebuilt UI, and I want to customize the UI to fit the program. Say I want to set the background color to black and change the corner radius of the buttons, is there any way I can do this? I've tried sub-classing the authPickerViewController but somehow it didn't work. I did some searching, but couldn't find any tutorial or recent problems related to this either.
Here's what I have in my MainViewController
class LoginViewController: UIViewController, FUIAuthDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self
let providers: [FUIAuthProvider] = [
FUIEmailAuth(),
FUIGoogleAuth(),
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!)]
authUI?.providers = providers
let authViewController = authUI?.authViewController()
authViewController!.modalPresentationStyle = .fullScreen
authViewController!.setNavigationBarHidden(true, animated: false)
self.present(authViewController!, animated: false, completion: nil)
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication]
if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication as? String) ?? false {
return true
}
return false
}
}
And here is the subclass I created:
class FUICustomAuthPickerViewController: FUIAuthPickerViewController,FUIAuthDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .black
}
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
}
On the Firebase documentation for customization, they say that:
You can customize the sign-in screens by subclassing FirebaseUI's view controllers and specifying them in FUIAuth's delegate methods.
I am a beginner, how can I do that?
Edited:
So by following the instructions on this link I managed to add stuff to the pre-built UI by creating an extension to the FUIAuthDelegate.
extension LoginViewController:FUIAuthDelegate {
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
let sourceApplication = options[UIApplicationOpenURLOptionsKey.sourceApplication]
if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication as? String) ?? false {
return true
}
return false
}
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
let vc = FUIAuthPickerViewController(authUI: authUI)
let view = UIView(frame: .zero)
view.backgroundColor = .black
view.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(view)
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalTo: vc.view.heightAnchor, multiplier: 1),
view.widthAnchor.constraint(equalTo: vc.view.widthAnchor, multiplier: 1)])
return vc
}
}
Turns out subclass is not necessarily needed. However, I can't seem to make this view I created to be the background, it either covers everything or nothing at all.I tried changing the background color of the view directly, didn't work. Anyone knows how to do this?
Solved the problem using the method and one of the comments provided in this link. Turns out, apart from subclassing, you have to add the following two methods to your subclass for it to work.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authUI: FUIAuth?) {
super.init(nibName: nil, bundle: Bundle.main, authUI: authUI!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
However, the approach I used (not sure if other approaches raise the same problem) also caused another problem - the email sign-in button stopped responding - which is addressed in this link by presenting the view controller with a navigationViewController because the email sign-in button works together with the navigation bar, you can get rid of it once you have presented the view with a navigationViewController.
Now the complete subclass looks like this:
import UIKit
import FirebaseUI
class FUICustomAuthPickerViewController: FUIAuthPickerViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authUI: FUIAuth?) {
super.init(nibName: nil, bundle: Bundle.main, authUI: authUI!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.backgroundColor = .eatstrOrange
view.insertSubview(imageViewBackground, at: 0)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
And here's the main view controller:
import UIKit
import FirebaseUI
class LoginViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let authUI = FUIAuth.defaultAuthUI()
let delegate = authUI?.delegate
authUI?.delegate = delegate
let providers: [FUIAuthProvider] = [
FUIGoogleAuth(),
FUIEmailAuth(),
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!)]
authUI?.providers = providers
let authViewController = FUICustomAuthPickerViewController(authUI: authUI!)
authViewController.modalPresentationStyle = .fullScreen
navigationController?.pushViewController(authViewController, animated: false)
}
}
Final outcome
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)
I am trying to understand the logic behind initialising a UINavigationController using the initialiser:
UINavigationController(rootViewController: MyViewController())
The app I am modifying used this initialiser and inside MyViewController custom initialiser I had:
class MyViewController:UIViewController{
init(){
super.init(nibName: "MyViewController", bundle: nil)
}
}
Inside the MyViewController lifecycle methods, the viewDidLoad used to have not nil for the navigationController value.
The only change I have done is inside the custom initialiser. Now:
init(){
super.init(nibName: nil, bundle: nil)
/*EDIT*/
let dummyMe = UIView()
view.addSubview(dummyMe)
}
In other words, I don't want to use a xib file. That change got me into a crash and, after some debugging, I realised that the navigationController value was nil inside the viewDidLoad method but not in my viewWillAppear method.
How is that possible? Should the UINavigationController initialiser call viewDidLoad? Why is navigationController value nil now?
Thank you
you should check your code. Example works as expected, you can paste it to playground:
import UIKit
import PlaygroundSupport
class MyViewController:UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
print(#function)
print(navigationController)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(#function)
print(navigationController)
}
}
let controller = UINavigationController(rootViewController: MyViewController())
this code will not trigger life cycle methods. Add PlaygroundPage.current.liveView = controller.view to trigger and see:
viewDidLoad()
Optional(<UINavigationController: 0x7ffb18001200>)
viewWillAppear
Optional(<UINavigationController: 0x7ffb18001200>)
UPD:
to add custom views override loadView:
override func loadView() {
super.loadView()
let subviewFrame = view.bounds
let dummyMe = UIView(frame: subviewFrame)
dummyMe.autoresizingMask = [.flexibleHeight, .flexibleWidth]
dummyMe.backgroundColor = .yellow
view.addSubview(dummyMe)
}
or even:
override func loadView() {
let dummyMe = UIView()
dummyMe.backgroundColor = .yellow
view = dummyMe
}
I tried to load a viewcontroller from a custom cell using delegates.But i get nil from the set delegate!
Here is a sample if anyone can help!
1. In Cell
protocol hotelFindDelegate{
func modalDidFinished(modalText: String)
}
class hotelFindCell: UITableViewCell {
var delegate:hotelFindDelegate?
#IBAction func findButton(sender: AnyObject) {
self.delegate!.modalDidFinished("HELLO")
print("Damn nothing works")
}
2. In Main View
class MainViewController:hotelFindDelegate {
let modalView = hotelFindCell()
override func viewDidLoad() {
super.viewDidLoad()
self.modalView?.delegate = self
}
func modalDidFinished(modalText: String){
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HotelListVC") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
self.modalView.delegate = self
print(modalText)
}
To load view controller from XIB do the following steps.
let settingVC : SettingsViewController = SettingsViewController(nibName :"SettingsViewController",bundle : nil)
later on you can push the same view controller object like
self.navigationController?.pushViewController(settingsVC, animated: true)
NSBundle.mainBundle().loadNibNamed("viewController", owner: self, options: nil)