Swift: Delegation protocol not setting UILabel properly - ios

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

Related

Swift: ProtocolDelegate optional value error

I want to delegate some tasks to the AcceptController, but my delegate property inside of 'SendController' always returns nil, so no delegation will ever be executed. I just can't figure out why my delegate property 'übergabeDelegate' always returns nil.
protocol ÜbergabeDelegate {
func übergebeText(text: String)
}
class SendController: UIViewController {
#IBOutlet weak var textField: UITextField!
var übergabeDelegate: ÜbergabeDelegate?
#IBAction func save(_ sender: UIButton) {
if let text = textField.text {
if übergabeDelegate != nil {
übergabeDelegate!.übergebeText(text: text)
} else {
print("\nübergabeDelegate is nil\n")
}
}
}
}
class AcceptController: UIViewController {
#IBOutlet weak var label: UILabel!
let sendController = SendController()
override func viewDidLoad() {
super.viewDidLoad()
sendController.übergabeDelegate = self
//print("Delegate gesetzt")
}
}
extension AcceptController: ÜbergabeDelegate {
func übergebeText(text: String) {
label.text = "\(text)"
}
}
I expect the label to present the input I gave on my SendController but the text of the label never actually changes.
inside viewDidLoad let sendController = SendController() creates a local variable. the sendController will then be released after viewDidLoad returns. set the sendController as a property of the AcceptController and the delegate will persist when you assign it because the SendController object will not be released after viewDidLoad returns.

iOS (Swift): Protocol for UIViewController that adds a new object

I have a view controller that is responsible for adding a new object, say a new contact. This view controller (AddContactViewController) has the following UIBarButtonItem on a UINavigationBar, which is starts of disabled until enough information is provided to enable it. Then when this button is pressed a method (doneButtonPressed) is called.
The layout is as follows:
class AddContactViewController: UIViewController {
#IBOutlet weak var doneButton: UIBarButtonItem! {
didSet {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
#objc fileprivate func doneButtonPressed() {
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
As this is quite a common thing to have and there's a lot of boiler plate code, I've been working on a protocol AddingHandler but haven't quite worked out how to have UIBarButtonItem as a weak variable which hooks up to a storboard or if this is even the right way to go.
protocol AddingHandler {
var doneButton: UIBarButtonItem? { get set }
func doneButtonPressed()
}
extension protocol where Self: UIViewController {
func configureDoneButton() {
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
}
Any help or comments in making this work would be much appreciated.
The problem How is best to add a weak UIButton to a protocol which can then be hooked up in a story board where UIViewController implements it? As there is a lot of repetitive code here should I wish to have another AddSomethingViewController I was wondering if there was a neater way of only writing this once (in a protocol with an extension) then calling the protocol in any view controller that is adding something new ...
You can simply configure the doneButton in viewDidLoad()
override func viewDidLoad()
{
super.viewDidLoad()
doneButton.isEnabled = false
doneButton.target = self
doneButton.action = #selector(self.doneButtonPressed)
}
Edit 1:
#objc protocol AddingHandler
{
var doneButton: UIBarButtonItem? { get }
#objc func doneButtonPressed()
}
extension AddingHandler where Self: UIViewController
{
func configureDoneButton()
{
doneButton?.isEnabled = false
doneButton?.target = self
doneButton?.action = #selector(doneButtonPressed)
}
}
class AddContactViewController: UIViewController, AddingHandler
{
#IBOutlet weak var doneButton: UIBarButtonItem!
override func viewDidLoad()
{
super.viewDidLoad()
configureDoneButton()
}
func doneButtonPressed()
{
// do some stuff ...
self.dismiss(animated: false, completion: nil)
}
}
I've used ObjC runtime to resolve the issue. Try implementing it at your end and check if it works for you.

Setting label text on a UIViewController from a Non-UIViewController Class

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

How to access variables which located in another class

I'm trying to access a var which located in another class(ViewController), but I cannot access answeredCorrectly variable in LastView class. How can I access it and when I call answeredCorrectly like that(marked with 1) is it going to use the default instance of ViewController?
I tried that(LastView.swift)
import Foundation
import UIKit
class LastView: ViewController {
#IBOutlet weak var numberLabel: UILabel!
func assignLabelToCount(){
numberLabel.text = "\(answeredCorrectly)"
}
}
Whole View Controller
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var answerBox: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
answerBox.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
var questionShowing = ""
var answerForControl = 0
#IBAction func newButton() {
var question = getQuestion()
questionShowing = question.0
answerForControl = question.1
questionLabel.text = questionShowing
var timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("endGame"), userInfo: nil, repeats: false)
print()
}
func print(){
println("\(questionShowing) >>>>>> \(answerForControl)")
}
var answeredCorrectly = 0
func textFieldDidChange(textField: UITextField) {
var answerInInt = String(stringInterpolationSegment: answerForControl)
var answer: String? = String(answerInInt)
if answerBox.text == answer {
newButton()
answeredCorrectly++
answerBox.text = ""
} else {
}
}
func endGame(){
println("Count of correct answers: \(answeredCorrectly)")
answeredCorrectly = 0
LastView().assignLabelToCount()
performSegueWithIdentifier("toEnd", sender: nil)
}
func getQuestion() -> (String, Int){...}
}
There are a couple of things you could do, if you want to utilize inheritance, go ahead and try this kind of structure:
class ViewController : UIViewController {
//the var you want to access
var answeredCorrectly: Int = ViewController().answeredCorrectly
//your other code
//....
}
then, inherit the class, since your class LastView inherits ViewController, any class that inherits ViewController will now have access to UIViewController.
Note
If you haven't changed the subclass of your ViewController, it should be UIViewController by default.
let's inherit the class for your LastView class:
class LastView : ViewController {
//now your LastView class inherits from ViewController, which also inherits
//from UIViewController, it's like a big chain of classes
#IBOutlet weak var numberLabel: UILabel!
func assignLabelToCount() {
numberLabel.text = "\(answeredCorrectly)"
}
}
The function just simply assigns your variable answeredCorrectly, which is located in ViewController.
declare variable numberLabel as public
You need to create instance of that class to access the variable.
Ex:
var lastViewInstance: LastView = LastView() // Declare in the class in which you want to access the variable
lastViewInstance.numberLabel.text = "Access from Another class"
This is how you can access any variable or outlet!
You can store your object into disk by using NSUserDefaults and you can use it this way:
Store your object to NSUserDefaults:
NSUserDefaults.standardUserDefaults().setObject("YouObjectValue", forKey: "YourKey")
After that you can access it anywhere into your project this way:
let yourVar: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("YourKey")
Hope it will help you.

