App crashing when setting UITabBarDelegate to UIViewController - ios

Having an issue with my app crashing when I try and set the UITabBarDelegate to self in subclass of UIViewController. I have a UITabBarViewController with several TabItems linked to View Controllers. One of those View Controllers is HomeViewController. I have the following code in HomeViewController:
class HomeViewController: UIViewController, UITabBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.translucent = false
self.extendedLayoutIncludesOpaqueBars = true
self.tabBarController?.tabBar.delegate = self //This is causing crash
}
}
If I delete the self.tabBarController?.tabBar.delegate = self line everything works fine and my tabBar behaves as expected, but when I re-add that line I get the following crash:
ibc++abi.dylib: terminating with uncaught exception of type NSException
Not entirely sure how to resolve this. Have found some other answers on SO but they still seemed a little unclear on what the process is to make this work.
Thanks!

Your UITabBarViewController is already a delegate to your UITabBar. Instead of making your viewController your tabBarDelegate, use your tabBarViewController and put your logic there.
You can do something like this. In the tabBarViewController's didSelectItem delegate method
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem)
{
let index = tabBar.items?.indexOf(item)
if (index == /*the required index of HomeViewController*/)
{
let homeVC = self.viewControllers.objectAtIndex(index) as! HomeViewController
homeVC.myMethod()
}
}

Related

UITabBarItem delegate stops from insertSubview

Have a UITabBar that worked fine until some changes that were needed where I am now loading the controller with the UITabBar as a subview. Loading the controller with the UITabView as a subview has caused the delegate for the UITabBar to no longer function. Walking through the code shows the delegate being set for the UITabView to the controller. Checked that the UITabView is still the top view and it is. User interaction is still active on the page but only UITabBar isn't working. This used to work fine until the insertSubview was used. Here is how things are loaded:
let storyBoard = UIStoryboard(name: "MainStoryBoard", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier:"HomeController") as! T
vc.view.tag = 99
view.insertSubview(vc.view, at: 1)
addChild(vc)
vc.didMove(toParent: self)
In the "HomeController" delegate is set self.tabBar.delegate = self in viewDidLoad. Also in this viewcontroller is the extension on UITabBarDelegate.
extension HomePageViewController: UITabBarDelegate {
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print(item)
}
}
Anyone have any ideas of what needs to changed so that the UITabView can work again?

Error passing data between two ViewControllers when declaring the delegate within a UITabBarController

