I have a UIViewController and I'm refactoring it and I've bumped into a situation where I need to update it from another class. I know I need to use the delegate pattern, but I'm having a tough time finding an example that fits my situation (which I think it dirt simple).
ItemViewController has a Timer class that's instantiated. I'm trying to update an ItemViewController label from the Timer class.
On Timer, I've done the following:
weak var delegate: TimerDelegate? // weak to avoid a retain cycle
func updateLabel(timeRemaining: Int) -> String {
return formatTimeInSeconds(timeRemaining) // another method w/in Timer
}
I declare the protocol at the bottom of the Timer class
protocol TimerDelegate: class {
func updateLabel(timeString: String) -> String
}
On ItemViewController I have the following property declared:
#IBOutlet weak var timeValueLabel: UILabel?
I set it as a delegate of Timer in viewDidLoad as follows:
timer.delegate = self
What I'm trying to make happen is when updateLabel is called on Timer, I'd like to update timeValueLabel.text on ItemViewController. This is where I'm stuck...what next?
If your Timer class assign from any where and you need to change the text of label you can use singleton object for that, create one singleton object with your Timer class then use that object to set delegate like this way.
class Timer {
static let sharedInstance = Timer()
weak var delegate: TimerDelegate?
//Now use this delegate to call method.
func updateLabel(timeRemaining: Int) -> String {
delegate?.updateLabel(formatTimeInSeconds(timeRemaining)
return formatTimeInSeconds(timeRemaining)
}
}
Now you need to just set this delegate in your ItemViewController like this.
class ItemViewController: UIViewController, TimerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
Timer.sharedInstance.delegate = self
}
func updateLabel(timeString: String) -> String
self.label.text = timeString
return "" //return string that you want.
}
}
I guess you should do:
func updateLabel(timeRemaining: Int) -> String {
let formattedTime = formatTimeInSeconds(timeRemaining)
delegate.updateLabel(formattedTime)
return formattedTime
}
And in the ItemViewController you should declare that the class follows the delegate and implement the declared method. Something like:
class ItemViewController: TimerDelegate {
...
func updateLabel(timeString: String) -> String {
...
}
}
Related
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.
I am new to MVVM, am little confused how to avoid typecasting a view model instance in child view controllers. To explain it better am adding the code below.
Now I know that each ViewController is supposed to have a view model and it should be initialized in init or awakeFromNib.So I declared a protocol as
public protocol ViewModelCreatorProtocol : class {
var viewModel : BaseControllerViewModel! {get}
func createViewModel()
}
and my BaseControllerViewModel looks like
public class BaseControllerViewModel {
//some variables common to all child class
}
Now I have a BaseViewController which confirms to this protocol and all other VCs in my case will extend from BaseViewController
class BaseViewController : UIViewController, ViewModelCreatorProtocol {
var viewModel: BaseControllerViewModel!
lazy var disposeBag : DisposeBag = {
return DisposeBag()
}()
override func awakeFromNib() {
super.awakeFromNib()
self.createViewModel()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func createViewModel() {
viewModel = BaseControllerViewModel()
}
func hookupViewModelObservables() {
//hookup observables
}
}
As you can see BaseViewController provides some common properties like disposeBag to all childViewControllers and also calls createViewModel in awakeFromNib and ensures that viewModel is always instantiated even before ViewController is loaded. (Lets assume for now that all my viewModels are initialized using a default init and has no param passed to them)
Issue:
Now I create a ChildViewController lets say AudioPlayerViewController
class AudioPlayerViewController: BaseViewController {
//override createViewModel of parent to pass my ViewController specific view model
override func createViewModel() {
self.viewModel = AudioPlayerViewModel()
}
}
And obviously my AudioPlayerViewModel extends from BaseControllerViewModel as
class AudioPlayerViewModel : BaseControllerViewModel {
var totalDuration : Int = 0
var timeObserver : Any? = nil
//some more variables and logics
}
Everything works fine, but now If I have to access totalDuration in my AudioPlayerViewController I have to access it as
(self.viewModel as! AudioPlayerViewModel).totalDuration
This makes my entire code base in ViewController to be filled with (self.viewModel as! AudioPlayerViewModel) which I think is running my code readability.
Is there a better way to solve this?
Did you try to use generics? So BaseViewController will have a generic type that must inherit the BaseControllerViewModel. Something like this:
public class BaseControllerViewModel {
required public init() {
// needed to do T() in view model creation.
}
}
class BaseViewController<T: BaseControllerViewModel> : UIViewController, ViewModelCreatorProtocol {
var viewModel: T! // this will change based on the type you pass in subclass
lazy var disposeBag : DisposeBag = {
return DisposeBag()
}()
override func awakeFromNib() {
super.awakeFromNib()
self.createViewModel()
}
override func viewDidLoad() {
super.viewDidLoad()
}
func createViewModel() {
viewModel = T() // instantiate the view model
}
func hookupViewModelObservables() {
//hookup observables
}
}
And for example the concrete classes will be:
class ExampleVcViewModel: BaseControllerViewModel {
var subclassVar: Int?
}
class EampleVc: BaseViewController<ExampleVcViewModel> {
override func viewDidLoad() {
super.viewDidLoad()
print(viewModel.subclassVar) // no need to cast
}
}
Remove viewModel from BaseViewController, every view controller should have it's own viewModel, there is no other way
For this situation i personally use another property that returns appropriate viewModel. So, it could be:
var audioPlayerViewModel: AudioPlayerViewModel? {
return viewModel as? AudioPlayerViewModel
}
func createViewModel() {
viewModel = AudioPlayerViewModel()
}
Or you can remove optional in property, because you know that it will be exactly AudioPlayerViewModel.
var audioPlayerViewModel: AudioPlayerViewModel {
return viewModel as! AudioPlayerViewModel
}
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
I have the following Protocol:
protocol SoundEventDelegate{
func eventStarted(text:String)
}
which I call in this class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
var delegate : SoundEventDelegate?
func startEvent(){
delegate?.eventStarted(self.text)
}
func getDuration() -> Double{
return self.duration //TODO is this common practice?
}
}
Which I have my ViewController conform to:
class ViewController: UIViewController, SoundEventDelegate {
//MARK:Properties
#IBOutlet weak var beginButton: UIButton!
#IBOutlet weak var kleinGrossLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//DELEGATE method
func eventStarted(text:String){
kleinGrossLabel.text = text
}
//MARK: actions
#IBAction func startImprovisation(sender: UIButton) {
var s1:Sentence = Sentence(type: "S3")
var s2:Sentence = Sentence(type: "S1")
var newModel = SentenceMarkov(Ult: s1, Penult: s2)
s1.start()
beginButton.hidden = true
}
}
But when I run the app kleinGrossLabel.text does not change. Am I referring to the label in the wrong way? Or is it the way that I do delegation that is incorrect?
Here are links to the complete Class definitions of Sentence and SentenceMarkov
https://gist.github.com/anonymous/9757d0ff00a4df7a29cb - Sentence
https://gist.github.com/anonymous/91d5d6a59b0c69cba915 - SentenceMarkov
You never set the delegate property. It's nil. It will never be called.
First off it's not common practice to have a setter in swift. if you want to have a readonly property you can use private(set) var propertyName
in other cases simply access the property like mentioned in the comment
Also i don't see a reason why you eventArray in sentence is of type [SoundEvent?] not [SoundEvent] as SoundEventdoes not seem to have a failable initialiser
Like mentioned before you need to not only implement the SoundEventDelegate protocol but also set the delegate
the problem is that you can't really access the SoundEventDelegate from the viewcontroller because you instantiate the SoundEvents inside Sentence
var soundEventDelegate: SoundEventDelegate?
the easiest way to do this would be adding a soundEventDelegate property for sentence and setting it like this:
let s1:Sentence = Sentence(type: "S3")
let s2:Sentence = Sentence(type: "S1")
s1.soundEventDelegate = self
s2.soundEventDelegate = self
and inside sound you would need the set the delegate for every event to the soundEventDelegate of Sentence
you could do it like this:
var soundEventDelegate: SoundEventDelegate? = nil {
didSet {
eventArray.forEach({$0.delegate = soundEventDelegate})
}
}
or write another initialiser that takes the delegate
hope this helps
p.s: you shouldn't inherit form NSObject in swift excepts it's really necessary
I am trying to make a UITextField extension which performs additional functions upon the setting of a delegate.
extension UITextField {
override weak public var delegate: UITextFieldDelegate? {
didSet {
print("Do stuff")
}
}
}
This fails with three errors:
'delegate' used within its own type
'weak' cannot be applied to non-class type '<<error type>>'
Property does not override any property from its superclass
What do I need to change for Do stuff to be printed upon the setting of the delegate?
You can't override delegate property using extension, you need to create subclass:
class TextField: UITextField {
override weak var delegate: UITextFieldDelegate? {
didSet {
super.delegate = delegate
print("Do stuff")
}
}
}
But this seems kinda wrong. What are you trying to achieve?