Can extension replace delegation in swift? - ios

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.

Related

Having trouble avoiding subclassing

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

Should I cast my view to UIViewController in my presenter on iOS in a MVP pattern?

In a MVP structured iOS app, very often I would need to call some functions in UIViewController class in my Presenter.
For example, an UI event is triggered and my presenter has done some business logic and decide to do one or some of the following UI updates
Hide back button
Update Navigation bar title
Pop up an UIAlertController
It's a lot easier and tidier to do the following
func didClickAButton() {
//Some business logic
let vc = mUI as! UIViewController
vc.navigationItem.hidesBackButton = true
vc.title = "New Title"
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
vc.present(alert, animated: true, completion: nil)
}
Than creating a protocol function for every functions of UIViewController class that I'm possibly need.
My question is what would be a good way to handling this.
Edit:
Maybe I wasn't clear about my question, so the below code should explain it better
protocol ViewProtocol {
func hideBackButton()
//Potientially one protocol function for each UIViewController's
//function I might need
}
class Presenter {
weak var mUI: ViewProtocol
func updateUIAfterSomeLogic() {
//At this point, I can do
mUI.hideBackButton()
//or cast mUI to UIViewController because I know it will always
//be a subclass of UIViewController
let vc = mUI as! UIViewController
vc.navigationItem.hidesBackButton = true
}
}
class View: UIViewController, ViewProtocol {
func hideBackButton() {
self.navigationItem.hidesBackButton = true
}
}
The only kind of object that you should be sending UIViewController messages to is an object that is a UIViewController. If you have some other type of object like a Presenter that implements some of the methods of UIViewController but is not a view controller then you should not cast it to a UIViewController.
If your mUI object is a subclass of UIViewController then it already is a UIViewController and there's no reason to cast it to that type.
So no, the code you posted does not seem useful, and if your mUI object is not a subclass of UIViewController then it's more than unhelpful, it's a bad idea.
EDIT:
If mUI is always a ViewController, make it a ViewController that conforms to your protocol:
var mUI: UIViewController & ViewProtocol

Parent VC delegate methods not called when embedded containerVC methods are called

I have a Parent VC with a Child VC embedded in a container. Both VCs conform to delegate, but only the child delegate methods are called. How can I get both VC's delegate methods to respond? Am I missing something with delegate pattern for container views? Thanks in advance for any help.
Central class:
public protocol BLEManagerDelegate: class {
func bLEManagerShowAlert(message: String)
}
public class BLEManager: NSObject {
static let sharedInstance = BLEManager()
weak var delegate: BLEManagerDelegate?
public func postMessage() {
delegate?.bLEManagerShowAlert(message: message)
}
}
ParentVC
class HomeVC: ContentViewController, BLEManagerDelegate {
var bLEManager = BLEManager.sharedInstance
override func viewWillAppear(_ animated: Bool) {
bLEManager.delegate = self
}
// delegate methods
func bLEManagerShowAlert(message: String) {
// THIS METHOD IS NOT GETTING CALLED
}
}
Container view embedded into ParentVC
class ChildVC: UITableViewController, BLEManagerDelegate {
var bLEManager = BLEManager.sharedInstance
override func viewWillAppear(_ animated: Bool) {
bLEManager.delegate = self
// delegate methods
func bLEManagerShowAlert(message: String) {
// This method IS getting called
}
}
Your delegate property can only hold a reference to one object at a time. As soon as your ChildVC sets itself as the delegate, the parentVC is no longer the delegate.
If you want to notify multiple objects you could look at using NotificationCenter
Why do you need Singleton BLEManager? Where do you call postMessage()? If alerts are displayed in their own view controllers just write default implementation for a default alert message via protocol extension. Then just implement the methods in VCs for custom messages. If you want multiple delegates you should try this: http://www.gregread.com/2016/02/23/multicast-delegates-in-swift/

Use extension for my certain View Controller

I have created a class used for an extension at my certain view controllers,
class STPopupTransitionAnimator: NSObject, STPopupControllerTransitioning {
....
}
and now I would like to set it to my View Controller, so I must not use the same code in my Home, profile, browse, search, etc like this:
extension HomeViewController: STPopupControllerTransitioning {
func popupControllerTransitionDuration(_ context: STPopupControllerTransitioningContext) -> TimeInterval {
return context.action == .present ? 0.9 : 0.4
}
}
The question is, how could I use my STPopupTransitionAnimator for my home, profile, and search view controller ?
What I don't want to do is to extend my UIViewController like this:
extension UIViewController: STPopupTransitionAnimator {}
because I only used that protocol only at certain view controllers and I used it to check it for something.
create a subclass of UIViewController like
class STPopupViewController: UIViewController, STPopupControllerTransitioning {
//all functions implementations
}
and subclass your UIViewControllers from it
class HomeViewController: STPopupViewController {
}

'UITabBarDelegate' cannot be constructed because it has no accessible initializers

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.

Resources