How to create a base view controller - ios

I would like to have a BaseViewController that subclasses UIViewController, overrides one of it's methods, but also require it's subclasses to implement new ones.
My case is a bit more complex, but this simple case represents my problem:
My BaseViewController would override viewWillAppear to set it's title, but the title string would come from it's subclasses. I thought about some options, not sure if/which one of them is best.
1 - Class with error throwing methods (my current solution):
class BaseViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = getTitle()
}
func getTitle() -> String {
fatalError("Unimplemented method")
}
}
2 - Receive the title in constructor:
class BaseViewController: UIViewController {
var myTitle: String!
convenience init(title titleSent: String) {
self.init(nibName: nil, bundle: nil)
myTitle = sentTitle
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = myTitle
}
}
Note that this options gets really bad if there's more parameters to send
I thought that using protocol would be perfect, until I find out that (of course) protocols can't subclass a class.
Didn't anybody do anything like this before? I don't think so, please share your thoughts.
Update
I tried another way, but got stuck in a compiler error, would this ever work?
procotol BaseViewController {
var myTitle: String { get }
}
extension BaseViewController where Self: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
title = myTitle
}
}
The compiler says Method does not override any method from its superclass.

I usually create protocol in which I declare what would be nice to have in the controllers. Then I check in the base controller if it's actually implemented and if so, just use the values, like this:
protocol ControllerType {
var navigationTitle: String { get }
}
extension ControllerType {
var navigationTitle: String {
return "Default title"
}
}
class BaseViewController: UIViewController {
override func viewDidLoad() {
if let controller = self as? ControllerType {
self.title = controller.navigationTitle
}
}
}
class ViewController: BaseViewController, ControllerType {
var navigationTitle: String {
return "ViewController"
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Downfall is you have to implement the ControllerType protocol and there's no way to enforce it.

Something similar would work.
class BaseViewController: UIViewController {
override func viewDidLoad() { }
func setTitle(_ title: String) {
self.title = title
}
}
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setTitle("ViewController")
}
}

Related

issue passing data back to first view controller with delegate and protocol (Swift)

So what I'm trying to do is pass a String and an Int back from one ViewController (NewCellViewController) to the previous one (SecondScreenViewController) when I close it. I added a print statement in the method in SecondScreenViewController that is supposed to receive this data, and it didn't print so I guess the method never ran. This is my code (removed some stuff to only include whats relevant):
SecondScreenViewController:
import UIKit
protocol DataDelegate {
func insertEvent(eventString: String, pos: Int)
}
class SecondScreenViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DataDelegate {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
advance()
}
//DataDelegate methods
func insertEvent(eventString: String, pos: Int)
{
print("if this prints, it worked")
if pos == -1
{
eventNames.append(eventString)
}
else
{
eventNames.insert(eventString, at: pos)
}
}
#objc func advance()
{
let vc = NewCellViewController(nibName: "NewCellViewController", bundle: nil)
vc.delegate = self
present(vc, animated: true, completion: nil)
}
}
NewCellViewController:
import UIKit
class NewCellViewController: UIViewController {
var delegate:DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true, completion: nil)
}
func insertNewEvent()
{
let eventName = addEventName!.text
delegate?.insertEvent(eventString: eventName!, pos: -1) //add different positions
}
}
I have used the same controller name as yours, just to make you understand better.
SecondScreenViewController
import UIKit
class SecondScreenViewController: UIViewController {
var eventNames = ["event1", "event2", "event3"]
override func viewDidLoad() {
super.viewDidLoad()
//advance()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
advance()
}
private func advance() {
// Dont forget to add `Storyboard ID` as "NewCellViewController" on your Main.storyboard.
// See the image attached below.
if let vc = self.storyboard?.instantiateViewController(identifier: "NewCellViewController") as? NewCellViewController {
vc.delegate = self
present(vc, animated: true)
}
}
}
// Better this way.
extension SecondScreenViewController: DataDelegate {
func insertEvent(eventString: String, pos: Int) {
print(eventString, pos)
}
}
NewCellViewController
import UIKit
// Better to create protocols here.
protocol DataDelegate: class {
func insertEvent(eventString: String, pos: Int)
}
class NewCellViewController: UIViewController {
weak var delegate: DataDelegate?
#IBOutlet var addEventName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addItem() {
insertNewEvent()
dismiss(animated: true)
}
private func insertNewEvent() {
delegate?.insertEvent(eventString: "your text", pos: -1)
}
}
Hope, this helps.
You can try using segue in the first VC to push the Second VC and pop the Second VC and come back to First VC. This might help you work with the delegate.
And also you can use the UserDefaults to pass and synchronize such values.
Depending on what exactly you want to pass you could use user defaults. Not recommended by many but I feel for simple data it's really quick and effective