Store input number inside a variable from a textbox

In my application I have a textbox that should be filled with a Double and the number should be saved into a variable but there's an error.
I dragged and dropped the textbox into ViewController.swift so it should be linked. I created a #IBOutlet. I called the textbox mmolText and the variable mmol.
I tried something like: var mmol = mmolText.text but it shows an error:
'ViewController.Type' does not have a member named 'mmolText'.
What's the problem? How can I solve it? Besides the type of the content of the textbox is a string but I should convert it into Double.
Here the code of ViewController.swift is:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol = mmolText.text
#IBOutlet weak var mmolLabel: UILabel!
#IBOutlet weak var mgLabel: UILabel!
#IBAction func convertBM(sender: AnyObject) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It seems like we probably simply want mmol to exist as a convenient way for getting the text property out of the mmolText textfield, right? So why not use a computed property:
var mmol: String {
get {
return mmolText.text ?? ""
}
set {
mmolText.text = newValue
}
}
The get makes use of the nil coalescing operator. UITextField's text property hasn't been updated with the Objective-C nullability annotations yet, so we need to handle the case of it potentially returning nil.
If we want this to be readonly, we can simply omit the set part.
If we want this as a Double, we can modify the above computed property to look more like this:
var mmol: Double {
get {
return ((mmolText.text ?? "0") as NSString).doubleValue
}
set {
mmolText.text = String("%f", newValue)
}
}
And again, if we want this to be readonly, we can simply omit the set half. And of course, the format string can be played around with to get the string version of the double to show up exactly as you intend when using this set method.
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol: String!
override func viewDidLoad() {
super.viewDidLoad()
mmol = mmolText.text
}
}
This way it works. I can remember something like because at that stage, the properties can exist. Which means, it can be there or it isn't. That's why you can't do it like that.
Don't pin me on this explanation though, I'm not very sure.
mmolText is a property on self. You can't refer to it there because self has not been initialized yet.
You'll have to assign it within awakeFromNib, viewDidLoad, etc.

Resources