I’m trying to pass data between two ViewControllers with the initial call being made from a UITabBarController.
Here is what I’m doing. I’m using a class called RaisedTabBarController to add a custom button to a TabBarController, which works fine displaying the button, my issue is that when I tap the custom button I want it to take me to FirstViewController and then I want to pass data from FirstViewController to SecondViewController via protocols but for some reason I’m getting an error that in my opinion doesn’t make any sense, it complains about a labels not being accessible within SecondViewController.
Here is the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is the code…
Class ref from GitHub:
RaisedTabBarController
TabBarController
Here I'm adding the custom button and making the call to go to FirstViewController
import UIKit
/// TabBarController subclasses RaisedTabBarController
class TabBarController: RaisedTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Insert empty tab item at center index. In this case we have 5 tabs.
self.insertEmptyTabItem("", atIndex: 2)
// Raise the center button with image
let img = UIImage(named: “myImage”)
self.addRaisedButton(img, highlightImage: nil, offset: -10.0)
}
// Handler for raised button
override func onRaisedButton(_ sender: UIButton!) {
super.onRaisedButton(sender)
// Go to FirstViewController
let pvc = storyboard?.instantiateViewController(withIdentifier: “firstStoryBoardID”) as! FirstViewController
/// Here, I’m not sure if this is the right way to tell that
/// SecondViewController will be the delegate not TabBarController, seem to work
pvc.delegate = SecondViewController() as FirstViewControllerDelegate
self.present(pvc, animated:true, completion:nil)
}
}
FirstViewController
From here I want to send data to SecondViewController
protocol FirstViewControllerDelegate {
func messageData(greeting: String)
}
class FirstViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
func sendData() {
self.delegate?.messageData(greeting: “Hello SecondViewController”)
}
}
SecondViewController
Here I want to receive the data sent from FirstViewController
class SecondViewController: UIViewController, FirstViewControllerDelegate{
#IBOutlet weak var labelMessage: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func messageData(greeting: String) {
/// I do get message from FirstViewController
print(" Message received from FirstViewController: \(greeting)")
/// Here I get error, fatal error: unexpectedly found nil while unwrapping an Optional value
/// I think it has something to do with the labelMessage not being accessible, but why?
labelMessage.text = greeting
}
}
Any idea why am I getting the error in SecondViewController, why wouldn't labels be accessible if they are declared in SecondViewController?
Ideally I would like to be able to call method onRaisedButton(_ sender: UIButton!) directly from SecondViewController but without having to subclass RaisedTabBarController. I’m not usr if this would solve the error but I think this would make my code cleaner.
EDIT: 06/19/2017 - Solved
The effect I was looking for can be done directly in XCode, in the storyboards. I stopped using the third party class (RaisedTabBarController), problem solved.
This seems wrong.
pvc.delegate = SecondViewController() as FirstViewControllerDelegate
Try to instantiate the SecondViewController like you did for the first from storyboard.
let svc = storyboard?.instantiateViewController(withIdentifier: “secondStoryBoardID”) as! SecondViewController
And then set the delegate to SecondViewController
pvc.delegate = svc

BannerView Nil when calling function from another class