Force child view controllers to call a base view controller method

I need to post a notification on viewDidLoad of every one of my ViewControllers. I have a BaseViewController with a postNotification method, and it gets an enum as a parameter to identify the screen. It looks like this
class BaseViewController {
func postNotification(for screen: ScreenName) {
NotificationCenter.default.post(name: notification,
object: nil,
userInfo: ["ScreenName": screen])
}
}
class AViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
postNotification(for: screenA)
}
}
class BViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
postNotification(for: screenB)
}
}
Suppose we need to add another view controller later in the future, such as CViewController. I want to force the developer of CViewController to call this postNotification method with screen enum.
What is the best practice to achieve this on Swift?
Edit
Thanks to Loren's suggestion, I added a protocol on my base class
typealias BaseController = BaseViewController & BaseProtocol
protocol BaseProtocol {
var screenName: ScreenName { get }
}
This forces all my viewcontrollers to conform protocol and initialize screenName, but now I can't get it from my BaseViewController. If I can get child view controller's screenName property from BaseViewController, I would eliminate calling postNotification method on each child, and call it only on BaseViewController
class BaseViewController: UIViewController {
var screenName: ScreenName!
override func viewDidLoad() {
super.viewDidLoad()
if let screenName = self.screenName {
self.postNotification(for: screenName)
} else {
fatalError("screenName must be instantiated")
}
}
func postNotification(for screen: ScreenName) {
NotificationCenter.default.post(name: notification,
object: nil,
userInfo: ["ScreenName": screen])
}
}
class AViewController: BaseViewController {
override func viewDidLoad() {
self.screenName = screenA
super.viewDidLoad()
}
}
class BViewController: BaseViewController {
override func viewDidLoad() {
self.screenName = screenB
super.viewDidLoad()
}
}
Try this since you are using inheritance, have BaseViewController inherit UIViewController superclass, then create a screenName variable. In your childViewControllers instantiate the screenName before calling super.viewDidLoad(). You can use protocols like you mentioned to force the implementation of the variable, but it just seems like overkill for just one variable.

Delegate not executing after call swift

I have a viewController with another containerView insider set up to appear temporarily (added programmatically). The containerView is a sort of operation bar, which allows you to change values of the viewController. The protocol called from an IBAction of a button however, does not call the protocol set up inside the viewController class.
Here is the code from both classes:
class viewController: UIViewController, updateListDelegate {
let dataSource = containerView()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
func updateList(sender: containerView) {
print("is called") //is not printed
}
}
The code from the containerView:
protocol updateListDelegate {
func updateList(containerView)
}
class containerView: UIViewController {
var delegate: updateListDelegate?
#IBAction func AddSong(_ sender: UIButton) {
self.delegate?.updateList(sender: self)
}
}
If this method is only to be called from one object, then, in my opinion, I would not define a protocol. If multiple objects are to call this method, then I would define a protocol. This is typically how you would call a method backwards, using a basic delegate.
class ViewController: UIViewController {
let container = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
container.viewControllerDelegate = self
// push to this instance of container at some point
}
func doSomething() {
print("great success")
}
}
class ContainerView: UIViewController {
weak var viewControllerDelegate: ViewController?
#objc func someAction() {
if let viewControllerDelegate = viewControllerDelegate {
viewControllerDelegate.doSomething()
}
}
}
// prints "great success" when someAction() called
One of the most common mistakes people make is not keeping track of instances. For delegates to work, you must be sure you are using the specific instances that you've instantiated and assigned those delegates to.

Swift - Function in other view controller is not being called (protocol / delegate)

