delegate to Storyboard created View Controller - swift

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")
}
}

Related

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

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)

How to show another View controller when a label is clicked

Juts like clicking a button to show another view contoller, is there a way to do that with a label?
Call below function
NOTE: Please set identifier same which you are you in below code
class firstViewController: UIViewController {
#IBOutlet weak var yourlabel: UILabel
override func viewDidLoad() {
super.viewDidLoad()
self.addGesture()
}
func addGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(self. labelTapped(_:)))
tap.numberOfTapsRequired = 1
self.yourlabel.isUserInteractionEnabled = true
self.yourlabel.addGestureRecognizer(tap)
}
#objc
func labelTapped(_ tap: UITapGestureRecognizer) {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let SecondVC = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(SecondVC, animated: animated)
}
}
Second ViewController
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}

Is there a workaround to bring the method "present" in a UIimageView class?

I created a class for my Logo UIimageView which is on every ViewController in my app. Now I want to create a tapGesture to jump back to the Home ViewController when it is tapped like Logos on Homepages back to index. How can I get the present method to work in the class UIimageView?
import Foundation
import UIKit
class LogoImageView: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
let TapGesture = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped))
self.addGestureRecognizer(TapGesture)
}
#objc func imageTapped() {
let mainStoryboad = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let destinationViewController = mainStoryboad.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
return
}
present(destinationViewController, animated: true, completion: nil)
}
}
I´ve got the solution for my problem. I added an extension to check the current ViewController to use the present method. Now I´m able to add every UIimageview this class and it brings me back to the root viewcontroller.
import Foundation
import UIKit
class LogoImageView: UIImageView {
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
let TapGesture = UITapGestureRecognizer(target: self, action: #selector(self
.imageTapped))
self.addGestureRecognizer(TapGesture)
}
#objc func imageTapped() {
let mainStoryboad = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let destinationViewController = mainStoryboad.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
return
}
guard let currentViewController = UIApplication.shared.keyWindow?.topMostViewController() else {
return
}
destinationViewController.modalTransitionStyle = .flipHorizontal
currentViewController.present(destinationViewController, animated: true, completion: nil)
}
}
extension UIWindow {
func topMostViewController() -> UIViewController? {
guard let rootViewController = self.rootViewController else {
return nil
}
return topViewController(for: rootViewController)
}
func topViewController(for rootViewController: UIViewController?) -> UIViewController? {
guard let rootViewController = rootViewController else {
return nil
}
guard let presentedViewController = rootViewController.presentedViewController else {
return rootViewController
}
switch presentedViewController {
case is UINavigationController:
let navigationController = presentedViewController as! UINavigationController
return topViewController(for: navigationController.viewControllers.last)
case is UITabBarController:
let tabBarController = presentedViewController as! UITabBarController
return topViewController(for: tabBarController.selectedViewController)
default:
return topViewController(for: presentedViewController)
}
}
}

Push UIViewController with Alert without UINavigationController