I'm getting nil when unwrapping an optional value with GADBannerView..
I setup my ad banner like this, in FlashViewController.swift..
class FlashViewController: UIViewController {
#IBOutlet weak var bannerView: GADBannerView!
and then in ViewDidLoad:
func initAdMobBanner() {
bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
bannerView.rootViewController = self
bannerView.load(GADRequest())
}
bannerView has an outlet in storyboard to Root View Controller, which is class FlashViewController.
Then in TableViewController.swift I have my purchase button. Purchase button runs:
FlashViewController().HideMyBanner();
The function HideMyBanner is in FlashViewController and will run this code:
if bannerView != nil {
print("bannerview Contains a value!")
bannerView.isHidden = true
} else {
print("bannerview Doesn’t contain a value.")
}
The issue is, if I create a button directly in FlashViewContorller.swift and run the same function, bannerView contains a value and can be hidden.. If I call the function from TableViewController.swift, it returns nil, (or crashes if I try to hide bannerView... I feel like I missing something easy here, but already spent a long time trying to figure it out...
By using this line FlashViewController().HideMyBanner(); you are creating new object of FlashViewController. so it will crash.you need to use the object of FlashViewController which is already created and loaded in memory.
I think you need to pass the reference of FlashViewContorller to TableViewController
If your TableViewController is load from FlashViewContorller than you need to create reference FlashViewContorller in TableViewController like this way.
class TableViewController: UIViewController {
var objFlashViewContorller : FlashViewContorller?
override func viewDidLoad() {
super.viewDidLoad()
//This is the UILabel
}
func purchasebuttonClick() {
objFlashViewContorller?.HideMyBanner();
}
}
While setup Navigation FlashViewContorller to TableViewController you need to pass reference.
let tableViewController = self.storyboard!.instantiateViewController(withIdentifier: "TableViewController") as! TableViewController
tableViewController.objFlashViewContorller = self
self.navigationController?.pushViewController(tableViewController, animated: true)
OR
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "TableViewController" {
let vc = segue.destinationViewController as! TableViewController
vc.objFlashViewContorller = self
}
Don't use FlashViewController().HideMyBanner();
I think you need to use this in in TableViewController.swift -> my purchase button method.
self.revealViewController.frontViewController.HideMyBanner()
I used Notifications to finally work this out... Controller 2 sends a notification that a purchase has been made, and Controller 1 observes and waits for this notification, then takes care of hiding the banner in Controller 1.
https://blog.bobthedeveloper.io/pass-data-with-nsnotification-in-swift-3-73743723c84b

Swift Search Result Controller in search results segue to another view controller

Problem:
I have a table view that the user can either scroll through to find something or use a search bar. The search bar was not created using the StoryBoard. My view has a UISearchController that handles the search bar and search result updating. The issue that I'm running into is that since my SearchResultsController is instantiated by another controller I cannot perform a segue to another view, or at least I can't find the solution. Everything works except for the segue between my Search Results Controller and the view it's destined for.
What I would like to do
Have my MyNavTableViewController delegate for MySearchResultsController. In the search results controller it will segue to another view when the user taps on a TableViewCell. I'm not using the StoryBoard to accomplish this I'm having difficulty using segues to transition to another view.
If I can't get this to work what I will probably do:
It's essential that I pass information between views, and for me I've always done it using segues. However if this doesn't work I will probably try presenting a view modally by pushing it unto the navigation controller stack and write the data to a shared database or something. I would prefer to use a segue though.
Research:
There is definitely more than this, but I'm not going to take too much space on urls.
Creating a segue programmatically
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html
My Setup
I'm going to try and keep this as concise as possible. There is more code than what I'm displaying. I'm just going to try and clean it up so that I'm only presenting the important stuff. I'm also changing a few names around because there may be sensitive information. It's probably not a big deal, but I'd rather be safe.
class MyNavTableViewController: UIViewController, UITableViewDataSource{
//this is
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
override func viewDidLoad(){
...code
tableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: tblId)
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
}
}
MySearchResultsController: UITableViewController, UISearchResultsUpdating {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
//self.performSegueWithIdentifier(imagePrepareStoryBoardId, sender: tableView.cellForRowAtIndexPath(indexPath))
/*let imagePrepare = ImagePrepareController()
customSegue = SearchResultSegue(identifier: imagePrepareId, source: self, destination: imagePrepare)*/
//neither storyboard nor navigationController can be nil.
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
//destVC.photo = photo!
//self.presentViewController(destVC, animated: false, completion: nil)
self.navigationController!.pushViewController(destVC, animated: false)
}
}
My Failed Attempts
1) Straight up segue - Doesn't work since the MySearchResultsController is not a view in the storyboard. Everything from what I've read is that segues can only be created in the SB.
2) Push view onto the navigation stack. The only problem with this is that I can't send data between views (or at least from what I've read). I'm also getting this error right at this break point:
let destVC = self.storyboard!.instantiateViewControllerWithIdentifier(imagePrepareStoryBoardId)
fatal error: unexpectedly found nil while unwrapping an Optional value
I double checked the imagePrepareStoryBoardId. It's correct.
3) Use custom segue - As you can see from the commented out lines that this isn't working either. I've always used segues with the SB so this method is a little new to me. I might be messing it up somewhere.
First create a protocol
protocol SelectedCellProtocol {
func didSelectedCell(text: String)
}
on your UITableViewClass declare it
class MySearchResultsController: UITableViewController, UISearchResultsUpdating {
var delegate:SelectedCellProtocol?
}
and on the selected cell method call it like :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
self.delegate?.didSelectedCell(cell.textLabel?.text)
}
when you declare your results controller, set the delegate
let resultsController = MySearchResultsController()
resultsController.databaseFilePath = databaseFilePath()
//this is essential that I use a segue because between my views I'm passing information between them.
resultsController.photo = photo!
resultsController.delegate = self
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = searchController.searchBar
searchBar.placeholder = searchBarPlaceHolderText
searchBar.sizeToFit()
tableView.tableHeaderView = searchBar
searchController.searchResultsUpdater = resultsController
and then implement the protocol
class MyNavTableViewController: UIViewController, UITableViewDataSource, SelectedCellProtocol{
func didSelectedCell(text: String) {
print(text)
//make your custom segue
}
}