I'm trying to call the function updateProgress from a LoadingDataHelper object but my delegate is not being called. I think the problem is that this LoadingDataHelper is not connected to the LoadingDataViewController (I mean like when you're having a UIView and a viewController).
LoadingDataHelper
protocol LoadingNewDataDelegate: class {
func updateProgress(progress : Float)
}
class LoadingDataHelper: NSObject {
var delegate: LoadingNewDataDelegate?
static let shared = LoadingDataHelper() // shared instance
func loginUser() {
//more code
updateProgressInViewController()
//more code
}
func updateProgressInViewController() {
delegate?.updateProgress(0.3)
}
}
LoadingDataViewController
class LoadingDataViewController: UIViewController, LoadingNewDataDelegate {
let loadingDataHelper: LoadingDataHelper = LoadingDataHelper()
override func viewDidLoad() {
super.viewDidLoad()
loadingDataHelper.delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (NSUserDefaults.standardUserDefaults().boolForKey("approvedTermsOfUse")) {
self.updateProgress(0.1)
LoadingDataHelper.shared.loginUser()
} else {
self.askForTerms()
}
}
func updateProgress(progress : Float) {
self.progressBar.setProgress(progress, animated: true)
self.progressBar.setNeedsDisplay()
}
}
Is there a way to solve this?
There is some wrong in above codeL:
In LoadingDataViewController, you are creating static property for LoadingDataHelper class and setting delegate to that class. But you are call "loginUser" using direct call.
You need to change like below:
class LoadingDataViewController: UIViewController, LoadingNewDataDelegate {
**//Change to shared initialisation of object//**
let loadingDataHelper: LoadingDataHelper = LoadingDataHelper.shared
override func viewDidLoad() {
super.viewDidLoad()
loadingDataHelper.delegate = self
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (NSUserDefaults.standardUserDefaults().boolForKey("approvedTermsOfUse")) {
self.updateProgress(0.1)
**//Call using static object instead of new instance or direct call//**
loadingDataHelper.loginUser()
} else {
self.askForTerms()
}
}
func updateProgress(progress : Float) {
self.progressBar.setProgress(progress, animated: true)
self.progressBar.setNeedsDisplay()
}
}
This will make your controller to get delegate call back.

Swift can't call protocol method via delegate

I have two classes. One class is named ViewController and the other class is named TabView.
My goal is to call a function changeTab() which is inside the TabView class from the ViewController.
Somehow I am having trouble with it because everytime my delegate is nil.
Here is my code for ViewController:
protocol TabViewProtocol: class {
func changeTab()
}
class ViewController: NSViewController {
// delegate
weak var delegateCustom : TabViewProtocol?
override func viewDidLoad() {
print(delegateCustom) // outputs "nil"
}
buttonClickFunction() {
print(delegateCustom) // outputs "nil"
delegateCustom?.changeTab() // doesn't work
}
}
Here is my code for TabView:
class TabView: NSTabViewController, TabViewProtocol {
let myVC = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
myVC.delegateCustom = self
}
func changeTab() {
print("test succeed")
}
}
Can someone explain me what I am doing wrong? - I am new to delegates and protocols...
You are using the delegate pattern wrongly. It is hard to tell which controller you want to define the protocol for and which one you want to adopt it - but here is one possible way.
// 1. Define your protocol in the same class file as delegate property.
protocol TabViewProtocol: class {
func changeTab()
}
// 2. Define your delegate property
class ViewController: NSViewController {
// delegate
weak var delegateCustom : TabViewProtocol?
override func viewDidLoad() {
// It should be nil as you have not set the delegate yet.
print(delegateCustom) // outputs "nil"
}
func buttonClickFunction() {
print(delegateCustom) // outputs "nil"
delegateCustom?.changeTab() // doesn't work
}
}
// 3. In the class that will use the protocol add it to the class definition statement
class TabView: NSTabViewController, TabViewProtocol {
let myVC = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
myVC.delegateCustom = self
// Should output a value now
print(myVC.delegateCustom) // outputs "self"
}
func changeTab() {
print("test succeed")
}
}
you are creating a new instance in this line:
let myVC = ViewController()
you should get existing instance of your ViewController.then set
myVC.delegateCustom = self

Resources