I came from a background in Android development to the iOS world.
I'm trying to avoid those odd patterns (at least for a Android dev) that connects storyboard items directly to my controller (like #IBOutlet).
I want to know if it's possible to create an anonymous function to delegate some events of a UITabBar:
let tabBarDelegate = UITabBarDelegate {
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem!) {
label.text = item.title
}
}
tabBar.delegate = tabBarDelegate
The error I'm facing is this one: 'UITabBarDelegate' cannot be constructed because it has no accessible initializers.
I'm really new to this world. How can i accomplish that?
You can't instantiate a UITabBarDelegate because it is a protocol (similar to an interface in Java), not a class.
You have to have one of your classes declare that it implements UITabBarDelegate, then set an instance of that class as the tab bar's delegate.
Here's a short example:
class MyViewController: UIViewController, UITabBarDelegate {
//all of UITabBarDelegate's methods are optional, so you don't have to implement them
func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem!) {
}
}
var viewController = MyViewController()
var tabBar = UITabBar()
tabBar.delegate = viewController
Also, I don't believe you can create anonymous classes in Swift.
Related
I have an app with multiple view controllers. I am implementing a search bar to navigate a table view that is in each of these view controllers.
I have chosen to implement the search controller using a custom class, where I handle all the search logic. In order to make this possible, I am currently using a superclass from which each view controller inherits. I would like to know if there is a way for me to make this work without subclassing.
Here is the current implementation of my SearchController class:
class SearchController: NSObject, UISearchBarDelegate {
/* This is the trouble spot. If I change this to UIViewController?,
I get the compiler error "value of type UIViewController has no member tableView" */
weak var viewController: BaseViewController?
/*
... rest of SearchController implementation
includes methods that interact with view controller table views
*/
}
this is the BaseViewController class:
class BaseViewController: UIViewController {
let searchController = SearchController()
let tableView = UITableView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
/*
... rest of BaseViewController implementation
*/
}
To summarize, the issue I am having is that I have several view controllers with tableviews and I can't seem to make this work without creating a new base class that they can inherit from. Using UIViewController simply won't work because the UIViewController class does not have a tableView property built into it.
Any ideas?
You do not need to force all your viewControllers to subclasses BaseViewController. If the only requirement is for the viewController to have a tableView property then define a protocol with that requirement and make the relevant viewControllers implement that protocol.
Rewriting your example:
protocol BaseControllerProtocol: class {
var tableview: UITableView { get }
}
class SearchController: NSObject, UISearchBarDelegate {
//We store any class that implements the BaseControllerProtocol protocol
//Now you can use viewController.tableview
weak var viewController: BaseControllerProtocol?
//If you what to have UIViewcontrollers instances only use:
//weak var viewController: (UIViewController & BaseControllerProtocol)?
}
//An example of a viewcontroller that implements the BaseControllerProtocol
class ARandomViewController : UIViewController, BaseControllerProtocol {
var tableview: UITableView = UITableView()
}
I think you could still use UIViewController? if you assign its UITableView to your SearchViewController's private tableView calculated instance variable like so:
class SearchController: NSObject, UISearchBarDelegate {
weak var viewController: UIViewController?
fileprivate var _tableView: UITableView? {
if let vc = self.viewController {
for subview in vc.view.subviews {
if let tableView = subview as? UITableView {
return tableView
}
}
}
return nil
}
// Whatever methods interact with table view should now use self._tableView.
func doSomething() {
guard let tableView = self._tableView else { return }
// Do something with the tableView
}
}
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()
}
}
I am learning delegation in swift. This paragraph uses example to explain how delegation works.
In this simple example, I want to ensure that my app’s root view
controller, a UINavigationController, doesn’t permit the app to rotate
— the app should appear only in portrait orientation when this view
controller is in charge. But UINavigation‐ Controller isn’t my class;
it belongs to Cocoa. My own class is a different view controller, a
UIViewController subclass, which acts as the UINavigationController’s
child. How can the child tell the parent how to rotate? Well,
UINavigationController has a delegate property, typed as
UINavigationControllerDelegate (a protocol). It promises to send this
delegate the navigationControllerSupportedInterfaceOrientations
message when it needs to know how to rotate.
My question: Can we extend myOwnViewController to have method "navigationControllerSupportedInterfaceOrientations" to replace the delegation pattern to achieve the same goal?
You can extend myOwnViewController, tell extension to conform to UINavigationControllerDelegate protocol and implement method that you want, however this does not replace the delegation pattern. If your extension does not conform to this delegate's protocol, you won't be able to attach myOwnViewController as UINavigationController's delegate.
class MyController: UIViewController {
}
extension MyController: UINavigationControllerDelegate {
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Portrait
}
}
let navigationController = UINavigationController()
let controller = MyController()
navigationController.delegate = controller
Same result can be achieved by telling MyController class to conform to UINavigationControllerDelegateprotocol.
class MyController: UIViewController, UINavigationControllerDelegate {
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Portrait
}
}
let navigationController = UINavigationController()
let controller = MyController()
navigationController.delegate = controller
Those two examples result in MyController being the delegate for your UINavigationController. This can lead to having many responsibilities in one place. In order to split this responsibility, you can create another class that will be the delegate for your navigation controller.
class MyController: UIViewController {}
class NavigationDelegate: NSObject, UINavigationControllerDelegate {
func navigationControllerSupportedInterfaceOrientations(navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return .Portrait
}
}
let navigationController = UINavigationController()
let controller = MyController()
let navigationDelegate = NavigationDelegate()
navigationController.delegate = navigationDelegate
What is the difference? Imagine that you have implemented a complex logic for your supported orientation method and you find out that you don't use MyController any more, instead, you will use DifferentController. Since your orientation logic is in MyController, you would need to create it anyway. However, if your delegate logic is separated in NavigationDelegate class, you can use DifferentController without need of creating MyController class just for these delegate methods.
A portion of my app has an embedded master-detail section. Each detail view is using a custom UIViewController. When I change the value of something inside one of these UIViewControllers I need to be able to grey out one of the table rows in the master UITableViewController.
The closest I have seen to a solution is to use NSNotificationCenter to bubble up any changes, though this feels a little untidy..
Another solution is to use delegates? But I haven't come across any example solutions or tutorials in how to use this in Swift?
I've also experimented just trying to access the table view by navigating back up the hierarchy:
let navController = self.splitViewController!.viewControllers[0];
navController.tableView.reloadData()
I know the example above is wrong, but I don't know how to access the master view that way, or even if it is the right approach.
Oh, I am trying to call reloadData() because in the master view there is some logic which checks the condition as to wether to grey out a table row is applicable (i'm using Core Data)
I've seen that you figured this one out already. However a cleaner and more future proof way would be to use a delegate protocol:
protocol DetailViewControllerDelegate: class {
func reloadTableView()
}
Then add a delegate property to your DetailViewController class and implement the call to the delegate:
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
....
func reloadMasterTableView() {
delegate?.reloadTableView()
}
}
And then in your MainViewController implement the delegate method:
extension MainViewController: DetailViewControllerDelegate {
func reloadTableView() {
tableView.reloadData()
}
}
Don't forget to set the delegate on your DetailViewController instances when you create them:
let detailViewController = DetailViewController()
detailViewController.delegate = self
I would suggest you use NSNotificationCenter .
If you want to to do it via Navigation controller here is to code should work for you in swift.
let navController: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let controller: MasterViewController = navController.topViewController as! MasterViewController
controller.tableView.reloadData()
Since I was able to access my viewController, I was able to access the parent viewcontroller like so:
func reloadMasterTableView(){
let navVC: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let sectionsVC : UIMasterViewController = navVC.topViewController as! UIMasterViewController
sectionsVC.tableView.reloadData()
}
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