I have a button in UITableViewController. when I press this button, alert controller appears asks if the user wants to go to the cart page or stay in the same page, as follows,
if user?.uid != nil{
let title = "Item is added to your cart "
let alert = AlertController.instance(title: title)
present(alert, animated: true, completion: nil)
} else{
// Alert asks user to register..
}
AlertController is in different class and I created it with custom animation.
I want when user press (go to cart page) button, the cart page is displayed as if it was as (show,"push") segue.
This is how I pushed the view controller programmatically in alert controller class.
#IBAction func showCartButton(_ sender: Any) {
//---> go to cart page ...
print("cart button pressed")
//-----> this code is working fine with other UIviewcontrollers (however it is 'not working in UItableviewcontroller)
let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
navigationController?.pushViewController(CartViewController, animated: true)
//-----> this code shows the cart page without the navigation and tabs..
// let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
// let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
// self.present(CartViewController, animated:true, completion:nil)
}
The push view controller is not working with Alert controller, I am not sure if it is because I used the navigation with the alert, and if so, is there a way I can push the view controller??
This is the whole AlertController Class I created in seperate storyboard and put it as initial view controller..
import UIKit
class AlertController: UIViewController{
#IBOutlet weak fileprivate var AddProductToCartLabel: UILabel!
#IBOutlet weak fileprivate var cartButton: UIButton!
#IBOutlet weak fileprivate var shoppingButton: UIButton!
#IBOutlet weak fileprivate var container: UIView!
var text = String()
override func viewDidLoad() {
setContainer()
setCartButton()
setShoppingButton()
}
override func viewDidLayoutSubviews() {
layoutContainer()
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func showCartButton(_ sender: Any) {
//---> go to cart page ...
print("cart button pressed")
let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
navigationController?.pushViewController(CartViewController, animated: true)
// let storyBoard : UIStoryboard = UIStoryboard(name: "Cart", bundle:nil)
// let CartViewController = storyBoard.instantiateViewController(withIdentifier: "CartPage")
// self.present(CartViewController, animated:true, completion:nil)
}
#IBAction func completeShopButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
fileprivate extension AlertController{
// to set the view above
func setContainer() {
let shape = CAShapeLayer()
shape.fillColor = UIColor.white.cgColor
container.backgroundColor = UIColor.clear
container.layer.insertSublayer(shape, at: 0)
}
func setCartButton(){
cartButton.clipsToBounds = true
//cartButton.layer.cornerRadius = 10
}
func setShoppingButton(){
shoppingButton.clipsToBounds = true
shoppingButton.layer.borderColor = UIColor.darkGray.cgColor
shoppingButton.layer.borderWidth = 1
//shoppingButton.layer.cornerRadius = 10
}
}
fileprivate extension AlertController{
// design the size of the container
func layoutContainer() {
let path = UIBezierPath(roundedRect: container.bounds, cornerRadius: 10)
let layer = container.layer.sublayers?.first as! CAShapeLayer
layer.path = path.cgPath
}
}
extension AlertController{
static func instance(title: String?) -> AlertController{
let storyboard = UIStoryboard(name: String(describing: self), bundle: Bundle(for: self))
let controller = storyboard.instantiateInitialViewController() as! AlertController
controller.text = title!
return controller
}
}
The problem is that when you try to push CartViewController, there is no navigation controller to push it on.
You present AlertController modally, and then try to do:
navigationController?.pushViewController(CartViewController, animated: true). If you put a breakpoint here and print out navigationController, I expect it is nil.
I suggest you have AlertController delegate back to your UITableViewController that is inside a navigation controller, and have the tableview controller push to the next screen. You could also present AlertController inside of a navigation view, but this seems unnecessary.

slideMenuController not working

i am using slideMenuController from github for left drawer effect.
i had implemented successfully.
but i am having one issue that from the menu i am navigate to viewController2 and viewController2 have one uibutton which push viewcontroller1.
now i am selecting viewcontroller2 from slideMenuController but it navigate to viewcontroller1 instead of viewcontroller2.
i am having navigation in appdelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var mainViewController : MainViewController = storyboard.instantiateViewControllerWithIdentifier("MainViewController") as MainViewController
let leftViewController = storyboard.instantiateViewControllerWithIdentifier("LeftViewController") as LeftViewController
let rightViewController = storyboard.instantiateViewControllerWithIdentifier("RightViewController") as RightViewController
nvc = UINavigationController(rootViewController: mainViewController)
leftViewController.mainViewController = nvc
let slideMenuController = SlideMenuController(mainViewController:nvc!, leftMenuViewController: leftViewController, rightMenuViewController: rightViewController)
println("\(nvc!.viewControllers.count)")
self.window?.backgroundColor = UIColor(red: 236.0, green: 238.0, blue: 241.0, alpha: 1.0)
self.window?.rootViewController = slideMenuController
self.window?.makeKeyAndVisible()<br>
and navigation from slideMenus this kind of navigation
func changeViewController(menu: LeftMenu) {
switch menu {
case .Main:
self.slideMenuController()?.changeMainViewController(self.mainViewController, close: true)
case .Swift:
self.slideMenuController()?.changeMainViewController(self.swiftViewController, close: true)
break
case .Java:
self.slideMenuController()?.changeMainViewController(self.javaViewController, close: true)
break
case .Go:
self.slideMenuController()?.changeMainViewController(self.goViewController, close: true)
break
case .NonMenu:
self.slideMenuController()?.changeMainViewController(self.nonMenuViewController, close: true)
break
default:
break
}
}
in slideMenuController the function
public func changeMainViewController(mainViewController: UIViewController, close: Bool) {
removeViewController(self.mainViewController)
self.mainViewController = mainViewController
setUpViewController(mainContainerView, targetViewController: mainViewController)
if (close) {
closeLeft()
closeRight()
}
}
i am trying to remove also navigation stack of appdelegate and assign it again while changing the menu. but it didn't work.how can i achieve. please help me out.
Error is in your JavaViewController , correct it like this :-
class JavaViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var btn : UIButton = UIButton(frame: CGRectMake(150, 150, 60, 60))
btn.setTitle("Click", forState: UIControlState.Normal)
btn.addTarget(self, action: "btnClick:", forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(btn)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.setNavigationBarItem()
}
func btnClick(sender: UIButton) {
var storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController = storyboard.instantiateViewControllerWithIdentifier("MainViewController") as! MainViewController
let leftViewController = storyboard.instantiateViewControllerWithIdentifier("LeftViewController") as! LeftViewController
let nvc: UINavigationController = UINavigationController(rootViewController: mainViewController)
leftViewController.mainViewController = nvc
self.slideMenuController()?.changeMainViewController(nvc, close: true)
}
}