Pushing to a new Viewcontroller from an inactive Viewcontroller (programatically)

Short explanation.
I have a ContainerViewController that I'm pushing to the navigationStack.
The ContainerViewController has 2 child ViewControllers. A SlidePanelViewController (a slide-out menu) and a CenterViewController (the content)
I have a button in my menu to "sign Out". When this button is clicked I want to push ContainerViewController (and it's 2 childViewControllers) to my LandingPageViewController.
Here's the function I am trying to call:
func signOut() {
println("signOut")
// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)
// Disable menu access
menuEnabled = false
// change status bar style back to default (black)
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.Default
}
At first I tried putting this in my SlidePanelViewController. That didn't work. So I put it where I'm assuming it belongs in the ContainerViewController.
However when I click my signOutButton in my menu. I'm presented with the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
When looking into the error. This is the line causing it:
self.navigationController!.pushViewController(mainTableViewController, animated: true)
After the error I checked that the function works, by adding a UINavigationBarButtonItem that called the function (in my ContainerViewController). It did exactly what I wanted.
However when I call this function from my Menu (again my menu is a childViewController of the ContainerViewController). It does not work.
I'm attempting to call it like so:
ContainerViewController().signOut()
I also tried adding a Delegate to my SidePanelViewController like this:
Before the class:
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
optional func toggleLeftPanel()
optional func collapseSidePanels()
}
in viewDidLoad():
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
in my tap gesture function:
func signOutTapGesture() {
println("signOutTapGesture")
selectView(signOutView)
delegate?.needsSignOut?(self)
println(delegate)
}
before my ContainerViewController class:
var leftViewController: SidePanelViewController?
my ContainerViewController class:
class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {
in my ContainerViewController's viewDidLoad()
leftViewController?.delegate = self
And I changed the signOut function in the ContainerViewController class to this:
func needsSignOut(sender: SidePanelViewController) {
println("needsSignOut called")
self.signOut()
}
However using the delegate like above, doesn't seem to do anything either.
Any help as to How I can successfully push my LandingPageVC from the menu would be greatly appreciated! (I'm not using storyboards)
You're attempting to call signOut with ContainerViewController().signOut(). This will create a new ContainerViewController and because you haven't pushed it onto the navigation controller's stack, navigationController is nil. Try just calling self.signOut(). (I'm assuming signOut in a method of ContainerViewController)
Update - delegates
Your delegate property should go in SidePanelViewController. I'll give you and example of how to implement it:
SidePanelViewController:
(Note - the protocol doesn't have to go here but I think it keeps things organised)
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
}
class SidePanelViewController: UIViewController {
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
// Called when the UIButton is pressed.
func myButtonWasPressed() {
delegate?.needsSignOut?(self)
}
}
ContainerViewController:
class ContainerViewController: UIViewController {
var sidePanel: SidePanelViewController!
// Setup the side panel...
override func viewDidLoad() {
super.viewDidLoad()
sidePanel.delegate = self
}
func signOut() {
// Sign out stuff here.
}
}
// The ContainerViewController needs to conform to the SidePanelViewControllerDelegate
// protocol if we want the delegate to work. (This could have gone in the initial
// class declaration.)
extension ContainerViewController : SidePanelViewControllerDelegate {
func needsSignOut(sender: SidePanelViewController) {
self.signOut()
}
}
Hope that helps.
The problem seems to be that navigationController is nil and you're trying to force unwrap it (as indicated by your error).
One problem I discussed in my other answer.
Another problem may be you haven't added a navigation controller. To do this you need to:
If you're using Storyboards
You need to make sure you've embedded your UINavigationController. After that, when you use navigationController it won't be nil and you'll be able to push your view controller.
When you're on your storyboard:
Also, if you're using storyboards, have you considered using segues to move around instead of calling presentViewController? I've found it makes everything much easier.
If you're not using Storyboards
Have a look at this post: Programatically creating UINavigationController in iOS

Resources